* Add opt-in Discord ping to standings notifications Users who have linked their Discord account can enable a ping preference in Settings > Notifications. When enabled, their bracket username is replaced with a Discord @mention (<@userId>) in league standings update messages, sending them a push notification and showing their Discord display name. Adds discordPingEnabled column to users, a findDiscordIdsByUserIds model helper, allowed_mentions support to the Discord webhook payload, and a NotificationsSection settings component. https://claude.ai/code/session_01NUv93WRrufHhpZSMyY4bjs * Remove Display Name field from user profile settings Username is the only user-facing name field. The display_name column remains in the database since BetterAuth writes the OAuth provider name there, and it serves as a programmatic fallback via getUserDisplayName. https://claude.ai/code/session_01NUv93WRrufHhpZSMyY4bjs * Address code review feedback on Discord ping feature - Fix broken Account settings link in NotificationsSection: replace the non-functional ?section=account href with an onNavigateToAccount callback that drives the parent's useState-based section switcher - Replace hand-rolled toggle button with the project's Switch component (Radix UI) for consistent sizing, accessibility, and dark-mode support - Guard update-discord-ping action: return an error if the user attempts to enable pings without a Discord account linked - Surface action error in NotificationsSection UI - Cap allowed_mentions.users at 100 to avoid Discord rejecting the payload in large leagues - Remove the showWinner alias in scoring-calculator; inline winnerScoreChanged - Add comment to league settings test notification explaining why discordUserId is omitted - Clarify database schema comment: displayName is write-only from BetterAuth https://claude.ai/code/session_01NUv93WRrufHhpZSMyY4bjs --------- Co-authored-by: Claude <noreply@anthropic.com>
81 lines
2.7 KiB
TypeScript
81 lines
2.7 KiB
TypeScript
import { useFetcher } from "react-router";
|
|
import { Switch } from "~/components/ui/switch";
|
|
import { Label } from "~/components/ui/label";
|
|
|
|
type Props = {
|
|
discordPingEnabled: boolean;
|
|
hasDiscordLinked: boolean;
|
|
success?: boolean;
|
|
error?: string;
|
|
onNavigateToAccount: () => void;
|
|
};
|
|
|
|
export function NotificationsSection({ discordPingEnabled, hasDiscordLinked, success, error, onNavigateToAccount }: Props) {
|
|
const fetcher = useFetcher();
|
|
const optimisticEnabled =
|
|
fetcher.state !== "idle"
|
|
? fetcher.formData?.get("discordPingEnabled") === "true"
|
|
: discordPingEnabled;
|
|
|
|
const handleToggle = (checked: boolean) => {
|
|
fetcher.submit(
|
|
{ intent: "update-discord-ping", discordPingEnabled: String(checked) },
|
|
{ method: "post" }
|
|
);
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<div>
|
|
<h2 className="text-lg font-semibold">Notifications</h2>
|
|
<p className="text-sm text-muted-foreground">
|
|
Manage how you receive league notifications.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="rounded-lg border p-4 space-y-4">
|
|
<h3 className="text-sm font-medium">Discord Pings</h3>
|
|
|
|
{!hasDiscordLinked ? (
|
|
<p className="text-sm text-muted-foreground">
|
|
Link your Discord account in{" "}
|
|
<button
|
|
type="button"
|
|
onClick={onNavigateToAccount}
|
|
className="text-primary underline-offset-4 hover:underline"
|
|
>
|
|
Account settings
|
|
</button>{" "}
|
|
to enable Discord pings for league notifications.
|
|
</p>
|
|
) : (
|
|
<>
|
|
<div className="flex items-center justify-between gap-4">
|
|
<div className="space-y-1">
|
|
<Label htmlFor="discord-ping-switch" className="text-sm font-normal">
|
|
Ping me in standings updates
|
|
</Label>
|
|
<p className="text-xs text-muted-foreground">
|
|
When enabled, you'll be @mentioned in league Discord notifications and your
|
|
Discord display name will appear in place of your brackt username.
|
|
</p>
|
|
</div>
|
|
<Switch
|
|
id="discord-ping-switch"
|
|
checked={optimisticEnabled}
|
|
onCheckedChange={handleToggle}
|
|
disabled={fetcher.state !== "idle"}
|
|
/>
|
|
</div>
|
|
{success && (
|
|
<p className="text-sm text-green-600">Notification preference saved.</p>
|
|
)}
|
|
{error && (
|
|
<p className="text-sm text-destructive">{error}</p>
|
|
)}
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|