pushy

Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
2026-06-28 11:30:01 +08:00
parent b03b204861
commit bac7168b1e
100 changed files with 5958 additions and 1976 deletions
+85 -6
View File
@@ -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>
);