Skip to content

Commit c779010

Browse files
committed
SSO pay
1 parent 28c4162 commit c779010

File tree

3 files changed

+256
-110
lines changed

3 files changed

+256
-110
lines changed

apps/dashboard/app/(main)/billing/components/cancel-subscription-dialog.tsx

Lines changed: 110 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
"use client";
22

3-
import { CalendarIcon, LightningIcon } from "@phosphor-icons/react";
3+
import {
4+
CalendarIcon,
5+
CircleNotchIcon,
6+
LightningIcon,
7+
WarningCircleIcon,
8+
} from "@phosphor-icons/react";
9+
import dayjs from "dayjs";
10+
import { useState } from "react";
11+
import { Badge } from "@/components/ui/badge";
412
import { Button } from "@/components/ui/button";
513
import {
614
Dialog,
@@ -20,6 +28,8 @@ interface CancelSubscriptionDialogProps {
2028
isLoading: boolean;
2129
}
2230

31+
type CancelOption = "end_of_period" | "immediate" | null;
32+
2333
export function CancelSubscriptionDialog({
2434
open,
2535
onOpenChange,
@@ -28,82 +38,135 @@ export function CancelSubscriptionDialog({
2838
currentPeriodEnd,
2939
isLoading,
3040
}: CancelSubscriptionDialogProps) {
41+
const [selected, setSelected] = useState<CancelOption>(null);
42+
const [confirming, setConfirming] = useState(false);
43+
3144
const periodEndDate = currentPeriodEnd
32-
? new Date(currentPeriodEnd).toLocaleDateString()
45+
? dayjs(currentPeriodEnd).format("MMMM D, YYYY")
3346
: null;
3447

48+
const handleConfirm = async () => {
49+
if (!selected) return;
50+
setConfirming(true);
51+
await onCancel(selected === "immediate");
52+
setConfirming(false);
53+
onOpenChange(false);
54+
setSelected(null);
55+
};
56+
57+
const handleClose = () => {
58+
onOpenChange(false);
59+
setSelected(null);
60+
};
61+
3562
return (
36-
<Dialog onOpenChange={onOpenChange} open={open}>
37-
<DialogContent className="max-w-lg">
38-
<DialogHeader className="space-y-3">
39-
<DialogTitle className="text-xl">Cancel {planName}</DialogTitle>
63+
<Dialog onOpenChange={handleClose} open={open}>
64+
<DialogContent className="w-[95vw] max-w-md sm:w-full">
65+
<DialogHeader>
66+
<DialogTitle>Cancel {planName}</DialogTitle>
4067
<DialogDescription>
41-
Choose how you'd like to cancel your subscription
68+
Choose when you'd like to cancel your subscription
4269
</DialogDescription>
4370
</DialogHeader>
4471

45-
<div className="space-y-3 py-4">
72+
<div className="space-y-2">
73+
{/* End of period option */}
4674
<button
47-
className="w-full cursor-pointer rounded-lg border p-4 text-left transition-colors hover:bg-muted/50 disabled:cursor-default disabled:opacity-50"
48-
disabled={isLoading}
49-
onClick={() => {
50-
onCancel(false);
51-
onOpenChange(false);
52-
}}
75+
className={`w-full rounded border p-4 text-left transition-all ${
76+
selected === "end_of_period"
77+
? "border-primary bg-primary/5 ring-1 ring-primary"
78+
: "hover:bg-accent/50"
79+
} disabled:cursor-not-allowed disabled:opacity-50`}
80+
disabled={isLoading || confirming}
81+
onClick={() => setSelected("end_of_period")}
5382
type="button"
5483
>
55-
<div className="mb-2 flex items-center gap-3">
56-
<div className="flex size-8 items-center justify-center rounded-full bg-blue-100">
57-
<CalendarIcon className="text-foreground" size={16} />
84+
<div className="flex items-start gap-3">
85+
<div className="flex size-10 shrink-0 items-center justify-center rounded border bg-accent">
86+
<CalendarIcon
87+
className="text-accent-foreground"
88+
size={20}
89+
weight="duotone"
90+
/>
5891
</div>
59-
<div>
60-
<div className="font-medium">Cancel at period end</div>
61-
<div className="text-muted-foreground text-sm">Recommended</div>
92+
<div className="flex-1">
93+
<div className="flex items-center gap-2">
94+
<span className="font-medium">Cancel at period end</span>
95+
<Badge variant="secondary">Recommended</Badge>
96+
</div>
97+
<p className="mt-1 text-muted-foreground text-sm">
98+
{periodEndDate
99+
? `Keep access until ${periodEndDate}`
100+
: "Keep access until your billing period ends"}
101+
</p>
62102
</div>
63103
</div>
64-
<p className="ml-11 text-muted-foreground text-sm">
65-
{periodEndDate
66-
? `Keep access until ${periodEndDate}. No additional charges.`
67-
: "Keep access until your current billing period ends. No additional charges."}
68-
</p>
69104
</button>
70105

106+
{/* Immediate option */}
71107
<button
72-
className="w-full cursor-pointer rounded-lg border p-4 text-left transition-colors hover:bg-muted/50 disabled:cursor-default disabled:opacity-50"
73-
disabled={isLoading}
74-
onClick={() => {
75-
onCancel(true);
76-
onOpenChange(false);
77-
}}
108+
className={`w-full rounded border p-4 text-left transition-all ${
109+
selected === "immediate"
110+
? "border-destructive bg-destructive/5 ring-1 ring-destructive"
111+
: "hover:bg-accent/50"
112+
} disabled:cursor-not-allowed disabled:opacity-50`}
113+
disabled={isLoading || confirming}
114+
onClick={() => setSelected("immediate")}
78115
type="button"
79116
>
80-
<div className="mb-2 flex items-center gap-3">
81-
<div className="flex size-8 items-center justify-center rounded-full bg-orange-100">
82-
<LightningIcon className="text-orange-600" size={16} />
117+
<div className="flex items-start gap-3">
118+
<div className="flex size-10 shrink-0 items-center justify-center rounded border border-destructive/20 bg-destructive/10">
119+
<LightningIcon
120+
className="text-destructive"
121+
size={20}
122+
weight="duotone"
123+
/>
83124
</div>
84-
<div>
85-
<div className="font-medium">Cancel immediately</div>
86-
<div className="text-muted-foreground text-sm">
87-
Lose access now
88-
</div>
125+
<div className="flex-1">
126+
<span className="font-medium">Cancel immediately</span>
127+
<p className="mt-1 text-muted-foreground text-sm">
128+
Lose access now. Any pending usage will be invoiced.
129+
</p>
89130
</div>
90131
</div>
91-
<p className="ml-11 text-muted-foreground text-sm">
92-
Access ends immediately. You'll be invoiced for any pending usage
93-
charges.
94-
</p>
95132
</button>
96133
</div>
97134

98-
<DialogFooter>
135+
{/* Warning for immediate cancellation */}
136+
{selected === "immediate" && (
137+
<div className="flex items-start gap-2 rounded border border-destructive/20 bg-destructive/5 p-3 text-sm">
138+
<WarningCircleIcon
139+
className="mt-0.5 shrink-0 text-destructive"
140+
size={16}
141+
weight="fill"
142+
/>
143+
<span className="text-destructive">
144+
This action cannot be undone. You will lose access to all{" "}
145+
{planName} features immediately.
146+
</span>
147+
</div>
148+
)}
149+
150+
<DialogFooter className="flex-col gap-2 sm:flex-row">
99151
<Button
100-
className="cursor-pointer"
101-
disabled={isLoading}
102-
onClick={() => onOpenChange(false)}
152+
className="w-full sm:w-auto"
153+
disabled={isLoading || confirming}
154+
onClick={handleClose}
103155
variant="outline"
104156
>
105157
Keep subscription
106158
</Button>
159+
<Button
160+
className="w-full sm:w-auto"
161+
disabled={!selected || isLoading || confirming}
162+
onClick={handleConfirm}
163+
variant={selected === "immediate" ? "destructive" : "default"}
164+
>
165+
{confirming && (
166+
<CircleNotchIcon className="mr-2 size-4 animate-spin" />
167+
)}
168+
{selected === "immediate" ? "Cancel now" : "Confirm cancellation"}
169+
</Button>
107170
</DialogFooter>
108171
</DialogContent>
109172
</Dialog>

0 commit comments

Comments
 (0)