Privacy-first: remove 'https:' from img-src CSP directive to prevent IP leaks via external avatar URLs (Google/Gravatar). AccountCard now shows user initials instead of loading a remote image. Also remove .keys-temp/ from .gitignore (not relevant to this PR). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
79 lines
2.8 KiB
TypeScript
79 lines
2.8 KiB
TypeScript
import { useTranslation } from "react-i18next";
|
|
import { User, LogIn, LogOut, Loader2, AlertCircle } from "lucide-react";
|
|
import { useAuth } from "../../hooks/useAuth";
|
|
|
|
export default function AccountCard() {
|
|
const { t } = useTranslation();
|
|
const { state, login, logout } = useAuth();
|
|
|
|
return (
|
|
<div className="bg-[var(--card)] border border-[var(--border)] rounded-xl p-6 space-y-4">
|
|
<h2 className="text-lg font-semibold flex items-center gap-2">
|
|
<User size={18} />
|
|
{t("account.title")}
|
|
<span className="text-xs font-normal text-[var(--muted-foreground)]">
|
|
{t("account.optional")}
|
|
</span>
|
|
</h2>
|
|
|
|
{state.status === "error" && state.error && (
|
|
<div className="flex items-start gap-2 text-sm text-[var(--negative)]">
|
|
<AlertCircle size={16} className="mt-0.5 shrink-0" />
|
|
<p>{state.error}</p>
|
|
</div>
|
|
)}
|
|
|
|
{state.status === "authenticated" && state.account && (
|
|
<div className="space-y-3">
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-10 h-10 rounded-full bg-[var(--primary)] text-white flex items-center justify-center font-semibold text-sm">
|
|
{(state.account.name || state.account.email).charAt(0).toUpperCase()}
|
|
</div>
|
|
<div>
|
|
<p className="font-medium">
|
|
{state.account.name || state.account.email}
|
|
</p>
|
|
{state.account.name && (
|
|
<p className="text-sm text-[var(--muted-foreground)]">
|
|
{state.account.email}
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<button
|
|
type="button"
|
|
onClick={logout}
|
|
className="flex items-center gap-2 px-4 py-2 border border-[var(--border)] rounded-lg hover:bg-[var(--border)] transition-colors text-sm"
|
|
>
|
|
<LogOut size={14} />
|
|
{t("account.signOut")}
|
|
</button>
|
|
</div>
|
|
)}
|
|
|
|
{(state.status === "unauthenticated" || state.status === "idle") && (
|
|
<div className="space-y-3">
|
|
<p className="text-sm text-[var(--muted-foreground)]">
|
|
{t("account.description")}
|
|
</p>
|
|
<button
|
|
type="button"
|
|
onClick={login}
|
|
className="flex items-center gap-2 px-4 py-2 bg-[var(--primary)] text-white rounded-lg hover:opacity-90 transition-opacity text-sm"
|
|
>
|
|
<LogIn size={14} />
|
|
{t("account.signIn")}
|
|
</button>
|
|
</div>
|
|
)}
|
|
|
|
{state.status === "loading" && (
|
|
<div className="flex items-center gap-2 text-sm text-[var(--muted-foreground)]">
|
|
<Loader2 size={14} className="animate-spin" />
|
|
{t("common.loading")}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|