Skip to content

feat: show social PFP and username on connected artist connectors#1644

Open
recoup-coding-agent wants to merge 1 commit intotestfrom
feat/artist-connector-social-pfp
Open

feat: show social PFP and username on connected artist connectors#1644
recoup-coding-agent wants to merge 1 commit intotestfrom
feat/artist-connector-social-pfp

Conversation

@recoup-coding-agent
Copy link
Copy Markdown
Collaborator

@recoup-coding-agent recoup-coding-agent commented Apr 6, 2026

Summary

  • When an artist has a connected Instagram or TikTok connector, the connector card now displays the social profile picture and @username
  • Social profile data is fetched from the existing artist socials API and matched to connectors by platform domain
  • Falls back to the default connector icon and description when no social profile is available

Changes

  • ConnectorCard — accepts optional socialAvatar and socialUsername props; renders PFP with connector icon badge overlay when available
  • ArtistConnectorsTab — fetches artist socials via react-query and passes matched profile data to each connector card
  • matchSocialToConnector — new utility to match a connector slug to its corresponding social profile by domain

Test plan

  • Open Artist Settings → Connectors tab for an artist with connected Instagram
  • Verify the Instagram connector shows the social PFP and @username
  • Open for an artist with connected TikTok — verify same behavior
  • Open for an artist with no connected socials — verify default icon/description shown
  • Disconnect a connector — verify it reverts to default display
  • User-level connectors page (Google integrations) remains unchanged

🤖 Generated with Claude Code


Summary by cubic

Show the artist’s social profile picture and @username on connected Instagram and TikTok connectors in Artist Settings. Falls back to the default icon and description when no social data is available.

  • New Features
    • Fetch artist socials with @tanstack/react-query (5 min cache) and match to connectors by domain using matchSocialToConnector.
    • ConnectorCard now accepts socialAvatar and socialUsername to render a PFP with a connector badge and show the @username in the subtitle.

Written for commit 0bbd0ec. Summary will update on new commits.

Summary by CodeRabbit

New Features

  • Connector cards now display social media profile information including avatars and usernames for connected accounts, giving users a more comprehensive view of artist social presence.
  • Social profiles are automatically matched and associated with their corresponding connectors, enhancing the artist profile management experience and providing immediate access to verified social connections.

When an artist has a connected Instagram or TikTok connector, the connector
card now displays the social profile picture and @username instead of the
generic connector icon and description.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Apr 6, 2026

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

Project Deployment Actions Updated (UTC)
recoup-chat Ready Ready Preview Apr 6, 2026 4:58pm

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 6, 2026

📝 Walkthrough

Walkthrough

This PR integrates artist social media metadata into the connectors interface. A new matching utility maps connector slugs to social profiles from fetched data, which ArtistConnectorsTab retrieves and passes to ConnectorCard, enabling social avatars and usernames to display alongside connector information.

Changes

Cohort / File(s) Summary
Social Metadata Integration
components/ArtistSetting/ArtistConnectorsTab.tsx, components/ConnectorsPage/ConnectorCard.tsx
ArtistConnectorsTab now fetches artist socials via useQuery and maps connector slugs to social entries using the new matching utility. ConnectorCard accepts optional socialAvatar and socialUsername props and conditionally renders social profile display (avatar overlay and @username) when connected and data is available.
Social Matching Utility
lib/composio/matchSocialToConnector.ts
New exported helper function that maps connector slugs (instagram, tiktok) to domain strings and searches a Social[] array for matching profile URLs, returning the first match or undefined.

Sequence Diagram

sequenceDiagram
    participant AT as ArtistConnectorsTab
    participant API as Query API
    participant M as matchSocialToConnector
    participant CC as ConnectorCard
    participant UI as UI Render

    AT->>API: useQuery(getArtistSocials, {artistAccountId})
    API-->>AT: socials[] data
    
    AT->>AT: For each connector
    AT->>M: matchSocialToConnector(slug, socials)
    M->>M: Map slug to domain
    M->>M: Search socials for matching URL
    M-->>AT: matching Social | undefined
    
    AT->>CC: Pass socialAvatar & socialUsername props
    CC->>CC: Compute showSocialProfile
    CC->>CC: Conditionally render avatar & username
    CC-->>UI: Social profile display or fallback
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

The changes introduce new data-fetching logic, cross-component data passing, and a utility function that requires understanding the matching pattern. The scope spans three files with coordinated responsibility, but the implementation follows a clear, focused pattern without excessive complexity.

Possibly related PRs

Suggested reviewers

  • sweetmantech

Poem

🎨 Social whispers now adorn the connectors bright,
Avatars dance with usernames in unified sight,
Instagram meets TikTok in harmonic display,
Artists' profiles blooming in a colorful way! ✨

🚥 Pre-merge checks | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Solid & Clean Code ⚠️ Warning Pull request violates SOLID principles: useMemo accessing window.location causes SSR errors; .includes() substring matching incorrectly matches unrelated domains. Move callbackUrl to useEffect; replace .includes(domain) with URL hostname parsing; add integration tests for SSR compatibility and social profile matching accuracy.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/artist-connector-social-pfp

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 3 files

Confidence score: 3/5

  • There is a concrete matching-risk in lib/composio/matchSocialToConnector.ts: using string .includes() for URL matching can produce false positives and map socials to the wrong connector.
  • Given the medium severity (6/10) and high confidence (9/10), this is a meaningful behavior/regression concern rather than a housekeeping note, so merge risk is moderate.
  • Pay close attention to lib/composio/matchSocialToConnector.ts - hostname-based matching should be used to prevent incorrect connector/social associations.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="lib/composio/matchSocialToConnector.ts">

<violation number="1" location="lib/composio/matchSocialToConnector.ts:17">
P2: Match against the URL hostname instead of string `.includes()` to avoid false-positive connector/social matches.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.

): Social | undefined {
const domain = CONNECTOR_SOCIAL_DOMAINS[slug];
if (!domain) return undefined;
return socials.find((s) => s.profile_url?.toLowerCase().includes(domain));
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Apr 6, 2026

Choose a reason for hiding this comment

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

P2: Match against the URL hostname instead of string .includes() to avoid false-positive connector/social matches.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/composio/matchSocialToConnector.ts, line 17:

<comment>Match against the URL hostname instead of string `.includes()` to avoid false-positive connector/social matches.</comment>

<file context>
@@ -0,0 +1,18 @@
+): Social | undefined {
+  const domain = CONNECTOR_SOCIAL_DOMAINS[slug];
+  if (!domain) return undefined;
+  return socials.find((s) => s.profile_url?.toLowerCase().includes(domain));
+}
</file context>
Fix with Cubic

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
components/ConnectorsPage/ConnectorCard.tsx (1)

45-52: Use next/image for remote social avatars instead of raw <img>.

The component already renders avatars from allowlisted hosts (Twitter, Discord, Facebook, Instagram, GitHub, Twitch, YouTube are all configured in next.config.mjs), so switching to next/image here is straightforward and unlocks Next.js optimization benefits: automatic resizing, modern format negotiation, and lazy loading. This aligns with Next.js performance best practices.

import Image from "next/image";

// In the JSX:
{showSocialProfile && socialAvatar ? (
  <div className="relative">
    <Image
      src={socialAvatar}
      alt={socialUsername || "Profile"}
      width={40}
      height={40}
      className="rounded-xl object-cover"
    />
    <div className="absolute -bottom-1 -right-1 p-0.5 rounded-md bg-card">
      {getConnectorIcon(connector.slug, 14)}
    </div>
  </div>
) : (
  // ...
)}

Remove the ESLint suppression once you migrate—no lint suppression needed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ConnectorsPage/ConnectorCard.tsx` around lines 45 - 52, The
component uses a raw <img> for socialAvatar which disables the Next.js image
lint rule; replace it with next/image to enable optimizations: import Image from
"next/image" at the top of ConnectorCard.tsx, render <Image src={socialAvatar}
alt={socialUsername || "Profile"} width={40} height={40} className="rounded-xl
object-cover" /> inside the existing conditional that checks showSocialProfile
and socialAvatar, keep the small connector badge
(getConnectorIcon(connector.slug, 14)) positioned as before, and remove the
eslint-disable-next-line `@next/next/no-img-element` comment.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@components/ArtistSetting/ArtistConnectorsTab.tsx`:
- Around line 23-29: The callback URL is being built from window.location inside
the useMemo in ArtistConnectorsTab (config -> callbackUrl), which runs during
render and will break server-side rendering; instead, stop reading window in the
render path by computing callbackUrl inside a useEffect (or derive it from
Next.js router state) and store it in state (e.g., callbackUrlState), then
include that state in the useMemo dependencies so config is created from
artistAccountId, ALLOWED_ARTIST_CONNECTORS and the callbackUrlState; update
references to use the new state-backed callback URL.

In `@lib/composio/matchSocialToConnector.ts`:
- Around line 15-17: The current matching uses
profile_url?.toLowerCase().includes(domain) which can false-match substrings;
instead parse profile_url with the URL constructor inside a try/catch and
compare the URL.hostname to the expected domain from
CONNECTOR_SOCIAL_DOMAINS[slug]: accept exact equality or hostname.endsWith('.' +
domain) to allow subdomains (e.g., www.instagram.com), and return undefined on
parse errors or non-matches; update the socials.find logic that references
profile_url to perform this hostname-based check using slug and
CONNECTOR_SOCIAL_DOMAINS.

---

Nitpick comments:
In `@components/ConnectorsPage/ConnectorCard.tsx`:
- Around line 45-52: The component uses a raw <img> for socialAvatar which
disables the Next.js image lint rule; replace it with next/image to enable
optimizations: import Image from "next/image" at the top of ConnectorCard.tsx,
render <Image src={socialAvatar} alt={socialUsername || "Profile"} width={40}
height={40} className="rounded-xl object-cover" /> inside the existing
conditional that checks showSocialProfile and socialAvatar, keep the small
connector badge (getConnectorIcon(connector.slug, 14)) positioned as before, and
remove the eslint-disable-next-line `@next/next/no-img-element` comment.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5f7b2b68-e1bb-4462-b761-e0e4a5083a0f

📥 Commits

Reviewing files that changed from the base of the PR and between 4c5c3fe and 0bbd0ec.

📒 Files selected for processing (3)
  • components/ArtistSetting/ArtistConnectorsTab.tsx
  • components/ConnectorsPage/ConnectorCard.tsx
  • lib/composio/matchSocialToConnector.ts

Comment on lines +23 to +29
const config = useMemo(
() => ({
accountId: artistAccountId,
allowedSlugs: [...ALLOWED_ARTIST_CONNECTORS],
callbackUrl: `${window.location.origin}${window.location.pathname}?artist_connected=true&artist_id=${artistAccountId}`,
}),
[artistAccountId],
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.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

In Next.js App Router, can a "use client"component safely readwindowduring render, or must browser APIs be accessed after mount (for example inuseEffect or event handlers)?

💡 Result:

In the App Router, a "use client" component still participates in the initial server render to produce HTML (“Client Components and the RSC Payload are used to prerender HTML”) and is only made interactive after hydration. So it is not safe to read window (or other browser-only APIs) during render, because that render can run in a non-browser environment. Use browser APIs after mount (e.g., in useEffect/useLayoutEffect) or in event handlers (which only run in the browser). [1][2]

If you truly need code to run only in the browser even for the initial render, you must opt out of SSR for that component (e.g., next/dynamic with ssr: false). [1]

Sources
[1] Next.js Docs — Server and Client Components (Last updated Mar 31, 2026) (nextjs.org)
[2] React Docs — useEffect (“Effects only run on the client. They don’t run during server rendering.”) (react.dev)

Citations:


Move callback URL construction out of the render path

useMemo runs during the render phase, and Next.js client components are still prerendered on the server before hydration. Reading window.location here will throw in the server environment and block rendering. Build the callback URL in useEffect or derive it from Next.js router state instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ArtistSetting/ArtistConnectorsTab.tsx` around lines 23 - 29, The
callback URL is being built from window.location inside the useMemo in
ArtistConnectorsTab (config -> callbackUrl), which runs during render and will
break server-side rendering; instead, stop reading window in the render path by
computing callbackUrl inside a useEffect (or derive it from Next.js router
state) and store it in state (e.g., callbackUrlState), then include that state
in the useMemo dependencies so config is created from artistAccountId,
ALLOWED_ARTIST_CONNECTORS and the callbackUrlState; update references to use the
new state-backed callback URL.

Comment on lines +15 to +17
const domain = CONNECTOR_SOCIAL_DOMAINS[slug];
if (!domain) return undefined;
return socials.find((s) => s.profile_url?.toLowerCase().includes(domain));
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.

⚠️ Potential issue | 🟡 Minor

Match on the hostname, not a substring.

includes(domain) also matches unrelated URLs like https://notinstagram.com/... or any URL that only mentions the platform in its path/query, so this can bind the wrong social record to a connector.

🔧 Safer match
 export function matchSocialToConnector(
   slug: string,
   socials: Social[],
 ): Social | undefined {
   const domain = CONNECTOR_SOCIAL_DOMAINS[slug];
   if (!domain) return undefined;
-  return socials.find((s) => s.profile_url?.toLowerCase().includes(domain));
+  return socials.find((social) => {
+    try {
+      const hostname = new URL(social.profile_url).hostname.toLowerCase();
+      return hostname === domain || hostname.endsWith(`.${domain}`);
+    } catch {
+      return false;
+    }
+  });
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/composio/matchSocialToConnector.ts` around lines 15 - 17, The current
matching uses profile_url?.toLowerCase().includes(domain) which can false-match
substrings; instead parse profile_url with the URL constructor inside a
try/catch and compare the URL.hostname to the expected domain from
CONNECTOR_SOCIAL_DOMAINS[slug]: accept exact equality or hostname.endsWith('.' +
domain) to allow subdomains (e.g., www.instagram.com), and return undefined on
parse errors or non-matches; update the socials.find logic that references
profile_url to perform this hostname-based check using slug and
CONNECTOR_SOCIAL_DOMAINS.

Comment on lines +34 to +39
const { data: socialsData } = useQuery({
queryKey: ["artistSocials", artistAccountId],
queryFn: () => getArtistSocials(artistAccountId),
enabled: !!artistAccountId,
staleTime: 1000 * 60 * 5,
});
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This solution won't work currently. The getArtistSocials does not include data from artist connections. We should find a way to update the API to return the data you're expecting.

  • actual: API for getArtistSocials does not include connections in the response.
  • required: update the docs and API so getArtistSocials also returns the equivalent data from composio to augment the existing supabase data.

Comment on lines +42 to +61
<div className="shrink-0 relative">
{showSocialProfile && socialAvatar ? (
<div className="relative">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={socialAvatar}
alt={socialUsername || "Profile"}
width={40}
height={40}
className="rounded-xl object-cover"
/>
<div className="absolute -bottom-1 -right-1 p-0.5 rounded-md bg-card">
{getConnectorIcon(connector.slug, 14)}
</div>
</div>
) : (
<div className="p-2.5 rounded-xl transition-colors bg-muted/50 group-hover:bg-muted">
{getConnectorIcon(connector.slug, 22)}
</div>
)}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Open closed principle

  • actual: pfp component code added inline to ConnectorCard.
  • required: new component file for the ConnectorPfp.

Comment on lines +3 to +6
const CONNECTOR_SOCIAL_DOMAINS: Record<string, string> = {
instagram: "instagram.com",
tiktok: "tiktok.com",
};
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

KISS - why is this mapping needed? doesn't the Social type already include a platform tag?

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