Context
With shopping cards now captured from Perplexity / AI Mode / Copilot (and ChatGPT pending in #95), brands need a dedicated lens on commercial-intent AI presence — separate from text-mention visibility. A product showing up in an AI shopping card is a fundamentally different signal than getting mentioned in a paragraph: high purchase intent, image + price + merchant in the slot, different competitive set.
This issue adds the Shopping page shell + Overview tab. Tabs My Products / Competitors / Prompts ship in follow-up issues (#C, #D).
Depends on: #103 (normalized prompt_result_shopping_cards table).
Acceptance criteria
Plan gating
- Free / Starter — shows shopping card rate KPI only; My Products / Competitors / Prompts tabs (#C, #D) are pay-walled with a Growth CTA.
- Growth / Enterprise / Self-hosted — full access.
Implement the gate via existing use-feature-gate hook with a new shopping_analytics feature flag in web/src/config/plans.ts. Self-host stays unrestricted.
Out of scope
- My Products / Competitors / Prompts tab content — #C, #D
- Pricing intelligence + Merchants tab — later (#F, #G)
- MCP exposure — #E
- Anomaly alerts — separate epic
Context
With shopping cards now captured from Perplexity / AI Mode / Copilot (and ChatGPT pending in #95), brands need a dedicated lens on commercial-intent AI presence — separate from text-mention visibility. A product showing up in an AI shopping card is a fundamentally different signal than getting mentioned in a paragraph: high purchase intent, image + price + merchant in the slot, different competitive set.
This issue adds the Shopping page shell + Overview tab. Tabs My Products / Competitors / Prompts ship in follow-up issues (#C, #D).
Depends on: #103 (normalized
prompt_result_shopping_cardstable).Acceptance criteria
web/src/config/dashboard.tswithshopping-bag(lucide) icon.web/messages/en.jsonand at minimum copy-propagated totr.json(other 11 locales fall back via next-intl).web/src/app/[locale]/(dashboard)/dashboard/shopping/page.tsx— Cache Components (use cache), reads from the normalized table via a new server action inweb/src/lib/actions/shopping.ts.matched_brand_role='own'countown / (own + competitor + other)cards in cards-bearing promptsmerchant_domainwhere role=own)Plan gating
Implement the gate via existing
use-feature-gatehook with a newshopping_analyticsfeature flag inweb/src/config/plans.ts. Self-host stays unrestricted.Out of scope