()
| 41 | }; |
| 42 | |
| 43 | export const CreateOrgPage = () => { |
| 44 | const auth = useAuth(); |
| 45 | const navigate = useNavigate(); |
| 46 | const invitationsResult = useAtomValue(pendingInvitationsAtom); |
| 47 | const doAccept = useAtomSet(acceptInvitation, { mode: "promiseExit" }); |
| 48 | |
| 49 | const [acceptingId, setAcceptingId] = useState<string | null>(null); |
| 50 | const [errorByInvitationId, setErrorByInvitationId] = useState<Record<string, string>>({}); |
| 51 | |
| 52 | const handleAccept = async (invitation: PendingInvitation) => { |
| 53 | setAcceptingId(invitation.id); |
| 54 | setErrorByInvitationId((prev) => { |
| 55 | if (!(invitation.id in prev)) return prev; |
| 56 | const next = { ...prev }; |
| 57 | delete next[invitation.id]; |
| 58 | return next; |
| 59 | }); |
| 60 | const exit = await doAccept({ |
| 61 | payload: { invitationId: invitation.id }, |
| 62 | reactivityKeys: authWriteKeys, |
| 63 | }); |
| 64 | setAcceptingId(null); |
| 65 | trackEvent("org_invitation_accepted", { success: Exit.isSuccess(exit) }); |
| 66 | if (!Exit.isSuccess(exit)) { |
| 67 | setErrorByInvitationId((prev) => ({ |
| 68 | ...prev, |
| 69 | [invitation.id]: "Couldn't accept this invitation. Try again or ask the inviter to resend.", |
| 70 | })); |
| 71 | } |
| 72 | }; |
| 73 | |
| 74 | const suggestedName = |
| 75 | auth.status === "authenticated" && auth.user.name != null && auth.user.name.trim() !== "" |
| 76 | ? `${auth.user.name}'s Organization` |
| 77 | : ""; |
| 78 | |
| 79 | const form = useCreateOrganizationForm({ |
| 80 | defaultName: suggestedName, |
| 81 | onSuccess: () => { |
| 82 | void navigate({ to: "/setup-mcp" }); |
| 83 | }, |
| 84 | }); |
| 85 | |
| 86 | // Almost no new user has a pending invitation, so we never show a loading |
| 87 | // placeholder for them — the create form renders immediately and stays put. |
| 88 | // Invitations (when they exist) just appear above it once the fetch resolves; |
| 89 | // a refetch (the create-org submit invalidates the auth-keyed atom) keeps the |
| 90 | // last value, so nothing flickers. |
| 91 | const invitations = AsyncResult.match(invitationsResult, { |
| 92 | onInitial: () => [] as readonly PendingInvitation[], |
| 93 | onFailure: () => [] as readonly PendingInvitation[], |
| 94 | onSuccess: ({ value }) => value.invitations, |
| 95 | }); |
| 96 | |
| 97 | const count = invitations.length; |
| 98 | const sole = count === 1 ? invitations[0]! : null; |
| 99 | |
| 100 | return ( |
nothing calls this directly
no test coverage detected