{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "auth-reset-password",
  "title": "Auth Reset Password",
  "description": "Reset password with strength indicator and token validation.",
  "registryDependencies": [
    "button",
    "card",
    "field",
    "input-group",
    "progress"
  ],
  "files": [
    {
      "path": "registry/new-york/blocks/auth/auth-reset-password.tsx",
      "content": "\"use client\";\n\nimport { CheckCircle2, Eye, EyeOff, Loader2, Lock } from \"lucide-react\";\nimport { useCallback, useMemo, 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\";\nimport { Progress } from \"@/registry/new-york/ui/progress\";\n\nexport interface PasswordStrength {\n  score: number; // 0-4\n  feedback: string[];\n}\n\nexport interface AuthResetPasswordProps {\n  token?: string;\n  onSubmit?: (data: { password: string; token: string }) => void;\n  onTokenValidate?: (token: string) => Promise<boolean>;\n  className?: string;\n  isLoading?: boolean;\n  isTokenValid?: boolean;\n  isTokenValidating?: boolean;\n  errors?: {\n    password?: string;\n    confirmPassword?: string;\n    token?: string;\n    general?: string;\n  };\n}\n\nfunction calculatePasswordStrength(password: string): PasswordStrength {\n  let score = 0;\n  const feedback: string[] = [];\n\n  if (password.length >= 8) {\n    score += 1;\n  } else {\n    feedback.push(\"At least 8 characters\");\n  }\n\n  if (/[a-z]/.test(password)) {\n    score += 1;\n  } else {\n    feedback.push(\"One lowercase letter\");\n  }\n\n  if (/[A-Z]/.test(password)) {\n    score += 1;\n  } else {\n    feedback.push(\"One uppercase letter\");\n  }\n\n  if (/\\d/.test(password)) {\n    score += 1;\n  } else {\n    feedback.push(\"One number\");\n  }\n\n  if (/[^a-zA-Z\\d]/.test(password)) {\n    score += 1;\n  } else {\n    feedback.push(\"One special character\");\n  }\n\n  return { score, feedback };\n}\n\nfunction getStrengthLabel(score: number): string {\n  if (score <= 1) return \"Weak\";\n  if (score <= 2) return \"Fair\";\n  if (score <= 3) return \"Good\";\n  return \"Strong\";\n}\n\nfunction validatePassword(value: string): string | undefined {\n  if (!value) {\n    return \"Password is required\";\n  }\n  if (value.length < 8) {\n    return \"Password must be at least 8 characters\";\n  }\n  if (!/(?=.*[a-z])/.test(value)) {\n    return \"Password must contain at least one lowercase letter\";\n  }\n  if (!/(?=.*[A-Z])/.test(value)) {\n    return \"Password must contain at least one uppercase letter\";\n  }\n  if (!/(?=.*\\d)/.test(value)) {\n    return \"Password must contain at least one number\";\n  }\n  return;\n}\n\nfunction validateConfirmPassword(\n  value: string,\n  passwordValue: string\n): string | undefined {\n  if (!value) {\n    return \"Please confirm your password\";\n  }\n  if (value !== passwordValue) {\n    return \"Passwords do not match\";\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 TokenValidatingStateProps {\n  className?: string;\n}\n\nfunction TokenValidatingState({ className }: TokenValidatingStateProps) {\n  return (\n    <Card className={cn(\"w-full max-w-sm shadow-xs\", className)}>\n      <CardHeader>\n        <CardTitle>Validating reset token</CardTitle>\n        <CardDescription>\n          Please wait while we verify your reset link\n        </CardDescription>\n      </CardHeader>\n      <CardContent>\n        <div className=\"flex items-center justify-center py-8\">\n          <Loader2\n            aria-hidden=\"true\"\n            className=\"size-6 animate-spin text-muted-foreground\"\n          />\n        </div>\n      </CardContent>\n    </Card>\n  );\n}\n\ninterface InvalidTokenStateProps {\n  className?: string;\n  message?: string;\n}\n\nfunction InvalidTokenState({ className, message }: InvalidTokenStateProps) {\n  return (\n    <Card className={cn(\"w-full max-w-sm shadow-xs\", className)}>\n      <CardHeader>\n        <CardTitle>Invalid reset link</CardTitle>\n        <CardDescription>\n          This password reset link is invalid or has expired\n        </CardDescription>\n      </CardHeader>\n      <CardContent>\n        <div className=\"rounded-lg border border-destructive/50 bg-destructive/10 p-4 text-destructive text-sm\">\n          {message || \"Please request a new password reset link.\"}\n        </div>\n      </CardContent>\n    </Card>\n  );\n}\n\ninterface PasswordStrengthIndicatorProps {\n  feedback: string[];\n  score: number;\n}\n\nfunction PasswordStrengthIndicator({\n  feedback,\n  score,\n}: PasswordStrengthIndicatorProps) {\n  return (\n    <div className=\"flex flex-col gap-2\">\n      <div className=\"flex items-center justify-between gap-2\">\n        <Progress\n          aria-label={`Password strength: ${getStrengthLabel(score)}`}\n          className=\"h-1.5\"\n          value={(score / 5) * 100}\n        />\n        <span\n          className={cn(\n            \"font-medium text-xs\",\n            score <= 1 && \"text-destructive\",\n            score === 2 && \"text-yellow-500\",\n            score === 3 && \"text-blue-500\",\n            score >= 4 && \"text-green-500\"\n          )}\n        >\n          {getStrengthLabel(score)}\n        </span>\n      </div>\n      {feedback.length > 0 && (\n        <FieldDescription>\n          <span className=\"text-xs\">Missing: {feedback.join(\", \")}</span>\n        </FieldDescription>\n      )}\n    </div>\n  );\n}\n\ninterface PasswordFieldProps {\n  error?: string;\n  id: string;\n  password: string;\n  passwordStrength: PasswordStrength;\n  onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;\n  onTogglePassword: () => void;\n  showPassword: boolean;\n}\n\nfunction PasswordField({\n  error,\n  id,\n  password,\n  passwordStrength,\n  onChange,\n  onTogglePassword,\n  showPassword,\n}: PasswordFieldProps) {\n  return (\n    <Field data-invalid={!!error}>\n      <FieldLabel htmlFor={id}>\n        New password\n        <span aria-label=\"required\" className=\"text-destructive\">\n          *\n        </span>\n      </FieldLabel>\n      <FieldContent>\n        <InputGroup aria-invalid={!!error}>\n          <InputGroupAddon>\n            <Lock aria-hidden=\"true\" className=\"size-4\" />\n          </InputGroupAddon>\n          <InputGroupInput\n            aria-describedby={error ? `${id}-error` : undefined}\n            aria-invalid={!!error}\n            autoComplete=\"new-password\"\n            id={id}\n            name=\"password\"\n            onChange={onChange}\n            placeholder=\"Enter new password…\"\n            required\n            type={showPassword ? \"text\" : \"password\"}\n            value={password}\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        {password && (\n          <PasswordStrengthIndicator\n            feedback={passwordStrength.feedback}\n            score={passwordStrength.score}\n          />\n        )}\n      </FieldContent>\n    </Field>\n  );\n}\n\ninterface ConfirmPasswordFieldProps {\n  error?: string;\n  id: string;\n  onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;\n  onTogglePassword: () => void;\n  showPassword: boolean;\n  value: string;\n}\n\nfunction ConfirmPasswordField({\n  error,\n  id,\n  onChange,\n  onTogglePassword,\n  showPassword,\n  value,\n}: ConfirmPasswordFieldProps) {\n  return (\n    <Field data-invalid={!!error}>\n      <FieldLabel htmlFor={id}>\n        Confirm password\n        <span aria-label=\"required\" className=\"text-destructive\">\n          *\n        </span>\n      </FieldLabel>\n      <FieldContent>\n        <InputGroup aria-invalid={!!error}>\n          <InputGroupAddon>\n            <Lock aria-hidden=\"true\" className=\"size-4\" />\n          </InputGroupAddon>\n          <InputGroupInput\n            aria-describedby={error ? `${id}-error` : undefined}\n            aria-invalid={!!error}\n            autoComplete=\"new-password\"\n            id={id}\n            name=\"confirmPassword\"\n            onChange={onChange}\n            placeholder=\"Confirm new password…\"\n            required\n            type={showPassword ? \"text\" : \"password\"}\n            value={value}\n          />\n          <InputGroupButton\n            aria-label={\n              showPassword ? \"Hide confirm password\" : \"Show confirm password\"\n            }\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      </FieldContent>\n    </Field>\n  );\n}\n\nexport default function AuthResetPassword({\n  token = \"\",\n  onSubmit,\n  onTokenValidate,\n  className,\n  isLoading = false,\n  isTokenValid,\n  isTokenValidating = false,\n  errors,\n}: AuthResetPasswordProps) {\n  const [password, setPassword] = useState(\"\");\n  const [confirmPassword, setConfirmPassword] = useState(\"\");\n  const [showPassword, setShowPassword] = useState(false);\n  const [showConfirmPassword, setShowConfirmPassword] = useState(false);\n  const [localErrors, setLocalErrors] = useState<{\n    password?: string;\n    confirmPassword?: string;\n  }>({});\n\n  const passwordStrength = useMemo(\n    () => calculatePasswordStrength(password),\n    [password]\n  );\n\n  const handleSubmit = useCallback(\n    (e: React.FormEvent) => {\n      e.preventDefault();\n\n      const passwordError = validatePassword(password);\n      const confirmPasswordError = validateConfirmPassword(\n        confirmPassword,\n        password\n      );\n\n      if (passwordError || confirmPasswordError) {\n        setLocalErrors({\n          password: passwordError,\n          confirmPassword: confirmPasswordError,\n        });\n        return;\n      }\n\n      setLocalErrors({});\n      onSubmit?.({\n        password,\n        token,\n      });\n    },\n    [password, confirmPassword, token, onSubmit]\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      if (localErrors.confirmPassword && confirmPassword) {\n        setLocalErrors((prev) => ({\n          ...prev,\n          confirmPassword: validateConfirmPassword(confirmPassword, value),\n        }));\n      }\n    },\n    [localErrors.password, localErrors.confirmPassword, confirmPassword]\n  );\n\n  const handleConfirmPasswordChange = useCallback(\n    (e: React.ChangeEvent<HTMLInputElement>) => {\n      const value = e.target.value;\n      setConfirmPassword(value);\n      if (localErrors.confirmPassword) {\n        setLocalErrors((prev) => ({\n          ...prev,\n          confirmPassword: validateConfirmPassword(value, password),\n        }));\n      }\n    },\n    [localErrors.confirmPassword, password]\n  );\n\n  const handleTogglePassword = useCallback(() => {\n    setShowPassword((prev) => !prev);\n  }, []);\n\n  const handleToggleConfirmPassword = useCallback(() => {\n    setShowConfirmPassword((prev) => !prev);\n  }, []);\n\n  const passwordError = errors?.password || localErrors.password;\n  const confirmPasswordError =\n    errors?.confirmPassword || localErrors.confirmPassword;\n  const generalError = errors?.general;\n  const tokenError = errors?.token;\n\n  if (onTokenValidate && isTokenValid === undefined && !isTokenValidating) {\n    onTokenValidate(token);\n  }\n\n  if (isTokenValidating) {\n    return <TokenValidatingState className={className} />;\n  }\n\n  if (isTokenValid === false || tokenError) {\n    return <InvalidTokenState className={className} message={tokenError} />;\n  }\n\n  return (\n    <Card className={cn(\"w-full max-w-sm shadow-xs\", className)}>\n      <CardHeader>\n        <CardTitle>Reset your password</CardTitle>\n        <CardDescription>Enter your new password below</CardDescription>\n      </CardHeader>\n      <CardContent>\n        <form className=\"flex flex-col gap-6\" onSubmit={handleSubmit}>\n          {generalError && <ErrorAlert message={generalError} />}\n\n          <div className=\"flex flex-col gap-4\">\n            <PasswordField\n              error={passwordError}\n              id=\"reset-password\"\n              onChange={handlePasswordChange}\n              onTogglePassword={handleTogglePassword}\n              password={password}\n              passwordStrength={passwordStrength}\n              showPassword={showPassword}\n            />\n\n            <ConfirmPasswordField\n              error={confirmPasswordError}\n              id=\"reset-confirm-password\"\n              onChange={handleConfirmPasswordChange}\n              onTogglePassword={handleToggleConfirmPassword}\n              showPassword={showConfirmPassword}\n              value={confirmPassword}\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                Resetting password…\n              </>\n            ) : (\n              <>\n                <CheckCircle2 aria-hidden=\"true\" className=\"size-4\" />\n                Reset password\n              </>\n            )}\n          </Button>\n        </form>\n      </CardContent>\n    </Card>\n  );\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "block",
    "auth"
  ],
  "type": "registry:ui"
}