Sign In

Installation

Add the following Soul components

The sign-in component uses the button, button-link, animated-link, form-status and input components. Make sure you have added them to your project.

Install the following dependencies

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

Copy and paste the following code into your project

sections/sign-in/index.tsx

import { AnimatedLink } from '@/vibes/soul/primitives/animated-link';import { ButtonLink } from '@/vibes/soul/primitives/button-link';import { SignInAction, SignInForm } from './sign-in-form';export interface SignInProps {  title?: string;  signUpTitle?: string;  signUpDescription?: string;  signUpBenefits?: string[];  action: SignInAction;  submitLabel?: string;  emailLabel?: string;  passwordLabel?: string;  forgotPasswordHref: string;  forgotPasswordLabel?: string;  signUpHref?: string;}/** * This component supports various CSS variables for theming. Here's a comprehensive list, along * with their default values: * * ```css * :root { *   --sign-in-font-family: var(--font-family-body); *   --sign-in-title-font-family: var(--font-family-heading); *   --sign-in-title: hsl(var(--foreground)); *   --sign-in-description: hsl(var(--contrast-500)); * } * ``` */export function SignIn({  title = 'Sign In',  signUpTitle = 'New Customer?',  signUpDescription = 'Create an account with us and be able to:',  signUpBenefits = [    'Check out faster',    'Save multiple shipping addresses',    'Access your order history',    'Track new orders',    'Save items to your Wish List',  ],  action,  submitLabel,  emailLabel,  passwordLabel,  forgotPasswordHref = '/forgot-password',  forgotPasswordLabel = 'Forgot your password?',  signUpHref = '/sign-up',}: SignInProps) {  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="w-full @xl:max-w-md @xl:border-r @xl:pr-10 @4xl:pr-20">          <h1 className="mb-10 font-[family-name:var(--sign-in-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>          <SignInForm            action={action}            emailLabel={emailLabel}            passwordLabel={passwordLabel}            submitLabel={submitLabel}          />          <AnimatedLink            className="mt-4 block w-fit text-sm font-semibold"            href={forgotPasswordHref}          >            {forgotPasswordLabel}          </AnimatedLink>        </div>        <div className="flex w-full flex-col @xl:max-w-md @xl:pl-10 @4xl:pl-20">          <div className="font-[family-name:var(--sign-in-font-family,var(--font-family-body))]">            <h2 className="mb-10 font-[family-name:var(--sign-in-title-font-family,var(--font-family-heading))] text-4xl font-medium leading-none text-[var(--reset-password-title,hsl(var(--foreground)))] @xl:text-5xl">              {signUpTitle}            </h2>            <div className="text-[var(--sign-in-description,hsl(var(--contrast-500)))]">              <p>{signUpDescription}</p>              <ul className="mb-10 ml-4 mt-4 list-disc">                {signUpBenefits.map((benefit, idx) => (                  <li key={idx}>{benefit}</li>                ))}              </ul>              <ButtonLink className="mt-auto w-full" href={signUpHref} variant="secondary">                Create Account              </ButtonLink>            </div>          </div>        </div>      </div>    </div>  );}

sections/sign-in/sign-in-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 { useFormStatus } from 'react-dom';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 SignInAction = Action<SubmissionResult | null, FormData>;export interface SignInFormProps {  action: SignInAction;  emailLabel?: string;  passwordLabel?: string;  submitLabel?: string;}export function SignInForm({  action,  emailLabel = 'Email',  passwordLabel = 'Password',  submitLabel = 'Sign in',}: SignInFormProps) {  const [lastResult, formAction] = useActionState(action, null);  const [form, fields] = useForm({    lastResult,    defaultValue: { email: '', password: '' },    constraint: getZodConstraint(schema),    shouldValidate: 'onBlur',    shouldRevalidate: 'onInput',    onValidate({ formData }) {      return parseWithZod(formData, { schema });    },  });  return (    <form {...getFormProps(form)} action={formAction} className="flex grow flex-col gap-5">      <Input        {...getInputProps(fields.email, { type: 'text' })}        errors={fields.email.errors}        key={fields.email.id}        label={emailLabel}      />      <Input        {...getInputProps(fields.password, { type: 'password' })}        className="mb-6"        errors={fields.password.errors}        key={fields.password.id}        label={passwordLabel}      />      <SubmitButton>{submitLabel}</SubmitButton>      {form.errors?.map((error, index) => (        <FormStatus key={index} type="error">          {error}        </FormStatus>      ))}    </form>  );}function SubmitButton({ children }: { children: React.ReactNode }) {  const { pending } = useFormStatus();  return (    <Button className="mt-auto w-full" loading={pending} type="submit" variant="secondary">      {children}    </Button>  );}

sections/sign-in/schema.ts

import { z } from 'zod';export const schema = z.object({  email: z.string().email(),  password: z.string(),});

Usage

import { SignIn } from '@/vibes/soul/sections/sign-in';import { schema } from '@/vibes/soul/sections/sign-in/schema';import { SubmissionResult } from '@conform-to/react';import { parseWithZod } from '@conform-to/zod';function Usage() {  return <SignIn action={signInAction} forgotPasswordHref="#" />;}async function signInAction(lastResult: SubmissionResult | null, formData: FormData) {  'use server';  const submission = parseWithZod(formData, { schema });  if (submission.status !== 'success') {      return submission.reply({ formErrors: ['Boom!'] });  }  // Simulate a network request  await new Promise((resolve) => setTimeout(resolve, 1000));  // const user = await logIn(submission.value)  return submission.reply({ resetForm: true });}

API Reference

PropTypeDefault
title
string
'Sign In'
signUpTitle
string
'New Customer?'
signUpDescription
string
'Create an account with us and be able to:'
signUpBenefits
string[]
[ 'Check out faster', 'Save multiple shipping addresses', 'Access your order history', 'Track new orders', 'Save items to your Wish List']
action*
SignInAction
submitLabel
string
emailLabel
string
passwordLabel
string
forgotPasswordHref*
string
'/forgot-password'
forgotPasswordLabel
string
'Forgot your password?'
signUpHref
string
'/sign-up'

SignInAction

type Action<State, Payload> = (state: Awaited<State>, payload: Payload) => State | Promise<State>;export type SignInAction = Action<SubmissionResult | null, 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:

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

CSS Variables

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

:root {  --sign-in-font-family: var(--font-family-body);  --sign-in-title-font-family: var(--font-family-heading);  --sign-in-title: hsl(var(--foreground));  --sign-in-description: hsl(var(--contrast-500));}