"use client"; import { Mail, Plus, X } from "lucide-react"; import type React from "react"; import { useState } from "react"; import { cx } from "../lib/utils"; import { OnboardingButton } from "../primitives/onboarding-button"; import { OnboardingInput } from "../primitives/onboarding-input"; import { OnboardingLabel } from "../primitives/onboarding-label"; import { OnboardingSelect, OnboardingSelectContent, OnboardingSelectItem, OnboardingSelectTrigger, OnboardingSelectValue, } from "../primitives/onboarding-select"; export interface TeamRole { value: string; label: string; description?: string; } export interface TeamMember { email: string; role: string; } export interface TeamInviteStepProps { /** Title displayed at the top of the step */ title?: string; /** Description text below the title */ description?: string; /** Available roles for team members */ roles: TeamRole[]; /** Default role for new members */ defaultRole?: string; /** Default members to pre-populate */ defaultMembers?: TeamMember[]; /** Minimum number of invites required (0 = optional) */ minInvites?: number; /** Maximum number of invites allowed */ maxInvites?: number; /** Called when invites change */ onInvitesChange?: (members: TeamMember[]) => void; /** Called when the user submits the form */ onSubmit: (members: TeamMember[]) => void | Promise; /** Text for the submit button */ submitText?: string; /** Text shown while submitting */ loadingText?: string; /** Text for skip button (if minInvites is 0) */ skipText?: string; /** Optional back button config */ backButton?: { text: string; onClick: () => void; }; } export function TeamInviteStep({ title = "Invite your team", description = "Add team members to collaborate on this project.", roles, defaultRole, defaultMembers, minInvites = 0, maxInvites = 10, onInvitesChange, onSubmit, submitText = "Send Invites", loadingText = "Sending...", skipText = "Skip for now", backButton, }: TeamInviteStepProps) { const [members, setMembers] = useState( defaultMembers && defaultMembers.length > 0 ? defaultMembers : [{ email: "", role: defaultRole || roles[0]?.value || "" }], ); const [errors, setErrors] = useState>({}); const [loading, setLoading] = useState(false); const validateEmail = (email: string): boolean => { if (!email) return true; // Empty is okay, we filter them out const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); }; const handleEmailChange = (index: number, email: string) => { const newMembers = [...members]; newMembers[index].email = email; setMembers(newMembers); // Validate if (email && !validateEmail(email)) { setErrors((prev) => ({ ...prev, [index]: "Please enter a valid email" })); } else { setErrors((prev) => { const newErrors = { ...prev }; delete newErrors[index]; return newErrors; }); } onInvitesChange?.(newMembers.filter((m) => m.email.trim())); }; const handleRoleChange = (index: number, role: string) => { const newMembers = [...members]; newMembers[index].role = role; setMembers(newMembers); onInvitesChange?.(newMembers.filter((m) => m.email.trim())); }; const addMember = () => { if (members.length < maxInvites) { setMembers([ ...members, { email: "", role: defaultRole || roles[0]?.value || "" }, ]); } }; const removeMember = (index: number) => { if (members.length > 1) { const newMembers = members.filter((_, i) => i !== index); setMembers(newMembers); // Clean up errors setErrors((prev) => { const newErrors: Record = {}; Object.entries(prev).forEach(([key, value]) => { const keyNum = parseInt(key); if (keyNum < index) { newErrors[keyNum] = value; } else if (keyNum > index) { newErrors[keyNum - 1] = value; } }); return newErrors; }); onInvitesChange?.(newMembers.filter((m) => m.email.trim())); } }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); // Validate all emails const newErrors: Record = {}; members.forEach((member, index) => { if (member.email && !validateEmail(member.email)) { newErrors[index] = "Please enter a valid email"; } }); if (Object.keys(newErrors).length > 0) { setErrors(newErrors); return; } const validMembers = members.filter((m) => m.email.trim()); if (validMembers.length < minInvites) { // This shouldn't happen if button is disabled properly return; } setLoading(true); try { await onSubmit(validMembers); } finally { setLoading(false); } }; const handleSkip = async () => { setLoading(true); try { await onSubmit([]); } finally { setLoading(false); } }; const validMemberCount = members.filter( (m) => m.email.trim() && validateEmail(m.email), ).length; const hasErrors = Object.keys(errors).length > 0; const canSubmit = validMemberCount >= minInvites && !hasErrors; const canSkip = minInvites === 0; return (

{title}

{description}

{members.map((member, index) => (
Email address handleEmailChange(index, e.target.value)} aria-invalid={!!errors[index]} className={cx(errors[index] && "border-destructive")} />
Role handleRoleChange(index, value)} > {roles.map((role) => ( {role.label} ))}
{members.length > 1 && ( )}
{errors[index] && (

{errors[index]}

)}
))}
{members.length < maxInvites && ( )}
{backButton && ( {backButton.text} )}
{canSkip && validMemberCount === 0 && ( {skipText} )} {loading ? loadingText : validMemberCount > 0 ? `${submitText} (${validMemberCount})` : submitText}
); }