365 lines
10 KiB
Vue
365 lines
10 KiB
Vue
<template>
|
||
<Dialog
|
||
:options="{
|
||
title: $t('Subscribe Now'),
|
||
size: '2xl'
|
||
}"
|
||
v-model="showDialog"
|
||
>
|
||
<template v-slot:body-content>
|
||
<div class="border-0">
|
||
<p class="text-base text-gray-800">
|
||
{{ $t('You are just a few steps away from creating your first website.') }}
|
||
</p>
|
||
<div class="mt-6 space-y-6">
|
||
<!-- 第一步:选择计划 -->
|
||
<div>
|
||
<div v-if="step == 1">
|
||
<div class="flex items-center space-x-2">
|
||
<TextInsideCircle>1</TextInsideCircle>
|
||
<span class="text-base font-medium">{{ $t('Select Website Plan') }}</span>
|
||
</div>
|
||
<div class="pl-7">
|
||
<SitePlansCards
|
||
:hideRestrictedPlans="true"
|
||
v-model="selectedPlan"
|
||
class="mt-4"
|
||
/>
|
||
<p></p>
|
||
<div class="flex w-full justify-end">
|
||
<Button
|
||
class="mt-2 w-full sm:w-fit"
|
||
variant="solid"
|
||
@click="confirmPlan"
|
||
>
|
||
{{ $t('Confirm Plan') }}
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div v-else>
|
||
<div class="flex items-center justify-between space-x-2">
|
||
<div class="flex items-center space-x-2">
|
||
<TextInsideCircle>1</TextInsideCircle>
|
||
<span class="text-base font-medium">
|
||
{{ $t('Website Plan Selected ({name})', { name: selectedPlan.name }) }}
|
||
</span>
|
||
</div>
|
||
<div
|
||
class="grid h-4 w-4 place-items-center rounded-full bg-green-500/90"
|
||
>
|
||
<i-lucide-check class="h-3 w-3 text-white" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- 第二步:更新账单信息 -->
|
||
<div
|
||
class="rounded-md"
|
||
:class="{ 'pointer-events-none opacity-50': step < 2 }"
|
||
>
|
||
<div v-if="step <= 2">
|
||
<div class="flex items-center space-x-2">
|
||
<TextInsideCircle>2</TextInsideCircle>
|
||
<span class="text-base font-medium">
|
||
{{ $t('Update Billing Information') }}
|
||
</span>
|
||
</div>
|
||
<div class="pl-7" v-if="step == 2">
|
||
<UpdateBillingDetailsForm
|
||
@updated="onBillingAddresUpdateSuccess"
|
||
/>
|
||
</div>
|
||
</div>
|
||
<div v-else>
|
||
<div class="flex items-center justify-between space-x-2">
|
||
<div class="flex items-center space-x-2">
|
||
<TextInsideCircle>2</TextInsideCircle>
|
||
<span class="text-base font-medium">
|
||
{{ $t('Billing Information Updated') }}
|
||
</span>
|
||
</div>
|
||
<div
|
||
class="grid h-4 w-4 place-items-center rounded-full bg-green-500/90"
|
||
>
|
||
<i-lucide-check class="h-3 w-3 text-white" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- 第三步:添加支付方式 -->
|
||
<div
|
||
class="rounded-md"
|
||
:class="{ 'pointer-events-none opacity-50': step < 3 }"
|
||
>
|
||
<div v-if="step <= 3">
|
||
<div class="flex items-center space-x-2">
|
||
<TextInsideCircle>3</TextInsideCircle>
|
||
<span class="text-base font-medium">{{ $t('Add Payment Method') }}</span>
|
||
</div>
|
||
|
||
<div class="mt-4 pl-7" v-if="step == 3">
|
||
<!-- 支付方式选择器 -->
|
||
<div
|
||
class="flex w-full flex-row gap-2 rounded-md border p-1 text-p-base text-gray-800"
|
||
>
|
||
<div
|
||
class="w-1/2 cursor-pointer rounded-sm py-1.5 text-center transition-all"
|
||
:class="{
|
||
'bg-gray-100': isAutomatedBilling
|
||
}"
|
||
@click="isAutomatedBilling = true"
|
||
>
|
||
{{ $t('Automated Billing') }}
|
||
</div>
|
||
<div
|
||
class="w-1/2 cursor-pointer rounded-sm py-1.5 text-center transition-all"
|
||
:class="{
|
||
'bg-gray-100': !isAutomatedBilling
|
||
}"
|
||
@click="isAutomatedBilling = false"
|
||
>
|
||
{{ $t('Recharge') }}
|
||
</div>
|
||
</div>
|
||
<div class="mt-2 w-full">
|
||
<!-- 自动计费部分 -->
|
||
<div v-if="isAutomatedBilling">
|
||
<!-- Stripe 卡 -->
|
||
<StripeCard2
|
||
@complete="onAddCardSuccess"
|
||
:withoutAddress="true"
|
||
/>
|
||
</div>
|
||
<!-- 购买预付费信用 -->
|
||
<div v-else class="mt-3">
|
||
<BuyPrepaidCreditsForm
|
||
:isOnboarding="true"
|
||
:minimumAmount="minimumAmount"
|
||
@success="onBuyCreditsSuccess"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- 支付方式已添加 -->
|
||
<div v-else>
|
||
<div class="flex items-center justify-between space-x-2">
|
||
<div class="flex items-center space-x-2">
|
||
<TextInsideCircle>3</TextInsideCircle>
|
||
<span
|
||
class="text-base font-medium"
|
||
v-if="$team.pg.payment_mode === 'Card'"
|
||
>
|
||
{{ $t('Automated Billing Setup Completed') }}
|
||
</span>
|
||
<span
|
||
class="text-base font-medium"
|
||
v-if="$team.pg.payment_mode === 'Prepaid Credits'"
|
||
>
|
||
{{ $t('Prepaid Credits Payment Enabled') }}
|
||
</span>
|
||
</div>
|
||
<div
|
||
class="grid h-4 w-4 place-items-center rounded-full bg-green-500/90"
|
||
>
|
||
<i-lucide-check class="h-3 w-3 text-white" />
|
||
</div>
|
||
</div>
|
||
<div
|
||
class="mt-1.5 pl-7 text-p-base text-gray-800"
|
||
v-if="$team.pg.payment_mode === 'Prepaid Credits'"
|
||
>
|
||
{{ $t('Account Balance') }}: {{ $format.userCurrency($team.pg.balance) }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<!-- 第4步:确认 -->
|
||
<div>
|
||
<div
|
||
v-if="step < 4"
|
||
class="pointer-events-none rounded-md opacity-50"
|
||
>
|
||
<div class="flex items-center justify-between space-x-2">
|
||
<div class="flex items-center space-x-2">
|
||
<TextInsideCircle>4</TextInsideCircle>
|
||
<div class="text-base font-medium">
|
||
{{ $t('Subscription Confirmation') }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div v-else-if="isChangingPlan">
|
||
<div class="flex items-center justify-between space-x-2">
|
||
<div class="flex items-center space-x-2">
|
||
<TextInsideCircle>4</TextInsideCircle>
|
||
<div class="text-base font-medium">{{ $t('Updating Site Plan') }}</div>
|
||
</div>
|
||
</div>
|
||
<div class="mt-3 pl-7">
|
||
<Button :loading="true" :loadingText="$t('Updating site plan...')">
|
||
{{ $t('Site Plan Updated') }}
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
<div v-else>
|
||
<div class="flex items-center justify-between space-x-2">
|
||
<div class="flex items-center space-x-2">
|
||
<TextInsideCircle>4</TextInsideCircle>
|
||
<span class="text-base font-medium">
|
||
🎉 {{ $t('Subscription Confirmed') }}
|
||
</span>
|
||
</div>
|
||
<div
|
||
class="grid h-4 w-4 place-items-center rounded-full bg-green-500/90"
|
||
>
|
||
<i-lucide-check class="h-3 w-3 text-white" />
|
||
</div>
|
||
</div>
|
||
<div class="mt-1.5 pl-7">
|
||
<div class="flex items-center space-x-2">
|
||
<span class="text-p-base text-gray-800">
|
||
{{ $t('Your subscription has been confirmed.') }}<br />
|
||
{{ $t('If any of your sites have been disabled, they will be re-enabled soon.') }}
|
||
</span>
|
||
</div>
|
||
<div class="flex w-full justify-end">
|
||
<Button
|
||
class="mt-2 w-full sm:w-fit"
|
||
variant="solid"
|
||
iconRight="arrow-right"
|
||
@click="showDialog = false"
|
||
>
|
||
{{ $t('Return to Dashboard') }}
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</Dialog>
|
||
</template>
|
||
<script>
|
||
import { createResource } from 'jingrow-ui';
|
||
import { defineAsyncComponent } from 'vue';
|
||
import TextInsideCircle from './TextInsideCircle.vue';
|
||
import { toast } from 'vue-sonner';
|
||
|
||
export default {
|
||
name: 'AppTrialSubscriptionDialog',
|
||
components: {
|
||
SitePlansCards: defineAsyncComponent(() => import('./SitePlansCards.vue')),
|
||
StripeCard2: defineAsyncComponent(() =>
|
||
import('../components/StripeCard.vue')
|
||
),
|
||
UpdateBillingDetailsForm: defineAsyncComponent(() =>
|
||
import('./UpdateBillingDetailsForm.vue')
|
||
),
|
||
CardWithDetails: defineAsyncComponent(() =>
|
||
import('@/components/CardWithDetails.vue')
|
||
),
|
||
BuyPrepaidCreditsForm: defineAsyncComponent(() =>
|
||
import('./BuyPrepaidCreditsForm.vue')
|
||
),
|
||
AlertBanner: defineAsyncComponent(() => import('./AlertBanner.vue')),
|
||
TextInsideCircle
|
||
},
|
||
props: ['site', 'currentPlan', 'trialPlan'],
|
||
data() {
|
||
return {
|
||
step: 1,
|
||
selectedPlan: null,
|
||
billingInformation: {
|
||
cardHolderName: '',
|
||
country: '',
|
||
gstin: ''
|
||
},
|
||
isAutomatedBilling: true,
|
||
isChangingPlan: false
|
||
};
|
||
},
|
||
emits: ['update:modelValue', 'success'],
|
||
mounted() {
|
||
if (this.trialPlan && this.currentPlan !== this.trialPlan) {
|
||
this.selectedPlan = {
|
||
name: this.trialPlan
|
||
};
|
||
} else {
|
||
this.selectedPlan = null;
|
||
}
|
||
},
|
||
methods: {
|
||
confirmPlan() {
|
||
if (!this.selectedPlan) {
|
||
toast.error(this.$t('Please select a plan'));
|
||
return;
|
||
}
|
||
this.step = 2;
|
||
},
|
||
onBillingAddresUpdateSuccess() {
|
||
this.$team.reload();
|
||
if (this.$team.pg.payment_mode) {
|
||
this.step = 4;
|
||
this.changePlan();
|
||
} else {
|
||
this.step = 3;
|
||
}
|
||
},
|
||
onBuyCreditsSuccess() {
|
||
this.$team.reload();
|
||
this.step = 4;
|
||
this.changePlan();
|
||
},
|
||
onAddCardSuccess() {
|
||
this.$team.reload();
|
||
this.step = 4;
|
||
this.changePlan();
|
||
},
|
||
changePlan() {
|
||
if (this.isChangingPlan) return;
|
||
this.isChangingPlan = true;
|
||
const plan_name = this.selectedPlan?.name;
|
||
let request = createResource({
|
||
url: '/api/action/jcloud.api.client.run_pg_method',
|
||
params: {
|
||
dt: 'Site',
|
||
dn: this.site,
|
||
method: 'set_plan',
|
||
args: {
|
||
plan: plan_name
|
||
}
|
||
},
|
||
onSuccess: res => {
|
||
this.isChangingPlan = false;
|
||
this.$team.reload();
|
||
this.$emit('success');
|
||
},
|
||
onError: () => {
|
||
this.isChangingPlan = false;
|
||
toast.error(this.$t('Failed to change plan, please try again later.'));
|
||
this.showDialog = false;
|
||
}
|
||
});
|
||
request.submit();
|
||
}
|
||
},
|
||
computed: {
|
||
isBillingDetailsSet() {
|
||
return Boolean(this.$team.pg.billing_details?.name);
|
||
},
|
||
minimumAmount() {
|
||
return this.$team.pg.currency == 'CNY' ? 0.01 : 0.01;
|
||
},
|
||
showDialog: {
|
||
get() {
|
||
return this.modelValue;
|
||
},
|
||
set(value) {
|
||
this.$emit('update:modelValue', value);
|
||
}
|
||
}
|
||
}
|
||
};
|
||
</script> |