Agent Skills: Subscription System Developer

Expert assistant for designing and implementing subscription features across the Raamattu Nyt monorepo. Use when adding feature limits, implementing quota checks, creating plan-based access controls, building upgrade flows, or extending the subscription system.

UncategorizedID: Spectaculous-Code/raamattu-nyt/subscription-system

Install this agent skill to your local

pnpm dlx add-skill https://github.com/Spectaculous-Code/raamattu-nyt/tree/HEAD/.claude/skills/subscription-system

Skill Files

Browse the full folder contents for subscription-system.

Download Skill

Loading file tree…

.claude/skills/subscription-system/SKILL.md

Skill Metadata

Name
subscription-system
Description
Expert assistant for designing and implementing subscription features across the Raamattu Nyt monorepo. Use when adding feature limits, implementing quota checks, creating plan-based access controls, building upgrade flows, extending the subscription system, or updating the plans/pricing page (tilaussivu) to reflect which features each plan includes.

Subscription System Developer

Context Files (Read First)

For schema and plan details, read from Docs/context/:

  • Docs/context/db-schema-short.md - Quota tables and structures
  • Docs/context/supabase-map.md - RPC functions for quotas

Capabilities

  • Design and implement feature gating with plan-based access
  • Create database migrations for quota tables and usage tracking
  • Build React hooks for subscription state management
  • Implement Edge Function quota enforcement
  • Create upgrade modal flows and UI components
  • Extend quota system for new feature types (tokens, counts, size limits)

Reference Documentation

Primary: Docs/13-SUBSCRIPTION-SYSTEM.md

Shared Package Architecture

All subscription logic lives in packages/shared-subscription/ and is imported via the @shared-subscription alias.

Package Structure

packages/shared-subscription/src/
├── components/
│   ├── PlanBadge.tsx           # Plan tier badge
│   ├── PremiumTag.tsx          # Premium feature indicator (Crown icon + upgrade dialog)
│   ├── PlansComparison.tsx     # Plans comparison cards
│   ├── UpgradeModal.tsx        # Upgrade flow modal
│   └── useSubscriptionOptional.ts  # Optional context hook (no provider required)
├── context/
│   ├── SubscriptionContext.tsx  # React context definition
│   └── SubscriptionProvider.tsx # Context provider (wraps app)
├── lib/
│   ├── cn.ts                   # Utility
│   └── stripe-mock.ts          # Mock Stripe utilities
├── index.ts                    # Public API
└── types.ts                    # PlanKey, UsageInfo, PlanInfo, etc.

Import Pattern

// CORRECT — use @shared-subscription alias
import { SubscriptionProvider } from "@shared-subscription/context/SubscriptionProvider";
import { PremiumTag } from "@shared-subscription/components/PremiumTag";
import { UpgradeModal } from "@shared-subscription/components/UpgradeModal";
import type { PlanKey } from "@shared-subscription/types";

// WRONG — old paths, do NOT use
// import type { PlanKey } from "@/components/ai-quota";
// import { UpgradeModal } from "@/components/ai-quota";

SubscriptionProvider Pattern

Wrap the app (or a subtree) with SubscriptionProvider to make subscription state available:

<SubscriptionProvider
  plan={userPlan}          // PlanKey: unauth | basic | pro | premium | admin
  usage={usageInfo}        // UsageInfo | null
  plans={availablePlans}   // PlanInfo[]
  loading={isLoading}
  canUseFeature={(key, tokens?) => boolean}  // Feature gating function
  onNavigateToPlans={handleNavigate}
  onNavigateToAuth={handleAuth}
>
  {children}
</SubscriptionProvider>

The provider manages upgrade modal state internally and renders <UpgradeModal> as a portal.

PremiumTag Component

Mark premium features in UI with a Crown icon:

<PremiumTag size="md" label={true} featureName="Plan from Summary" />
  • Premium/Admin users: Shows subtle amber Crown icon only
  • Non-premium users: Clickable Crown that opens upgrade dialog with CTA
  • Admin-configurable promo text via setPremiumTagConfig(config)

Gated Feature Example: plan-from-summary

-- Feature key: 'plan-from-summary'
-- Premium only: 3 plans per 6-hour window
INSERT INTO bible_schema.ai_plan_quotas (plan_key, feature_key, tokens_per_window, enabled) VALUES
  ('unauth',  'plan-from-summary', 0,      false),
  ('basic',   'plan-from-summary', 0,      false),
  ('pro',     'plan-from-summary', 0,      false),
  ('premium', 'plan-from-summary', 3,      true),
  ('admin',   'plan-from-summary', 999999, true);

Architecture Overview

Plan Tiers

| Plan | Target | AI Tokens | Feature Access | |------|--------|-----------|----------------| | unauth | Guests | 2,000 | Very limited | | basic | Free users | 10,000 | Moderate | | pro | Paid | 50,000 | Full | | premium | Top tier | 200,000 | Full + extras | | admin | Admins | Unlimited | All |

Limit Types

| Type | Window | Example | |------|--------|---------| | tokens | rolling | AI features (6-hour window) | | count | lifetime | Max notes per user | | count | monthly | PDF exports per month | | size | none | Max content length | | boolean | none | Feature on/off |

Usage Examples

Three complete, copy-pasteable end-to-end examples (migration + RPC + hook + component) live in references/examples.md — read that file when implementing a new gated feature:

  1. Add a feature with a count limit (user notes, 50/plan) — full table, RLS, can_create_note RPC, useUserNotes hook, component wiring.
  2. Add a notification channel (WhatsApp for pro) — preferences/queue tables, can_use_notification_channel RPC, Twilio edge function.
  3. Extend the quota table for new limit typeslimit_type / window_type / size_limit columns + unified can_use_feature RPC.

All three follow the same shape: quota rows for all 5 plans → feature RPC returning {allowed, reason, upgrade_suggestion} → hook exposing checkOrPrompt → component calls checkOrPrompt() then acts.

Unified Pattern: checkOrPrompt

The recommended pattern for all gated features:

// Hook provides checkOrPrompt function
const { checkOrPrompt } = useFeatureHook();

// In component - single call handles everything:
// 1. Checks if feature allowed
// 2. Shows appropriate modal if denied
// 3. Returns boolean for flow control
const handleAction = async () => {
  if (!await checkOrPrompt()) return;  // Modal shown automatically

  // Proceed with action
  await performAction();
};

This pattern:

  • Reduces boilerplate in components
  • Ensures consistent upgrade flow
  • Handles all deny reasons (auth, locked, quota, limit)
  • Works for all feature types

Updating the Plans/Pricing Page (Tilaussivu)

When adding or changing features per plan, update three places:

1. PlansComparison component

File: apps/raamattu-nyt/src/components/ai-quota/PlansComparison.tsx

The PLAN_DETAILS array defines features shown per plan via i18n keys:

// Add new feature key to the relevant plan's featureKeys array
{
  key: "pro",
  featureKeys: [
    "plans.pro.features.existingFeature",
    "plans.pro.features.newFeature",  // Add here
  ],
}

2. Translation files (fi + en)

  • FI: apps/raamattu-nyt/public/locales/fi/common.jsonplans.<tier>.features.<key>
  • EN: apps/raamattu-nyt/public/locales/en/common.json → same path

Add the translation for the new feature key under the correct plan tier:

"features": {
  "existingFeature": "Olemassa oleva ominaisuus",
  "newFeature": "Uusi ominaisuus"
}

3. PlansPage

File: apps/raamattu-nyt/src/pages/PlansPage.tsx

Usually no changes needed — it renders PlansComparison. Only update if adding new sections (FAQ items, hero text, etc.).

Pages using PlansComparison: PlansPage, AccountPlanPage, UpgradeModal, CheckoutPage

Key Files

| File | Purpose | |------|---------| | Docs/13-SUBSCRIPTION-SYSTEM.md | Full system documentation | | supabase/migrations/20260107180305_*.sql | AI quota core schema | | supabase/migrations/20260107180306_*.sql | Quota RPC functions | | packages/shared-subscription/src/ | Shared subscription package (SubscriptionProvider, PremiumTag, UpgradeModal) | | apps/raamattu-nyt/src/hooks/useAIQuota.ts | AI quota hook (reference) | | apps/raamattu-nyt/src/components/ai-quota/ | UI components (legacy — prefer @shared-subscription imports) | | packages/shared-subscription/src/components/PlansComparison.tsx | Plan feature lists + pricing cards | | apps/raamattu-nyt/public/locales/fi/common.json | FI translations (plans.*) | | apps/raamattu-nyt/public/locales/en/common.json | EN translations (plans.*) | | packages/shared-auth/ | Shared auth hooks |

Checklist for New Features

  • [ ] Add quota entries to ai_plan_quotas for all 5 plans
  • [ ] Create RPC function for feature-specific checks
  • [ ] Build React hook with checkOrPrompt pattern
  • [ ] Integrate UpgradeModal in component
  • [ ] Add feature to PlansComparison PLAN_DETAILS featureKeys
  • [ ] Add feature translation to fi/common.json and en/common.json under plans.<tier>.features
  • [ ] Update admin panel if needed
  • [ ] Add feature to 13-SUBSCRIPTION-SYSTEM.md
  • [ ] Test all plan tiers (unauth, basic, pro, premium, admin)

Related Skills

  • supabase-migration-writer - Database migrations
  • edge-function-generator - Edge Functions
  • admin-panel-builder - Admin UI
  • rls-policy-validator - RLS security