Skip to content

Conversation

@steebchen
Copy link
Member

@steebchen steebchen commented Jan 7, 2026

Summary

  • Add UI for managing TikTok OAuth connections in Settings (next to Signers)
  • Display connected accounts with profile info (username, avatar)
  • Allow users to disconnect OAuth connections
  • Feature flag for gradual rollout (connections: true)

Changes

  • New files: connection-card.tsx, connections-section.tsx, oauth-connections.ts (GraphQL queries)
  • Modified: settings/index.tsx (add ConnectionsSection)

Dependencies

Requires backend PR in cartridge-internal to be merged first: https://github.com/cartridge-gg/internal/pull/4003

Test plan

  • Verify ConnectionsSection renders when feature flag is enabled
  • Test "Connect TikTok" button opens OAuth flow
  • Test disconnect confirmation and API call
  • Verify expired token badge shows correctly
  • Check loading and error states

🤖 Generated with Claude Code


Note

Adds a feature-flagged UI to manage social connections in Settings.

  • New ConnectionsSection, ConnectionCard, and AddConnection flow with popup OAuth; route '/settings/add-connection'
  • GraphQL utilities for GET_OAUTH_CONNECTIONS and DISCONNECT_OAUTH plus getTikTokAuthUrl(); integrates react-query for fetch/invalidate
  • Feature flag via useFeatures to toggle connections; Settings updated to render section when enabled
  • Dependency added: @icons-pack/react-simple-icons for TikTok icon

Written by Cursor Bugbot for commit 47bb9fd. This will update automatically on new commits. Configure here.

Copilot AI review requested due to automatic review settings January 7, 2026 19:27
@vercel
Copy link

vercel bot commented Jan 7, 2026

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

Project Deployment Review Updated (UTC)
controller-example-next Ready Ready Preview Jan 8, 2026 0:44am
keychain Ready Ready Preview Jan 8, 2026 0:44am
keychain-storybook Ready Ready Preview Jan 8, 2026 0:44am

Copy link

Copilot AI left a comment

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 adds a new "Connected Accounts" section to the Settings page, enabling users to manage TikTok OAuth connections. The feature allows users to connect their TikTok accounts via OAuth flow, view connection status (including expiration), and disconnect accounts when needed. The implementation is controlled by a feature flag for gradual rollout.

Key changes:

  • New GraphQL schema definitions and queries for OAuth connection management
  • ConnectionsSection component that displays connected accounts and handles OAuth flow
  • ConnectionCard component for displaying individual connections with disconnect functionality

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
packages/keychain/src/utils/api/oauth-connections.ts Defines TypeScript types and GraphQL queries/mutations for OAuth connection operations
packages/keychain/src/utils/api/index.ts Exports OAuth connections API for use throughout the application
packages/keychain/src/components/settings/index.tsx Integrates ConnectionsSection into Settings page with feature flag control
packages/keychain/src/components/settings/connections/index.ts Exports connection components from the module
packages/keychain/src/components/settings/connections/connections-section.tsx Main component for managing OAuth connections list and initiating new connections
packages/keychain/src/components/settings/connections/connection-card.tsx Card component displaying individual OAuth connection with disconnect action

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 41 to 45
window.open(
authUrl,
"tiktok-oauth",
`width=${width},height=${height},left=${left},top=${top}`,
);
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

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

The OAuth popup window is opened but there's no mechanism to detect when the OAuth flow completes and refresh the connection list. After the user successfully authenticates in the popup, the UI won't automatically update to show the connected account. Consider adding a window message listener to detect when the OAuth callback completes and then invalidate the query to refresh the connections list.

Suggested change
window.open(
authUrl,
"tiktok-oauth",
`width=${width},height=${height},left=${left},top=${top}`,
);
const popup = window.open(
authUrl,
"tiktok-oauth",
`width=${width},height=${height},left=${left},top=${top}`,
);
// Listen for OAuth completion message from the popup and refresh connections
const handleMessage = (event: MessageEvent) => {
const data = event.data;
if (!data || typeof data !== "object") {
return;
}
const { type, provider } = data as { type?: string; provider?: string };
if (type === "oauth-complete" && provider === "TIKTOK") {
queryClient.invalidateQueries("oauthConnections");
window.removeEventListener("message", handleMessage);
if (popup && !popup.closed) {
popup.close();
}
}
};
window.addEventListener("message", handleMessage);

Copilot uses AI. Check for mistakes.
Comment on lines +105 to +117
onClick={async () => {
setIsLoading(true);
try {
await onDisconnect?.();
setIsOpen(false);
} catch (error) {
console.error(error);
} finally {
setIsLoading(false);
}
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

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

The disconnect error is silently swallowed with only a console.error. If the disconnection fails (e.g., due to network error or backend issue), the user won't see any error feedback and the sheet will close, giving the false impression that it succeeded. Consider displaying an error message to the user and keeping the sheet open when the disconnect operation fails.

Copilot uses AI. Check for mistakes.
Comment on lines +76 to +68
<div className="text-destructive-100 text-sm">
Failed to load connections
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

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

The error message "Failed to load connections" is too generic and doesn't help the user understand what went wrong or how to fix it. Consider providing more specific error information, such as whether it's a network error, authentication issue, or backend error, along with suggested actions like "Try refreshing the page" or "Check your internet connection".

Copilot uses AI. Check for mistakes.
className="border-background-100 p-6 gap-6 rounded-t-xl"
showClose={false}
>
<SheetTitle className="hidden"></SheetTitle>
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

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

The SheetTitle has an empty string and is hidden with className="hidden". This is an accessibility issue as it leaves the sheet without a proper accessible name for screen readers. While the visual heading exists in the content, the SheetTitle should contain meaningful text that describes the sheet's purpose, such as "Disconnect OAuth Connection Confirmation".

Suggested change
<SheetTitle className="hidden"></SheetTitle>
<SheetTitle className="hidden">
Disconnect {providerName} connection
</SheetTitle>

Copilot uses AI. Check for mistakes.
Comment on lines 50 to 54
const disconnectMutation = useMutation<boolean, Error, string>(
async (provider: string) => {
const result = await request<DisconnectOAuthData>(DISCONNECT_OAUTH, {
provider,
});
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

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

The provider parameter is typed as string but should be typed as OAuthProvider. This creates a type safety issue where any string could be passed instead of the valid enum values. The mutation should accept OAuthProvider type to match the GraphQL schema and prevent runtime errors from invalid provider values.

Copilot uses AI. Check for mistakes.
"tiktok-oauth",
`width=${width},height=${height},left=${left},top=${top}`,
);
},
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

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

There's no error handling for the connectTikTok mutation failure. If the INITIATE_TIKTOK_OAUTH mutation fails (e.g., due to network error or backend issue), the user won't see any error message. Consider adding an onError callback to the mutation to display an error message to the user.

Suggested change
},
},
onError: () => {
window.alert(
"Failed to initiate TikTok connection. Please try again.",
);
},

Copilot uses AI. Check for mistakes.
inProgress: false,
});
}
}, 500);
Copy link

Choose a reason for hiding this comment

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

OAuth popup closure falsely indicates successful connection

High Severity

The OAuth flow logic assumes success whenever the popup closes or is null. The polling logic checks !popup || popup.closed and then displays "TikTok Connected" with a check icon and navigates away. However, the popup can close for many reasons: user manually closed it, user denied authorization, popup was blocked by the browser (returns null), or actual success. Without message passing or verifying the connection status via API, users see a false success message when the OAuth flow actually failed.

Additional Locations (1)

Fix in Cursor Fix in Web

Connect Socials
</span>
</Button>
)}
Copy link

Choose a reason for hiding this comment

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

Connect button shows during loading and error states

Low Severity

The hasConnections variable is data && data.length > 0, which evaluates to falsy when data is undefined during loading or error states. This causes the "Connect Socials" button to appear alongside the loading skeleton or error message, creating a confusing UI where users see both a loading/error indicator and a call-to-action button simultaneously.

Fix in Cursor Fix in Web

queryClient.invalidateQueries("oauthConnections");
},
},
);
Copy link

Choose a reason for hiding this comment

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

Disconnect mutation ignores server failure response value

Medium Severity

The disconnectOAuth GraphQL mutation returns a boolean indicating success, but this value is never validated. The onSuccess callback in the mutation fires regardless of whether disconnectOAuth is true or false, and the connection card closes the sheet after awaiting onDisconnect() without checking the return value. If the server returns false (disconnect failed), the UI incorrectly indicates success by closing the sheet and invalidating the cache, even though the connection wasn't actually removed.

Additional Locations (1)

Fix in Cursor Fix in Web

inProgress: false,
});
}
}, 500);
Copy link

Choose a reason for hiding this comment

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

Interval not cleaned up on component unmount

Medium Severity

The pollTimer interval created in handleTikTokConnect is not cleaned up when the component unmounts. Unlike the established codebase pattern (e.g., header.tsx and quests/index.tsx) where intervals are created in useEffect with a cleanup return function, this interval lives inside a callback with no unmount cleanup. If the user navigates away before the popup closes, the interval continues running, potentially causing memory leaks and attempting state updates on an unmounted component.

Fix in Cursor Fix in Web

navigate("/settings");
}, 2000);
}
}, [connectionPending, navigate]);
Copy link

Choose a reason for hiding this comment

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

useEffect setTimeout lacks cleanup on unmount

Medium Severity

The useEffect that schedules navigation after a successful connection creates a setTimeout without returning a cleanup function. If the user navigates away during the 2-second delay (e.g., browser back button), the timeout will still fire and trigger navigate("/settings"), causing unexpected navigation. Unlike the interval issue in the callback, this useEffect has a well-established pattern for cleanup (return () => clearTimeout(id)) that isn't being followed.

Fix in Cursor Fix in Web

steebchen and others added 4 commits January 8, 2026 00:42
Add UI for managing TikTok OAuth connections in settings:
- ConnectionCard component for displaying connected accounts
- ConnectionsSection for listing and managing connections
- Integration with initiateTikTokOAuth and disconnectOAuth mutations
- Feature flag for gradual rollout

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add @icons-pack/react-simple-icons dependency for TikTok icon
- Fix onDisconnect callback return type (Promise<void> instead of Promise<boolean>)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Use useFetchData hook instead of raw request function to ensure
auth headers are included when querying OAuth connections.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The keychain iframe doesn't have session cookies for the API, so
the `me` query fails with unauthenticated error. Changed to use
`account(username: $username)` query which doesn't require auth,
similar to how sessions are queried.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
steebchen and others added 3 commits January 8, 2026 00:42
Updated mutations to pass username parameter:
- initiateTikTokOAuth now accepts username
- disconnectOAuth now accepts username and provider

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add AddConnectionRoute and add-connection component
- Update connections-section with "+ Connect Socials" button
- Use direct URL for OAuth init instead of GraphQL mutation
- Add /settings/add-connection route

Flow matches existing signer pattern with pending state while OAuth
completes in popup window.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The connections section can now be toggled per-session via URL:
- /feature/connections/enable
- /feature/connections/disable

This allows testing the feature without affecting all users.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
export function AddConnectionRoute() {
const { controller } = useConnection();

return <AddConnection username={controller?.username()} />;
Copy link

Choose a reason for hiding this comment

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

Feature flag bypassed via direct route navigation

Medium Severity

The connections feature flag controls visibility of ConnectionsSection in the settings page, but the /settings/add-connection route is always registered and accessible. AddConnectionRoute doesn't check isFeatureEnabled("connections"), so users can directly navigate to the URL and access the OAuth connection flow even when the feature flag is disabled. This defeats the purpose of the gradual rollout mechanism described in the PR.

Additional Locations (1)

Fix in Cursor Fix in Web

@codecov
Copy link

codecov bot commented Jan 8, 2026

Codecov Report

❌ Patch coverage is 7.16418% with 311 lines in your changes missing coverage. Please review.
✅ Project coverage is 20.68%. Comparing base (0e418ec) to head (47bb9fd).
⚠️ Report is 9 commits behind head on main.

Files with missing lines Patch % Lines
...components/settings/connections/add-connection.tsx 0.00% 147 Missing ⚠️
...omponents/settings/connections/connection-card.tsx 6.38% 88 Missing ⚠️
...nents/settings/connections/connections-section.tsx 8.19% 56 Missing ⚠️
...ain/src/components/settings/AddConnectionRoute.tsx 0.00% 5 Missing ⚠️
...ackages/keychain/src/components/settings/index.tsx 0.00% 5 Missing ⚠️
...ckages/keychain/src/utils/api/oauth-connections.ts 75.00% 4 Missing ⚠️
packages/keychain/src/components/app.tsx 0.00% 3 Missing ⚠️
...chain/src/components/settings/connections/index.ts 0.00% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2313      +/-   ##
==========================================
- Coverage   20.89%   20.68%   -0.21%     
==========================================
  Files         307      313       +6     
  Lines       31079    31489     +410     
  Branches      880      883       +3     
==========================================
+ Hits         6493     6514      +21     
- Misses      24574    24963     +389     
  Partials       12       12              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@steebchen steebchen merged commit 8e4ba9d into main Jan 8, 2026
14 of 16 checks passed
@steebchen steebchen deleted the feat/tiktok-oauth-connections branch January 8, 2026 13:26
@claude
Copy link
Contributor

claude bot commented Jan 8, 2026

Claude finished @steebchen's task —— View job


Documentation analysis and updates completed

I've analyzed the TikTok OAuth connections feature from PR #2313 and updated the documentation accordingly.

Todo List:

  • Explore docs repository structure
  • Review the controller PR changes to understand the OAuth connections feature
  • Check existing documentation for authentication/settings
  • Determine what documentation needs to be created or updated
  • Create/update documentation files as needed

What I Added:

I successfully added comprehensive documentation for the new TikTok OAuth connections feature to /home/runner/work/controller/controller/docs-repo/src/pages/controller/signer-management.md. The new section includes:

📝 Social Connections (OAuth) Section:

  • Clear distinction between social connections (for content publishing) and authentication signers
  • Explanation of the feature flag and gradual rollout process
  • Supported platforms section highlighting TikTok integration

👤 User Guide:

  • Step-by-step instructions for connecting TikTok accounts via OAuth popup flow
  • Connection management: viewing status, handling expired tokens, disconnecting accounts
  • Detailed troubleshooting for common issues (popup blockers, authorization failures, etc.)

🔒 Security & Privacy:

  • OAuth security details and token handling
  • Data privacy explanations (only public profile data stored)
  • User control over connections and revocation

💻 Developer Integration:

  • Feature detection code examples using the connections feature flag
  • GraphQL queries for GET_OAUTH_CONNECTIONS and DISCONNECT_OAUTH
  • TypeScript examples for proper integration

Key Documentation Points:

  1. Feature Purpose: Clearly explains that social connections are for content publishing features, not authentication
  2. OAuth Flow: Details the secure popup-based TikTok authorization process
  3. User Experience: Comprehensive walkthrough from Settings → Connected Accounts → Add Connection flow
  4. Troubleshooting: Addresses common issues like popup blocking and authorization failures
  5. Developer Support: Provides code examples for proper feature flag detection and GraphQL usage

Files Modified:

  • docs-repo/src/pages/controller/signer-management.md - Added comprehensive OAuth connections section (165+ lines)

The documentation is ready and staged for commit. Due to git configuration permissions, the changes are prepared but not yet committed to the repository. The documentation comprehensively covers the new TikTok OAuth connections feature and maintains consistency with the existing documentation structure and style.


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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants