UIPackage

Auth Password Reset

block auth
Edit on GitHub

Four-stage password reset surface in a single card: request (email form) -> sent (check-your-inbox confirmation with "Open reset form" demo button) -> reset (new password + confirm with match validation) -> done (success confirmation linking back to sign-in). Emits `request` (email) when the link is asked for and `reset` (password) when the new password is set; consumer wires the actual mail/db calls.

Also available for Vue ->

Installation

$ npx shadcn@latest add https://react.uipkge.dev/r/react/auth-password-reset.json

Or with the named registry: npx shadcn@latest add @uipkge-react/auth-password-reset

Examples

npm dependencies

Files (1)

  • components/blocks/AuthPasswordReset.tsx 6 kB
    'use client'
    
    import * as React from 'react'
    import { ArrowLeft, MailCheck } from 'lucide-react'
    import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
    import { Input } from '@/components/ui/input'
    import { Label } from '@/components/ui/label'
    import { Button } from '@/components/ui/button'
    
    type Stage = 'request' | 'sent' | 'reset' | 'done'
    
    export interface AuthPasswordResetProps {
      signInHref?: string
      onRequest?: (email: string) => void
      onReset?: (password: string) => void
    }
    
    export function AuthPasswordReset({
      signInHref = '/login',
      onRequest,
      onReset,
    }: AuthPasswordResetProps) {
      const [stage, setStage] = React.useState<Stage>('request')
    
      const [email, setEmail] = React.useState('')
      const [password, setPassword] = React.useState('')
      const [confirm, setConfirm] = React.useState('')
      const passwordsMatch = !confirm || password === confirm
    
      function submitRequest(e: React.FormEvent) {
        e.preventDefault()
        if (!email) return
        onRequest?.(email)
        setStage('sent')
      }
    
      function submitReset(e: React.FormEvent) {
        e.preventDefault()
        if (!password || !passwordsMatch) return
        onReset?.(password)
        setStage('done')
      }
    
      return (
        <div className="bg-background flex min-h-svh items-center justify-center p-6">
          <Card className="w-full max-w-sm">
            {stage === 'request' && (
              <>
                <CardHeader className="text-center">
                  <CardTitle className="text-2xl">Forgot password?</CardTitle>
                  <CardDescription>Enter your email and we'll send you a reset link.</CardDescription>
                </CardHeader>
                <CardContent>
                  <form className="space-y-4" onSubmit={submitRequest}>
                    <div className="grid gap-2">
                      <Label htmlFor="reset-email">Email</Label>
                      <Input
                        id="reset-email"
                        value={email}
                        onChange={(e) => setEmail(e.target.value)}
                        type="email"
                        placeholder="[email protected]"
                        required
                      />
                    </div>
                    <Button type="submit" className="w-full">
                      Send reset link
                    </Button>
                  </form>
                </CardContent>
                <CardFooter className="justify-center">
                  <a
                    href={signInHref}
                    className="text-muted-foreground hover:text-foreground inline-flex items-center gap-1 text-sm"
                  >
                    <ArrowLeft className="size-3" />Back to sign in
                  </a>
                </CardFooter>
              </>
            )}
    
            {stage === 'sent' && (
              <CardContent className="space-y-4 pt-6 text-center">
                <div className="bg-primary/10 text-primary mx-auto flex size-12 items-center justify-center rounded-full">
                  <MailCheck className="size-6" />
                </div>
                <div className="space-y-1">
                  <h3 className="text-lg font-semibold">Check your inbox</h3>
                  <p className="text-muted-foreground text-sm">
                    We've sent a reset link to <span className="text-foreground font-medium">{email}</span>.
                  </p>
                </div>
                <Button variant="outline" className="w-full" onClick={() => setStage('reset')}>
                  Open reset form (demo)
                </Button>
                <button
                  className="text-muted-foreground hover:text-foreground text-xs underline-offset-4 hover:underline"
                  onClick={() => setStage('request')}
                >
                  Wrong email?
                </button>
              </CardContent>
            )}
    
            {stage === 'reset' && (
              <>
                <CardHeader className="text-center">
                  <CardTitle className="text-2xl">Set new password</CardTitle>
                  <CardDescription>Pick a strong password you haven't used before.</CardDescription>
                </CardHeader>
                <CardContent>
                  <form className="space-y-4" onSubmit={submitReset}>
                    <div className="grid gap-2">
                      <Label htmlFor="reset-pw">New password</Label>
                      <Input
                        id="reset-pw"
                        value={password}
                        onChange={(e) => setPassword(e.target.value)}
                        type="password"
                        autoComplete="new-password"
                        required
                      />
                    </div>
                    <div className="grid gap-2">
                      <Label htmlFor="reset-confirm">Confirm password</Label>
                      <Input
                        id="reset-confirm"
                        value={confirm}
                        onChange={(e) => setConfirm(e.target.value)}
                        type="password"
                        autoComplete="new-password"
                        aria-invalid={!passwordsMatch}
                        required
                      />
                      {!passwordsMatch && <p className="text-destructive text-xs">Passwords don't match.</p>}
                    </div>
                    <Button type="submit" className="w-full">
                      Reset password
                    </Button>
                  </form>
                </CardContent>
              </>
            )}
    
            {stage === 'done' && (
              <CardContent className="space-y-4 pt-6 text-center">
                <div className="bg-success/10 text-success mx-auto flex size-12 items-center justify-center rounded-full">
                  <MailCheck className="size-6" />
                </div>
                <div className="space-y-1">
                  <h3 className="text-lg font-semibold">All set</h3>
                  <p className="text-muted-foreground text-sm">
                    Your password has been updated. You can now sign in with the new password.
                  </p>
                </div>
                <a href={signInHref}>
                  <Button className="w-full">Continue to sign in</Button>
                </a>
              </CardContent>
            )}
          </Card>
        </div>
      )
    }

Raw manifest: https://react.uipkge.dev/r/react/auth-password-reset.json