- Updated English, Nepali, and Chinese locale files to include new translations for the "apply rebate to payout" feature, enhancing clarity on its functionality. - Added new export options for previewing CSV and Excel files in reports, improving user experience with clearer export capabilities. - Enhanced internationalization support across multiple locales to ensure consistent messaging in the admin interface.
154 lines
5.0 KiB
TypeScript
154 lines
5.0 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { toast } from "sonner";
|
|
import { Loader2 } from "lucide-react";
|
|
|
|
import { putAdminUser } from "@/api/admin-users";
|
|
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(() => {
|
|
queueMicrotask(() => {
|
|
if (adminProfile) {
|
|
setNickname(adminProfile.nickname ?? "");
|
|
}
|
|
});
|
|
}, [adminProfile]);
|
|
|
|
async function handleUpdateProfile() {
|
|
if (!nickname.trim()) {
|
|
toast.error(t("validation.required", { field: t("fields.nickname") }));
|
|
return;
|
|
}
|
|
if (!adminProfile) {
|
|
toast.error(t("actions.updateFailed"));
|
|
return;
|
|
}
|
|
setLoading(true);
|
|
try {
|
|
await putAdminUser(adminProfile.id, { 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;
|
|
}
|
|
if (!adminProfile) {
|
|
toast.error(t("actions.updateFailed"));
|
|
return;
|
|
}
|
|
setLoading(true);
|
|
try {
|
|
await putAdminUser(adminProfile.id, { 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>
|
|
);
|
|
}
|