brackt/app/components/user/settings/AccountSection.tsx
chrisp 3273cf1bcb
All checks were successful
🚀 Deploy / ʦ🔍 Typecheck & Lint (push) Successful in 1m17s
🚀 Deploy / 🐳 Build (push) Successful in 1m9s
🚀 Deploy / 🧪 Test (push) Successful in 3m17s
🚀 Deploy / 🚀 Deploy (push) Successful in 14s
claude/settings-menu-urls-kbp6x8 (#116)
Co-authored-by: Claude <noreply@anthropic.com>
Reviewed-on: #116
2026-06-30 16:01:36 +00:00

111 lines
3.7 KiB
TypeScript

import { useState } from "react";
import { Link } from "react-router";
import { Badge } from "~/components/ui/badge";
import { Button } from "~/components/ui/button";
import { authClient } from "~/lib/auth-client";
type LinkedAccount = {
id: string;
providerId: string;
};
type Props = {
email: string;
linkedAccounts: LinkedAccount[];
};
const PROVIDER_LABELS: Record<string, string> = {
credential: "Email & Password",
google: "Google",
discord: "Discord",
};
export function AccountSection({ email, linkedAccounts }: Props) {
const [linkingDiscord, setLinkingDiscord] = useState(false);
const [linkError, setLinkError] = useState<string | null>(null);
const hasPassword = linkedAccounts.some((a) => a.providerId === "credential");
const oauthAccounts = linkedAccounts.filter((a) => a.providerId !== "credential");
const hasDiscord = linkedAccounts.some((a) => a.providerId === "discord");
async function handleConnectDiscord() {
setLinkingDiscord(true);
setLinkError(null);
try {
await authClient.linkSocial({ provider: "discord", callbackURL: "/settings/account" });
} catch {
setLinkError("Failed to connect Discord. Please try again.");
setLinkingDiscord(false);
}
}
return (
<div className="space-y-6">
<div>
<h2 className="text-lg font-semibold">Account</h2>
<p className="text-sm text-muted-foreground">
Your login email and connected providers.
</p>
</div>
<div className="space-y-4">
<div className="rounded-lg border p-4 space-y-3">
<h3 className="text-sm font-medium">Email Address</h3>
<p className="text-sm text-muted-foreground">{email}</p>
</div>
<div className="rounded-lg border p-4 space-y-3">
<h3 className="text-sm font-medium">Sign-in Methods</h3>
<div className="space-y-2">
{hasPassword && (
<div className="flex items-center justify-between">
<span className="text-sm">Email &amp; Password</span>
<Badge variant="secondary">Active</Badge>
</div>
)}
{oauthAccounts.map((account) => (
<div key={account.id} className="flex items-center justify-between">
<span className="text-sm">
{PROVIDER_LABELS[account.providerId] ?? account.providerId}
</span>
<Badge variant="secondary">Connected</Badge>
</div>
))}
{linkedAccounts.length === 0 && (
<p className="text-sm text-muted-foreground">No sign-in methods found.</p>
)}
</div>
{!hasDiscord && (
<div className="space-y-2">
<Button
variant="outline"
size="sm"
onClick={handleConnectDiscord}
disabled={linkingDiscord}
>
{linkingDiscord ? "Redirecting…" : "Connect Discord"}
</Button>
{linkError && (
<p className="text-sm text-destructive">{linkError}</p>
)}
</div>
)}
</div>
{hasPassword && (
<div className="rounded-lg border p-4 space-y-2">
<h3 className="text-sm font-medium">Password</h3>
<p className="text-sm text-muted-foreground">
To change your password, use the forgot password flow.
</p>
<Link
to="/forgot-password"
className="text-sm text-primary underline-offset-4 hover:underline"
>
Reset password
</Link>
</div>
)}
</div>
</div>
);
}