--- phase: 01-foundation-auth plan: 06 type: execute wave: 4 depends_on: ["01-05"] files_modified: - src/app/(dashboard)/subscription/page.tsx - src/app/actions/subscription.ts - src/components/subscription/plan-card.tsx - src/lib/plans.ts autonomous: true must_haves: truths: - "User can view all available plans (Free, Creator, Pro)" - "User can see their current plan highlighted" - "User can switch to a different plan" - "Plan change updates profile in database" - "Plan features are displayed clearly" artifacts: - path: "src/app/(dashboard)/subscription/page.tsx" provides: "Subscription management page" min_lines: 30 - path: "src/app/actions/subscription.ts" provides: "Server action for plan switching" exports: ["switchPlan"] - path: "src/components/subscription/plan-card.tsx" provides: "Reusable plan display component" exports: ["PlanCard"] key_links: - from: "src/components/subscription/plan-card.tsx" to: "src/app/actions/subscription.ts" via: "switchPlan action" pattern: "switchPlan" - from: "src/app/actions/subscription.ts" to: "profiles table" via: "update plan_id" pattern: "update.*plan_id" --- Implement subscription management allowing users to view plans and switch between them. Purpose: Enable users to view and change their subscription plan per AUTH-03 requirement. Payment integration deferred to later phase. Output: Working subscription page where users can view all plans and switch their plan. @C:\Users\miche\.claude/get-shit-done/workflows/execute-plan.md @C:\Users\miche\.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/phases/01-foundation-auth/01-RESEARCH.md @.planning/phases/01-foundation-auth/01-02-SUMMARY.md @.planning/phases/01-foundation-auth/01-05-SUMMARY.md Task 1: Create plan utilities and types src/lib/plans.ts src/types/database.ts Create type definitions at src/types/database.ts: ```typescript export interface Plan { id: string name: 'free' | 'creator' | 'pro' display_name: string display_name_it: string price_monthly: number features: PlanFeatures created_at: string } export interface PlanFeatures { posts_per_month: number ai_models: string[] social_accounts: number image_generation: boolean automation: boolean | 'manual' | 'full' } export interface Profile { id: string tenant_id: string plan_id: string email: string full_name: string | null avatar_url: string | null created_at: string updated_at: string plans?: Plan } ``` Create plan utilities at src/lib/plans.ts: ```typescript import { PlanFeatures } from '@/types/database' export const PLAN_DISPLAY_ORDER = ['free', 'creator', 'pro'] as const // Feature display names in Italian export const FEATURE_LABELS: Record = { posts_per_month: 'Post al mese', ai_models: 'Modelli AI', social_accounts: 'Account social', image_generation: 'Generazione immagini', automation: 'Automazione', } export function formatFeatureValue( key: keyof PlanFeatures, value: PlanFeatures[keyof PlanFeatures] ): string { if (typeof value === 'boolean') { return value ? 'Incluso' : 'Non incluso' } if (Array.isArray(value)) { return value.length.toString() } if (key === 'automation') { if (value === 'manual') return 'Solo manuale' if (value === 'full') return 'Completa' return 'Non inclusa' } return value.toString() } export function formatPrice(cents: number): string { if (cents === 0) return 'Gratis' return `€${(cents / 100).toFixed(0)}/mese` } export function getPlanBadgeColor(planName: string): string { switch (planName) { case 'pro': return 'bg-purple-100 text-purple-800 border-purple-200' case 'creator': return 'bg-blue-100 text-blue-800 border-blue-200' default: return 'bg-gray-100 text-gray-800 border-gray-200' } } ``` These utilities: - Define TypeScript types for plans - Provide Italian labels for features - Format prices and feature values - Handle plan badge colors - Both files exist - Types are correctly defined - Utility functions work - No TypeScript errors Plan types and utility functions created. Task 2: Create plan card component and switch action src/components/subscription/plan-card.tsx src/app/actions/subscription.ts Create plan card component at src/components/subscription/plan-card.tsx: ```typescript 'use client' import { Plan, PlanFeatures } from '@/types/database' import { formatFeatureValue, formatPrice, FEATURE_LABELS, getPlanBadgeColor } from '@/lib/plans' import { Button } from '@/components/ui/button' import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card' import { switchPlan } from '@/app/actions/subscription' import { useTransition } from 'react' interface PlanCardProps { plan: Plan isCurrentPlan: boolean onPlanChange?: () => void } export function PlanCard({ plan, isCurrentPlan, onPlanChange }: PlanCardProps) { const [isPending, startTransition] = useTransition() const features = plan.features as PlanFeatures function handleSwitchPlan() { startTransition(async () => { const result = await switchPlan(plan.id) if (result.success && onPlanChange) { onPlanChange() } }) } // Highlight features to show const displayFeatures: (keyof PlanFeatures)[] = [ 'posts_per_month', 'social_accounts', 'ai_models', 'image_generation', 'automation', ] return ( {isCurrentPlan && (
Piano attuale
)}
{plan.display_name_it}
{formatPrice(plan.price_monthly)} {plan.name === 'free' && 'Perfetto per iniziare'} {plan.name === 'creator' && 'Per creator seri'} {plan.name === 'pro' && 'Per professionisti'}
    {displayFeatures.map((featureKey) => { const value = features[featureKey] const isIncluded = value !== false && value !== 'non incluso' return (
  • {isIncluded ? '✓' : '—'} {formatFeatureValue(featureKey, value)} {' '} {FEATURE_LABELS[featureKey].toLowerCase()}
  • ) })}
{isCurrentPlan ? ( ) : ( )}
) } ``` Create subscription action at src/app/actions/subscription.ts: ```typescript 'use server' import { createClient } from '@/lib/supabase/server' import { revalidatePath } from 'next/cache' export type SubscriptionActionState = { success?: boolean error?: string message?: string } export async function switchPlan(planId: string): Promise { const supabase = await createClient() // Get current user const { data: { user }, error: authError } = await supabase.auth.getUser() if (authError || !user) { return { error: 'Devi essere autenticato per cambiare piano' } } // Verify plan exists const { data: plan, error: planError } = await supabase .from('plans') .select('id, name, display_name_it') .eq('id', planId) .single() if (planError || !plan) { return { error: 'Piano non trovato' } } // Update user's plan const { error: updateError } = await supabase .from('profiles') .update({ plan_id: planId }) .eq('id', user.id) if (updateError) { console.error('Failed to update plan:', updateError) return { error: 'Errore durante il cambio piano. Riprova.' } } // Revalidate pages that show plan info revalidatePath('/dashboard') revalidatePath('/subscription') return { success: true, message: `Piano cambiato a ${plan.display_name_it}` } } export async function getCurrentPlan() { const supabase = await createClient() const { data: { user } } = await supabase.auth.getUser() if (!user) { return null } const { data: profile } = await supabase .from('profiles') .select(` plan_id, plans ( id, name, display_name, display_name_it, price_monthly, features ) `) .eq('id', user.id) .single() return profile?.plans || null } ``` NOTE: This is a simplified plan switching for v1. In production: - Payment would be processed before switching to paid plans - Downgrade might be scheduled for end of billing period - Proration logic would be needed - These complexities are deferred per CONTEXT.md
- PlanCard component renders all plan features - switchPlan action updates database - Current plan is highlighted - Non-current plans have switch button Plan card component and switch action created.
Task 3: Create subscription page src/app/(dashboard)/subscription/page.tsx Create subscription management page at src/app/(dashboard)/subscription/page.tsx: ```typescript import { createClient } from '@/lib/supabase/server' import { redirect } from 'next/navigation' import { PlanCard } from '@/components/subscription/plan-card' import { Plan } from '@/types/database' import { PLAN_DISPLAY_ORDER } from '@/lib/plans' export default async function SubscriptionPage() { const supabase = await createClient() const { data: { user } } = await supabase.auth.getUser() if (!user) { redirect('/login') } // Get user's current plan const { data: profile } = await supabase .from('profiles') .select('plan_id') .eq('id', user.id) .single() // Get all plans const { data: plans, error: plansError } = await supabase .from('plans') .select('*') .order('price_monthly', { ascending: true }) if (plansError || !plans) { return (

Errore nel caricamento dei piani

) } // Sort plans by our display order const sortedPlans = [...plans].sort((a, b) => { return PLAN_DISPLAY_ORDER.indexOf(a.name as typeof PLAN_DISPLAY_ORDER[number]) - PLAN_DISPLAY_ORDER.indexOf(b.name as typeof PLAN_DISPLAY_ORDER[number]) }) return (

Il tuo abbonamento

Scegli il piano piu adatto alle tue esigenze

{/* Info banner */}

Nota: Il pagamento verra implementato nelle prossime versioni. Per ora puoi passare liberamente tra i piani per testare le funzionalita.

{/* Plans grid */}
{sortedPlans.map((plan) => ( ))}
{/* Feature comparison */}

Confronto funzionalita

{sortedPlans.map((plan) => ( ))} (p.features as Plan['features']).posts_per_month.toString()} /> (p.features as Plan['features']).social_accounts.toString()} /> (p.features as Plan['features']).ai_models.length.toString()} /> (p.features as Plan['features']).image_generation ? '✓' : '—'} /> { const auto = (p.features as Plan['features']).automation if (auto === false) return '—' if (auto === 'manual') return 'Manuale' if (auto === 'full') return 'Completa' return '—' }} />
Funzionalita {plan.display_name_it}
{/* FAQ */}

Domande frequenti

) } function FeatureRow({ feature, plans, getValue, }: { feature: string plans: Plan[] getValue: (plan: Plan) => string }) { return ( {feature} {plans.map((plan) => ( {getValue(plan)} ))} ) } function FaqItem({ question, answer }: { question: string; answer: string }) { return (

{question}

{answer}

) } ``` This page: - Shows all three plans in cards - Highlights current plan - Allows switching between plans - Shows feature comparison table - Includes FAQ section - Notes that payment is deferred (transparency) - All text in Italian
- Page loads at /subscription - All three plans display - Current plan is highlighted - Can click to switch plans - Feature comparison table is accurate Complete subscription management page with plan display and switching.
After all tasks complete: 1. Visit /subscription when logged in 2. See all three plans (Free, Creator, Pro) 3. Current plan is highlighted with "Piano attuale" badge 4. Click switch button on different plan -> plan changes 5. Dashboard reflects new plan after switch 6. Feature comparison table shows correct values - All three plans display with correct pricing - User can view their current plan - User can switch to a different plan - Plan switch updates database (verify in Supabase) - Feature limits are clearly displayed - All text is in Italian - Note about payment deferral is visible After completion, create `.planning/phases/01-foundation-auth/01-06-SUMMARY.md`