mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { KeyRound, CreditCard, Mail, ChevronRight, Eye, EyeOff, ShieldAlert } from "lucide-react";
|
||||
import { KeyRound, CreditCard, Mail, ChevronRight, Eye, EyeOff, ShieldAlert, Trash2 } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
@@ -9,9 +9,20 @@ import { Separator } from "@/components/ui/separator";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from "@/components/ui/alert-dialog";
|
||||
import { useAuth } from "@/contexts/AuthContext";
|
||||
import { useProfile } from "@/contexts/ProfileProvider";
|
||||
import { useClientTiers } from "@/contexts/ClientTiersProvider";
|
||||
import { useDateFormat } from "@/hooks/useDateFormat";
|
||||
import api from "@/utils/api.util";
|
||||
import { toast } from "sonner";
|
||||
|
||||
@@ -132,6 +143,7 @@ const TIER_COLORS = {
|
||||
function SubscriptionSection() {
|
||||
const navigate = useNavigate();
|
||||
const { myTier, tierLoading, getMyTier, payments, paymentsLoading, getMyPayments } = useClientTiers();
|
||||
const { fmtDate, fmtNumber } = useDateFormat();
|
||||
|
||||
useEffect(() => {
|
||||
getMyTier();
|
||||
@@ -139,9 +151,7 @@ function SubscriptionSection() {
|
||||
}, []);
|
||||
|
||||
const tier = myTier?.tier ?? "free";
|
||||
const expiresAt = myTier?.expires_at
|
||||
? new Date(myTier.expires_at).toLocaleDateString("en-US", { month: "long", day: "numeric", year: "numeric" })
|
||||
: null;
|
||||
const expiresAt = myTier?.expires_at ? fmtDate(myTier.expires_at) : null;
|
||||
|
||||
return (
|
||||
<div className="space-y-5">
|
||||
@@ -192,11 +202,11 @@ function SubscriptionSection() {
|
||||
{payments.map((p) => (
|
||||
<tr key={p.payment_id} className="hover:bg-muted/30 transition-colors">
|
||||
<td className="px-4 py-3 text-muted-foreground">
|
||||
{new Date(p.createdAt).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" })}
|
||||
{fmtDate(p.createdAt)}
|
||||
</td>
|
||||
<td className="px-4 py-3 capitalize">{p.plan?.tier ?? "—"}</td>
|
||||
<td className="px-4 py-3">
|
||||
{p.currency} {Number(p.amount ?? 0).toLocaleString("en-US", { minimumFractionDigits: 2 })}
|
||||
{p.currency} {fmtNumber(p.amount ?? 0)}
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
<Badge variant={p.status === "completed" ? "outline" : ""} className="capitalize text-xs">
|
||||
@@ -268,6 +278,71 @@ function NewsletterSection() {
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Delete Account ───────────────────────────────────────────────────────────
|
||||
|
||||
function DeleteAccountSection({ logout }) {
|
||||
const navigate = useNavigate();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleDelete = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
await api.delete("/client/profile");
|
||||
toast.success("Account deleted. Goodbye!");
|
||||
await logout();
|
||||
navigate("/login");
|
||||
} catch (err) {
|
||||
toast.error(err?.response?.data?.message ?? "Could not delete account.");
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="space-y-1">
|
||||
<p className="text-sm font-medium text-destructive">Delete account</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Permanently remove your account and all associated data. This action cannot be undone.
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
className="shrink-0"
|
||||
onClick={() => setOpen(true)}
|
||||
>
|
||||
<Trash2 className="size-3.5 mr-1.5" />
|
||||
Delete account
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<AlertDialog open={open} onOpenChange={setOpen}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Delete your account?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This will permanently delete your account and sign you out of all sessions.
|
||||
Your data cannot be recovered after deletion.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel disabled={loading}>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction
|
||||
onClick={handleDelete}
|
||||
disabled={loading}
|
||||
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||
>
|
||||
{loading ? "Deleting…" : "Yes, delete my account"}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Page ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
export default function AccountSettings() {
|
||||
@@ -292,6 +367,10 @@ export default function AccountSettings() {
|
||||
<Section icon={Mail} title="Newsletter" description="Choose what emails you want to receive from us.">
|
||||
<NewsletterSection />
|
||||
</Section>
|
||||
|
||||
<Section icon={Trash2} title="Danger Zone" description="Irreversible account actions.">
|
||||
<DeleteAccountSection logout={logout} />
|
||||
</Section>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user