testing 101

Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
2026-06-30 19:32:00 +08:00
parent fe47bdea3c
commit 17326b2c2e
78 changed files with 4804 additions and 1054 deletions
+102 -20
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, Trash2 } from "lucide-react";
import { KeyRound, CreditCard, Mail, ChevronRight, Eye, EyeOff, ShieldAlert, Trash2, Globe } 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,6 +9,7 @@ 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 { ScrollArea } from "@/components/ui/scroll-area";
import {
AlertDialog,
AlertDialogAction,
@@ -19,10 +20,12 @@ import {
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { useAuth } from "@/contexts/AuthContext";
import { useProfile } from "@/contexts/ProfileProvider";
import { useClientTiers } from "@/contexts/ClientTiersProvider";
import { useDateFormat } from "@/hooks/useDateFormat";
import { useCurrencyPreference } from "@/contexts/CurrencyPreferenceContext";
import api from "@/utils/api.util";
import { toast } from "sonner";
@@ -190,7 +193,7 @@ function SubscriptionSection() {
) : (
<div className="rounded-lg border overflow-hidden">
<table className="w-full text-sm">
<thead className="bg-muted/50">
<thead className="bg-muted/50 sticky top-0 z-10">
<tr>
<th className="text-left px-4 py-2.5 text-xs font-medium text-muted-foreground">Date</th>
<th className="text-left px-4 py-2.5 text-xs font-medium text-muted-foreground">Plan</th>
@@ -198,25 +201,29 @@ function SubscriptionSection() {
<th className="text-left px-4 py-2.5 text-xs font-medium text-muted-foreground">Status</th>
</tr>
</thead>
<tbody className="divide-y">
{payments.map((p) => (
<tr key={p.payment_id} className="hover:bg-muted/30 transition-colors">
<td className="px-4 py-3 text-muted-foreground">
{fmtDate(p.createdAt)}
</td>
<td className="px-4 py-3 capitalize">{p.plan?.tier ?? "—"}</td>
<td className="px-4 py-3">
{p.currency} {fmtNumber(p.amount ?? 0)}
</td>
<td className="px-4 py-3">
<Badge variant={p.status === "completed" ? "outline" : ""} className="capitalize text-xs">
{p.status}
</Badge>
</td>
</tr>
))}
</tbody>
</table>
<ScrollArea className="h-[192px]">
<table className="w-full text-sm">
<tbody className="divide-y">
{payments.map((p) => (
<tr key={p.payment_id} className="hover:bg-muted/30 transition-colors">
<td className="px-4 py-3 text-muted-foreground">
{fmtDate(p.createdAt)}
</td>
<td className="px-4 py-3 capitalize">{p.plan?.tier ?? "—"}</td>
<td className="px-4 py-3">
{p.currency} {fmtNumber(p.amount ?? 0)}
</td>
<td className="px-4 py-3">
<Badge variant={p.status === "completed" ? "outline" : ""} className="capitalize text-xs">
{p.status}
</Badge>
</td>
</tr>
))}
</tbody>
</table>
</ScrollArea>
</div>
)}
</div>
@@ -278,6 +285,77 @@ function NewsletterSection() {
);
}
// ─── Currency Preference ─────────────────────────────────────────────────────
function CurrencySection() {
const { profile, getProfile } = useProfile();
const { setCurrency } = useCurrencyPreference();
const [currencies, setCurrencies] = useState([]);
const [localValue, setLocalValue] = useState("USD");
const [saving, setSaving] = useState(false);
useEffect(() => {
getProfile();
api.get("/client/tiers/currencies")
.then(({ data }) => setCurrencies(data.data ?? []))
.catch(() => {});
}, []);
// Sync local value when profile loads or changes
useEffect(() => {
if (profile?.preferred_currency) setLocalValue(profile.preferred_currency);
}, [profile?.preferred_currency]);
const savedValue = profile?.preferred_currency ?? "USD";
const isDirty = localValue !== savedValue;
const handleSave = async () => {
setSaving(true);
try {
await api.patch("/client/profile/currency", { currency: localValue });
setCurrency(localValue);
toast.success("Currency preference saved.");
} catch (err) {
toast.error(err?.response?.data?.message ?? "Could not save preference.");
setLocalValue(savedValue); // revert on error
} finally {
setSaving(false);
}
};
return (
<div className="space-y-3">
<div className="space-y-1.5 max-w-xs">
<Label>Preferred currency</Label>
{!profile ? (
<Skeleton className="h-9 w-full" />
) : (
<Select value={localValue} onValueChange={setLocalValue} disabled={saving}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
{currencies.map((c) => (
<SelectItem key={c.code} value={c.code}>
{c.code} — {c.name}
</SelectItem>
))}
</SelectContent>
</Select>
)}
<p className="text-xs text-muted-foreground">
Plans without localized prices always display in USD regardless of this setting.
</p>
</div>
{isDirty && (
<Button size="sm" onClick={handleSave} disabled={saving}>
{saving ? "Saving…" : "Save"}
</Button>
)}
</div>
);
}
// ─── Delete Account ───────────────────────────────────────────────────────────
function DeleteAccountSection({ logout }) {
@@ -368,6 +446,10 @@ export default function AccountSettings() {
<NewsletterSection />
</Section>
<Section icon={Globe} title="Currency Preference" description="Set the currency used to display plan prices across the platform.">
<CurrencySection />
</Section>
<Section icon={Trash2} title="Danger Zone" description="Irreversible account actions.">
<DeleteAccountSection logout={logout} />
</Section>