---
title: "Public Profile Pages"
description: "Public /{username} profile with NFT, blog, Player, Games, Gallery, and unique personal_page_view visit stats (24h/7d by role)"
locale: "en"
---
# Public Profile Pages

> **Info**
> Use **Founder** / **Developer** tabs in the docs sidebar to filter this page. Personal file media, mood audio, and peer-game availability are separate routes under the same username — see [Ring File Cabinet](/docs/features/file-cabinet.md) and [Peer Games](/docs/features/peer-games.md). Visit telemetry joins ring-wide analytics — see [Monitoring & Analytics](/docs/deployment/monitoring.md).

Every user with a reserved username can expose a **public profile** at `/{username}` (locale-prefixed as needed). The page is App Router server-rendered with dynamic SEO metadata. Related public surfaces:

| URL | Purpose |
|-----|---------|
| `/{username}` | Profile header, bio, NFT listings, blog/staking when enabled; records `personal_page_view` for non-owners |
| `/{username}/img` | Public File Cabinet gallery lightbox (`visibility=public`) |
| `/{username}/player` | Public mood player (DaVinci shell + right-rail playlists) |
| `/{username}/games` | Public peer-game availability (“Play with me”) |
| `/profile/songs` | **Private** mood-player manage |
| `/profile/games` | **Private** Member manage for enabled game slugs |

Owners see a **visit stats** widget (unique 24h / 7d, broken down by visitor role). Self-views are skipped so refreshing your own page does not inflate uniques.

### For founders

## Why this matters for your clone

The public profile is the member’s **trust face**: who they are, what they sell/list, which media they choose to show, and (via Peer Games) which free mini-games they accept. Visibility for the main profile page is all-or-nothing today — private profiles 404. Unique-visit stats help members see whether their page is attracting guests vs subscribers vs members without counting themselves.

  
- **[Username reservation](/docs/features/username-reservation.md)** — Claim `@handle` first — public routes resolve via getUserByUsername.

  
- **[File Cabinet gallery](/docs/features/file-cabinet.md)** — Member+ own manager at `/file-cabinet`; public items on `/{username}/img`.

  
- **[Peer Games](/docs/features/peer-games.md)** — Members enable slugs on /profile/games; visitors challenge from /{username}/games.

  
- **[Monitoring & Analytics](/docs/deployment/monitoring.md)** — personal_page_view events land in analytics_events; admins see personalPages on /admin/analytics.

### Operator checklist

- Confirm username claim + “Personal page” visibility in profile settings.
- Smoke `/{username}` as anonymous visitor — owner widget should show a unique visit after a non-owner hit.
- Confirm owner reload does **not** bump unique counts.
- Own gallery manage stays at `/profile/gallery` (**member+**).
- Private playlist CRUD stays at `/profile/songs`.

  Per-field public contact toggles (email, phone, social) are not shipped as independent switches yet — treat the personal-page toggle as the current gate.

### For developers

## Route map

| App path | Notes |
|----------|--------|
| `app/[locale]/[username]/page.tsx` | Main profile; `getUserByUsername`; `notFound()` if missing / private; skips `recordPersonalPageView` when `isOwner` |
| `app/[locale]/[username]/img/page.tsx` | `listPublicGalleryByOwner` + `PublicProfileImgGallery` |
| `app/[locale]/[username]/player/page.tsx` | `PublicPlayerShell` + mood-player service |
| `app/[locale]/[username]/games/page.tsx` | Public enabled slugs + Play-with-me |
| `app/[locale]/(protected)/profile/songs/page.tsx` | Authenticated mood-player manage |
| `app/[locale]/(protected)/profile/games/page.tsx` | Authenticated Member game availability |

Constants: `ROUTES.PUBLIC_PROFILE_IMG`, `ROUTES.PUBLIC_PROFILE_PLAYER`, `ROUTES.PUBLIC_PROFILE_GAMES`, `ROUTES.PROFILE_SONGS`, `ROUTES.PROFILE_GAMES`, `ROUTES.PROFILE_GALLERY`.

### Personal page visit analytics (shipped)

| Concern | Path |
|---------|------|
| Event type | `personal_page_view` → `analytics_events` via `insertAnalyticsEventBatch` |
| Recorder | `features/analytics/lib/personal-page-analytics.ts` → `recordPersonalPageView` |
| Owner skip | Page: `if (isOwner) return` before record; recorder: skip when `visitorUserId === profileUserId` |
| Types SSOT | `features/analytics/types/personal-page.ts` (`PersonalPageVisitStats`, role buckets) |
| Owner widget action | `getPersonalPageViewStats` in `features/auth/services/personal-page-stats.ts` (owner or platform admin) |
| Widget UI | `features/auth/components/personal-page-widget.tsx` |
| Admin rollup | `getPersonalPagePlatformStats` → `PlatformAnalyticsSummary.personalPages` on `/admin/analytics` |
| Engagement join | `analytics-db.ts` includes `personal_page_view` with `docs_page_view` / `page_view` / `app_load` in pageViews aggregates |
| Kill switch | `ANALYTICS_DISABLE_STORAGE=true` — recorder no-ops |

Stats shape: `unique24h` / `unique7d`, `visits24h` / `visits7d`, `byRole24h` / `byRole7d` (unique + visits per role). Visitor identity prefers user id, else session key, else anon IP.

### Identity lookup

`features/auth/services/get-user-by-username.ts` — `cache()`’d `queryDocs` on `users` with case-insensitive username.

### What’s on the main profile (verified surface)

- Avatar (`photoURL`), name, `@username`, bio
- Message button for authenticated visitors (not self)
- Telegram verification badge when verified
- NFT listings via public GET `/api/nft-market/listings?username=…`
- Blog / staking sections when product flags and data allow
- Header/nav links to **Player**, **Games**, and **Gallery**
- Owner visit-stats widget (when owner chrome is shown)

### Visibility

Users control public visibility through the username / personal-page flow. **Public** → page renders; **Private** → route returns **404**.

### Related APIs (profile-adjacent)

| Endpoint | Purpose |
|----------|---------|
| `GET /api/auth/check-username` | Availability |
| `GET /api/nft-market/listings?username=` | Public listings |
| `POST /api/auth/telegram/callback` | Telegram verification callback |

File Cabinet gallery data is **not** a separate public REST list — the img page reads via `listPublicGalleryByOwner`. Peer-game availability uses `user_peer_games` (migration `043_peer_games.sql`).

## Related documentation

- [features/profile-account](/docs/features/profile-account.md) — Same-workflow: signed-in /profile Professional widgets (Organization, tags, My CV) feed the public trust face.

- [features/file-cabinet](/docs/features/file-cabinet.md) — Next-step: member-gated /file-cabinet + gallery curation; public /img from visibility=public.

- [deployment/monitoring](/docs/deployment/monitoring.md) — Depends-on: personal_page_view joins first-party analytics_events and admin personalPages.

- [features/peer-games](/docs/features/peer-games.md) — Same-workflow: public /{username}/games availability and Member /profile/games manage.

- [features/username-reservation](/docs/features/username-reservation.md) — Prerequisite: reserved username before public /{username} routes resolve.

- [features/member-blog](/docs/features/member-blog.md) — Same-workflow: blog section on the profile when membership is active.

- [features/nft-market](/docs/features/nft-market.md) — See-also: ProfileListings public NFT grid on the profile page.

- [integrations/ring-cdn](/docs/integrations/ring-cdn.md) — Depends-on: public gallery images use CDN /files/{uuid} URLs.
