Skip to content

Epic 20 — Profile Analytics (Talent)

Talents on higher-tier plans see who viewed their profile, saves, and view trends. Referenced in spec "AFTER THOUGHTS #6".


BE-ANALYTICS-001 — Profile view tracking and analytics API

  • [x] Done

✅ SHIPPED 2026-06-20. models/mongo/ProfileView.ts ({ talentId, viewerId, viewerType, viewedAt }, compound indexes on talentId+viewedAt and talentId+viewerId+viewedAt). services/analytics/profileViews.ts: recordProfileView (fire-and-forget; skips owner / anonymous / impersonated reads; viewerType resolved from the viewer's profiles, PRODUCER-priority), getProfileViewStats (this/last calendar-month counts + trendPercent + saveCount from folderEntry.count), getProfileViewers (distinct producers via aggregate, most-recent-first, page/pageSize). Wired into both talentProfile(id) and talentByHandle resolvers (the public page uses either). Notifications: PROFILE_VIEW (producers only, capped 3/UTC-day) via new notifyProfileView helper; PROFILE_VIEW_SPIKE when this week ≥ 1.5× last week, deduped to once/7-day window. GraphQL profileViewStats: ProfileViewStats! + profileViewers(page,pageSize): ProfileViewerPage! (resolvers/analytics.ts); ProfileViewer.producer is nullable (deleted-producer safety, filtered client-side). Tier gate (decision): post-Epic-40 talents have only Free/Pro, so the spec's "Talent Pro+" (stats) and "top tier" (who-viewed) both resolve to the same gate — any active paid subscription; Free → TIER_REQUIRED, admins bypass. authz-matrix entries added (401 tests). v1 records authenticated non-owner views only (no anonymous, avoids bot inflation). BE 1484 tests green (12 new in profileViews.test.ts); typecheck clean; schema.graphql re-exported.

Files:

  • Create: src/models/mongo/ProfileView.ts{ _id, talentId, viewerId, viewerType, viewedAt }
  • Edit: src/graphql/resolvers/talent.ts — call recordProfileView whenever talentProfile(id) is queried by a non-owner
  • Create: src/services/analytics/profileViews.ts
  • Edit: src/graphql/schema/index.ts — add analytics queries

GraphQL (talent-only, tier-gated):

graphql
profileViewStats: ProfileViewStats!   # total views, trend, save count
profileViewers(first: Int, after: String): ProfileViewerConnection!  # who viewed (top tier only)

type ProfileViewStats {
  viewsThisMonth:  Int!
  viewsLastMonth:  Int!
  trendPercent:    Float!   # (this - last) / last * 100
  saveCount:       Int!     # how many producer folders this talent appears in
}
type ProfileViewer {
  producer:   ProducerProfile!
  viewedAt:   DateTime!
}

Tier gating: profileViewStats available to Talent Pro+. profileViewers (who viewed) available to top tier only. Check subscription/plan before resolving.

Notifications: record a PROFILE_VIEW notification (already in BE-NOTIF-001) when a producer views a talent. Cap at 3 notifications per talent per day to avoid spam.

Trend alerts: when weekly views increase by ≥ 50% vs the prior week, fire a PROFILE_VIEW_SPIKE notification.


FE-ANALYTICS-001 — Profile analytics UI (talent)

  • [x] Done

✅ SHIPPED 2026-06-20. pages/profile/ProfileAnalyticsPage.tsx (route /profile/analytics, inside ProtectedShellLayout): views-this-month with up/down trend badge + %, folder-save count, "who viewed" producer list (avatar + company name + relative time, links to /producer/:handle, page/pageSize pagination), and a spike banner when the monthly trend ≥50%. hooks/useProfileAnalytics.ts loads stats + first viewers page via gqlClient.requestSilent (so the expected TIER_REQUIRED never pops a global toast) and maps it to a locked state → blurred-preview + "Unlock profile analytics / See plans" upgrade card (reuses isUpgradeError). lib/queries/analytics.ts query lib. Talent-only "Profile analytics" CTA card added to ProfilePage; /profile/analytics registered in App.tsx (lazy) + pageTitles.ts (profileAnalytics). i18n: profileAnalytics.* + profile.analyticsCta.* + pageTitle.profileAnalytics in en/pt/es. Tests: ProfileAnalyticsPage.test.tsx (loading / unlocked stats+list / spike banner / locked upgrade / error). FE app 585 / DS 44 green; typecheck + eslint clean.

Files:

  • Create: apps/app/src/pages/profile/ProfileAnalyticsPage.tsx
  • Create: apps/app/src/hooks/useProfileAnalytics.ts
  • Edit: apps/app/src/pages/profile/ProfilePage.tsx — add "Analytics" CTA for eligible tier

Description:

  • Views this month with trend arrow (↑ / ↓) and percentage.
  • Saves count ("Your profile has been saved into X producer folders").
  • Who Viewed list (top tier): avatar, producer name, company, time ago. Locked/blurred for lower tiers with upgrade prompt.
  • Trend alert banner: "You got X new views this week — something's happening!" (triggered by PROFILE_VIEW_SPIKE notification).