Appearance
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 ontalentId+viewedAtandtalentId+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+saveCountfromfolderEntry.count),getProfileViewers(distinct producers via aggregate, most-recent-first, page/pageSize). Wired into bothtalentProfile(id)andtalentByHandleresolvers (the public page uses either). Notifications:PROFILE_VIEW(producers only, capped 3/UTC-day) via newnotifyProfileViewhelper;PROFILE_VIEW_SPIKEwhen this week ≥ 1.5× last week, deduped to once/7-day window. GraphQLprofileViewStats: ProfileViewStats!+profileViewers(page,pageSize): ProfileViewerPage!(resolvers/analytics.ts);ProfileViewer.produceris 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 inprofileViews.test.ts); typecheck clean;schema.graphqlre-exported.
Files:
- Create:
src/models/mongo/ProfileView.ts—{ _id, talentId, viewerId, viewerType, viewedAt } - Edit:
src/graphql/resolvers/talent.ts— callrecordProfileViewwhenevertalentProfile(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.tsloads stats + first viewers page viagqlClient.requestSilent(so the expectedTIER_REQUIREDnever pops a global toast) and maps it to a locked state → blurred-preview + "Unlock profile analytics / See plans" upgrade card (reusesisUpgradeError).lib/queries/analytics.tsquery lib. Talent-only "Profile analytics" CTA card added toProfilePage;/profile/analyticsregistered inApp.tsx(lazy) +pageTitles.ts(profileAnalytics). i18n:profileAnalytics.*+profile.analyticsCta.*+pageTitle.profileAnalyticsin 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_SPIKEnotification).