IgnitionStack ships a full affiliate program with public signup (/[locale]/affiliates), a logged-in dashboard (/[locale]/affiliates/dashboard), Supabase-backed tracking tables, and GA4 instrumentation. This guide explains how the feature is wired so future changes stay aligned with the architecture playbook.
| Layer | File | Responsibility |
|---|---|---|
| Landing Route | src/app/[locale]/(pages)/affiliates/page.tsx | Sets locale, SEO metadata, and redirects existing affiliates to the dashboard. Renders hero/benefits plus AffiliateSignupForm or login CTA. Includes <AffiliateLandingAnalytics locale={locale} /> for client-only GA tracking. |
| Dashboard Route | src/app/[locale]/(pages)/affiliates/dashboard/page.tsx | Auth-guarded page that loads the current affiliate + stats via server queries and renders <AffiliateDashboard />. Marked dynamic = "force-dynamic" to allow cookie reads. |
| Components | src/app/components/affiliates/* | Dashboard widgets (AffiliateDashboard, AffiliateLinkShare, AffiliateRecentConversions), signup form, and AffiliateTracker (global referral tracker injected in src/app/[locale]/layout.tsx). |
| Server Queries | src/app/server/affiliates/* | Read-only helpers: getCurrentUserAffiliate, getAffiliateStats, isAffiliateCodeValid, etc. |
| Server Actions | src/app/actions/affiliates/* | Mutations such as becomeAffiliate, updateAffiliate, recordAffiliateClick/Conversion, and requestPayout. All use ActionResult + validation wrappers. |
| Repository + Mapper | src/app/lib/repositories/affiliate-repository.ts, src/app/lib/mappers/affiliate.ts | Centralized Supabase access, type mapping, cache revalidation tags. |
| Supabase Schema | supabase/migrations/schema/66-72_*.sql | Tables (affiliates, affiliate_clicks, affiliate_conversions, affiliate_payouts), RLS, indexes, triggers, auto-code generator, and seed script (seed/05_seed_affiliates.sql). |
Types live in src/app/types/affiliate.ts and mirror the Supabase schema:
Affiliate – aggregated counters (totalClicks, pendingPayout, commissionRate, etc.) plus payment props (paypalEmail, pixKey).AffiliateClick, AffiliateConversion, AffiliatePayout – used in dashboard tables.CreateAffiliateInput, RecordClickInput, RequestPayoutInput, …). Always validate via src/app/lib/validations/affiliate.ts (Zod schemas for commission ranges, payment methods, PIX keys, etc.).Key migrations:
66_affiliates.sql – base table with affiliate_code, commission/discount ranges, and counter columns updated by triggers.67-69_* – click/conversion/payout tables with foreign keys and indexes.70_affiliate_rls_enable.sql + 71_affiliate_policies.sql – enforce RLS: users read/update their own records, anonymous visitors can insert clicks, only service-role inserts conversions.72_affiliate_triggers.sql – auto-increment stats, keep updated_at fresh, generate codes, and sync payouts. Remember to update supabase/migrations/roolback/06_drop_tables.sql when adding new functions/triggers.seed/05_seed_affiliates.sql – demo records for local testing (admin user + sample activity).After editing migrations run:
npm run db:push
npm run db:genGA4 events live in src/app/lib/analytics-events.ts under affiliateEvents:
pageViewed, signupStarted, signupCompleteddashboardViewed, linkCopied, referralClickedconversion, payoutRequested, settingsUpdated, couponAppliedEmitters:
AffiliateLandingAnalytics (client) fires pageViewed.AffiliateSignupForm emits signupStarted/completed and logs failures with createLogger.AffiliateLinkShare fires linkCopied and wraps clipboard/share errors in structured logs.AffiliateTracker (client) validates codes via server action, records clicks, and triggers referralClicked. It’s mounted inside a <Suspense> boundary in the locale layout to satisfy Next.js’ useSearchParams requirements.affiliateEvents.dashboardViewed upon render.For any new interaction, extend affiliateEvents instead of scattering trackGAEvent calls.
All copy sits under the Affiliates namespace inside src/i18n/messages/{locale}.json with the following structure:
Affiliates: {
meta, landing, signup, linkShare,
dashboard: { meta, title, subtitle, stats.*, commissionInfo.*, payout.* },
conversions, settings
}When adding new UI, add keys to pt.json (source), then mirror en/es. Use npm run i18n:check before committing.
paymentMethodSchema, update Supabase column checks, extend repository createPayout, and surface UI in AffiliateSignupForm + translations.AffiliateRepository.getStats to return aggregated data, update AffiliateDashboard to render cards, and document metrics here.recordAffiliateConversion from Stripe webhooks, then call completeConversion once payment settles.AFFILIATE_COOKIE_DAYS in affiliate-tracker.tsx and ensure marketing copy (landing stats) matches.AffiliateRepository.findAll/count from a protected admin route; reuse adminUpdateAffiliateSchema for moderation.src/app/test/unit/components/affiliates/*) or validation logic.becomeAffiliate, requestPayout) using Vitest’s Supabase mocks./pt/affiliates (signup), /pt/affiliates/dashboard (stats render, copy link), and affiliate referral cookie tracking.npm run lint, npm test, and npm run build:app after touching affiliate code—the build uses dynamic = "force-dynamic" pages and will fail if cookies/search params are accessed outside Suspense boundaries.AffiliateTracker in sync with any new campaign parameters (e.g., additional query params). Expose helpers in src/app/lib/affiliates/link.ts for utility usage.Document updates here whenever the affiliate experience changes (pricing, payout logic, dashboard metrics, analytics schema, etc.).