Reset Password

Installation

Add the following Soul components

The reset-password component uses the button, input and form-status components. Make sure you have added them to your project.

Install the following dependencies

npm install @conform-to/zod @conform-to/react

Copy and paste the following code into your project

sections/reset-password/index.tsx

import { ResetPasswordAction, ResetPasswordForm } from './reset-password-form';export interface ResetPasswordProps {  title?: string;  subtitle?: string;  action: ResetPasswordAction;  submitLabel?: string;  newPasswordLabel?: string;  confirmPasswordLabel?: string;}/** * This component supports various CSS variables for theming. Here's a comprehensive list, along * with their default values: * * ```css * :root { *   --reset-password-font-family: var(--font-family-body); *   --reset-password-title-font-family: var(--font-family-heading); *   --reset-password-title: hsl(var(--foreground)); *   --reset-password-subtitle: hsl(var(--contrast-500)) * } * ``` */export function ResetPassword({  title = 'Reset password',  subtitle = 'Enter a new password below to reset your account password.',  submitLabel,  newPasswordLabel,  confirmPasswordLabel,  action,}: ResetPasswordProps) {  return (    <div className="@container">      <div className="flex flex-col justify-center gap-y-24 px-3 py-10 @xl:flex-row @xl:px-6 @4xl:py-20 @5xl:px-20">        <div className="flex w-full flex-col @xl:max-w-md @xl:pr-10 @4xl:pr-20">          <header className="font-[family-name:var(--reset-password-font-family,var(--font-family-body))]">            <h1 className="mb-5 font-[family-name:var(--reset-password-title-font-family,var(--font-family-heading))] text-4xl font-medium leading-none text-[var(--reset-password-title,hsl(var(--foreground)))] @xl:text-5xl">              {title}            </h1>            <p className="mb-10 text-base font-light leading-none text-[var(--reset-password-subtitle,hsl(var(--contrast-500)))] @xl:text-lg">              {subtitle}            </p>          </header>          <ResetPasswordForm            action={action}            confirmPasswordLabel={confirmPasswordLabel}            newPasswordLabel={newPasswordLabel}            submitLabel={submitLabel}          />        </div>      </div>    </div>  );}

sections/reset-password/reset-password-form.tsx

'use client';import { getFormProps, getInputProps, SubmissionResult, useForm } from '@conform-to/react';import { getZodConstraint, parseWithZod } from '@conform-to/zod';import { useActionState } from 'react';import { FormStatus } from '@/vibes/soul/form/form-status';import { Input } from '@/vibes/soul/form/input';import { Button } from '@/vibes/soul/primitives/button';import { schema } from './schema';type Action<State, Payload> = (state: Awaited<State>, payload: Payload) => State | Promise<State>;export type ResetPasswordAction = Action<  { lastResult: SubmissionResult | null; successMessage?: string },  FormData>;export interface ResetPasswordFormProps {  action: ResetPasswordAction;  submitLabel?: string;  newPasswordLabel?: string;  confirmPasswordLabel?: string;}export function ResetPasswordForm({  action,  newPasswordLabel = 'New password',  confirmPasswordLabel = 'Confirm Password',  submitLabel = 'Update',}: ResetPasswordFormProps) {  const [{ lastResult, successMessage }, formAction, isPending] = useActionState(action, {    lastResult: null,  });  const [form, fields] = useForm({    lastResult,    constraint: getZodConstraint(schema),    shouldValidate: 'onBlur',    shouldRevalidate: 'onInput',    onValidate({ formData }) {      return parseWithZod(formData, { schema });    },  });  return (    <form {...getFormProps(form)} action={formAction} className="space-y-5">      <Input        {...getInputProps(fields.password, { type: 'password' })}        errors={fields.password.errors}        key={fields.password.id}        label={newPasswordLabel}      />      <Input        {...getInputProps(fields.confirmPassword, { type: 'password' })}        className="mb-6"        errors={fields.confirmPassword.errors}        key={fields.confirmPassword.id}        label={confirmPasswordLabel}      />      <Button loading={isPending} size="small" type="submit" variant="secondary">        {submitLabel}      </Button>      {form.errors?.map((error, index) => (        <FormStatus key={index} type="error">          {error}        </FormStatus>      ))}      {form.status === 'success' && successMessage != null && (        <FormStatus>{successMessage}</FormStatus>      )}    </form>  );}

sections/reset-password/schema.ts

import { z } from 'zod';export const schema = z  .object({    password: z      .string()      .min(8, { message: 'Be at least 8 characters long' })      .regex(/[a-zA-Z]/, { message: 'Contain at least one letter.' })      .regex(/[0-9]/, { message: 'Contain at least one number.' })      .regex(/[^a-zA-Z0-9]/, {        message: 'Contain at least one special character.',      })      .trim(),    confirmPassword: z.string(),  })  .superRefine(({ confirmPassword, password }, ctx) => {    if (confirmPassword !== password) {      ctx.addIssue({        code: 'custom',        message: 'The passwords did not match',        path: ['confirmPassword'],      });    }  });

Usage

import { ResetPassword } from '@/vibes/soul/sections/reset-password';import { SubmissionResult } from '@conform-to/react';import { parseWithZod } from '@conform-to/zod';import { schema } from '@/vibes/soul/sections/reset-password/schema';function Usage() {  return <ResetPassword action={resetPasswordAction} />;}async function resetPasswordAction(  lastResult: { lastResult: SubmissionResult | null; successMessage?: string },      formData: FormData,  ): Promise<{ lastResult: SubmissionResult | null; successMessage?: string }> {  'use server';  const submission = parseWithZod(formData, { schema });  if (submission.status !== 'success') {      return { lastResult: submission.reply({ formErrors: ['Boom!'] }) };  }  // Simulate a network request  await new Promise((resolve) => setTimeout(resolve, 1000));  return { lastResult: submission.reply({ resetForm: true }), successMessage: 'Password updated' };}

API Reference

PropTypeDefault
title
string
'Reset password'
subtitle
string
'Enter a new password below to reset your account password.'
action*
ResetPasswordAction
submitLabel
string
newPasswordLabel
string
confirmPasswordLabel
string

ResetPasswordAction

type Action<State, Payload> = (state: Awaited<State>, payload: Payload) => State | Promise<State>;export type ResetPasswordAction = Action<  { lastResult: SubmissionResult | null; successMessage?: string },  FormData>;

This component uses Confom to handle form submissions. Refer to the Conform docs for more details.

Here's an example of an action function that does validation and simulates making a network request:

export async function resetPasswordAction(  lastResult: { lastResult: SubmissionResult | null; successMessage?: string },  formData: FormData,): Promise<{ lastResult: SubmissionResult | null; successMessage?: string }> {  'use server';  const submission = parseWithZod(formData, { schema });  if (submission.status !== 'success') {    return { lastResult: submission.reply({ formErrors: ['Boom!'] }) };  }  // Simulate a network request  await new Promise((resolve) => setTimeout(resolve, 1000));  return { lastResult: submission.reply({ resetForm: true }), successMessage: 'Password updated' };}

CSS Variables

This component supports various CSS variables for theming. Here's a comprehensive list.

:root {  --reset-password-font-family: var(--font-family-body);  --reset-password-title-font-family: var(--font-family-heading);  --reset-password-title: hsl(var(--foreground));  --reset-password-subtitle: hsl(var(--contrast-500));}