Fixed issue with one-time payments; Updated packages
This commit is contained in:
@@ -28,7 +28,7 @@
|
||||
"@kit/tsconfig": "workspace:*",
|
||||
"@kit/ui": "workspace:^",
|
||||
"@supabase/supabase-js": "^2.43.2",
|
||||
"@types/react": "^18.3.1",
|
||||
"@types/react": "^18.3.2",
|
||||
"date-fns": "^3.6.0",
|
||||
"lucide-react": "^0.378.0",
|
||||
"next": "14.2.3",
|
||||
|
||||
@@ -48,6 +48,8 @@ export function PlanPicker(
|
||||
pending?: boolean;
|
||||
}>,
|
||||
) {
|
||||
const { t } = useTranslation(`billing`);
|
||||
|
||||
const intervals = useMemo(
|
||||
() => getPlanIntervals(props.config),
|
||||
[props.config],
|
||||
@@ -61,7 +63,7 @@ export function PlanPicker(
|
||||
.object({
|
||||
planId: z.string(),
|
||||
productId: z.string(),
|
||||
interval: z.string(),
|
||||
interval: z.string().optional(),
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
@@ -76,7 +78,22 @@ export function PlanPicker(
|
||||
return false;
|
||||
}
|
||||
},
|
||||
{ message: `Please pick a plan to continue`, path: ['planId'] },
|
||||
{ message: t('noPlanChosen'), path: ['planId'] },
|
||||
)
|
||||
.refine(
|
||||
(data) => {
|
||||
try {
|
||||
const { plan } = getProductPlanPair(props.config, data.planId);
|
||||
|
||||
return !(plan.paymentType === 'recurring' && !data.interval);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
{
|
||||
message: t('noIntervalPlanChosen'),
|
||||
path: ['interval'],
|
||||
},
|
||||
),
|
||||
),
|
||||
defaultValues: {
|
||||
@@ -100,8 +117,6 @@ export function PlanPicker(
|
||||
}
|
||||
}, [props.config, planId]);
|
||||
|
||||
const { t } = useTranslation(`billing`);
|
||||
|
||||
// display the period picker if the selected plan is recurring or if no plan is selected
|
||||
const isRecurringPlan =
|
||||
selectedPlan?.paymentType === 'recurring' || !selectedPlan;
|
||||
@@ -117,82 +132,88 @@ export function PlanPicker(
|
||||
className={'flex w-full max-w-xl flex-col space-y-4'}
|
||||
onSubmit={form.handleSubmit(props.onSubmit)}
|
||||
>
|
||||
<div
|
||||
className={cn('transition-all', {
|
||||
['pointer-events-none opacity-50']: !isRecurringPlan,
|
||||
})}
|
||||
>
|
||||
<FormField
|
||||
name={'interval'}
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem className={'rounded-md border p-4'}>
|
||||
<FormLabel htmlFor={'plan-picker-id'}>
|
||||
<Trans i18nKey={'common:billingInterval.label'} />
|
||||
</FormLabel>
|
||||
<If condition={intervals.length}>
|
||||
<div
|
||||
className={cn('transition-all', {
|
||||
['pointer-events-none opacity-50']: !isRecurringPlan,
|
||||
})}
|
||||
>
|
||||
<FormField
|
||||
name={'interval'}
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<FormItem className={'rounded-md border p-4'}>
|
||||
<FormLabel htmlFor={'plan-picker-id'}>
|
||||
<Trans i18nKey={'common:billingInterval.label'} />
|
||||
</FormLabel>
|
||||
|
||||
<FormControl id={'plan-picker-id'}>
|
||||
<RadioGroup name={field.name} value={field.value}>
|
||||
<div className={'flex space-x-2.5'}>
|
||||
{intervals.map((interval) => {
|
||||
const selected = field.value === interval;
|
||||
<FormControl id={'plan-picker-id'}>
|
||||
<RadioGroup name={field.name} value={field.value}>
|
||||
<div className={'flex space-x-2.5'}>
|
||||
{intervals.map((interval) => {
|
||||
const selected = field.value === interval;
|
||||
|
||||
return (
|
||||
<label
|
||||
htmlFor={interval}
|
||||
key={interval}
|
||||
className={cn(
|
||||
'flex items-center space-x-2 rounded-md border border-transparent px-4 py-2 transition-colors',
|
||||
{
|
||||
['border-primary']: selected,
|
||||
['hover:border-primary']: !selected,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<RadioGroupItem
|
||||
id={interval}
|
||||
value={interval}
|
||||
onClick={() => {
|
||||
form.setValue('interval', interval, {
|
||||
shouldValidate: true,
|
||||
});
|
||||
|
||||
if (selectedProduct) {
|
||||
const plan = selectedProduct.plans.find(
|
||||
(item) => item.interval === interval,
|
||||
);
|
||||
|
||||
form.setValue('planId', plan?.id ?? '', {
|
||||
shouldValidate: true,
|
||||
shouldDirty: true,
|
||||
shouldTouch: true,
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<span
|
||||
className={cn('text-sm', {
|
||||
['cursor-pointer']: !selected,
|
||||
})}
|
||||
return (
|
||||
<label
|
||||
htmlFor={interval}
|
||||
key={interval}
|
||||
className={cn(
|
||||
'flex items-center space-x-2 rounded-md border border-transparent px-4 py-2 transition-colors',
|
||||
{
|
||||
['border-primary']: selected,
|
||||
['hover:border-primary']: !selected,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<Trans
|
||||
i18nKey={`billing:billingInterval.${interval}`}
|
||||
/>
|
||||
</span>
|
||||
</label>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
<RadioGroupItem
|
||||
id={interval}
|
||||
value={interval}
|
||||
onClick={() => {
|
||||
form.setValue('interval', interval, {
|
||||
shouldValidate: true,
|
||||
});
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
if (selectedProduct) {
|
||||
const plan = selectedProduct.plans.find(
|
||||
(item) => item.interval === interval,
|
||||
);
|
||||
|
||||
form.setValue(
|
||||
'planId',
|
||||
plan?.id ?? '',
|
||||
{
|
||||
shouldValidate: true,
|
||||
shouldDirty: true,
|
||||
shouldTouch: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<span
|
||||
className={cn('text-sm', {
|
||||
['cursor-pointer']: !selected,
|
||||
})}
|
||||
>
|
||||
<Trans
|
||||
i18nKey={`billing:billingInterval.${interval}`}
|
||||
/>
|
||||
</span>
|
||||
</label>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</If>
|
||||
|
||||
<FormField
|
||||
name={'planId'}
|
||||
|
||||
Reference in New Issue
Block a user