| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101 |
- "use client";
- import { useState } from "react";
- import { useMutation } from "@tanstack/react-query";
- import { useRouter } from "next/navigation";
- import { DISPLAY_NAME_MAX_LENGTH } from "@/lib/constants";
- import { getSupabaseBrowserClient } from "@/lib/supabase/client";
- import { AvatarColorPicker } from "./avatar-color-picker";
- function validateDisplayName(name: string): string | null {
- const trimmed = name.trim();
- if (trimmed.length === 0) return "Display name is required";
- if (trimmed.length > DISPLAY_NAME_MAX_LENGTH)
- return `Display name must be ${DISPLAY_NAME_MAX_LENGTH} characters or less`;
- if (/<|>/.test(trimmed)) return "Display name cannot contain < or >";
- return null;
- }
- export function DisplayNameForm() {
- const router = useRouter();
- const [displayName, setDisplayName] = useState("");
- const [avatarColor, setAvatarColor] = useState<string | null>(null);
- const [validationError, setValidationError] = useState<string | null>(null);
- const signUpMutation = useMutation({
- mutationFn: async ({ name, color }: { name: string; color: string | null }) => {
- const supabase = getSupabaseBrowserClient();
- const { data: authData, error: authError } = await supabase.auth.signInAnonymously();
- if (authError) throw authError;
- if (!authData.user) throw new Error("Sign-in failed: no user returned");
- const { error: insertError } = await supabase.from("users").insert({
- id: authData.user.id,
- display_name: name,
- avatar_color: color,
- });
- if (insertError) throw insertError;
- return { userId: authData.user.id };
- },
- onSuccess: () => {
- router.push("/recovery");
- },
- });
- function handleSubmit(e: React.FormEvent) {
- e.preventDefault();
- const trimmed = displayName.trim();
- const error = validateDisplayName(trimmed);
- if (error) {
- setValidationError(error);
- return;
- }
- setValidationError(null);
- signUpMutation.mutate({ name: trimmed, color: avatarColor });
- }
- return (
- <form onSubmit={handleSubmit} className="space-y-6">
- <div>
- <label htmlFor="display-name" className="block text-sm font-medium mb-1">
- Display name
- </label>
- <input
- id="display-name"
- type="text"
- value={displayName}
- onChange={(e) => {
- setDisplayName(e.target.value);
- setValidationError(null);
- }}
- maxLength={DISPLAY_NAME_MAX_LENGTH}
- placeholder="What should we call you?"
- className="w-full rounded-lg border border-foreground/20 bg-background px-3 py-2 text-foreground placeholder:text-foreground/40 focus:outline-none focus:ring-2 focus:ring-blue-500"
- autoFocus
- autoComplete="off"
- />
- {validationError && <p className="mt-1 text-sm text-red-500">{validationError}</p>}
- </div>
- <AvatarColorPicker value={avatarColor} onChange={setAvatarColor} />
- {signUpMutation.error && (
- <p className="text-sm text-red-500" role="alert">
- {signUpMutation.error instanceof Error
- ? signUpMutation.error.message
- : "Something went wrong. Please try again."}
- </p>
- )}
- <button
- type="submit"
- disabled={signUpMutation.isPending}
- className="w-full rounded-lg bg-blue-600 px-4 py-2 font-medium text-white transition-colors hover:bg-blue-700 disabled:opacity-50"
- >
- {signUpMutation.isPending ? "Setting up..." : "Get started"}
- </button>
- </form>
- );
- }
|