feat: Frontend platform linking page (/link/{token})#12624
feat: Frontend platform linking page (/link/{token})#12624Bentlybro wants to merge 11 commits intofeat/copilot-bot-servicefrom
Conversation
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
a7c5379 to
4a5a9a5
Compare
59e7852 to
ef89528
Compare
|
/review |
There was a problem hiding this comment.
All 8 specialists have reported. Compiling the final verdict now.
PR #12624 — feat: Frontend platform linking page (/link/{token})
Author: Bentlybro | Files: +autogpt_platform/frontend/src/app/(no-navbar)/link/[token]/page.tsx (+290)
🎯 Verdict: REQUEST_CHANGES
What This PR Does
Adds a new frontend page at /link/{token} that lets users connect external chat platform accounts (Discord, Telegram, Slack, etc.) to their AutoGPT account. A bot on the external platform generates a unique token URL; the user clicks it, signs in if needed, confirms the link, and gets a success screen. This is a frontend-only PR — the backend API endpoints are expected from stacked PRs (#12618, #12615).
Specialist Findings
🛡️ Security
⚠️ page.tsx:183— Signupnextparam not URL-encoded while login URL at:170correctly usesencodeURIComponent. Inconsistent and fragile — if token format ever changes, signup redirects break.⚠️ Pre-existing open redirect in signup —signup/useSignupPage.tsdoesn't validate thenextparameter (unlikelogin/useLoginPage.tswhich checksstartsWith("/") && !startsWith("//")). Not introduced by this PR but exposed by it.- ℹ️ Unauthenticated token status endpoint —
GET /tokens/{token}/statusreveals token state (pending/expired/linked) without auth. Low risk given UUID entropy, but violates least-privilege.
🏗️ Architecture (no-navbar) is correct. LinkState discriminated union is a clean pattern. But the PR bypasses the established API client pattern:
⚠️ page.tsx:45,95— Rawfetch()instead of generated React Query hooks. The codebase has orval-generated hooks for these exact endpoints (useGetPlatformLinkingCheckIfALinkTokenHasBeenConsumed,usePostPlatformLinkingConfirmALinkTokenUserMustBeAuthenticated). The siblingshare/[token]/page.tsxuses the generated hooks. Raw fetch duplicates API paths, skips thecustomMutatorauth flow (including impersonation headers), and loses React Query caching/retry.⚠️ page.tsx:91-93— Directsupabase.auth.getSession()for auth instead of thecustomMutatorthat the rest of the codebase uses. If auth strategy changes (e.g., impersonation), this page silently breaks.⚠️ page.tsx:11-19—PLATFORM_INFOis dead weight during the check flow. The status endpoint doesn't return platform info (acknowledged in comment at:70), soplatformis always"your platform"until after confirm. Theiconfield is never read anywhere.
⚡ Performance
⚠️ page.tsx:82—useEffectdepends on[token, user]—userfromuseSupabaseStorechanges reference on every Supabase auth event (token refresh, session restore), causing redundantcheckToken()API calls. Should split into two effects: one for token validation ([token]), one for auth state.⚠️ NoAbortController— If the effect re-fires, concurrent fetches race and the loser can overwrite the winner's state.- ℹ️
"use client"on entire page — Token validation could be a Server Component (eliminating the loading spinner for error/expired states entirely).
🧪 Testing ❌ — Zero tests included. 290 lines of new client-side logic with multiple state transitions, two API calls, and auth gating — completely untested. The codebase has established patterns for both unit tests (Vitest + RTL, e.g. build/__tests__/CustomNode.test.tsx) and E2E tests (Playwright, e.g. signin.spec.ts). At minimum, unit tests should cover: loading state, invalid/expired token, already-linked token, unauthenticated → redirect, authenticated → ready, link success, link failure, network errors. ~20 test cases identified.
⚠️ page.tsx:34—params.token as stringunsafe cast —useParams()returnsstring | string[]; no test validates this assumption.⚠️ page.tsx:82— Silent no-op ifsupabaseis null —handleLinkreturns without user feedback.
📖 Quality PlatformLinkPage, LoadingView, etc.). Issues:
⚠️ page.tsx:148-149,224-225— Hand-rolled spinner instead of existing<LoadingSpinner>component from@/components/atoms/LoadingSpinner/LoadingSpinner(used bylogout/page.tsx).⚠️ page.tsx:214—bg-slate-50hardcoded color — Won't respect dark mode. Codebase uses semantic tokens (bg-muted,bg-background). Same issue at:249(bg-green-100) and:262(bg-red-100).⚠️ page.tsx:24—platformUsernameinreadystate is dead code — Never populated bycheckToken(), soReadyView's conditional JSX (:201-206) can never trigger.⚠️ page.tsx:30— UsesuseSupabaseStoredirectly instead of theuseSupabasewrapper hook that most other pages use.- ℹ️ Bare
catchblocks (:77,121) match existing codebase style but swallow errors silently.
📦 Product
⚠️ "your platform" placeholder text (page.tsx:73-76) — User sees "Connect your your platform account" because the status endpoint doesn't return platform info. Poor UX for the confirmation screen.⚠️ No retry for transient errors — Error view tells users to go back to chat for a new link, even for network failures. A "Try again" button would save unnecessary round-trips.⚠️ Success is a dead end — No "Go to Dashboard" link, no auto-close, just "you can close this page."⚠️ Auth flash —usertransitionsundefined → null → Useron mount, causing a brief flash through loading → not-authenticated → ready.- ℹ️ Accessibility: spinners lack
role="status"/aria-label, emoji status indicators (✅❌) won't translate for screen readers.
📬 Discussion ✅ — CI is clean: 10/11 checks passing, only chromatic skipped (expected for draft). This is a draft PR with zero human reviews submitted and no requested reviewers. It's stacked on #12618 → #12615 (base branch feat/copilot-bot-service, not main). CodeRabbit auto-skipped review due to draft status. autogpt-pr-reviewer[bot] queued a review but hasn't posted results yet.
🔎 QA
- ✅ Landing page renders correctly
- ✅ Signup flow works (fresh DB,
qatest@example.com) - ✅
/link/[token]route resolves with dynamic token param - ✅ Invalid token shows "Link failed" with user-friendly error message
- ✅ Missing token (
/link/) returns proper 404 - ✅ No console crashes or unhandled rejections
⚠️ Dark modebg-slate-50concern confirmed visually- ❌ Ready/linking/success states untested (no backend)
Blockers (Must Fix)
-
page.tsx:45,95— Use generated React Query hooks instead of rawfetch()— The orval-generated hooks exist for both endpoints. Raw fetch duplicates API paths, bypasses thecustomMutatorauth flow (breaking impersonation support), and loses caching/retry. This is the single biggest architectural issue — it introduces a second API access pattern. (Flagged by: Architect, Security, Quality) -
page.tsx:183— MissingencodeURIComponenton signupnextparam — Login URL at:170correctly encodes; signup URL doesn't. Inconsistent and can break URL parsing. Fix:href={/signup?next=${encodeURIComponent(/link/${token})}}(Flagged by: Security, Quality, Product) -
page.tsx:214,249,262— Hardcoded colors break dark mode —bg-slate-50,bg-green-100,bg-red-100won't adapt to dark theme. Use semantic tokens:bg-muted,bg-green-100/50 dark:bg-green-900/20, etc. (Flagged by: Quality, Product, QA — confirmed visually)
Should Fix (Follow-up OK)
-
page.tsx:148-149,224-225— Use existing<LoadingSpinner>component instead of hand-rolled spinner divs. Thelogout/page.tsxalready imports it. (Quality) -
page.tsx:73-76— "your platform" generic text — The ready state always shows "Connect your your platform account." Enrich the status endpoint to return platform name, or use thePLATFORM_INFOmap with data from the status response. (Product, Architect) -
page.tsx:37-82— SplituseEffectto avoid redundant API calls — Separate token validation ([token]) from auth-state reaction ([user]). AddAbortControllerfor request cancellation. (Performance) -
Zero test coverage — Add at minimum: loading state, invalid/expired token, auth redirect, link success/failure, network error unit tests. The codebase has established Vitest + RTL patterns to follow. (Testing)
-
page.tsx:24—platformUsernamedead code — Never populated, soReadyView's conditional JSX can never render. Remove or wire up. (Quality) -
page.tsx:11-19—PLATFORM_INFO.iconnever read — Remove deadiconfield or use it. (Quality, Architect) -
Error view lacks retry — Add a "Try again" button for transient network failures instead of forcing users to get a new token. (Product)
-
Accessibility — Add
role="status"+aria-labelto spinners, addaria-liveregions for state transitions. (Product)
Risk Assessment
Merge risk: LOW — Single new page, no existing code modified, feature-gated behind token URL flow.
Rollback: EASY — Delete one file, no migrations, no shared state.
Note: This is a draft PR stacked on #12618 → #12615. No human reviews have been submitted yet. CI is green. The 3 blockers above are straightforward fixes (use generated hooks, encode URL, fix dark mode colors). Once addressed, this is a clean APPROVE.
REVIEW_COMPLETE
PR: #12624
Verdict: REQUEST_CHANGES
Blockers: 3
4a5a9a5 to
8ef22f3
Compare
ef89528 to
a80e7bc
Compare
8ef22f3 to
b3098a2
Compare
0a2ff1b to
70a862a
Compare
b3098a2 to
3a44efa
Compare
70a862a to
79978d9
Compare
|
/review |
79978d9 to
5c23efd
Compare
There was a problem hiding this comment.
All 8 specialists have reported. Compiling the final verdict now.
PR #12624 — feat: Frontend platform linking page (/link/{token})
Author: Bentlybro | Requested by: ntindle | Files: +autogpt_platform/frontend/src/app/(no-navbar)/link/[token]/page.tsx (+290)
🎯 Verdict: REQUEST_CHANGES
What This PR Does
Adds a new frontend page at /link/{token} that lets users connect their chat platform identity (Discord, Telegram, Slack, etc.) to their AutoGPT account. A bot sends the user a unique link → they visit it → sign in if needed → click "Link account" → accounts are connected. The page has 6 states: loading, not-authenticated, ready, linking, success, and error. This is part of the CoPilot bot service feature stack (#12624 → #12618 → #12615).
Specialist Findings
🛡️ Security
⚠️ MissingencodeURIComponenton signup redirect (page.tsx:178): Login URL correctly encodes withencodeURIComponent, but signup link uses raw interpolation/signup?next=/link/${token}. A crafted token with&,#, or?could manipulate the query string. If the signup handler doesn't validatenext, this becomes an open redirect vector for phishing.⚠️ Token path interpolation without validation (page.tsx:34,49,101): Token fromuseParams()is interpolated directly into fetch URLs. A token with../could alter the API path. Backend must enforce format, but client-side regex guard would add defense-in-depth.⚠️ Token enumeration (page.tsx:49-73): Status endpoint returns differentiatedlinked/expired/pendingvs 404 for invalid — allows brute-force discovery of valid tokens.- ✅ Auth pattern is correct — Bearer token via
Authorizationheader (not cookies), so CSRF is not a concern. No secrets in client code.
🏗️ Architecture fetch bypasses the project's generated API client; confirmed by both Architect and Discussion (bot reviewer flagged same issue).
⚠️ Bypasses generated orval hooks (page.tsx:46-48, 89-98): The codebase already has auto-generated hooks atsrc/app/api/__generated__/endpoints/platform-linking/platform-linking.ts—useGetPlatformLinkingCheckIfALinkTokenHasBeenConsumedandusePostPlatformLinkingConfirmALinkTokenUserMustBeAuthenticated. Using rawfetchskips thecustomMutatorthat handles auth injection, error normalization, and base URL resolution. The siblingshare/[token]/page.tsxuses generated hooks — this page should follow suit.⚠️ HardcodedPLATFORM_INFOmap (page.tsx:11-19): Duplicates backend platform knowledge. Falls back to raw string when unknown platform appears, losing the icon. Should come from the API or live in a shared constants file.⚠️ Nolayout.tsxfor the route: Siblingshare/[token]has its own layout with<title>androbots: noindex. This token-based URL should not be search-indexed either.- ✅ Route group placement in
(no-navbar)is correct and consistent with existing patterns.
⚡ Performance
⚠️ Double-fetch on auth state settle (page.tsx:82):useEffectdepends on[token, user]. Supabase emitsuserasnull→Useron load, causing the effect to fire twice — wasted fetch + visible UI flicker (loading → not-authenticated → loading → ready).⚠️ NoAbortController(page.tsx:42-81): Race condition ifuserchanges during the fetch — stale response can overwrite current state.⚠️ No double-click protection (page.tsx:197): Button can firehandleLinkmultiple times before React re-renders to the "linking" spinner state. Should disable button or use auseRefguard.- ✅ Bundle impact is negligible — code-split by Next.js, only small UI atom imports.
🧪 Testing ❌ — Zero test coverage on 290 lines of interactive code.
- ❌ No tests added — 0% coverage on a component with 6 states, 2 API calls, auth integration, and multiple error paths.
- The codebase has established patterns: Vitest + React Testing Library,
test-utils.tsxwith provider wrappers,mock-supabase-request.tsxfor auth mocking, and Playwright e2e specs. - 12 distinct test cases needed: token valid/invalid/expired/linked states, auth/unauth flows, confirm success/failure, network errors, double-click, session expiry.
📖 Quality
⚠️ Dead code:iconfield never read (page.tsx:11-19): EveryPLATFORM_INFOentry hasiconbut no component renders it.⚠️ Dead code:platformUsernamenever populated (page.tsx:24):LinkState.readyhasplatformUsername?butcheckToken()never sets it.ReadyViewrenders the username branch but it's unreachable.⚠️ Dark mode broken (page.tsx:213,249,262):bg-slate-50,bg-green-100,bg-red-100are hardcoded light colors with nodark:variants — will render as jarring bright boxes in dark mode. Flagged by Quality, Product, and QA independently.⚠️ Noaria-liveregion or focus management on state transitions — screen readers won't announce loading/error/success state changes.- ✅ Discriminated union
LinkStateis well-typed. Component decomposition is appropriate for 290 lines.
📦 Product
⚠️ "Connect your your platform account" (page.tsx:73-75): Status endpoint doesn't return platform info, so the ready state always shows the literal string "your platform" — confusing and reduces trust. Users from Discord should see "Connect your Discord account". ThePLATFORM_INFOmap exists but is only used after successful linking, not when it matters most.⚠️ No "Cancel" / "Not you?" option (page.tsx:189-208): If logged into the wrong account, no way to switch without manually navigating to logout.⚠️ No retry button in error state (page.tsx:236-257): For transient network errors, users must go back to their chat for a new link — a "Try again" button would reduce friction.- ✅ Login redirect with
?next=works correctly —useLoginPage.tsxvalidates and preserves the param.
📬 Discussion
⚠️ Bot review REQUEST_CHANGES atef89528with 3 blockers (raw fetch, missing encodeURIComponent, dark mode) + 8 suggestions — author pushed79978d9and re-triggered/reviewwithout commenting on what was fixed.⚠️ Zero human reviewers requested or self-assigned. Still a draft PR.- ✅ CI: 14/15 checks passing (only
chromaticskipped — expected for draft). - ✅ PR description is well-written with a clear feature walkthrough and state table.
- ℹ️ Stacked on #12618 → #12615 (base:
feat/copilot-bot-service), no linked issues or milestone.
🔎 QA ❌ — Critical: API routing is completely broken. The page cannot function.
- 🔴 All API calls 404 — The page fetches from
/api/platform-linking/tokens/{token}/statuswhich resolves tohttp://localhost:3000/api/platform-linking/...(the Next.js server). No Next.js API route exists at this path, and no rewrite/proxy is configured innext.config.mjs. The actual backend is athttp://localhost:8006/api/platform-linking/.... Every token — valid or invalid — always hits the error state. The page can never reach "not-authenticated", "ready", or "success" states. - ✅ UI components render cleanly — layout, styling, AuthCard integration, mobile responsiveness all look correct.
- ✅ Backend health confirmed —
GET http://localhost:8006/api/platform-linking/tokens/test-token/statusreturns proper JSON{"detail": "Token not found"}. ⚠️ Dark modebg-slate-50confirmed visually.
Blockers (Must Fix)
-
page.tsx:46,99— API routing broken: Fetch calls to/api/platform-linking/...404 on the Next.js frontend server. Need either a Next.js API route proxy, anext.config.mjsrewrite, or use the generated orval hooks (which already handle base URL resolution). This is the root cause of all functional failures. (Flagged by: QA 🔴, Architect⚠️ , Discussion⚠️ ) -
page.tsx:178— MissingencodeURIComponenton signupnextparam: Login URL encodes correctly, signup URL doesn't. Inconsistency that can break the redirect flow and potentially enable open redirect. (Flagged by: Security 🟠, Product 🔴, Quality⚠️ , Discussion⚠️ ) -
page.tsx:46-48,89-98— Rawfetchinstead of generated orval hooks: Bypasses the project's API client, auth injection, error normalization, and base URL resolution. This is likely the cause of Blocker #1 — the generated hooks know the backend URL; rawfetchdoesn't. (Flagged by: Architect ❌, Discussion⚠️ )
Should Fix (Follow-up OK)
-
page.tsx:73-75— "your platform" placeholder in ready/success states: Status endpoint doesn't return platform info, making the confirmation screen generic and trust-reducing. Either enrich the API response or add a detail-fetch endpoint. (Flagged by: Product⚠️ , Quality⚠️ ) -
page.tsx:82—useEffectdependency on[token, user]causes double-fetch + flicker: Split the effect or gate on auth settling to prevent wasted requests and visible UI jitter on every page load. (Flagged by: Performance⚠️ ) -
page.tsx:42-81— NoAbortControllerfor fetch cleanup: Race condition on rapid auth state changes. Add cleanup return in useEffect. (Flagged by: Performance⚠️ ) -
page.tsx:197— No double-click protection on "Link account" button: Adddisabledstate oruseRefguard. (Flagged by: Performance⚠️ , Testing⚠️ ) -
page.tsx:213,249,262— Hardcoded light colors break dark mode:bg-slate-50,bg-green-100,bg-red-100needdark:variants. (Flagged by: Quality⚠️ , Product⚠️ , QA⚠️ ) -
No tests: 290 lines of interactive code with 0% coverage. At minimum need unit tests for the 6 state transitions and 2 API paths. (Flagged by: Testing ❌)
-
No
layout.tsxforrobots: noindexand proper<title>. (Flagged by: Architect⚠️ ) -
page.tsx:189-208— No "Not your account?" / cancel option in ready state. (Flagged by: Product⚠️ )
Risk Assessment
Merge risk: HIGH | Rollback: EASY (single new file, no modifications to existing code)
The page is non-functional as written — API calls always 404 because there's no routing from the Next.js frontend to the backend for /api/platform-linking/ paths. Using the project's generated orval hooks (Blocker #3) would likely fix Blocker #1 automatically, since those hooks already resolve the correct backend URL. The UI shell is well-constructed and mobile-responsive, but can't be evaluated end-to-end until routing is fixed.
REVIEW_COMPLETE
PR: #12624
Verdict: REQUEST_CHANGES
Blockers: 3
3dbf78f to
17218c6
Compare
7cb47c4 to
0c114c3
Compare
|
This pull request has conflicts with the base branch, please resolve those so we can evaluate the pull request. |
63e27cc to
28010b2
Compare
|
Conflicts have been resolved! 🎉 A maintainer will review the pull request shortly. |
0c114c3 to
ad21550
Compare
|
This pull request has conflicts with the base branch, please resolve those so we can evaluate the pull request. |
ad21550 to
8730766
Compare
28010b2 to
0b257bc
Compare
|
Conflicts have been resolved! 🎉 A maintainer will review the pull request shortly. |
fe7c5d3 to
b46adf9
Compare
4add2b9 to
96ca439
Compare
Adds the user-facing page that completes the platform bot linking flow. When an unlinked user messages the bot, they get a URL like: https://platform.agpt.co/link/{token} This page: 1. Validates the token (expired? already used?) 2. If user isn't logged in → redirects to login with ?next=/link/{token} 3. Shows a confirmation screen: 'Link your [platform] account to AutoGPT' 4. On click → calls POST /api/platform-linking/tokens/{token}/confirm 5. Shows success or error state ## Implementation - Lives in (no-navbar) route group (standalone page, no main nav) - Reuses AuthCard, Button, Text, Link components from existing auth pages - Same visual style as login/signup pages - Handles all edge cases: expired token, already linked, not authenticated ## Stacked on - feat/copilot-bot-service (PR #12618) - feat/platform-bot-linking (PR #12615)
- Add /api/platform-linking/tokens/[token]/status route - Add /api/platform-linking/tokens/[token]/confirm route - Fix platform-api.ts to include bot headers in createLinkToken
Route frontend platform-linking requests through the existing /api/proxy/[...path] catch-all instead of adding dedicated routes.
The /tokens/{token}/status endpoint requires X-Bot-API-Key and is
meant for bot polling, not frontend use. Instead, show the link UI
directly and let the confirm call handle invalid/expired tokens.
- Replace emoji icons (✅❌🎮) with Phosphor Icons (CheckCircle, LinkBreak, Spinner) - Remove useCallback in favor of plain function declaration per CLAUDE.md - Fix 'your platform' hardcoded text to 'your chat platform' - Remove dead platformUsername prop and code - Send empty JSON body on confirm POST to avoid console warning - Remove unnecessary client-side Authorization header (proxy handles auth) - Add 30-second AbortController timeout on confirm fetch - Simplify PLATFORM_INFO to PLATFORM_NAMES (removed unused icon field)
The useSupabaseStore requires initialization via useSupabase() hook which sets up the API client, router, and fetches the user. Using the raw store directly meant user was always null on the link page. Also wait for isUserLoading before deciding auth state to avoid flashing the not-authenticated view.
…Link The proxy handles auth via server-side Supabase session cookies. The client-side getSession() call was returning null (store not fully initialized) and bailing before the fetch ever happened.
b46adf9 to
8a6c42e
Compare










Summary
Adds the user-facing page that completes the platform bot account linking flow.
Stacked on: #12618 → #12615
The flow
Link your account: https://platform.agpt.co/link/{token}?next=/link/{token}POST /api/platform-linking/tokens/{token}/confirmImplementation
/app/(no-navbar)/link/[token]/page.tsx— standalone page, no main navAuthCard,Button,Text,Linkcomponents (matches login/signup style)Screenshots
Page matches the existing login/signup card layout — same AuthCard component, same spacing.
States