Computing for good
Atlanta Food Consortium/Feature Docs

Onboarding Route

Registration flows for new suppliers and nonprofits. Two multi-step wizards built with react-hook-form and Zod.

Overview

The Onboarding route provides two separate multi-step registration wizards:

  • /onboarding/supplier - 3-step supplier registration
  • /onboarding/nonprofit - 4-step nonprofit registration

Both flows use react-hook-form for form state and Zod for schema validation. On successful completion a <SuccessPopup> modal is shown before redirecting to the appropriate dashboard.

Source directory: src/app/onboarding/

Access control

CheckValue
RequiredNo session (public route)
Redirect if already authenticatedRedirects to role-appropriate dashboard

Supplier onboarding (/onboarding/supplier)

Steps

StepFields
1 - AccountEmail, password, confirm password
2 - OrganizationCompany name, contact name, phone, address
3 - ReviewSummary of entered data with submit button

Form schema

// Defined in src/app/onboarding/supplier/schema.ts
const supplierSchema = z
  .object({
    email: z.string().email(),
    password: z.string().min(8),
    confirmPassword: z.string(),
    companyName: z.string().min(2),
    contactName: z.string().min(2),
    phone: z.string(),
    address: z.string(),
  })
  .refine((data) => data.password === data.confirmPassword, {
    message: 'Passwords do not match',
    path: ['confirmPassword'],
  });

API endpoint

POST /api/onboarding/supplier - Creates the user and supplier organization records in one transaction.


Nonprofit onboarding (/onboarding/nonprofit)

Steps

StepFields
1 - AccountEmail, password, confirm password
2 - OrganizationNonprofit name, EIN, contact name, phone
3 - AddressStreet, city, state, zip
4 - ReviewSummary with document upload and submit

Document upload

Step 4 allows optional upload of a verification document (e.g. 501(c)(3) letter). Files are stored in uploads/nonprofit-documents/.

Form schema

// Defined in src/app/onboarding/nonprofit/schema.ts
const nonprofitSchema = z
  .object({
    email: z.string().email(),
    password: z.string().min(8),
    confirmPassword: z.string(),
    nonprofitName: z.string().min(2),
    ein: z.string().regex(/^\d{2}-\d{7}$/, 'Must be in format XX-XXXXXXX'),
    contactName: z.string().min(2),
    phone: z.string(),
    address: z.string(),
    city: z.string(),
    state: z.string().length(2),
    zip: z.string().regex(/^\d{5}$/, 'Must be a 5-digit ZIP code'),
  })
  .refine((data) => data.password === data.confirmPassword, {
    message: 'Passwords do not match',
    path: ['confirmPassword'],
  });

API endpoint

POST /api/onboarding/nonprofit - Creates the user and nonprofit records. Sets nonprofit status to 'PENDING' awaiting admin approval.


SuccessPopup

After a successful submission both flows show a <SuccessPopup> modal.

import SuccessPopup from '@/components/Onboarding/SuccessPopup';

<SuccessPopup
  title='Registration submitted!'
  message='An admin will review your application shortly.'
  onClose={() => router.push('/')}
/>;

Source file: src/components/Onboarding/SuccessPopup.tsx