import { zodResolver } from '@hookform/resolvers/zod' import { AlertCircle } from 'lucide-react' import { FC, memo, useEffect, useState } from 'react' import { useForm } from 'react-hook-form' import { Button, Form_Shadcn_, FormControl_Shadcn_, FormDescription_Shadcn_, FormField_Shadcn_, FormItem_Shadcn_, FormLabel_Shadcn_, FormMessage_Shadcn_, Input_Shadcn_, Separator, TextArea_Shadcn_, } from 'ui' import { MultiSelector, MultiSelectorContent, MultiSelectorItem, MultiSelectorList, MultiSelectorTrigger, } from 'ui-patterns/multi-select' import { Alert, AlertDescription } from 'ui/src/components/shadcn/ui/alert' import { AlertDialog, AlertDialogAction, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from 'ui/src/components/shadcn/ui/alert-dialog' import { SupaSquadApplication, supaSquadApplicationSchema, } from '~/data/open-source/contributing/supasquad.utils' import { CountrySelector } from '../Supasquad/CountrySelector' interface FormItem_Shadcn_ { type: 'text' | 'textarea' label: string placeholder: string required: boolean className?: string component: typeof TextArea_Shadcn_ | typeof Input_Shadcn_ } interface Track { heading: string description: string } interface Props { className?: string } const tracks: Track[] = [ { heading: 'Advocate', description: 'Help spread word on socials answer community questions', }, { heading: 'Helper', description: 'Answer questions and improve docs across our platforms', }, { heading: 'Builder/Maintainer', description: 'Contribute to client libraries, manage issues, fix bugs', }, { heading: 'Moderator', description: 'Ensure our social spaces remain productive and helpful', }, ] const productAreasOfInterest: string[] = [ 'Auth', 'AI Builders', 'Branching', 'Client libraries', 'Database / Postgres', 'Dashboard', 'CLI', 'Edge Functions', 'Integrations', 'Realtime', 'Storage', 'Vectors / AI', 'Other', ] const languagesSpoken: string[] = [ 'English', 'Spanish', 'Portuguese', 'French', 'German', 'Italian', 'Russian', 'Arabic', 'Hindi', 'Mandarin Chinese', 'Japanese', 'Turkish', 'Indonesian', 'Thai', 'Vietnamese', 'Bengali', 'Urdu', 'Polish', 'Dutch', 'Other', ] const headerContent = { title: 'Apply to join SupaSquad', description: 'Join our community of passionate contributors and help shape the future of Supabase. Fill out the form below to apply.', } const FormContent = memo(function FormContent({ form, errors, onSubmit, honeypot, setHoneypot, isMobile, isSubmitted, handleCancel, isSubmitting, }: { form: ReturnType> errors: { [key: string]: string } onSubmit: (data: SupaSquadApplication) => Promise honeypot: string setHoneypot: React.Dispatch> isMobile: boolean isSubmitted: boolean handleCancel: () => void isSubmitting: boolean }) { return (
( First Name * )} /> ( Last Name *
)} />
( Email Address *
)} />

Interests and skills

( What track would you like to be considered for? * See longer descriptions of the 4 options above
{ // Convert selected headings back to track objects const selectedTracks = values.map( (heading) => tracks.find((track) => track.heading === heading)! ) field.onChange(selectedTracks) }} values={field.value.map((track) => track.heading)} size="small" > {tracks.map((item) => (
{item.heading}
{item.description}
))}
)} /> ( Why do you want to join the program? * What do you have to contribute? What would you like to get out of it?
)} /> ( Share some of your recent contributions *

Any relevant links to show your current engagement with the Supabase community.

If you haven't contributed yet, spend some time engaging with the community, then reapply once you've built up a few contributions.

)} /> ( Product Areas of Interest * What specific areas would you like to help with? Leave blank if you're not sure.
{productAreasOfInterest.map((item) => ( {item} ))}
)} /> ( Skills (frameworks, tools, programming languages) Know Postgres really well? React? Expo? Python? Rust? Terraform? Add it here!
)} />

Location and Availability

( Country *
)} /> ( City *
)} />
( Monthly Commitment How many hours can you commit per month? If not sure, leave blank.
)} /> ( Languages spoken * What languages do you speak?
{languagesSpoken.map((item) => ( {item} ))}
)} />

Social Links

( Discord
)} /> ( GitHub
)} /> ( Twitter
)} />
{!isSubmitted && (
)} {/* Spam prevention */} ) => setHoneypot(e.target.value)} className="hidden" tabIndex={-1} autoComplete="off" aria-hidden="true" />
{Object.values(errors).length > 0 && ( {Object.values(errors).join('\n')} )}
) }) const ApplyToSupaSquadForm: FC = ({ className }) => { const [honeypot, setHoneypot] = useState('') // field to prevent spam const [errors, setErrors] = useState<{ [key: string]: string }>({}) const [isSubmitting, setIsSubmitting] = useState(false) const [isSubmitted, setIsSubmitted] = useState(false) const [success, setSuccess] = useState(null) const [showConfirmation, setShowConfirmation] = useState(false) const [startTime, setStartTime] = useState(0) const form = useForm({ resolver: zodResolver(supaSquadApplicationSchema), defaultValues: { first_name: '', last_name: '', email: '', tracks: [], areas_of_interest: [], contributions: '', skills: '', why_you_want_to_join: '', city: '', country: '', monthly_commitment: '', languages_spoken: [], github: '', twitter: '', discord: '', }, mode: 'onBlur', reValidateMode: 'onBlur', }) const handleCancel = () => { form.reset() setIsSubmitted(false) } const handleConfirmationClose = () => { form.reset() setShowConfirmation(false) setIsSubmitted(false) setErrors({}) } const onSubmit = async (data: SupaSquadApplication) => { const currentTime = Date.now() const timeElapsed = (currentTime - startTime) / 1000 // Spam prevention: Reject form if submitted too quickly (less than 3 seconds) if (timeElapsed < 3) { setErrors({ general: 'Submission too fast. Please fill the form correctly.' }) return } setIsSubmitting(true) setSuccess(null) try { const response = await fetch('/api-v2/submit-form-apply-to-supasquad', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(data), }) if (response.ok) { setSuccess('Thank you for your submission!') setShowConfirmation(true) } else { const errorData = await response.json() setErrors({ general: `Submission failed: ${errorData.message}` }) } } catch (error) { setErrors({ general: 'An unexpected error occurred. Please try again.' }) } finally { setIsSubmitting(false) } } useEffect(() => { setStartTime(Date.now()) }, []) return ( <>

{headerContent.title}

{headerContent.description}

{/* Confirmation AlertDialog Overlay */} {}}> Application submitted Thank you for your submission! Please check your email for a confirmation link to complete your application. Got it ) } export default ApplyToSupaSquadForm