Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions docs/admin/plans-checkout-completion.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Plans Checkout Completion Analytics

The current Plans analytics page measures checkout intent only. Completion must remain deferred until Capgo emits a reliable server-side `Checkout Completed` event.

The future event must contain `org_id`, a stable `checkout_attempt_id`, Stripe checkout session ID, product ID, recurrence, and completion timestamp. `Checkout Started` must carry the same `checkout_attempt_id` into Stripe metadata so completion is joined directly rather than inferred from a redirect.

The future full-width daily stacked chart uses the attributed Plans-opening UTC day. Each organization that started checkout that day appears once as Completed or Not completed. Recent attempts remain pending until the agreed observation window has elapsed; they must not be labeled abandoned prematurely.

Implementation requires a separate approved design for the observation window, late completions, retries, plan changes, and existing subscribers.
1,518 changes: 1,518 additions & 0 deletions docs/superpowers/plans/2026-08-10-plans-analytics-dashboard.md

Large diffs are not rendered by default.

293 changes: 293 additions & 0 deletions docs/superpowers/specs/2026-08-10-plans-analytics-dashboard-design.md

Large diffs are not rendered by default.

31 changes: 31 additions & 0 deletions messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1982,6 +1982,37 @@
"plan-upgrade": "Subscribe",
"plan-upgrade-v2": "Upgrade",
"plans": "plans",
"plans-analytics-title": "Plans analytics",
"plans-analytics-timezone": "Reporting timezone: UTC",
"plans-analytics-traffic": "Plans page traffic",
"plans-analytics-traffic-description": "Unique organizations on their first Plans opening in the selected range, alongside total logical openings per UTC day",
"plans-analytics-unique-visitor-orgs": "Unique visitor orgs",
"plans-analytics-total-opens": "Total opens",
"plans-analytics-who-opened": "Who opened Plans?",
"plans-analytics-who-opened-description": "Daily unique organizations by billing state at their first Plans opening",
"plans-analytics-checkout-intent": "Checkout intent",
"plans-analytics-checkout-intent-description": "Daily Plans visitors who started checkout within the attribution window",
"plans-analytics-started-checkout": "Started checkout",
"plans-analytics-did-not-start": "Did not start",
"plans-analytics-who-opened-checkout": "Who opened checkout?",
"plans-analytics-who-opened-checkout-description": "Daily checkout starters by billing state at the attributed Plans opening",
"plans-analytics-checkout-completion": "Checkout completion",
"plans-analytics-checkout-completion-description": "TODO — this graph will be implemented after reliable checkout-completion tracking is available.",
"plans-analytics-checkout-completion-link": "Read the implementation requirements",
"plans-category-paying": "Paying",
"plans-category-active-trial": "Active trial",
"plans-category-expired-trial": "Expired trial — never subscribed",
"plans-category-canceled": "Canceled",
"plans-category-payment-problem": "Payment problem",
"plans-category-credits-only": "Credits only",
"plans-category-unknown": "Unknown",
"plans-analytics-partial-warning": "Some organizations could not be classified from historical billing records and appear as Unknown.",
"plans-analytics-legacy-unavailable": "Legacy Plans visits are unavailable because no event-time pathname could be verified.",
"plans-analytics-posthog-unconfigured": "PostHog analytics is not configured.",
"plans-analytics-posthog-timeout": "Plans analytics timed out. Try again, or select a shorter period.",
"plans-analytics-range-too-large": "This range returned too much data to process. Select a shorter period and try again.",
"plans-analytics-unavailable": "Plans analytics is temporarily unavailable.",
"plans-analytics-empty": "No Plans visits were recorded in this period.",
"plans-super-only": "Only super admins are allowed to view plans and billing",
"platform": "Platform",
"platform-android": "Android",
Expand Down
12 changes: 10 additions & 2 deletions src/components/admin/AdminStackedBarChart.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
} from 'chart.js'
import { computed } from 'vue'
import { Bar } from 'vue-chartjs'
import { buildAdminStackedBarChartData, buildAdminStackedBarChartOptions } from '~/components/admin/adminStackedBarChart'
import { applyAdminStackedBarAccessibleBorders, buildAdminStackedBarChartData, buildAdminStackedBarChartOptions } from '~/components/admin/adminStackedBarChart'
import { formatLocalDate } from '~/services/date'

interface DataSeries {
Expand All @@ -30,6 +30,10 @@ const props = defineProps({
type: Boolean,
default: false,
},
accessibleBorders: {
type: Boolean,
default: false,
},
})

const isDark = useDark()
Expand All @@ -54,7 +58,11 @@ const chartData = computed(() => {
color: item.color,
}))

return buildAdminStackedBarChartData(labels, datasets)
return applyAdminStackedBarAccessibleBorders(
buildAdminStackedBarChartData(labels, datasets),
props.accessibleBorders,
isDark.value,
)
})

const chartOptions = computed(() => buildAdminStackedBarChartOptions(isDark.value))
Expand Down
18 changes: 18 additions & 0 deletions src/components/admin/adminStackedBarChart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,24 @@ export function buildAdminStackedBarChartData(
}
}

export function applyAdminStackedBarAccessibleBorders(
data: ChartData<'bar'>,
accessibleBorders: boolean,
isDark: boolean,
): ChartData<'bar'> {
if (!accessibleBorders)
return data

return {
...data,
datasets: data.datasets.map(dataset => ({
...dataset,
borderColor: isDark ? '#f8fafc' : '#0f172a',
borderWidth: 1,
})),
}
}

export function formatAdminStackedBarTooltip(label: string, value: number, total: number) {
const percentage = total > 0 ? (value / total) * 100 : 0
return `${label}: ${formatNumberValue(value)} (${formatNumberValue(percentage, { maximumFractionDigits: 1 })}%)`
Expand Down
2 changes: 2 additions & 0 deletions src/constants/adminTabs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import IconArrowPath from '~icons/heroicons/arrow-path'
import IconBanknotes from '~icons/heroicons/banknotes'
import IconBell from '~icons/heroicons/bell'
import IconBuildingOffice from '~icons/heroicons/building-office-2'
import IconChartBar from '~icons/heroicons/chart-bar-square'
import IconCircleStack from '~icons/heroicons/circle-stack'
import IconCommandLine from '~icons/heroicons/command-line'
import IconCurrencyDollar from '~icons/heroicons/currency-dollar'
Expand All @@ -19,6 +20,7 @@ export const adminTabs: Tab[] = [
{ label: 'users', icon: IconUsers, key: '/users' },
{ label: 'admin-organizations', icon: IconBuildingOffice, key: '/organizations' },
{ label: 'revenue', icon: IconBanknotes, key: '/revenue' },
{ label: 'plans-analytics-title', icon: IconChartBar, key: '/plans' },
{ label: 'credits', icon: IconCurrencyDollar, key: '/credits' },
{ label: 'notifications', icon: IconBell, key: '/notifications' },
]
Loading
Loading