"use client" import { useMemo, useState } from "react" import { Button } from "@ui/components/button" import { Input } from "@ui/components/input" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@ui/components/select" import { ArrowRight, Loader2, Mail, Plus, Trash2, Users } from "lucide-react" import { cn } from "@lib/utils" import { dmSans125ClassName } from "@/lib/fonts" const modalCardStyle = { boxShadow: "0 2.842px 14.211px 0 rgba(0, 0, 0, 0.25), 0.711px 0.711px 0.711px 0 rgba(255, 255, 255, 0.10) inset", } const inputBevelStyle = { boxShadow: "0px 1px 2px 0px rgba(0,43,87,0.1), inset 0px 0px 0px 1px rgba(43,49,67,0.08), inset 0px 1px 1px 0px rgba(0,0,0,0.08), inset 0px 2px 4px 0px rgba(0,0,0,0.02)", } const inputClass = "bg-[#0F1217] border border-[rgba(82,89,102,0.2)] rounded-[12px] text-[#fafafa] text-[14px] placeholder:text-[#525D6E] h-12 shadow-none focus-visible:ring-0 focus-visible:border-[rgba(115,115,115,0.3)] transition-colors" export interface TeamValues { invites: { email: string; role: "admin" | "member" }[] visibility: "team-private" | "org-shared" suggestChanges: boolean } interface Props { mode: "personal" | "team" isScale: boolean inviteDomain: string | null values: TeamValues onChange: (next: TeamValues) => void onContinue: () => void onSkip?: () => void onUpgrade: () => void submitting?: boolean } const EMAIL_RE = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi export function StepTeam({ mode, inviteDomain, values, onChange, onContinue, onSkip, submitting, }: Props) { const [draft, setDraft] = useState("") const domainOrFallback = (inviteDomain || "acme.com").trim().toLowerCase() const addInvites = (text: string) => { const found = text.match(EMAIL_RE) ?? [] if (found.length === 0) return const existing = new Set(values.invites.map((i) => i.email.toLowerCase())) const next: { email: string; role: "admin" | "member" }[] = [] for (const raw of found) { const email = raw.trim().toLowerCase() if (!email || existing.has(email)) continue existing.add(email) next.push({ email, role: "member" }) } if (next.length === 0) { setDraft("") return } onChange({ ...values, invites: [...values.invites, ...next] }) setDraft("") } const removeInvite = (email: string) => { onChange({ ...values, invites: values.invites.filter((i) => i.email !== email), }) } const setRole = (email: string, role: "admin" | "member") => { onChange({ ...values, invites: values.invites.map((i) => i.email === email ? { ...i, role } : i, ), }) } const domainBreakdown = useMemo(() => { const counts = new Map() for (const inv of values.invites) { const at = inv.email.lastIndexOf("@") if (at < 0) continue const domain = inv.email.slice(at + 1).toLowerCase() counts.set(domain, (counts.get(domain) ?? 0) + 1) } return [...counts.entries()].sort((a, b) => b[1] - a[1]) }, [values.invites]) if (mode === "personal") { return (

Going solo — for now

You're in Personal mode, so there's no team step. Switch to Team in the top bar anytime to invite others.

) } const count = values.invites.length return (

Invite your team

A brain gets sharper as more people contribute. You can also do this later.

{ e.preventDefault() addInvites(draft) }} className="mt-5 flex min-w-0 gap-2 md:mt-6" >
setDraft(e.target.value)} onPaste={(e) => { const pasted = e.clipboardData.getData("text") if (pasted && EMAIL_RE.test(pasted)) { e.preventDefault() addInvites(pasted) } }} placeholder={`alex@${domainOrFallback}`} className={cn(inputClass, "min-w-0 pl-10")} style={inputBevelStyle} />

Paste multiple emails at once — we'll split them for you.

{count === 0 ? (

No invites yet.

Try{" "} alex@{domainOrFallback},{" "} sam@{domainOrFallback}, etc.

) : ( <>

{count} invite{count === 1 ? "" : "s"}

{domainBreakdown.length > 0 && (
{domainBreakdown.slice(0, 3).map(([d, n]) => ( {d} · {n} ))}
)}
{values.invites.map((inv) => (
{(inv.email[0] ?? "?").toUpperCase()}
{inv.email}
))}
)}
) }