Files
lotteryAdmin/src/modules/account/account-settings-console.tsx

144 lines
4.7 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
import { Loader2 } from "lucide-react";
import { putAdminMe } from "@/api/admin-auth";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { useAdminProfile, useAdminSessionStore } from "@/stores/admin-session";
import { LotteryApiBizError } from "@/types/api/errors";
export function AccountSettingsConsole() {
const { t } = useTranslation(["common"]);
const adminProfile = useAdminProfile();
const refreshAdminProfile = useAdminSessionStore((s) => s.refreshAdminProfile);
const [nickname, setNickname] = useState("");
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [loading, setLoading] = useState(false);
useEffect(() => {
if (adminProfile) {
setNickname(adminProfile.nickname ?? "");
}
}, [adminProfile]);
async function handleUpdateProfile() {
if (!nickname.trim()) {
toast.error(t("validation.required", { field: t("fields.nickname") }));
return;
}
setLoading(true);
try {
await putAdminMe({ nickname: nickname.trim() });
toast.success(t("actions.updateSuccess"));
void refreshAdminProfile();
} catch (err) {
toast.error(err instanceof LotteryApiBizError ? err.message : t("actions.updateFailed"));
} finally {
setLoading(false);
}
}
async function handleUpdatePassword() {
if (!password) {
toast.error(t("validation.required", { field: t("fields.newPassword") }));
return;
}
if (password !== confirmPassword) {
toast.error(t("validation.passwordMismatch"));
return;
}
setLoading(true);
try {
await putAdminMe({ password });
toast.success(t("actions.updateSuccess"));
setPassword("");
setConfirmPassword("");
} catch (err) {
toast.error(err instanceof LotteryApiBizError ? err.message : t("actions.updateFailed"));
} finally {
setLoading(false);
}
}
return (
<div className="mx-auto flex w-full max-w-4xl flex-col gap-6 p-4 md:p-6 lg:p-8">
<div className="flex flex-col gap-1">
<h1 className="text-xl font-semibold tracking-tight text-[#13315f]">
{t("accountSettings")}
</h1>
<p className="text-sm text-muted-foreground">
{t("accountSettingsDesc")}
</p>
</div>
<Card>
<CardHeader>
<CardTitle className="text-base">{t("profileSettings")}</CardTitle>
<CardDescription>
{t("profileSettingsDesc")}
</CardDescription>
</CardHeader>
<CardContent className="space-y-4 max-w-md">
<div className="space-y-1.5">
<Label htmlFor="nickname">{t("fields.nickname")}</Label>
<Input
id="nickname"
value={nickname}
onChange={(e) => setNickname(e.target.value)}
placeholder={t("placeholders.nickname")}
/>
</div>
<Button onClick={handleUpdateProfile} disabled={loading}>
{loading && <Loader2 className="mr-2 size-4 animate-spin" />}
{t("actions.save")}
</Button>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-base">{t("securitySettings")}</CardTitle>
<CardDescription>
{t("securitySettingsDesc")}
</CardDescription>
</CardHeader>
<CardContent className="space-y-4 max-w-md">
<div className="space-y-1.5">
<Label htmlFor="password">{t("fields.newPassword")}</Label>
<Input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder={t("placeholders.password")}
/>
</div>
<div className="space-y-1.5">
<Label htmlFor="confirm-password">{t("fields.confirmPassword")}</Label>
<Input
id="confirm-password"
type="password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
placeholder={t("placeholders.confirmPassword")}
/>
</div>
<Button onClick={handleUpdatePassword} disabled={loading || !password}>
{loading && <Loader2 className="mr-2 size-4 animate-spin" />}
{t("actions.updatePassword")}
</Button>
</CardContent>
</Card>
</div>
);
}