-
-
Notifications
You must be signed in to change notification settings - Fork 132
feat(admin): intention-based admin hubs with Tabs sub-tabs #2975
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
riderx
wants to merge
15
commits into
main
Choose a base branch
from
cursor/admin-intention-reorg-proposal-185b
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
4986985
docs(admin): propose intention-based admin IA with Tabs sub-tabs
cursoragent d715097
feat(admin): split users dashboard into onboarding and retention hubs
cursoragent f7b827a
feat(admin): add pulse hub, capacity/revenue subpages, legacy redirects
cursoragent 72d8191
feat(admin): ship intention hubs with real product screenshots
cursoragent 449f630
fix(admin): drop duplicate redirect stubs and repair CI
cursoragent 3fd1769
merge(main): sync email captcha and recent fixes
cursoragent e238ce9
fix(admin): address review comments and cut IA duplication
cursoragent 47ee6f0
fix(admin): harden capacity/risk loaders and drop dead redirect
cursoragent 3d546f0
fix(admin): clear review threads and cut Sonar duplication
cursoragent c47dcfd
fix(admin): clear last Sonar duplication and review nits
cursoragent 864b5d3
fix(admin): unify stats types and finish review nits
cursoragent 0eb7f55
fix(admin): clear loader on failure and add paid-at i18n
cursoragent ff5d308
merge(main): resolve conflicts and fold new admin pages into hubs
cursoragent 259d201
fix(admin): update hub path tests and translation contexts
cursoragent bd30e38
style(admin): autofix import order and blank lines
cursoragent File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| # Admin Dashboard: Intention-Based Information Architecture | ||
|
|
||
| **Status:** Implemented (this PR) | ||
| **UI primitive:** `src/components/Tabs.vue` primary + `secondaryTabs` (same pattern as app/settings) | ||
|
|
||
| ## What changed | ||
|
|
||
| Admin is no longer a flat feature list. Primary tabs are intentions; secondary tabs are the surfaces inside each intention. | ||
|
|
||
| | Primary | Secondary | | ||
| | --- | --- | | ||
| | Pulse | — | | ||
| | Onboarding | Funnel · Sources · Cohorts | | ||
| | Product | Updates · Plugins · CLI · Builder · Notifications | | ||
| | Retention | Trials · Churn · Inactive | | ||
| | Customers | Organizations · Credits | | ||
| | Revenue | Overview · Upgrades · Risk | | ||
| | Platform | Replication · Capacity · Debug | | ||
|
|
||
| Legacy URLs (`/admin/dashboard/users`, `/updates`, …) redirect to the new hubs. | ||
|
|
||
| ## Layout wiring | ||
|
|
||
| `src/layouts/admin.vue` mirrors `src/layouts/app.vue`: primary tab keeps the hub active; secondary tabs switch within the hub. | ||
|
|
||
| ## Onboarding focus | ||
|
|
||
| `users.vue` was split into: | ||
|
|
||
| - `/admin/dashboard/onboarding` — funnel + trends | ||
| - `/admin/dashboard/onboarding/sources` — registration sources / email / country | ||
| - `/admin/dashboard/onboarding/cohorts` — stuck-stage attention from live funnel stats | ||
| - `/admin/dashboard/retention/*` — trials / churn / inactive | ||
|
|
||
| ## Screenshots | ||
|
|
||
| Real product captures (local worktree, `admin@capgo.app`): | ||
|
|
||
| - `docs/pr-assets/admin-intention-ia/real-onboarding-funnel.webp` | ||
| - `docs/pr-assets/admin-intention-ia/real-product-updates.webp` | ||
| - `docs/pr-assets/admin-intention-ia/real-pulse.webp` |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| import { mkdirSync } from 'node:fs' | ||
| import { dirname, join } from 'node:path' | ||
| import { fileURLToPath } from 'node:url' | ||
| import { chromium } from '@playwright/test' | ||
|
|
||
| const rootDir = join(dirname(fileURLToPath(import.meta.url)), '..') | ||
| const OUT = join(rootDir, '.context/admin-screens') | ||
| const DOCS = join(rootDir, 'docs/pr-assets/admin-intention-ia') | ||
| const BASE = 'http://localhost:5173' | ||
|
|
||
| mkdirSync(OUT, { recursive: true }) | ||
| mkdirSync(DOCS, { recursive: true }) | ||
|
|
||
| async function waitForAdmin(page, path, readyLocator) { | ||
| await page.goto(`${BASE}${path}`, { waitUntil: 'domcontentloaded' }) | ||
| await page.waitForURL((url) => { | ||
| try { | ||
| return new URL(url).pathname.replace(/\/$/, '') === path.replace(/\/$/, '') | ||
| } | ||
| catch { | ||
| return false | ||
| } | ||
| }, { timeout: 120000 }) | ||
| await page.getByRole('button', { name: /onboarding|pulse|product/i }).first().waitFor({ timeout: 120000 }) | ||
| if (readyLocator) | ||
| await readyLocator(page).waitFor({ timeout: 120000 }) | ||
| // give charts a moment | ||
| await page.waitForTimeout(3000) | ||
| } | ||
|
|
||
| async function shot(page, name) { | ||
| await page.screenshot({ path: join(OUT, `${name}.png`), fullPage: false }) | ||
| await page.screenshot({ path: join(DOCS, `${name}.png`), fullPage: false }) | ||
| console.log('saved', name, page.url()) | ||
| } | ||
|
|
||
| const browser = await chromium.launch({ headless: true }) | ||
| const page = await browser.newPage({ viewport: { width: 1440, height: 1100 } }) | ||
| page.setDefaultTimeout(120000) | ||
|
|
||
| await page.goto(`${BASE}/login`, { waitUntil: 'domcontentloaded' }) | ||
| await page.locator('input[type="email"]').first().fill('admin@capgo.app') | ||
| await page.getByRole('button', { name: /continue/i }).first().click() | ||
| await page.locator('input[type="password"]').first().waitFor({ state: 'visible' }) | ||
| await page.locator('input[type="password"]').first().fill('adminadmin') | ||
| await page.getByRole('button', { name: /log in/i }).first().click() | ||
| await page.waitForURL(u => !String(u).includes('/login'), { timeout: 90000 }) | ||
| console.log('logged in', page.url()) | ||
|
|
||
| await waitForAdmin(page, '/admin/dashboard/pulse', p => p.getByText(/new registrations|pulse/i).first()) | ||
| await shot(page, 'real-pulse') | ||
|
|
||
| await waitForAdmin(page, '/admin/dashboard/onboarding', p => p.getByRole('button', { name: /funnel|sources|cohorts/i }).first()) | ||
| await shot(page, 'real-onboarding-funnel') | ||
|
|
||
| await waitForAdmin(page, '/admin/dashboard/onboarding/sources', p => p.getByText(/registrations by source|sources/i).first()) | ||
| await shot(page, 'real-onboarding-sources') | ||
|
|
||
| await waitForAdmin(page, '/admin/dashboard/onboarding/cohorts', p => p.getByRole('button', { name: /cohorts/i }).first()) | ||
| await shot(page, 'real-onboarding-cohorts') | ||
|
|
||
| await waitForAdmin(page, '/admin/dashboard/product/updates', p => p.getByRole('button', { name: /updates|plugins|cli/i }).first()) | ||
| await shot(page, 'real-product-updates') | ||
|
|
||
| await waitForAdmin(page, '/admin/dashboard/retention', p => p.getByRole('button', { name: /trials|churn|inactive/i }).first()) | ||
| await shot(page, 'real-retention') | ||
|
|
||
| await waitForAdmin(page, '/admin/dashboard/customers/organizations', p => p.getByRole('button', { name: /organizations|credits/i }).first()) | ||
| await shot(page, 'real-customers') | ||
| await browser.close() | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.