{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "auth-email-change",
  "title": "Auth Email Change",
  "description": "Change email address with password verification.",
  "registryDependencies": [
    "button",
    "card",
    "field",
    "input-group"
  ],
  "files": [
    {
      "path": "registry/new-york/blocks/auth/auth-email-change.tsx",
      "content": "\"use client\";\n\nimport { CheckCircle2, Eye, EyeOff, Loader2, Mail, Shield } from \"lucide-react\";\nimport { useCallback, useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { Button } from \"@/registry/new-york/ui/button\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle,\n} from \"@/registry/new-york/ui/card\";\nimport {\n  Field,\n  FieldContent,\n  FieldDescription,\n  FieldError,\n  FieldLabel,\n} from \"@/registry/new-york/ui/field\";\nimport {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupButton,\n  InputGroupInput,\n} from \"@/registry/new-york/ui/input-group\";\n\nexport interface AuthEmailChangeProps {\n  currentEmail?: string;\n  onSubmit?: (data: { newEmail: string; password: string }) => void;\n  className?: string;\n  isLoading?: boolean;\n  isSuccess?: boolean;\n  errors?: {\n    newEmail?: string;\n    password?: string;\n    general?: string;\n  };\n  successMessage?: string;\n}\n\ninterface FormErrors {\n  newEmail?: string;\n  password?: string;\n}\n\nfunction validateEmail(\n  value: string,\n  currentEmail?: string\n): string | undefined {\n  if (!value.trim()) {\n    return \"Email is required\";\n  }\n  const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n  if (!emailRegex.test(value)) {\n    return \"Please enter a valid email address\";\n  }\n  if (value.trim().toLowerCase() === currentEmail?.toLowerCase()) {\n    return \"New email must be different from current email\";\n  }\n  return;\n}\n\nfunction validatePassword(value: string): string | undefined {\n  if (!value) {\n    return \"Password is required\";\n  }\n  return;\n}\n\ninterface ErrorAlertProps {\n  message: string;\n}\n\nfunction ErrorAlert({ message }: ErrorAlertProps) {\n  return (\n    <div\n      aria-live=\"polite\"\n      className=\"rounded-lg border border-destructive/50 bg-destructive/10 p-3 text-destructive text-sm\"\n      role=\"alert\"\n    >\n      {message}\n    </div>\n  );\n}\n\ninterface CurrentEmailDisplayProps {\n  email: string;\n}\n\nfunction CurrentEmailDisplay({ email }: CurrentEmailDisplayProps) {\n  return (\n    <section\n      aria-label=\"Current email\"\n      className=\"flex items-center gap-3 rounded-lg border border-muted bg-background px-4 py-3\"\n    >\n      <span className=\"hidden size-12 items-center justify-center rounded-full bg-muted/70 sm:flex\">\n        <Mail aria-hidden=\"true\" className=\"size-4 text-muted-foreground\" />\n      </span>\n      <div className=\"flex min-w-0 flex-col gap-0.5\">\n        <span className=\"text-muted-foreground text-xs leading-none\">\n          Signed in as\n        </span>\n        <span\n          className=\"truncate font-medium font-mono text-foreground text-sm\"\n          title={email}\n        >\n          {email}\n        </span>\n      </div>\n    </section>\n  );\n}\n\ninterface EmailFieldProps {\n  id: string;\n  value: string;\n  onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;\n  error?: string;\n}\n\nfunction EmailField({ id, value, onChange, error }: EmailFieldProps) {\n  return (\n    <Field data-invalid={!!error}>\n      <FieldLabel htmlFor={id}>\n        New email address\n        <span aria-label=\"required\" className=\"text-destructive\">\n          *\n        </span>\n      </FieldLabel>\n      <FieldContent>\n        <InputGroup aria-invalid={!!error}>\n          <InputGroupAddon>\n            <Mail aria-hidden=\"true\" className=\"size-4\" />\n          </InputGroupAddon>\n          <InputGroupInput\n            aria-describedby={error ? `${id}-error` : undefined}\n            aria-invalid={!!error}\n            autoComplete=\"email\"\n            id={id}\n            inputMode=\"email\"\n            name=\"newEmail\"\n            onChange={onChange}\n            placeholder=\"new@example.com…\"\n            required\n            type=\"email\"\n            value={value}\n          />\n        </InputGroup>\n        {error && <FieldError id={`${id}-error`}>{error}</FieldError>}\n        <FieldDescription>\n          We&apos;ll send a verification link to this address\n        </FieldDescription>\n      </FieldContent>\n    </Field>\n  );\n}\n\ninterface PasswordFieldProps {\n  id: string;\n  value: string;\n  onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;\n  showPassword: boolean;\n  onTogglePassword: () => void;\n  error?: string;\n}\n\nfunction PasswordField({\n  id,\n  value,\n  onChange,\n  showPassword,\n  onTogglePassword,\n  error,\n}: PasswordFieldProps) {\n  return (\n    <Field data-invalid={!!error}>\n      <FieldLabel htmlFor={id}>\n        Password\n        <span aria-label=\"required\" className=\"text-destructive\">\n          *\n        </span>\n      </FieldLabel>\n      <FieldContent>\n        <InputGroup aria-invalid={!!error}>\n          <InputGroupAddon>\n            <Shield aria-hidden=\"true\" className=\"size-4\" />\n          </InputGroupAddon>\n          <InputGroupInput\n            aria-describedby={error ? `${id}-error` : undefined}\n            aria-invalid={!!error}\n            autoComplete=\"current-password\"\n            id={id}\n            name=\"password\"\n            onChange={onChange}\n            placeholder=\"Enter your password…\"\n            required\n            type={showPassword ? \"text\" : \"password\"}\n            value={value}\n          />\n          <InputGroupButton\n            aria-label={showPassword ? \"Hide password\" : \"Show password\"}\n            className=\"min-h-[32px] min-w-[32px] touch-manipulation\"\n            onClick={(e) => {\n              e.preventDefault();\n              onTogglePassword();\n            }}\n            type=\"button\"\n          >\n            {showPassword ? (\n              <EyeOff aria-hidden=\"true\" className=\"size-4\" />\n            ) : (\n              <Eye aria-hidden=\"true\" className=\"size-4\" />\n            )}\n          </InputGroupButton>\n        </InputGroup>\n        {error && <FieldError id={`${id}-error`}>{error}</FieldError>}\n        <FieldDescription>\n          Enter your current password to confirm this change\n        </FieldDescription>\n      </FieldContent>\n    </Field>\n  );\n}\n\ninterface SuccessStateProps {\n  message: string;\n  newEmail?: string;\n  className?: string;\n}\n\nfunction SuccessState({ message, newEmail, className }: SuccessStateProps) {\n  return (\n    <Card className={cn(\"w-full max-w-sm shadow-xs\", className)}>\n      <CardHeader>\n        <CardTitle>Verification email sent</CardTitle>\n        <CardDescription>\n          Check your new email address to verify the change\n        </CardDescription>\n      </CardHeader>\n      <CardContent>\n        <div className=\"flex flex-col items-center gap-4 rounded-lg border border-primary/20 bg-primary/5 p-6 text-center\">\n          <div className=\"flex size-16 items-center justify-center rounded-full bg-primary/10\">\n            <CheckCircle2 aria-hidden=\"true\" className=\"size-8 text-primary\" />\n          </div>\n          <div className=\"flex flex-col gap-2\">\n            <p className=\"font-medium text-sm\">{message}</p>\n            {newEmail && (\n              <p className=\"text-muted-foreground text-sm\">\n                Sent to <span className=\"font-medium\">{newEmail}</span>\n              </p>\n            )}\n            <p className=\"text-muted-foreground text-xs\">\n              Click the verification link in the email to complete the change.\n              If you don&apos;t see it, check your spam folder.\n            </p>\n          </div>\n        </div>\n      </CardContent>\n    </Card>\n  );\n}\n\nexport default function AuthEmailChange({\n  currentEmail,\n  onSubmit,\n  className,\n  isLoading = false,\n  isSuccess = false,\n  errors,\n  successMessage = \"We've sent a verification link to your new email address.\",\n}: AuthEmailChangeProps) {\n  const [newEmail, setNewEmail] = useState(\"\");\n  const [password, setPassword] = useState(\"\");\n  const [showPassword, setShowPassword] = useState(false);\n  const [localErrors, setLocalErrors] = useState<FormErrors>({});\n\n  const handleSubmit = useCallback(\n    (e: React.FormEvent) => {\n      e.preventDefault();\n\n      const newEmailError = validateEmail(newEmail, currentEmail);\n      const passwordError = validatePassword(password);\n\n      if (newEmailError || passwordError) {\n        setLocalErrors({\n          newEmail: newEmailError,\n          password: passwordError,\n        });\n        return;\n      }\n\n      setLocalErrors({});\n      onSubmit?.({\n        newEmail: newEmail.trim(),\n        password,\n      });\n    },\n    [newEmail, password, currentEmail, onSubmit]\n  );\n\n  const handleNewEmailChange = useCallback(\n    (e: React.ChangeEvent<HTMLInputElement>) => {\n      const value = e.target.value;\n      setNewEmail(value);\n      if (localErrors.newEmail) {\n        setLocalErrors((prev) => ({\n          ...prev,\n          newEmail: validateEmail(value, currentEmail),\n        }));\n      }\n    },\n    [localErrors.newEmail, currentEmail]\n  );\n\n  const handlePasswordChange = useCallback(\n    (e: React.ChangeEvent<HTMLInputElement>) => {\n      const value = e.target.value;\n      setPassword(value);\n      if (localErrors.password) {\n        setLocalErrors((prev) => ({\n          ...prev,\n          password: validatePassword(value),\n        }));\n      }\n    },\n    [localErrors.password]\n  );\n\n  const handleTogglePassword = useCallback(() => {\n    setShowPassword((prev) => !prev);\n  }, []);\n\n  const newEmailError = errors?.newEmail || localErrors.newEmail;\n  const passwordError = errors?.password || localErrors.password;\n  const generalError = errors?.general;\n\n  if (isSuccess) {\n    return (\n      <SuccessState\n        className={className}\n        message={successMessage}\n        newEmail={newEmail}\n      />\n    );\n  }\n\n  return (\n    <Card className={cn(\"w-full max-w-sm shadow-xs\", className)}>\n      <CardHeader>\n        <CardTitle>Change email address</CardTitle>\n        <CardDescription>\n          Update your email address. We&apos;ll send a verification link to your\n          new email.\n        </CardDescription>\n      </CardHeader>\n      <CardContent>\n        <form className=\"flex flex-col gap-6\" onSubmit={handleSubmit}>\n          {generalError && <ErrorAlert message={generalError} />}\n\n          {currentEmail && <CurrentEmailDisplay email={currentEmail} />}\n\n          <div className=\"flex flex-col gap-4\">\n            <EmailField\n              error={newEmailError}\n              id=\"email-change-new\"\n              onChange={handleNewEmailChange}\n              value={newEmail}\n            />\n\n            <PasswordField\n              error={passwordError}\n              id=\"email-change-password\"\n              onChange={handlePasswordChange}\n              onTogglePassword={handleTogglePassword}\n              showPassword={showPassword}\n              value={password}\n            />\n          </div>\n\n          <Button\n            aria-busy={isLoading}\n            className=\"min-h-[44px] w-full touch-manipulation\"\n            data-loading={isLoading}\n            disabled={isLoading}\n            type=\"submit\"\n          >\n            {isLoading ? (\n              <>\n                <Loader2 aria-hidden=\"true\" className=\"size-4 animate-spin\" />\n                Sending verification email…\n              </>\n            ) : (\n              <>\n                <Mail aria-hidden=\"true\" className=\"size-4\" />\n                Change email address\n              </>\n            )}\n          </Button>\n        </form>\n      </CardContent>\n    </Card>\n  );\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "block",
    "auth"
  ],
  "type": "registry:ui"
}