page.tsx108 lines · main
| 1 | import { redirect } from 'next/navigation'; |
| 2 | |
| 3 | import { apiFetch, apiJson } from '../../../../../lib/api'; |
| 4 | |
| 5 | export const dynamic = 'force-dynamic'; |
| 6 | export const metadata = { title: 'new team' }; |
| 7 | |
| 8 | interface SubscriptionSummary { |
| 9 | tier: 'free' | 'pro' | 'team'; |
| 10 | } |
| 11 | |
| 12 | async function createTeam(formData: FormData) { |
| 13 | 'use server'; |
| 14 | const name = String(formData.get('name') ?? '').trim(); |
| 15 | const slug = String(formData.get('slug') ?? '').trim() || undefined; |
| 16 | if (!name) { |
| 17 | throw new Error('team name is required'); |
| 18 | } |
| 19 | const res = await apiFetch('/v1/orgs', { |
| 20 | method: 'POST', |
| 21 | headers: { 'content-type': 'application/json' }, |
| 22 | body: JSON.stringify({ name, slug }), |
| 23 | }); |
| 24 | if (!res.ok) { |
| 25 | const body = await res.text().catch(() => ''); |
| 26 | if (res.status === 402) { |
| 27 | redirect('/dashboard/billing'); |
| 28 | } |
| 29 | throw new Error(`team create failed (${res.status}): ${body}`); |
| 30 | } |
| 31 | redirect('/dashboard/teams'); |
| 32 | } |
| 33 | |
| 34 | export default async function NewTeamPage() { |
| 35 | const subscription = await apiJson<SubscriptionSummary>('/v1/billing/subscription').catch(() => ({ |
| 36 | tier: 'free' as const, |
| 37 | })); |
| 38 | if (subscription.tier === 'free') { |
| 39 | return ( |
| 40 | <section className="max-w-lg"> |
| 41 | <header className="mb-8"> |
| 42 | <h1 className="font-mono text-xl tracking-tight">teams are a paid feature</h1> |
| 43 | <p className="mt-1 font-mono text-sm text-[var(--color-text-muted)]"> |
| 44 | on the free tier you get one personal org. upgrade to pro or team to spin up |
| 45 | unlimited shared workspaces with invited collaborators. |
| 46 | </p> |
| 47 | </header> |
| 48 | <a |
| 49 | href="/dashboard/billing" |
| 50 | className="inline-block rounded-md bg-[var(--color-primary)] px-4 py-2 font-mono text-sm font-medium text-[var(--color-text-inverse)] transition hover:bg-[var(--color-primary-hover)]" |
| 51 | > |
| 52 | see upgrade options |
| 53 | </a> |
| 54 | </section> |
| 55 | ); |
| 56 | } |
| 57 | |
| 58 | return ( |
| 59 | <section className="max-w-lg"> |
| 60 | <header className="mb-8"> |
| 61 | <h1 className="font-mono text-xl tracking-tight">new team</h1> |
| 62 | <p className="mt-1 font-mono text-sm text-[var(--color-text-muted)]"> |
| 63 | a team org is a separate workspace from your personal org. projects, billing, and |
| 64 | membership are scoped to it. you can be in many teams; you can also rename a team |
| 65 | later from its settings. |
| 66 | </p> |
| 67 | </header> |
| 68 | |
| 69 | <form action={createTeam} className="flex flex-col gap-5"> |
| 70 | <label className="flex flex-col gap-2"> |
| 71 | <span className="font-mono text-xs text-[var(--color-text-muted)]">team name</span> |
| 72 | <input |
| 73 | name="name" |
| 74 | type="text" |
| 75 | required |
| 76 | maxLength={200} |
| 77 | placeholder="acme inc" |
| 78 | className="rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] px-3 py-2 font-mono text-sm outline-none focus:border-[var(--color-primary)]" |
| 79 | /> |
| 80 | </label> |
| 81 | |
| 82 | <label className="flex flex-col gap-2"> |
| 83 | <span className="font-mono text-xs text-[var(--color-text-muted)]"> |
| 84 | slug{' '} |
| 85 | <span className="text-[var(--color-text-subtle)]">(optional — generated if blank)</span> |
| 86 | </span> |
| 87 | <input |
| 88 | name="slug" |
| 89 | type="text" |
| 90 | pattern="[a-z0-9](?:[a-z0-9\-]{0,38}[a-z0-9])?" |
| 91 | maxLength={40} |
| 92 | placeholder="acme" |
| 93 | className="rounded-md border border-[var(--color-border)] bg-[var(--color-surface)] px-3 py-2 font-mono text-sm outline-none focus:border-[var(--color-primary)]" |
| 94 | /> |
| 95 | </label> |
| 96 | |
| 97 | <div className="flex gap-3"> |
| 98 | <button |
| 99 | type="submit" |
| 100 | className="rounded-md bg-[var(--color-primary)] px-4 py-2 font-mono text-sm font-medium text-[var(--color-text-inverse)] transition hover:bg-[var(--color-primary-hover)]" |
| 101 | > |
| 102 | create team |
| 103 | </button> |
| 104 | </div> |
| 105 | </form> |
| 106 | </section> |
| 107 | ); |
| 108 | } |