"use client"; import { useState } from "react"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import QRCode from "qrcode"; import { createMyInviteInputSchema, type CreateMyInviteInput, } from "@turbostarter/api/schema"; import { handle } from "@turbostarter/api/utils"; import { Badge } from "@turbostarter/ui-web/badge"; import { Button } from "@turbostarter/ui-web/button"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@turbostarter/ui-web/form"; import { Input } from "@turbostarter/ui-web/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@turbostarter/ui-web/select"; import { api } from "~/lib/api/client"; interface GeneratedInvite { id: string; token: string; inviteLink: string; joinUrl: string; expiresAt: Date; qrDataUrl: string; } export const InviteGenerator = ({ meshId }: { meshId: string }) => { const [result, setResult] = useState(null); const [copied, setCopied] = useState<"url" | "cli" | null>(null); const form = useForm({ resolver: zodResolver(createMyInviteInputSchema), defaultValues: { role: "member", maxUses: 1, expiresInDays: 7 }, }); const onSubmit = async (values: CreateMyInviteInput) => { try { const res = (await handle(api.my.meshes[":id"].invites.$post)({ param: { id: meshId }, json: values, })) as | { id: string; token: string; inviteLink: string; joinUrl: string; expiresAt: string; } | { error: string }; if ("error" in res) { form.setError("root", { message: res.error }); return; } // QR encodes the HTTPS join URL now — anyone with a camera can // scan and land on the friendly /join/[token] page. const qrDataUrl = await QRCode.toDataURL(res.joinUrl, { width: 256, margin: 1, color: { dark: "#141413", light: "#ffffff" }, }); setResult({ id: res.id, token: res.token, inviteLink: res.inviteLink, joinUrl: res.joinUrl, expiresAt: new Date(res.expiresAt), qrDataUrl, }); } catch (e) { form.setError("root", { message: e instanceof Error ? e.message : "Failed to generate invite.", }); } }; const copy = async (text: string, key: "url" | "cli") => { await navigator.clipboard.writeText(text); setCopied(key); setTimeout(() => setCopied(null), 2000); }; if (result) { const cliCmd = `claudemesh join ${result.token}`; return (
Invite QR code
Share this link
{result.joinUrl}
expires {result.expiresAt.toLocaleDateString()}

How your teammate joins:

Paste the link in Slack / Telegram / email. They land on a page with step-by-step install, or run the CLI directly if they already have it:

{cliCmd}
); } return (
( Role )} /> ( Max uses field.onChange(Number(e.target.value))} /> )} /> ( Expires in (days) field.onChange(Number(e.target.value))} /> )} /> {form.formState.errors.root && (

{form.formState.errors.root.message}

)} ); };