Skip to content

fix(invites): consume invite tokens on signup - #290

Open
chiptus wants to merge 6 commits into
mainfrom
claude/long-running-task-mman6l
Open

fix(invites): consume invite tokens on signup#290
chiptus wants to merge 6 commits into
mainfrom
claude/long-running-task-mman6l

Conversation

@chiptus

@chiptus chiptus commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Wires the never-called use_invite_token RPC into a useInviteAcceptance effect keyed on user + invite token, so invite links actually add users to the group (magic-link, OTP, and already-signed-in paths). Also fixes InviteLandingPage sending invite_id instead of the real token into the auth flow, and removes the deadlock-prone RPC call from AuthContext's onAuthStateChange.

Closes #268
Closes #165

Verification

  • Generate a group invite link, open it logged out, sign up via the 6-digit OTP code; you land in the app with a "Welcome to {group}" toast, the group appears in your groups, and ?invite= is cleared from the URL.
  • Same flow but click the magic link in the email instead of entering the code; joining works the same way.
  • As an existing logged-out user, open an invite link and log in; you're added to the group.
  • Re-open an invite link for a group you're already in; a friendly "Already a member" notice shows, no error and no duplicate membership.
  • Open an expired/deactivated/unknown invite link; the existing "Invalid Invite" states render unchanged.

Generated by Claude Code

claude added 2 commits August 6, 2026 15:08
Wires the never-called use_invite_token RPC into a central
useInviteAcceptance hook keyed on user + invite token, covering magic-link,
OTP, and already-signed-in paths. Fixes InviteLandingPage passing invite_id
instead of the real token into the auth flow (broken magic-link redirect),
removes the deadlock-prone RPC call from AuthContext's onAuthStateChange,
treats invite reuse by an existing member as a clean no-op, and invalidates
group queries after joining.

Closes #268

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011AAZzVYMN7csUv8kehZEsp
Reset the attempted-token guard on failure so a transient error doesn't
permanently block acceptance, drop the unused hook return value, and unify
the group-name fallback copy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011AAZzVYMN7csUv8kehZEsp
Copilot AI lite review requested due to automatic review settings August 6, 2026 15:13
@vercel

vercel Bot commented Aug 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
upline Ready Ready Preview Aug 9, 2026 2:27pm

@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes the invite flow so that a validated ?invite=<token> link is actually consumed after a session exists, ensuring users are inserted into group_members via the existing use_invite_token RPC (OTP, magic-link redirect, and already-signed-in cases).

Changes:

  • Adds useInviteAcceptance (effect keyed on user + inviteToken + validation) to call the accept-invite mutation and clear ?invite= from the URL on success.
  • Fixes InviteLandingPage to pass the real invite token into AuthDialog (instead of invite_id) so the auth flow preserves the correct token.
  • Removes the RPC call from AuthContext’s onAuthStateChange callback to avoid the deadlock-prone pattern described in the PR.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/routes/__root.tsx Wires invite acceptance into the root route and passes inviteToken into InviteLandingPage.
src/contexts/AuthContext.tsx Removes invite consumption side-effects from the auth callback; keeps auth dialog close on SIGNED_IN.
src/components/invite/useInviteValidation.ts Limits this hook to validation + toast side effects (removes unused “accept” behavior).
src/components/invite/useInviteAcceptance.ts New hook that consumes valid invites once a user exists, toasts outcome, and clears the URL param.
src/components/invite/useInviteAcceptance.test.tsx Adds unit tests covering success, already-member, invalid, error, and single-attempt behavior.
src/components/invite/InviteLandingPage.tsx Passes the actual token into AuthDialog and closes dialog on OTP success.
src/api/invite-validation/useInviteValidationQuery.ts Removes the unused invite-accept mutation from this query module.
src/api/invite-validation/useAcceptInviteMutation.ts Adds a dedicated mutation wrapper around use_invite_token and invalidates groups on join.
Suppressed comments (1)

src/api/invite-validation/useAcceptInviteMutation.ts:42

  • alreadyMember detection relies on a hard-coded message string ("User already in group"), which is fragile. The RPC also returns group_id = NULL for several failure cases, so it’s safer to treat the "already member" case as the only failure where group_id is present.
  if (result.message === "User already in group") {
    return { ...result, alreadyMember: true };
  }

  throw new Error(result.message);

Comment thread src/api/invite-validation/useAcceptInviteMutation.ts
The use_invite_token RPC returns a group_id only for the already-in-group
failure, so keying on it survives server copy changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011AAZzVYMN7csUv8kehZEsp
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown

Playwright test results

passed  50 passed

Details

stats  50 tests across 14 suites
duration  1 minute, 8 seconds
commit  20c871a

@chiptus chiptus left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need all the different invite hooks?

Comment thread src/components/invite/useInviteAcceptance.test.tsx Outdated
Comment thread src/contexts/AuthContext.tsx Outdated
Collapses useInviteValidation + useInviteAcceptance (both only used by the
root route) into a single flow hook, drops the AuthContext comment, and
reworks the test to drive the whole flow through the real query/mutation
hooks with only the supabase client mocked.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011AAZzVYMN7csUv8kehZEsp

chiptus commented Aug 9, 2026

Copy link
Copy Markdown
Owner Author

Why do we need all the different invite hooks?

Agreed it was one hook too many — consolidated in the latest push. There are now two layers:

  • src/api/invite-validation/useInviteValidationQuery and useAcceptInviteMutation, one file per endpoint as ADR-0001 prescribes (the mutation also owns the groups-query invalidation).
  • src/components/invite/useInviteFlow.ts — the single component-level hook. It validates the token, shows the invalid/expired toasts, and auto-consumes the invite once a session exists (which covers OTP, magic-link landing, and already-logged-in users in one code path). The former useInviteValidation + useInviteAcceptance pair is gone.

The AuthContext comment is also removed, and the test now runs the whole flow through the real query/mutation hooks with only the supabase client mocked (details in the thread on the test file).


Generated by Claude Code

Comment thread src/components/invite/useInviteFlow.ts Outdated
Comment thread src/components/invite/useInviteFlow.ts Outdated
A single effect driven by one getError helper also removes the edge case
where a stale validation result plus a refetch error could toast twice.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011AAZzVYMN7csUv8kehZEsp

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/components/invite/useInviteFlow.ts:36

  • attemptedTokenRef only keys on the invite token. If the signed-in user changes while the same inviteToken remains in the URL (e.g., sign out/in in the same tab, or switch accounts after a successful acceptance but the token is reintroduced via navigation), the hook will incorrectly skip consuming the invite for the new user. Key the “attempted” guard by both user ID and token.
    const groupName = inviteQuery.data?.group_name;
    if (!user || !inviteToken || inviteQuery.data?.is_valid !== true) return;
    if (attemptedTokenRef.current === inviteToken) return;
    attemptedTokenRef.current = inviteToken;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants