607 lines
20 KiB
Svelte
607 lines
20 KiB
Svelte
<script>
|
|
import {
|
|
ArrowLeft,
|
|
ArrowRight,
|
|
CheckCircle2,
|
|
CircleX,
|
|
LockKeyhole,
|
|
TriangleAlert,
|
|
} from "$components/ui/icons.js";
|
|
import { Tooltip } from "$components/ui/primitives.js";
|
|
|
|
import Button from "$components/ui/button.svelte";
|
|
import Checkbox from "$components/ui/checkbox.svelte";
|
|
import Dialog from "$components/ui/dialog.svelte";
|
|
import EmailCodeScreen from "./auth/EmailCodeScreen.svelte";
|
|
import Input from "$components/ui/input.svelte";
|
|
import {
|
|
EmptyCard,
|
|
PaymentMethodGrid,
|
|
StatusMessage,
|
|
} from "$components/patterns/webapp/index.js";
|
|
import {
|
|
planKey as planKeyFn,
|
|
planDisplayTitle as planDisplayTitleFn,
|
|
planSubtitle as planSubtitleFn,
|
|
planUnitHint as planUnitHintFn,
|
|
tariffLimitLabel as tariffLimitLabelFn,
|
|
priceLabel as priceLabelFn,
|
|
firstAvailableMethod,
|
|
methodSelectable,
|
|
methodsForPlan,
|
|
} from "../lib/webapp/tariffs.js";
|
|
|
|
export let createPayment = () => {};
|
|
export let deviceConfirmOpen = false;
|
|
export let deviceDisconnectBusy = false;
|
|
export let deviceToDisconnect = null;
|
|
export let disconnectDevice = () => {};
|
|
export let linkEmailBusy = false;
|
|
export let linkEmailCode = "";
|
|
export let linkEmailFieldError = "";
|
|
export let linkEmailIsError = false;
|
|
export let linkEmailOpen = false;
|
|
export let linkEmailPending = "";
|
|
export let linkEmailResendCooldown = 0;
|
|
export let linkEmailStatus = "";
|
|
export let linkEmailValue = "";
|
|
export let hasMultipleTariffs = false;
|
|
export let methods = [];
|
|
export let payBusy = false;
|
|
export let paymentModalOpen = false;
|
|
export let paymentStep = "tariff";
|
|
export let plans = [];
|
|
export let selectedMethod = "";
|
|
export let selectedPlan = null;
|
|
export let selectedTariff = null;
|
|
export let selectedTariffKey = "";
|
|
export let selectedTariffPlans = [];
|
|
export let renewHwidDevices = true;
|
|
export let setPasswordBusy = false;
|
|
export let setPasswordCode = "";
|
|
export let setPasswordConfirm = "";
|
|
export let setPasswordEmail = "";
|
|
export let setPasswordIsError = false;
|
|
export let setPasswordOpen = false;
|
|
export let setPasswordPending = false;
|
|
export let setPasswordResendCooldown = 0;
|
|
export let setPasswordStatus = "";
|
|
export let setPasswordValue = "";
|
|
export let singleTariffMode = false;
|
|
export let subscription = {};
|
|
export let subscriptionPurchaseDescription = "";
|
|
export let tariffCatalog = [];
|
|
export let tariffMode = false;
|
|
export let trafficMode = false;
|
|
|
|
function priceLabel(plan) {
|
|
return priceLabelFn(plan, selectedMethod);
|
|
}
|
|
function methodUsesStars() {
|
|
return String(selectedMethod || "")
|
|
.toLowerCase()
|
|
.includes("stars");
|
|
}
|
|
function hwidRenewalFor(plan) {
|
|
return plan?.hwid_renewal?.available ? plan.hwid_renewal : null;
|
|
}
|
|
function isSubscriptionPlan(plan) {
|
|
const saleMode = String(plan?.sale_mode || "subscription").toLowerCase();
|
|
return saleMode === "subscription";
|
|
}
|
|
function hwidRenewalAvailableForMethod(plan) {
|
|
const renewal = hwidRenewalFor(plan);
|
|
if (!subscription?.active || !isSubscriptionPlan(plan) || !renewal) return false;
|
|
if (methodUsesStars()) return Number(renewal.stars_price || 0) > 0;
|
|
return Number(renewal.price || 0) > 0;
|
|
}
|
|
function planWithSelectedHwidRenewal(plan) {
|
|
if (!plan || !renewHwidDevices || !hwidRenewalAvailableForMethod(plan)) return plan;
|
|
const renewal = hwidRenewalFor(plan);
|
|
const withRenewal = {
|
|
...plan,
|
|
price: Number(plan.price || 0) + Number(renewal.price || 0),
|
|
};
|
|
if (Number(plan.stars_price || 0) > 0 && Number(renewal.stars_price || 0) > 0) {
|
|
withRenewal.stars_price = Number(plan.stars_price || 0) + Number(renewal.stars_price || 0);
|
|
}
|
|
return withRenewal;
|
|
}
|
|
function paymentPriceLabel(plan) {
|
|
return priceLabelFn(planWithSelectedHwidRenewal(plan), selectedMethod);
|
|
}
|
|
$: selectedPlanForPayment = planWithSelectedHwidRenewal(selectedPlan);
|
|
$: paymentMethods = methodsForPlan(methods, selectedPlanForPayment);
|
|
$: paymentMethodSelected = methodSelectable(paymentMethods, selectedMethod);
|
|
$: if (paymentModalOpen && paymentStep === "checkout" && selectedPlan) {
|
|
const firstMethod = firstAvailableMethod(paymentMethods);
|
|
if (firstMethod && !methodSelectable(paymentMethods, selectedMethod)) {
|
|
selectedMethod = firstMethod;
|
|
}
|
|
}
|
|
function hwidRenewalPriceLabel(plan = selectedPlan) {
|
|
const renewal = hwidRenewalFor(plan);
|
|
if (!renewal) return "";
|
|
return priceLabelFn(
|
|
{
|
|
price: renewal.price || 0,
|
|
stars_price: renewal.stars_price,
|
|
currency: renewal.currency || plan?.currency,
|
|
},
|
|
selectedMethod
|
|
);
|
|
}
|
|
function showHwidRenewalBlock() {
|
|
return hwidRenewalAvailableForMethod(selectedPlan);
|
|
}
|
|
function showHwidRenewalUnavailableNote() {
|
|
return Boolean(
|
|
subscription?.active &&
|
|
Number(subscription?.extra_hwid_devices || 0) > 0 &&
|
|
isSubscriptionPlan(selectedPlan) &&
|
|
!showHwidRenewalBlock()
|
|
);
|
|
}
|
|
function hwidRenewalCount(plan = selectedPlan) {
|
|
return Number(hwidRenewalFor(plan)?.device_count || subscription?.extra_hwid_devices || 0);
|
|
}
|
|
function hwidRenewalHint(plan = selectedPlan) {
|
|
const renewal = hwidRenewalFor(plan);
|
|
if (renewal?.valid_from_text && renewal?.valid_until_text) {
|
|
return t("wa_hwid_devices_renewal_checkbox_hint", {
|
|
from: renewal.valid_from_text,
|
|
to: renewal.valid_until_text,
|
|
});
|
|
}
|
|
return t("wa_hwid_devices_renewal_checkbox_hint_short");
|
|
}
|
|
function showHwidDesyncNotice() {
|
|
return Boolean(
|
|
subscription?.device_topup_renewal_available &&
|
|
subscription?.extra_hwid_devices_valid_until_text
|
|
);
|
|
}
|
|
function planKey(plan) {
|
|
return planKeyFn(plan);
|
|
}
|
|
function planDisplayTitle(plan) {
|
|
return planDisplayTitleFn(plan, { trafficMode, t });
|
|
}
|
|
function planSubtitle(plan) {
|
|
return planSubtitleFn(plan, { t, termUnitLabel });
|
|
}
|
|
function planUnitHint(plan) {
|
|
return planUnitHintFn(plan, { trafficMode, selectedMethod, t });
|
|
}
|
|
function tariffLimitLabel(tariff) {
|
|
return tariffLimitLabelFn(tariff, { t });
|
|
}
|
|
|
|
function paymentTitle() {
|
|
if (singleTariffMode) {
|
|
return selectedTariff?.billing_model === "traffic"
|
|
? t("wa_traffic_packages_title")
|
|
: t("wa_subscription_title");
|
|
}
|
|
if (tariffMode) return t("wa_tariffs_title");
|
|
return trafficMode ? t("wa_traffic_packages_title") : t("wa_subscription_title");
|
|
}
|
|
|
|
function paymentDescription() {
|
|
if (tariffMode) {
|
|
if (singleTariffMode) {
|
|
return selectedTariff?.billing_model === "traffic"
|
|
? t("wa_traffic_packages_choose")
|
|
: t("wa_subscription_choose_period");
|
|
}
|
|
return paymentStep === "checkout" && selectedTariff
|
|
? t("wa_tariff_choose_period_payment", { tariff: selectedTariff.title })
|
|
: t("wa_tariffs_choose");
|
|
}
|
|
return trafficMode ? t("wa_traffic_packages_choose") : t("wa_subscription_choose_period");
|
|
}
|
|
|
|
function showSubscriptionPurchaseDescription() {
|
|
if (!subscriptionPurchaseDescription || trafficMode) return false;
|
|
if (!tariffMode) return true;
|
|
if (paymentStep === "tariff") return false;
|
|
return String(selectedTariff?.billing_model || "period").toLowerCase() !== "traffic";
|
|
}
|
|
|
|
export let closeDeviceDisconnectDialog = () => {};
|
|
export let closeLinkEmailDialog = () => {};
|
|
export let closePaymentModal = () => {};
|
|
export let closeSetPasswordDialog = () => {};
|
|
export let backToTariffList = () => {};
|
|
export let continueWithSelectedTariff = () => {};
|
|
export let requestLinkEmailCode = () => {};
|
|
export let requestSetPasswordCode = () => {};
|
|
export let selectTariff = () => {};
|
|
export let t = (key) => key;
|
|
export let termUnitLabel = () => "";
|
|
export let verifyLinkEmailCode = () => {};
|
|
export let confirmSetPassword = () => {};
|
|
</script>
|
|
|
|
<Dialog
|
|
open={paymentModalOpen}
|
|
title={paymentTitle()}
|
|
description={paymentDescription()}
|
|
closeLabel={t("wa_close")}
|
|
onclose={closePaymentModal}
|
|
class="payment-dialog-card"
|
|
>
|
|
<div class="payment-dialog-body">
|
|
{#if tariffMode && !singleTariffMode && paymentStep === "tariff"}
|
|
{#if tariffCatalog.length}
|
|
<div class="option-list tariff-list">
|
|
{#each tariffCatalog as tariff}
|
|
<button
|
|
class:active={selectedTariffKey === tariff.key}
|
|
class="option-row tariff-row"
|
|
type="button"
|
|
onclick={() => selectTariff(tariff)}
|
|
>
|
|
<span class="option-row-main">
|
|
<strong>{tariff.title}</strong>
|
|
<small>{tariff.description || t("wa_tariff_no_description")}</small>
|
|
</span>
|
|
<span class="option-row-meta">
|
|
<em>{tariffLimitLabel(tariff)}</em>
|
|
{#if selectedTariffKey === tariff.key}
|
|
<CheckCircle2 size={18} />
|
|
{:else}
|
|
<ArrowRight size={17} />
|
|
{/if}
|
|
</span>
|
|
</button>
|
|
{/each}
|
|
</div>
|
|
<Button
|
|
class="wide bottom-action payment-submit-button"
|
|
onclick={continueWithSelectedTariff}
|
|
disabled={!selectedTariffKey}
|
|
>
|
|
{t("wa_next")}
|
|
<ArrowRight size={17} />
|
|
</Button>
|
|
{:else}
|
|
<EmptyCard>{t("wa_no_tariff_change_options")}</EmptyCard>
|
|
{/if}
|
|
{:else if tariffMode}
|
|
{#if !singleTariffMode && !(subscription?.active && subscription?.tariff_key && tariffCatalog.some((t) => t.key === subscription.tariff_key))}
|
|
<button class="back-inline" type="button" onclick={backToTariffList}>
|
|
<ArrowLeft size={16} />
|
|
{t("wa_back_to_tariffs")}
|
|
</button>
|
|
{/if}
|
|
{#if hasMultipleTariffs && selectedTariff}
|
|
<p class="tariff-step-caption">
|
|
{t("wa_selected_tariff", { tariff: selectedTariff.title })}
|
|
</p>
|
|
{/if}
|
|
{#if selectedTariffPlans.length}
|
|
{#if showSubscriptionPurchaseDescription()}
|
|
<div class="subscription-purchase-description">
|
|
<p>{subscriptionPurchaseDescription}</p>
|
|
</div>
|
|
{/if}
|
|
{#if showHwidRenewalBlock()}
|
|
<label class="hwid-renewal-option">
|
|
<Checkbox
|
|
checked={renewHwidDevices}
|
|
ariaLabel={t("wa_hwid_devices_renewal_checkbox_aria")}
|
|
onCheckedChange={(checked) => (renewHwidDevices = checked)}
|
|
/>
|
|
<span>
|
|
<strong>
|
|
{t("wa_hwid_devices_renewal_checkbox", {
|
|
count: hwidRenewalCount(),
|
|
price: hwidRenewalPriceLabel(),
|
|
})}
|
|
</strong>
|
|
<small>{hwidRenewalHint()}</small>
|
|
{#if showHwidDesyncNotice()}
|
|
<small class="hwid-renewal-warning">
|
|
{t("wa_hwid_devices_desync_notice", {
|
|
date: subscription.extra_hwid_devices_valid_until_text,
|
|
})}
|
|
</small>
|
|
{/if}
|
|
</span>
|
|
</label>
|
|
{:else if showHwidRenewalUnavailableNote()}
|
|
<div class="subscription-purchase-description">
|
|
<p>
|
|
{t("wa_hwid_devices_renewal_unavailable", {
|
|
count: Number(subscription.extra_hwid_devices || 0),
|
|
date: subscription.extra_hwid_devices_valid_until_text || "",
|
|
})}
|
|
</p>
|
|
</div>
|
|
{/if}
|
|
<div class="period-grid period-grid-two-columns">
|
|
{#each selectedTariffPlans as plan}
|
|
<button
|
|
class:active={planKey(selectedPlan) === planKey(plan)}
|
|
class="period-card"
|
|
type="button"
|
|
onclick={() => (selectedPlan = plan)}
|
|
>
|
|
<strong>{planSubtitle(plan) || planDisplayTitle(plan)}</strong>
|
|
<span>{priceLabel(plan)}</span>
|
|
{#if planUnitHint(plan)}
|
|
<small>{planUnitHint(plan)}</small>
|
|
{/if}
|
|
{#if planKey(selectedPlan) === planKey(plan)}
|
|
<CheckCircle2 size={18} />
|
|
{/if}
|
|
</button>
|
|
{/each}
|
|
</div>
|
|
<div class="payment-divider" aria-hidden="true"></div>
|
|
{#if methods.length}
|
|
<PaymentMethodGrid
|
|
methods={paymentMethods}
|
|
{selectedMethod}
|
|
{t}
|
|
onSelect={(id) => (selectedMethod = id)}
|
|
/>
|
|
{:else}
|
|
<EmptyCard>{t("wa_payment_methods_not_configured")}</EmptyCard>
|
|
{/if}
|
|
<Button
|
|
class="wide bottom-action payment-submit-button"
|
|
onclick={createPayment}
|
|
disabled={!selectedPlan || !paymentMethodSelected || payBusy}
|
|
>
|
|
{t("wa_pay")}
|
|
{selectedPlan ? paymentPriceLabel(selectedPlan) : ""}
|
|
<LockKeyhole size={17} />
|
|
</Button>
|
|
{:else}
|
|
<EmptyCard>{t("wa_no_tariff_change_options")}</EmptyCard>
|
|
{/if}
|
|
{:else}
|
|
<!--
|
|
Legacy / non-tariff mode (no JSON tariffs catalog OR traffic-only).
|
|
Previously this block was also reached *in addition* to the tariff
|
|
branch above, so users on legacy mode saw the period grid, payment
|
|
method grid and pay button duplicated.
|
|
-->
|
|
{#if showSubscriptionPurchaseDescription()}
|
|
<div class="subscription-purchase-description">
|
|
<p>{subscriptionPurchaseDescription}</p>
|
|
</div>
|
|
{/if}
|
|
{#if showHwidRenewalBlock()}
|
|
<label class="hwid-renewal-option">
|
|
<Checkbox
|
|
checked={renewHwidDevices}
|
|
ariaLabel={t("wa_hwid_devices_renewal_checkbox_aria")}
|
|
onCheckedChange={(checked) => (renewHwidDevices = checked)}
|
|
/>
|
|
<span>
|
|
<strong>
|
|
{t("wa_hwid_devices_renewal_checkbox", {
|
|
count: hwidRenewalCount(),
|
|
price: hwidRenewalPriceLabel(),
|
|
})}
|
|
</strong>
|
|
<small>{hwidRenewalHint()}</small>
|
|
{#if showHwidDesyncNotice()}
|
|
<small class="hwid-renewal-warning">
|
|
{t("wa_hwid_devices_desync_notice", {
|
|
date: subscription.extra_hwid_devices_valid_until_text,
|
|
})}
|
|
</small>
|
|
{/if}
|
|
</span>
|
|
</label>
|
|
{:else if showHwidRenewalUnavailableNote()}
|
|
<div class="subscription-purchase-description">
|
|
<p>
|
|
{t("wa_hwid_devices_renewal_unavailable", {
|
|
count: Number(subscription.extra_hwid_devices || 0),
|
|
date: subscription.extra_hwid_devices_valid_until_text || "",
|
|
})}
|
|
</p>
|
|
</div>
|
|
{/if}
|
|
<div class="period-grid period-grid-two-columns">
|
|
{#each plans as plan}
|
|
<button
|
|
class:active={planKey(selectedPlan) === planKey(plan)}
|
|
class="period-card"
|
|
type="button"
|
|
onclick={() => (selectedPlan = plan)}
|
|
>
|
|
<strong>{planDisplayTitle(plan)}</strong>
|
|
{#if planSubtitle(plan)}
|
|
<em>{planSubtitle(plan)}</em>
|
|
{/if}
|
|
<span>{priceLabel(plan)}</span>
|
|
{#if planUnitHint(plan)}
|
|
<small>{planUnitHint(plan)}</small>
|
|
{/if}
|
|
{#if planKey(selectedPlan) === planKey(plan)}
|
|
<CheckCircle2 size={18} />
|
|
{/if}
|
|
</button>
|
|
{/each}
|
|
</div>
|
|
<div class="payment-divider" aria-hidden="true"></div>
|
|
{#if methods.length}
|
|
<PaymentMethodGrid
|
|
methods={paymentMethods}
|
|
{selectedMethod}
|
|
{t}
|
|
onSelect={(id) => (selectedMethod = id)}
|
|
/>
|
|
{:else}
|
|
<EmptyCard>{t("wa_payment_methods_not_configured")}</EmptyCard>
|
|
{/if}
|
|
<Button
|
|
class="wide bottom-action payment-submit-button"
|
|
onclick={createPayment}
|
|
disabled={!selectedPlan || !paymentMethodSelected || payBusy}
|
|
>
|
|
{t("wa_pay")}
|
|
{selectedPlan ? paymentPriceLabel(selectedPlan) : ""}
|
|
<LockKeyhole size={17} />
|
|
</Button>
|
|
{/if}
|
|
</div>
|
|
</Dialog>
|
|
|
|
<Dialog
|
|
open={deviceConfirmOpen}
|
|
title={t("wa_devices_disconnect_title")}
|
|
description={t("wa_devices_disconnect_desc", {
|
|
device:
|
|
deviceToDisconnect?.display_name ||
|
|
t("wa_device_fallback_name", { index: deviceToDisconnect?.index || "" }),
|
|
})}
|
|
closeLabel={t("wa_close")}
|
|
onclose={closeDeviceDisconnectDialog}
|
|
class="payment-dialog-card"
|
|
>
|
|
<div class="payment-dialog-body">
|
|
<Button
|
|
variant="outline"
|
|
class="wide device-danger-button"
|
|
onclick={disconnectDevice}
|
|
disabled={deviceDisconnectBusy}
|
|
>
|
|
<CircleX size={17} />
|
|
{t("wa_devices_disconnect_confirm")}
|
|
</Button>
|
|
<Button
|
|
variant="secondary"
|
|
class="wide"
|
|
onclick={closeDeviceDisconnectDialog}
|
|
disabled={deviceDisconnectBusy}
|
|
>
|
|
{t("wa_cancel")}
|
|
</Button>
|
|
</div>
|
|
</Dialog>
|
|
|
|
<Dialog
|
|
open={setPasswordOpen && !setPasswordPending}
|
|
title={t("wa_password_modal_title")}
|
|
description={t("wa_password_modal_desc")}
|
|
closeLabel={t("wa_close")}
|
|
onclose={closeSetPasswordDialog}
|
|
class="payment-dialog-card"
|
|
>
|
|
<div class="payment-dialog-body">
|
|
<Input
|
|
bind:value={setPasswordValue}
|
|
type="password"
|
|
placeholder={t("wa_password_new_placeholder")}
|
|
autocomplete="new-password"
|
|
/>
|
|
<Input
|
|
bind:value={setPasswordConfirm}
|
|
type="password"
|
|
placeholder={t("wa_password_confirm_placeholder")}
|
|
autocomplete="new-password"
|
|
on:keydown={(event) => {
|
|
if (event.key !== "Enter") return;
|
|
event.preventDefault();
|
|
requestSetPasswordCode();
|
|
}}
|
|
/>
|
|
<Button
|
|
class="wide bottom-action payment-submit-button"
|
|
onclick={requestSetPasswordCode}
|
|
disabled={setPasswordBusy}
|
|
>
|
|
<LockKeyhole size={17} />
|
|
{t("wa_password_send_code_action")}
|
|
</Button>
|
|
{#if setPasswordStatus}
|
|
<StatusMessage error={setPasswordIsError}>{setPasswordStatus}</StatusMessage>
|
|
{/if}
|
|
</div>
|
|
</Dialog>
|
|
|
|
{#if setPasswordOpen && setPasswordPending}
|
|
<div class="email-code-fullscreen" role="dialog" aria-modal="true">
|
|
<EmailCodeScreen
|
|
bind:code={setPasswordCode}
|
|
email={setPasswordEmail || ""}
|
|
busy={setPasswordBusy}
|
|
resendCooldown={setPasswordResendCooldown}
|
|
status={setPasswordStatus}
|
|
isError={setPasswordIsError}
|
|
{t}
|
|
onBack={closeSetPasswordDialog}
|
|
onConfirm={confirmSetPassword}
|
|
onResend={requestSetPasswordCode}
|
|
/>
|
|
</div>
|
|
{/if}
|
|
|
|
{#if linkEmailOpen && linkEmailPending}
|
|
<div class="email-code-fullscreen" role="dialog" aria-modal="true">
|
|
<EmailCodeScreen
|
|
bind:code={linkEmailCode}
|
|
email={linkEmailPending}
|
|
busy={linkEmailBusy}
|
|
resendCooldown={linkEmailResendCooldown}
|
|
status={linkEmailStatus}
|
|
isError={linkEmailIsError}
|
|
{t}
|
|
onBack={closeLinkEmailDialog}
|
|
onConfirm={verifyLinkEmailCode}
|
|
onResend={requestLinkEmailCode}
|
|
/>
|
|
</div>
|
|
{/if}
|
|
|
|
<Dialog
|
|
open={linkEmailOpen && !linkEmailPending}
|
|
title={t("wa_link_email_modal_title")}
|
|
description={t("wa_link_email_modal_desc")}
|
|
closeLabel={t("wa_close")}
|
|
onclose={closeLinkEmailDialog}
|
|
class="payment-dialog-card"
|
|
>
|
|
<div class="payment-dialog-body">
|
|
<div class="field-error-wrap">
|
|
<Tooltip.Root open={Boolean(linkEmailFieldError)}>
|
|
<Input
|
|
bind:value={linkEmailValue}
|
|
type="email"
|
|
placeholder={t("wa_email_placeholder")}
|
|
autocomplete="email"
|
|
class={linkEmailFieldError ? "input-error" : ""}
|
|
on:input={() => (linkEmailFieldError = "")}
|
|
/>
|
|
{#if linkEmailFieldError}
|
|
<Tooltip.Trigger class="field-error-trigger" aria-label={linkEmailFieldError}>
|
|
<span class="field-error-icon" aria-hidden="true"><TriangleAlert size={18} /></span>
|
|
</Tooltip.Trigger>
|
|
{/if}
|
|
{#if linkEmailFieldError}
|
|
<Tooltip.Portal>
|
|
<Tooltip.Content class="field-error-tooltip">{linkEmailFieldError}</Tooltip.Content>
|
|
</Tooltip.Portal>
|
|
{/if}
|
|
</Tooltip.Root>
|
|
</div>
|
|
<Button
|
|
class="wide bottom-action payment-submit-button"
|
|
onclick={requestLinkEmailCode}
|
|
disabled={linkEmailBusy}
|
|
>
|
|
{t("wa_send_code_email")}
|
|
</Button>
|
|
{#if linkEmailStatus}
|
|
<StatusMessage error={linkEmailIsError}>{linkEmailStatus}</StatusMessage>
|
|
{/if}
|
|
</div>
|
|
</Dialog>
|