mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
add: more commits
Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { KeyRound, CreditCard, Mail, ChevronRight, Eye, EyeOff, ShieldAlert, Trash2, Globe } from "lucide-react";
|
||||
import { KeyRound, CreditCard, Mail, Megaphone, 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";
|
||||
@@ -20,12 +20,10 @@ 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";
|
||||
|
||||
@@ -285,74 +283,122 @@ function NewsletterSection() {
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Currency Preference ─────────────────────────────────────────────────────
|
||||
// ─── Advertisements ───────────────────────────────────────────────────────────
|
||||
|
||||
function CurrencySection() {
|
||||
const { profile, getProfile } = useProfile();
|
||||
const { setCurrency } = useCurrencyPreference();
|
||||
const [currencies, setCurrencies] = useState([]);
|
||||
const [localValue, setLocalValue] = useState("USD");
|
||||
const [saving, setSaving] = useState(false);
|
||||
const AD_OPTIONS = [
|
||||
{
|
||||
key: "show_popup_ads",
|
||||
label: "Popup ads",
|
||||
description: "Show promotional popups when you open pages like the dashboard.",
|
||||
},
|
||||
{
|
||||
key: "show_other_ads",
|
||||
label: "Other ads",
|
||||
description: "Show banner, hero, and sidebar advertisements across the site.",
|
||||
},
|
||||
];
|
||||
|
||||
function AdvertisementsSection() {
|
||||
const { profile, getProfile, updateProfile, profileLoading } = useProfile();
|
||||
const [confirmPopupOff, setConfirmPopupOff] = 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 showPopupAds = profile?.personal_info?.show_popup_ads ?? true;
|
||||
const showOtherAds = profile?.personal_info?.show_other_ads ?? true;
|
||||
// No separate stored flag — "hidden" just means both underlying toggles are off,
|
||||
// so it can never drift out of sync with them.
|
||||
const hideAllAds = !showPopupAds && !showOtherAds;
|
||||
|
||||
const savedValue = profile?.preferred_currency ?? "USD";
|
||||
const isDirty = localValue !== savedValue;
|
||||
const handleToggle = async (key, value) => {
|
||||
// Turning popup ads off also turns off other ads — confirm first since
|
||||
// it's a bigger change than the switch being flipped suggests.
|
||||
if (key === "show_popup_ads" && value === false) {
|
||||
setConfirmPopupOff(true);
|
||||
return;
|
||||
}
|
||||
const result = await updateProfile({ [key]: value });
|
||||
if (result?.success) {
|
||||
toast.success("Preference saved.", { description: "Reload the page for this to take effect." });
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
const confirmTurnOffPopupAndOther = async () => {
|
||||
setConfirmPopupOff(false);
|
||||
const result = await updateProfile({ show_popup_ads: false, show_other_ads: false });
|
||||
if (result?.success) {
|
||||
toast.success("Preference saved.", { description: "Reload the page for this to take effect." });
|
||||
}
|
||||
};
|
||||
|
||||
const handleHideAllToggle = async (hide) => {
|
||||
const result = await updateProfile({ show_popup_ads: !hide, show_other_ads: !hide });
|
||||
if (result?.success) {
|
||||
toast.success("Preference saved.", { description: "Reload the page for this to take effect." });
|
||||
}
|
||||
};
|
||||
|
||||
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 className="space-y-4">
|
||||
<div>
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="space-y-0.5">
|
||||
<p className="text-sm font-medium">Hide all ads</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Turn off every advertisement across the platform, popups included.
|
||||
</p>
|
||||
</div>
|
||||
{profileLoading ? (
|
||||
<Skeleton className="h-6 w-11 rounded-full shrink-0" />
|
||||
) : (
|
||||
<Switch checked={hideAllAds} onCheckedChange={handleHideAllToggle} />
|
||||
)}
|
||||
</div>
|
||||
<Separator className="mt-4" />
|
||||
</div>
|
||||
|
||||
{AD_OPTIONS.map((opt, i) => (
|
||||
<div key={opt.key}>
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="space-y-0.5">
|
||||
<p className="text-sm font-medium">{opt.label}</p>
|
||||
<p className="text-xs text-muted-foreground">{opt.description}</p>
|
||||
</div>
|
||||
{profileLoading ? (
|
||||
<Skeleton className="h-6 w-11 rounded-full shrink-0" />
|
||||
) : (
|
||||
<Switch
|
||||
checked={profile?.personal_info?.[opt.key] ?? true}
|
||||
onCheckedChange={(v) => handleToggle(opt.key, v)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{i < AD_OPTIONS.length - 1 && <Separator className="mt-4" />}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{isDirty && (
|
||||
<Button size="sm" onClick={handleSave} disabled={saving}>
|
||||
{saving ? "Saving…" : "Save"}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<AlertDialog open={confirmPopupOff} onOpenChange={setConfirmPopupOff}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Turn off popup ads?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This will also turn off Other ads (banner, hero, and sidebar advertisements).
|
||||
You can turn either back on here anytime.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={confirmTurnOffPopupAndOther}>
|
||||
Turn off both
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -446,8 +492,8 @@ 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 icon={Megaphone} title="Advertisements" description="Control which advertisements you see across the platform.">
|
||||
<AdvertisementsSection />
|
||||
</Section>
|
||||
|
||||
<Section icon={Trash2} title="Danger Zone" description="Irreversible account actions.">
|
||||
|
||||
Reference in New Issue
Block a user