# Extras (/extras) # Extras ๐ŸŽ Level up your project with these hand-picked tools and resources! From UI components to brand assets, we've got you covered. โœจ ## shadcn/ui Component Library ๐Ÿงฉ Indie Kit is built on **shadcn/ui**, which means you have access to an incredible ecosystem of components and resources! ### Awesome shadcn/ui Collection Explore [awesome-shadcn-ui](https://github.com/birobirobiro/awesome-shadcn-ui) - a treasure trove of community-created resources: * ๐ŸŽจ **Beautiful Themes** - Pre-made color schemes and styles * ๐Ÿงฉ **Extra Components** - Beyond the default component set * ๐Ÿ› ๏ธ **Dev Tools** - Utilities to speed up development * ๐Ÿ“š **Tutorials** - Learn best practices and advanced patterns * ๐ŸŽฏ **Real Projects** - See shadcn/ui in production apps * ๐Ÿš€ **Starter Kits** - Boilerplates for specific use cases This repository is constantly updated with new gems from the community! Bookmark it and check back regularly for fresh components and tools. ๐Ÿ’Ž ## Brand Assets & Design ๐ŸŽจ Get your branding sorted in minutes with these free tools! ### Logo Generator โšก Create professional logos with [logofa.st](https://logofa.st/): * ๐ŸŽฏ **AI-Powered** - Generate unique logos instantly * ๐ŸŽจ **Fully Customizable** - Adjust colors, fonts, and layouts * ๐Ÿ“ฆ **Multiple Formats** - SVG, PNG, and more * โšก **Perfect for MVPs** - Launch fast without hiring a designer Generate multiple variations and A/B test them with your audience before committing! Your logo is often the first thing customers see. ### Favicon Generator ๐Ÿ–ผ๏ธ Generate pixel-perfect favicons with [RealFaviconGenerator](https://realfavicongenerator.net/): * ๐Ÿ“ฑ **All Platforms** - iOS, Android, Windows, and web * โœ… **Optimal Sizes** - Automatically creates all required dimensions * ๐ŸŽจ **Platform-Specific** - Tailored for each device/browser * ๐Ÿ’ป **Installation Code** - Copy-paste ready HTML **What you get:** * Browser tab icon * iOS home screen icon * Android splash screen * Windows tile * Safari pinned tab icon Both tools are **100% free** and perfect for indie hackers! Upload your logo, download the assets, and you're done in under 5 minutes. ๐Ÿš€ ## Additional Tools ๐Ÿ› ๏ธ ### Colors & Themes * [Coolors](https://coolors.co/) - Generate beautiful color palettes * [Realtime Colors](https://www.realtimecolors.com/) - Visualize your color scheme on real UI * [shadcn Themes](https://ui.shadcn.com/themes) - Pre-built theme configurations ### Icons & Graphics * [Lucide Icons](https://lucide.dev/) - Beautiful, consistent icon set (built-in!) * [Hero Patterns](https://heropatterns.com/) - SVG background patterns * [Unsplash](https://unsplash.com/) - Free high-quality stock photos ### Typography * [Google Fonts](https://fonts.google.com/) - Free web fonts * [Fontjoy](https://fontjoy.com/) - Font pairing suggestions * [Type Scale](https://typescale.com/) - Generate harmonious font sizes ## Need More? ๐Ÿ’ฌ Can't find what you're looking for? * ๐Ÿ’ฌ Ask in the [Indie Kit Discord](https://indiekit.pro/app/discord) * ๐Ÿ“š Check the [shadcn/ui docs](https://ui.shadcn.com/) * ๐Ÿ” Search [npm](https://www.npmjs.com/) for React components These resources exist to help you launch quickly! Don't spend weeks on design - use these tools to get 80% there in hours, then iterate based on user feedback. ๐ŸŽฏ # Installation (/getting-started) YouTube Video: https://www.youtube.com/watch?v=Ho9MygCGGuI import { Card, Cards } from 'fumadocs-ui/components/card'; Let's get Indie Kit running on your local machine! You'll be up and running in under 5 minutes. โšก Make sure you have **Node.js 18+** and **Git** installed. That's all you need! ## Installation Steps โš™๏ธ ### 1. Clone the Repository ```bash git clone https://github.com/Indie-Kit/indie-kit cd indie-kit ``` Building a B2B SaaS? Clone the B2B Kit instead: ```bash git clone https://github.com/Indie-Kit/b2b-boilerplate ``` ### 2. Install pnpm (if needed) We use pnpm because it's faster and more efficient: ```bash npm install -g pnpm ``` ### 3. Install Dependencies ```bash pnpm install ``` ### 4. Start Development Server ```bash pnpm dev ``` ### 5. Open in Browser Visit `http://localhost:3000` and see your app running! ๐ŸŽ‰ ### 6. Reset Git Origin Disconnect from Indie Kit repo and connect to yours: ```bash git remote remove origin git remote add origin YOUR_REPO_URL ``` Your local environment is set up! Start customizing and building your SaaS. โœจ ## Project Structure ๐Ÿ“ Understanding where things are helps you move fast: ``` indie-kit/ โ”œโ”€โ”€ src/ โ”‚ โ”œโ”€โ”€ app/ # Next.js app router (pages & layouts) โ”‚ โ”‚ โ””โ”€โ”€ api/ # API routes โ”‚ โ”œโ”€โ”€ components/ # React components โ”‚ โ”œโ”€โ”€ lib/ # Utilities, hooks, helpers โ”‚ โ”œโ”€โ”€ db/ # Database schema & queries โ”‚ โ””โ”€โ”€ emails/ # Email templates โ”œโ”€โ”€ public/ # Static assets โ””โ”€โ”€ .env.local # Environment variables (create this) ``` **Key folders:** * ๐ŸŽจ `/src/app` - Your pages and app logic * ๐Ÿงฉ `/src/components` - Reusable UI components * ๐Ÿ› ๏ธ `/src/lib` - Utilities and helpers * ๐Ÿ—„๏ธ `/src/db` - Database schema (Drizzle ORM) * โœ‰๏ธ `/src/emails` - Email templates (React Email) ## Configuration โš™๏ธ ### Config File Open `src/lib/config.ts` to customize your app: ```ts export const config = { appName: "Your App Name", auth: { enablePasswordAuth: true, }, // ... more options } ``` Everything is configured in one place! Update `config.ts` to enable/disable features like auth methods, payment providers, and more. ### Environment Variables Create `.env.local` for local development: ```bash # Database DATABASE_URL="your-database-url" # Auth NEXTAUTH_SECRET="your-secret-key" NEXTAUTH_URL="http://localhost:3000" # Email RESEND_API_KEY="your-api-key" # Payments (add when ready) # STRIPE_SECRET_KEY="your-stripe-key" ``` **Environment file hierarchy:** * ๐Ÿ“ `.env` - Defaults (committed to repo) * ๐Ÿ”’ `.env.local` - Local development (not committed) * ๐Ÿš€ `.env.production` - Production settings (in your hosting platform) Never commit `.env.local` to git! It contains sensitive keys and is already in `.gitignore`. ## Next Steps ๐ŸŽฏ Now that you're set up, here's what to do next: 1. **Configure your database** - [Set up PostgreSQL](/setup/database) 2. **Launch your landing page** - [Go live in 5 minutes](/launch-in-5-minutes) 3. **Set up authentication** - [Configure auth providers](/setup/auth) 4. **Add payments** - [Connect Stripe or others](/setup/payments) You're all set! Pick your next step above and start building your SaaS. ๐Ÿš€ # Overview (/) import { Card, Cards } from 'fumadocs-ui/components/card'; # Welcome to Indie Kit! ๐Ÿ‘‹ The fastest way to build and launch your SaaS. Everything you need is built-in and ready to go! โœจ Clone the repo, run `pnpm install`, and you're ready to build. No complex setup, no boilerplate fatigue! ## Quick Start ๐Ÿš€ Get Indie Kit running locally in 5 minutes Go live with a beautiful landing page Build hype before launch ## Setup & Configuration โš™๏ธ PostgreSQL with Drizzle ORM Password, magic links, and social logins Resend, Mailgun, SES, and more Stripe, LemonSqueezy, PayPal, DodoPayments, Polar.sh ## Key Features ๐Ÿ’Ž Organizations, roles, and team management Usage-based pricing for AI apps AI-ready docs with search and OG images Scheduled tasks and email sequences [Get Repository Access โ†’](https://indiekit.pro/app/repo-access) # Launch in 5 Minutes (/launch-in-5-minutes) YouTube Video: https://www.youtube.com/watch?v=dAKKJT3_DJ4 Get your beautiful landing page live and start collecting customers in under 5 minutes! ๐Ÿš€ ## What You'll Build ๐ŸŽจ A stunning, conversion-optimized landing page with: * ๐ŸŽฏ Hero section with CTA * ๐Ÿ’ฐ Pricing table (monthly/annual) * โœจ Social proof & testimonials * โ“ FAQ section * ๐Ÿ“ง Email collection (optional) No design skills needed! Just mix and match pre-built components to create your perfect landing page. ## Let's Build! ๐Ÿ› ๏ธ ### Step 1: Get the Code Running If you haven't already, [follow the setup guide](/getting-started) to clone and run locally. ### Step 2: Customize Your Homepage Open `src/app/(website-layout)/page.tsx` and compose your landing page: ```tsx import { WebsiteFAQs } from "@/components/website/faqs"; import { CTA2 } from "@/components/website/cta-2"; import { WithWithout } from "@/components/website/with-without"; import Hero2 from "@/components/sections/hero-2"; import CTA1 from "@/components/website/cta-1"; import MonthlyAnnualPricing from "@/components/website/monthly-annual-pricing"; import TextRevealByWord from "@/components/ui/text-reveal"; export default function WebsiteHomepage() { return ( <> ); } ``` **What each component does:** * `Hero2` - Eye-catching hero with headline and CTA button * `CTA1` - Primary call-to-action section * `MonthlyAnnualPricing` - Pricing table with toggle * `TextRevealByWord` - Animated testimonial section * `WithWithout` - Before/after comparison * `WebsiteFAQs` - Frequently asked questions * `CTA2` - Final conversion push Reorder, remove, or duplicate components to match your needs! Check [all available components](/customisation/installed-components) for more options. ### Step 3: Deploy! ๐Ÿš€ Push to GitHub and deploy to Vercel: ```bash git add . git commit -m "Launch landing page" git push ``` That's it! Your landing page is live! ๐ŸŽ‰ ## Next Steps ๐Ÿ“ˆ Now that your page is live: 1. [Customize components](/customisation/components) with your branding 2. [Set up payments](/payments/create-subscription) to start earning 3. [Launch with waitlist](/launch-with-waitlist) to build hype 4. [Add analytics](/analytics) to track conversions You're now live and ready to accept customers! Share your link and watch the signups roll in. ๐Ÿ’ฐ # Launch with Waitlist (/launch-with-waitlist) YouTube Video: https://www.youtube.com/watch?v=CJ2Tpiazm9U Want to build anticipation for your product? Let's set up a waitlist to gather early users! Here's how to do it step by step. โœจ ## Prerequisites ๐Ÿ“‹ 1. **Set Up Your Database** ๐Ÿ’พ First, make sure your database is configured properly. Follow our [database setup guide](/setup/database) if you haven't already. 2. **Enable Sign In** ๐Ÿ” ```bash # Add to your .env file NEXT_PUBLIC_SIGNIN_ENABLED=true # Run auth setup command npx auth secret ``` This command will set up secure authentication with proper encryption keys. 3. **Configure Admin Access** ๐Ÿ‘‘ ```bash # Add to your .env file SUPER_ADMIN_EMAILS=admin@example.com,admin2@example.com ``` > ๐Ÿ”‘ **Important** > Make sure to use the email addresses you'll use to sign in as admin. ## Authentication Setup ๐Ÿ›ก๏ธ 4. **Set Up Authentication** You have two options: * [Email Authentication Setup Guide](/setup/auth) * [Google Login Setup Guide](/setup/auth/google-login) ## Managing Your Waitlist ๐Ÿ“Š 5. **Access Admin Dashboard** ```bash http://localhost:3000/super-admin/waitlist ``` Here you can: * View all waitlist signups * Export user data 6. **Waitlist Landing Page** ```bash http://localhost:3000/join-waitlist ``` This is where users can: * Join your waitlist * Learn about your product ## Automatic Email Notifications ๐Ÿ“ง 7. **Configure Waitlist Emails** Customize the waitlist API route to send automatic welcome emails: ```tsx // src/app/api/waitlist/route.ts import { sendMail } from '@/lib/email/sendMail' import { NextResponse } from 'next/server' import { WaitlistWelcome } from '@/emails/waitlist-welcome' export async function POST(req: Request) { try { const body = await req.json() const { email, name } = body // Existing waitlist logic here... // Send welcome email await sendMail({ to: email, subject: "Welcome to the Waitlist! ๐ŸŽ‰", html: render( ) }) return NextResponse.json({ success: true }) } catch (error) { return NextResponse.json( { error: "Failed to join waitlist" }, { status: 500 } ) } } ``` > ๐Ÿ’ก **Pro Tip** > Check out our [Sending Emails tutorial](/setup/email) for more email customization options! ## Changes to your codebase ๐ŸŽจ 8. **Update CTA Elements** Modify these files to customize your waitlist messaging: ```tsx // src/components/website/hero.tsx ``` ```tsx // src/components/layout/header.tsx ``` > ๐Ÿ’ก **Pro Tip** > Use compelling copy that creates urgency and excitement! > Examples: > > * "Get Early Access" > * "Join the Exclusive Beta" > * "Be the First to Know" ## Launch Time ๐Ÿš€ 9. **Deploy Your Waitlist** Ready to go live? Follow our [deployment guide](/deployment) to launch your waitlist. ## Best Practices ๐Ÿ’ซ 1. **Email Communication** ๐Ÿ“ง * Send a welcome email immediately (automatically) * Keep users updated on progress (send emails manually or automatically) * Provide exclusive previews (send emails manually or automatically) * Customize email templates for your brand (see [Sending Emails tutorial](/setup/email)) 2. **Waitlist Management** ๐Ÿ“ * Regularly check your admin dashboard * Engage with early signups (automatic and manual) * Plan your onboarding strategy 3. **Marketing Tips** ๐ŸŽฏ * Share on social media * Create FOMO with limited spots * Offer early-bird benefits * Use email campaigns effectively Now you're ready to launch your product with a waitlist! Build that anticipation and grow your user base! ๐Ÿš€ # Managing Projects (/managing-projects) import Image from 'next/image' # Managing Projects ๐Ÿ“‹ Stay organized and ship faster with Indie Kit's built-in project tracker! Get a proven roadmap with actionable steps to take your project from idea to launch. โœจ This feature is completely optional but highly recommended for new users! It helps you stay on track and not miss important steps. ## What You Get ๐ŸŽฏ **A complete roadmap with:** * โœ… **10 proven phases** - From Mindset to Growth * ๐Ÿ“Š **Progress tracking** - See exactly where you are * โฑ๏ธ **Time estimates** - Know how long each phase takes * ๐Ÿ’ก **Pro tips** - Learn from experienced indie hackers * ๐Ÿ”— **Quick links** - Jump to relevant documentation * ๐Ÿ“‹ **45+ actionable steps** - Nothing gets forgotten **Perfect for:** * ๐Ÿš€ First-time indie hackers * ๐Ÿ“ฆ Building multiple projects * ๐ŸŽฏ Staying focused and organized * ๐Ÿ’ก Following a proven process ## How It Works ๐Ÿš€ ### Step 1: Create Your Project Visit the projects page and add your project details: ๐Ÿ‘‰ [Create Your Project](https://indiekit.pro/app/projects) **What you'll add:** * **Project Name** - Your app name * **Kit Used** - Indie Kit, Indie Kit Pro, or B2B Kit * **Domain/URL** (Optional) - Your project URL * **Description** (Optional) - What your project does Create Project Form Track multiple projects simultaneously! Perfect for indie hackers building several side projects. ### Step 2: Follow Your Roadmap Once created, you'll get access to a comprehensive roadmap with 10 phases: Project Roadmap Overview **The roadmap includes:** * ๐Ÿ’ก Mindset & Strategy * ๐Ÿ› ๏ธ Project Setup * ๐Ÿš€ Deployment * ๐ŸŽจ UX Design * ๐Ÿ—„๏ธ Database Design * ๐Ÿ’ป UI Implementation * ๐Ÿ’ณ Payment Integration * ๐Ÿงช Testing * ๐ŸŽ‰ Launch * ๐Ÿ“ˆ Growth ### Step 3: Track Your Progress Check off steps as you complete them and watch your progress grow! Project Progress Tracking **Each phase shows:** * โฑ๏ธ Time estimates * ๐Ÿ“‹ Sub-steps checklist * ๐Ÿ’ก Pro tips from experienced builders * ๐Ÿ“š Links to relevant documentation * โœ… Completion status ## Why It Helps ๐Ÿ’ช **Stay on track:** * Never forget critical setup steps * Follow a proven process that works * Get helpful tips at each stage * Know exactly what to do next **Save time:** * No more wondering "what's next?" * Quick links to relevant documentation * Learn from others' experience * Avoid common mistakes **Build confidence:** * See your progress visually * Celebrate completing each phase * Know you're following best practices * Feel organized and in control This roadmap is based on what actually works for indie hackers. Follow it to ship faster and avoid common pitfalls! ## Common Questions โ“ **Do I have to follow every step?** No! The roadmap is a helpful guide. Skip or adapt steps based on your project needs. **Can I track multiple projects?** Yes! Create separate projects for each of your ideas. Perfect for indie hackers building multiple side projects. **What if I get stuck?** Ask for help in [Indie Kit Discord](https://indiekit.pro)! The community is super helpful and responsive. **Is this required to use Indie Kit?** No, it's completely optional! But highly recommended for first-time users to stay organized. **How long does it typically take?** Most basic SaaS projects take 2-3 weeks following the roadmap. Complex projects may take longer. ## Get Started ๐Ÿš€ Ready to start tracking your project? ๐Ÿ‘‰ **[Create Your First Project](https://indiekit.pro/app/projects)** Once you create a project, you'll get instant access to: * ๐Ÿ“‹ Complete 10-phase roadmap * โœ… Interactive checklist with 45+ steps * ๐Ÿ’ก Pro tips from experienced builders * ๐Ÿ”— Quick links to documentation * ๐Ÿ“Š Visual progress tracking Start tracking your project today and ship faster with a proven roadmap! ๐Ÿ’ช ## Related Documentation ๐Ÿ“š * [Launch in 5 Minutes](/launch-in-5-minutes) - Quick start guide * [Launch with Waitlist](/launch-with-waitlist) - Pre-launch strategy * [Getting Started](/getting-started) - Installation guide * [Deployment Guide](/deployment) - Deploy your app # AI Features (/ai) # AI Features ๐Ÿค– Add powerful AI capabilities to your SaaS! Process AI tasks in the background for better performance and user experience. โœจ AI tasks can take seconds or minutes. Always process them in the background using Inngest or n8n! ## What You Can Build ๐ŸŽฏ With Indie Kit's AI support: * ๐ŸŽจ **Image Generation** - Create images from text prompts * ๐Ÿ“ **Text Processing** - Summarization, translation, content generation * ๐ŸŽฅ **Video Processing** - Transcription, editing, generation * ๐Ÿ—ฃ๏ธ **Voice AI** - Text-to-speech, speech-to-text * ๐Ÿ” **Content Moderation** - Auto-moderate user content * ๐Ÿ’ฌ **Chatbots** - AI-powered customer support * ๐Ÿ“Š **Data Analysis** - Extract insights from data * ๐ŸŽฏ **Recommendations** - Personalized suggestions ## Two Approaches ๐Ÿš€ ### 1. Inngest Step Functions โšก Built into Indie Kit! Perfect for simple AI tasks. **Pros:** * โœ… Already configured * โœ… Automatic retries * โœ… Step-by-step tracking * โœ… Easy debugging * โœ… Free tier available **Use for:** * Single AI API calls * Sequential processing * Retry-critical operations [Learn more โ†’](/ai/pipeline) ### 2. n8n Workflows ๐Ÿ”— Visual workflow builder with 400+ integrations. **Pros:** * โœ… Visual editor * โœ… 400+ integrations * โœ… Complex logic support * โœ… No-code friendly * โœ… Self-hostable **Use for:** * Multi-service workflows * Complex conditional logic * Data transformations * Team collaboration [Learn more โ†’](/ai/n8n) Simple AI task? Use Inngest. Complex workflow with multiple services? Use n8n. Both work great! ## Quick Start Examples ๐Ÿ’ก ### Image Generation ```ts // Trigger AI image generation await inngest.send({ name: "ai/image.generate", data: { prompt: "A sunset over mountains", userId: user.id, }, }) ``` ### Text Summarization ```ts // Trigger text summarization await inngest.send({ name: "ai/text.summarize", data: { text: longDocument, userId: user.id, }, }) ``` ### Content Moderation ```ts // Check content with AI await inngest.send({ name: "ai/moderate.content", data: { content: userPost, contentId: post.id, }, }) ``` ## Monetize with Credits ๐Ÿ’ฐ Charge users based on AI usage: ```ts // Deduct credits before AI processing await step.run("deduct-credits", async () => { await deductCredits(userId, "ai_credits", 1) }) // Then process await step.run("generate", async () => { return await generateWithAI(prompt) }) ``` [Set up usage-based pricing โ†’](/payments/credits-system) ## Best Practices โœ… **1. Always Process in Background** * Never block API responses waiting for AI * Show "processing" status immediately * Poll or webhook for results **2. Handle Failures Gracefully** * AI APIs can fail or timeout * Implement retries (automatic with Inngest) * Provide fallback responses **3. Manage Costs** * Track AI API usage * Set user quotas * Monitor spending * Use credits system **4. Show Progress** * Loading states * Progress indicators * Status updates * Real-time notifications ## Popular AI APIs ๐ŸŒŸ **Text & Chat:** * OpenAI GPT-4 * Anthropic Claude * Google Gemini * Mistral AI **Image Generation:** * OpenAI DALL-E * Stability AI * Midjourney API * Replicate **Voice:** * ElevenLabs * OpenAI Whisper * Google Text-to-Speech **Moderation:** * OpenAI Moderation * Google SafeSearch * AWS Rekognition ## Next Steps ๐Ÿš€ 1. **Choose your approach** - Inngest or n8n 2. **Set up background jobs** - [Configure Inngest](/setup/background-jobs) 3. **Get AI API keys** - OpenAI, Anthropic, etc. 4. **Build your first feature** - Start with image generation 5. **Add credits** - [Monetize with usage-based pricing](/payments/credits-system) You have everything you need to build AI-powered features! Start simple and expand from there. ๐Ÿค–โœจ ## Related Documentation ๐Ÿ“š * [AI Pipeline](/ai/pipeline) - Background processing patterns * [n8n Integration](/ai/n8n) - Complex workflows * [Background Jobs](/setup/background-jobs) - Inngest setup * [Credits System](/payments/credits-system) - Usage-based pricing # n8n Integration (/ai/n8n) # n8n Integration ๐Ÿ”— Build complex AI workflows with [n8n](https://n8n.io) - a powerful workflow automation platform with 400+ integrations! Perfect for multi-step AI pipelines. โœจ n8n is a visual workflow automation tool. Drag-and-drop to build workflows that connect APIs, AI services, databases, and more! ## Integration Flow ๐Ÿ”„ Here's how Indie Kit communicates with n8n: >IndieKit: Submit AI request IndieKit->>n8n: POST to webhook (async) IndieKit-->>User: Return "processing" status n8n->>AI Service: Process with AI AI Service-->>n8n: Return result n8n->>IndieKit: POST to callback URL IndieKit->>Database: Save result IndieKit->>User: Notify completion User->>IndieKit: Fetch result IndieKit->>Database: Get result Database-->>IndieKit: Return data IndieKit-->>User: Show result `} /> **Key points:** * ๐Ÿš€ **Async processing** - User doesn't wait for AI * ๐Ÿ”„ **Callback pattern** - n8n sends results back * ๐Ÿ’พ **Database storage** - Results saved for later * ๐Ÿ”” **User notification** - Alert when done ## Why Use n8n with Indie Kit? ๐Ÿค” **Perfect for:** * ๐Ÿ”„ **Multi-step AI pipelines** - Chain multiple AI services * ๐ŸŒ **External integrations** - Connect to 400+ services * ๐ŸŽจ **Visual workflow design** - No-code/low-code workflows * ๐Ÿ”€ **Complex logic** - Conditional branching, loops, error handling * ๐Ÿ“Š **Data transformation** - Advanced data manipulation **Example workflows:** * User uploads image โ†’ Analyze with AI โ†’ Moderate content โ†’ Save results * New user signs up โ†’ Enrich data โ†’ Add to CRM โ†’ Send welcome email * Document uploaded โ†’ Extract text โ†’ Summarize โ†’ Translate โ†’ Email results ## Setup n8n ๐Ÿš€ ### Option 1: n8n Cloud (Recommended) 1. Sign up at [n8n.cloud](https://n8n.cloud) 2. Create a new workflow 3. Get your webhook URL 4. Connect to Indie Kit (see below) ### Option 2: Self-Hosted ```bash # Using Docker docker run -it --rm \ --name n8n \ -p 5678:5678 \ -v ~/.n8n:/home/node/.n8n \ n8nio/n8n ``` Visit `http://localhost:5678` to access n8n editor. n8n Cloud is easier to start with. Self-hosting gives you full control and is free (open source)! ## Integrate with Indie Kit ๐Ÿ”Œ ### Step 1: Create n8n Workflow 1. Open n8n editor 2. Add **Webhook** node as trigger 3. Set to **POST** method 4. Copy the webhook URL 5. Add your processing nodes (AI, transformations, etc.) 6. Add **Respond to Webhook** node at the end Use **"Respond to Webhook"** node to send results back to Indie Kit! This enables async communication. ### Step 2: Send Data from Indie Kit ```ts // src/app/api/app/ai/process/route.ts import withAuthRequired from '@/lib/auth/withAuthRequired' import { NextResponse } from 'next/server' export const POST = withAuthRequired(async (req, context) => { const { data, action } = await req.json() const userId = context.session.user.id // Send to n8n webhook const response = await fetch(process.env.N8N_WEBHOOK_URL!, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action, data, userId, callbackUrl: `${process.env.NEXT_PUBLIC_APP_URL}/api/n8n/callback`, }), }) const result = await response.json() return NextResponse.json({ status: 'processing', jobId: result.jobId, }) }) ``` ### Step 3: Receive Results (Async) Create callback endpoint to receive n8n results: ```ts // src/app/api/n8n/callback/route.ts import { NextResponse } from 'next/server' export async function POST(req: Request) { const body = await req.json() const { jobId, userId, result, status } = body // Verify request (optional but recommended) const signature = req.headers.get('x-n8n-signature') if (signature !== process.env.N8N_WEBHOOK_SECRET) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } // Save results to database await saveAIResult({ userId, jobId, result, status, }) // Notify user (optional) await notifyUser(userId, { message: 'Your AI task is complete!', result, }) return NextResponse.json({ success: true }) } ``` Send request โ†’ n8n processes โ†’ n8n calls back โ†’ Show results. User doesn't wait for AI processing! ### Step 4: Add Environment Variables ```bash # .env.local N8N_WEBHOOK_URL="https://your-n8n-instance.app.n8n.cloud/webhook/your-webhook-id" N8N_WEBHOOK_SECRET="your-secret-key" ``` ## Example n8n Workflow ๐ŸŽจ ### AI Image Moderation Pipeline **n8n workflow nodes:** 1. **Webhook** - Receive image URL from Indie Kit 2. **HTTP Request** - Download image 3. **OpenAI** - Analyze image for inappropriate content 4. **IF** - Check moderation score 5. **Set** - Prepare response data 6. **Respond to Webhook** - Send results back **Indie Kit integration:** ```ts // Trigger moderation const response = await fetch(process.env.N8N_WEBHOOK_URL!, { method: 'POST', body: JSON.stringify({ imageUrl: uploadedImage.url, userId: user.id, callbackUrl: '/api/n8n/moderation-result', }), }) ``` ## Common Patterns ๐Ÿ’ก ### Pattern 1: Fire and Forget ```ts // Just trigger, don't wait for response await fetch(N8N_WEBHOOK_URL, { method: 'POST', body: JSON.stringify({ data }), }) return Response.json({ status: 'queued' }) ``` ### Pattern 2: Async with Callback ```ts // Send job, n8n calls back when done await fetch(N8N_WEBHOOK_URL, { method: 'POST', body: JSON.stringify({ data, callbackUrl: '/api/n8n/callback', }), }) return Response.json({ status: 'processing' }) ``` ### Pattern 3: Poll for Status ```ts // Client polls API for job status const pollStatus = async (jobId: string) => { const res = await fetch(`/api/ai/status/${jobId}`) const { status, result } = await res.json() if (status === 'completed') return result if (status === 'failed') throw new Error('Job failed') await new Promise(r => setTimeout(r, 2000)) return pollStatus(jobId) // Retry } ``` ## Best Practices โœ… **1. Use Webhooks for Communication** ```ts // โœ… Good - Webhook communication await fetch(N8N_WEBHOOK_URL, { ... }) // โŒ Bad - Trying to call n8n API directly await n8nClient.execute() // Complex, not recommended ``` **2. Always Include Callback URL** ```ts // โœ… Good - n8n can send results back { data: yourData, callbackUrl: '/api/n8n/callback', userId: user.id, } // โŒ Bad - No way to get results { data: yourData } ``` **3. Secure Your Webhooks** ```ts // โœ… Good - Verify webhook signature const signature = req.headers.get('x-n8n-signature') if (signature !== process.env.N8N_WEBHOOK_SECRET) { return Response.json({ error: 'Unauthorized' }, { status: 401 }) } // โŒ Bad - No verification ``` **4. Handle Errors** ```ts // โœ… Good - Try/catch and fallback try { const result = await fetch(N8N_WEBHOOK_URL, { ... }) if (!result.ok) throw new Error('n8n error') } catch (error) { // Fallback or notify user await notifyError(error) } ``` ## Learn More About n8n ๐Ÿ“š n8n has extensive documentation and resources: * ๐Ÿ“– [n8n Documentation](https://docs.n8n.io) - Complete guide * ๐ŸŽ“ [n8n Courses](https://docs.n8n.io/courses) - Free video courses * ๐Ÿ”— [Integrations](https://n8n.io/integrations) - Browse 400+ integrations * ๐Ÿ’ก [Templates](https://n8n.io/workflows) - Pre-built workflows * ๐Ÿ’ฌ [n8n Forum](https://community.n8n.io) - Community support n8n + Indie Kit = Build complex automation with AI! Perfect for building AI-powered SaaS products. ## Troubleshooting ๐Ÿ”ง **n8n webhook not responding?** * Check n8n workflow is activated * Verify webhook URL is correct * Test webhook in n8n editor first * Check n8n execution logs **Callback not received?** * Verify callback URL is publicly accessible * Check "Respond to Webhook" node is connected * Look at n8n execution history * Check for network/firewall issues **Webhook timing out?** * n8n workflows have timeout limits * Break long workflows into steps * Use async pattern with callbacks * Consider Inngest for very long tasks ## Next Steps ๐Ÿš€ 1. **Set up n8n** - Cloud or self-hosted 2. **Create test workflow** - Simple hello world 3. **Integrate with Indie Kit** - Send webhook from API 4. **Build AI pipeline** - Add AI processing nodes 5. **Go to production** - Monitor and optimize You can now build complex AI workflows with n8n! Start with a simple workflow and expand from there. ๐Ÿ”— ## Related Documentation ๐Ÿ“š * [AI Pipeline](/ai/pipeline) - Overview of AI processing * [Background Jobs](/setup/background-jobs) - Inngest alternative * [API Calls](/customisation/api-calls) - Working with webhooks * [n8n Documentation](https://docs.n8n.io) - Complete n8n guide # AI Pipeline (/ai/pipeline) # AI Pipeline ๐Ÿค– Build powerful AI features with background processing! Use Inngest or n8n to handle AI tasks asynchronously for better performance. โœจ AI tasks like image generation, video processing, or LLM calls can take seconds or minutes. Process them in the background for better UX! ## Choose Your Approach ๐ŸŽฏ Two powerful options for AI processing: ### 1. Inngest Step Functions โšก **Best for:** * ๐ŸŽจ AI image generation * ๐ŸŽฅ Video processing * ๐Ÿ“ Text generation * ๐Ÿ”„ Multi-step AI workflows * ๐Ÿ” Automatic retries **Why Inngest:** * โœ… Built into Indie Kit * โœ… Automatic retries on failures * โœ… Step-by-step progress tracking * โœ… Easy to debug locally * โœ… No external service needed ### 2. n8n Workflows ๐Ÿ”— **Best for:** * ๐Ÿ”„ Complex multi-service workflows * ๐ŸŒ Integration with 400+ external services * ๐ŸŽฏ Visual workflow builder * ๐Ÿ“Š Advanced data transformation * ๐Ÿ”€ Conditional branching **Why n8n:** * โœ… Visual workflow editor * โœ… Massive integration library * โœ… Advanced workflow logic * โœ… Great for non-developers * โœ… Self-hosted or cloud Inngest for simple AI tasks, n8n for complex integrations. They work great together! ## Using Inngest for AI ๐Ÿš€ ### Example: AI Image Generation ```ts // src/lib/inngest/functions/generate-image.ts import { inngest } from "@/lib/inngest/client" import { openai } from "@/lib/ai/openai" export const generateImage = inngest.createFunction( { id: "generate-ai-image" }, { event: "ai/image.generate" }, async ({ event, step }) => { const { prompt, userId } = event.data // Step 1: Deduct credits await step.run("deduct-credits", async () => { await deductCredits(userId, "image_generation", 1) }) // Step 2: Generate image (retries on failure) const imageUrl = await step.run("generate-image", async () => { const response = await openai.images.generate({ prompt, n: 1, size: "1024x1024", }) return response.data[0].url }) // Step 3: Upload to S3 const finalUrl = await step.run("upload-to-s3", async () => { const uploaded = await uploadToS3(imageUrl) return uploaded.url }) // Step 4: Save to database await step.run("save-to-db", async () => { await saveImage({ userId, prompt, url: finalUrl, }) }) return { imageUrl: finalUrl } } ) ``` **Trigger from your API:** ```ts // src/app/api/app/generate-image/route.ts import { inngest } from "@/lib/inngest/client" import withAuthRequired from "@/lib/auth/withAuthRequired" export const POST = withAuthRequired(async (req, context) => { const { prompt } = await req.json() const userId = context.session.user.id // Trigger background job await inngest.send({ name: "ai/image.generate", data: { prompt, userId }, }) return Response.json({ status: "processing", message: "Image generation started!" }) }) ``` Each step retries independently! If S3 upload fails, it won't regenerate the image - just retry the upload. ### Example: AI Text Processing ```ts export const processText = inngest.createFunction( { id: "process-text-with-ai" }, { event: "ai/text.process" }, async ({ event, step }) => { const { text, userId, action } = event.data // Deduct credits await step.run("deduct-credits", async () => { await deductCredits(userId, "ai_processing", 1) }) // Process with AI const result = await step.run("ai-process", async () => { if (action === "summarize") { return await openai.chat.completions.create({ model: "gpt-4", messages: [ { role: "system", content: "Summarize this text:" }, { role: "user", content: text } ], }) } // Other actions... }) // Save result await step.run("save-result", async () => { await saveAIResult({ userId, input: text, output: result.choices[0].message.content, action, }) }) return result } ) ``` ## Using n8n for AI ๐Ÿ”— For complex AI workflows with multiple services, [use n8n integration](/ai/n8n)! **What n8n enables:** * ๐Ÿ”„ Multi-step AI pipelines * ๐ŸŒ Integration with 400+ services * ๐ŸŽจ Visual workflow builder * ๐Ÿ“Š Advanced data transformation * ๐Ÿ”€ Conditional logic and branching [Read the n8n integration guide โ†’](/ai/n8n) ## Best Practices โœ… **1. Always Use Background Jobs** ```tsx // โœ… Good - Background processing await inngest.send({ name: "ai/generate" }) return Response.json({ status: "processing" }) // โŒ Bad - Blocking request const result = await generateAI() // Takes 30 seconds! return Response.json(result) // User waits... ``` **2. Deduct Credits First** ```ts // โœ… Good - Deduct before processing await deductCredits(userId, "ai", 1) await generateImage() // Then process // โŒ Bad - Deduct after (might fail) await generateImage() await deductCredits(userId, "ai", 1) ``` **3. Handle Errors Gracefully** ```ts await step.run("ai-call", async () => { try { return await openai.complete(prompt) } catch (error) { console.error("AI failed:", error) throw error // Triggers retry } }) ``` **4. Show Progress to Users** ```tsx // Poll for status const checkStatus = async () => { const res = await fetch('/api/ai/status/job-id') const { status, result } = await res.json() if (status === 'completed') { showResult(result) } else { setTimeout(checkStatus, 2000) // Check again } } ``` ## Common AI Patterns ๐ŸŽฏ ### Image Generation Pipeline 1. User submits prompt 2. Deduct credits 3. Generate image with AI 4. Upload to S3 5. Save to database 6. Notify user (email or real-time) ### Content Moderation 1. User posts content 2. Send to moderation AI 3. Check for violations 4. Flag or approve automatically 5. Notify moderators if needed ### Summarization Pipeline 1. User uploads document 2. Extract text from PDF/DOCX 3. Chunk into smaller pieces 4. Summarize each chunk 5. Combine summaries 6. Save and return result ## Next Steps ๐Ÿš€ 1. **Choose your tool** - Inngest for simple, n8n for complex 2. **Set up background jobs** - Configure [Inngest](/setup/background-jobs) 3. **Build your first AI feature** - Start with image generation 4. **Add credits system** - [Usage-based pricing](/payments/credits-system) 5. **Monitor & optimize** - Track costs and performance You can now build powerful AI features with background processing! Start simple and expand from there. ๐Ÿค– ## Related Documentation ๐Ÿ“š * [n8n Integration](/ai/n8n) - Complex AI workflows * [Background Jobs](/setup/background-jobs) - Inngest setup * [Credits System](/payments/credits-system) - Usage-based pricing * [Running Scheduled Jobs](/background-jobs/running-scheduled-jobs) - Cron patterns # API Calls (/customisation/api-calls) # Making API Calls ๐Ÿ”Œ Learn how to build powerful features by making API calls in your Indie Kit application! We'll cover public and protected endpoints with practical examples. โœจ ## Understanding API Routes ๐ŸŽฏ Indie Kit uses Next.js App Router API routes. All API endpoints live in the `src/app/api/` directory: ``` src/app/api/ products/route.ts โ† Public endpoint app/user-settings/route.ts โ† Protected endpoint (requires auth) ``` * **Public endpoints**: Anyone can access (e.g., blog posts, products) * **Protected endpoints**: Require authentication (e.g., user settings, admin actions) ## Public API Calls ๐ŸŒ Public endpoints don't require authentication. Use them for data that anyone can view. ### Step 1: Create the API Endpoint Create a new file at `src/app/api/products/route.ts`: ```tsx // src/app/api/products/route.ts import { NextResponse } from 'next/server' export async function GET() { // In a real app, fetch from your database const products = [ { id: 1, name: 'Product 1', price: 99 }, { id: 2, name: 'Product 2', price: 149 }, ] return NextResponse.json(products) } ``` ### Step 2: Fetch Data in Your Component Use SWR to fetch data with automatic caching and revalidation: ```tsx // src/app/products/page.tsx 'use client' import useSWR from 'swr' export default function ProductsPage() { const { data, error, isLoading } = useSWR('/api/products') if (error) return
Failed to load products
if (isLoading) return
Loading...
return (

Products

    {data.map((product) => (
  • {product.name} - ${product.price}
  • ))}
) } ``` SWR (Stale-While-Revalidate) is a data fetching library that comes pre-configured in Indie Kit. It provides automatic caching, revalidation, and loading states out of the box! ## Protected API Calls ๐Ÿ”’ Protected endpoints require authentication. Use them for user-specific data or admin actions. Always use `withAuthRequired` wrapper for protected endpoints. This ensures only authenticated users can access the data! ### Step 1: Create Protected API Endpoint Create a protected endpoint at `src/app/api/app/user-settings/route.ts`: ```tsx // src/app/api/app/user-settings/route.ts import { withAuthRequired } from '@/lib/auth/withAuthRequired' import { NextResponse } from 'next/server' import { db } from '@/db' import { users } from '@/db/schema' import { eq } from 'drizzle-orm' // GET user settings export const GET = withAuthRequired(async (req, context) => { const { session } = context // Fetch settings from database const userSettings = await db .select() .from(users) .where(eq(users.id, session.user.id)) .limit(1) return NextResponse.json({ userId: session.user.id, theme: userSettings[0]?.theme || 'light', notifications: userSettings[0]?.notifications || true, }) }) // POST to update settings export const POST = withAuthRequired(async (req, context) => { const { session } = context const body = await req.json() // Update in database await db .update(users) .set({ theme: body.theme }) .where(eq(users.id, session.user.id)) return NextResponse.json({ success: true }) }) ``` ### Step 2: Fetch and Update Data Use SWR for fetching and `mutate` for optimistic updates: ```tsx // src/app/settings/page.tsx 'use client' import useSWR, { mutate } from 'swr' import { toast } from 'sonner' export default function SettingsPage() { const { data, error, isLoading } = useSWR('/api/app/user-settings') const updateSettings = async (newSettings: { theme: string }) => { try { // Optimistically update the UI mutate( '/api/app/user-settings', { ...data, ...newSettings }, false ) // Make the API call const response = await fetch('/api/app/user-settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(newSettings), }) if (!response.ok) throw new Error('Failed to update') // Revalidate to get fresh data mutate('/api/app/user-settings') toast.success('Settings updated!') } catch (error) { // Rollback on error mutate('/api/app/user-settings') toast.error('Failed to update settings') } } if (error) return
Failed to load settings
if (isLoading) return
Loading...
return (

User Settings

Current theme: {data.theme}

) } ``` ## SWR Features & Patterns ๐ŸŽฏ SWR comes pre-configured in Indie Kit with powerful features: ### Basic Fetching ```tsx const { data, error, isLoading } = useSWR('/api/endpoint') ``` ### Type-Safe Fetching ```tsx interface Product { id: number name: string price: number } const { data, error } = useSWR('/api/products') ``` ### Conditional Fetching Only fetch when certain conditions are met: ```tsx // Only fetch when user is logged in const { data } = useSWR(session ? '/api/app/profile' : null) // Only fetch with valid ID const { data } = useSWR( productId ? `/api/products/${productId}` : null ) ``` ### Revalidation ```tsx import { mutate } from 'swr' // Revalidate specific endpoint mutate('/api/products') // Revalidate all endpoints matching pattern mutate((key) => typeof key === 'string' && key.startsWith('/api/products')) ``` ### Optimistic Updates Update the UI immediately, then sync with server: ```tsx const addProduct = async (newProduct) => { // Update UI optimistically mutate('/api/products', [...data, newProduct], false) // Make API call await fetch('/api/products', { method: 'POST', body: JSON.stringify(newProduct) }) // Revalidate to sync with server mutate('/api/products') } ``` SWR has many more features! Check out the [official documentation](https://swr.vercel.app/) for pagination, infinite loading, and more. ## Common Patterns ๐Ÿ“š ### Loading with Skeleton ```tsx import { Skeleton } from "@/components/ui/skeleton" export default function ProductsPage() { const { data, isLoading } = useSWR('/api/products') if (isLoading) { return (
) } return } ``` ### Error Handling ```tsx export default function ProductsPage() { const { data, error, isLoading } = useSWR('/api/products') if (error) { return (
Failed to load products. Please try again later.
) } if (isLoading) return
Loading...
return } ``` ### Mutation with Error Handling ```tsx const deleteProduct = async (id: number) => { try { // Optimistic update const newData = data.filter(p => p.id !== id) mutate('/api/products', newData, false) // Make API call const response = await fetch(`/api/products/${id}`, { method: 'DELETE' }) if (!response.ok) throw new Error('Delete failed') toast.success('Product deleted!') } catch (error) { // Rollback on error mutate('/api/products') toast.error('Failed to delete product') } } ``` ## HTTP Methods ๐Ÿ”„ Use the appropriate HTTP method for each action: ### GET - Fetch Data ```tsx export async function GET() { const data = await fetchFromDatabase() return NextResponse.json(data) } ``` ### POST - Create New Resource ```tsx export async function POST(req: Request) { const body = await req.json() const newItem = await createInDatabase(body) return NextResponse.json(newItem) } ``` ### PUT - Update Entire Resource ```tsx export async function PUT(req: Request) { const body = await req.json() const updated = await updateInDatabase(body) return NextResponse.json(updated) } ``` ### PATCH - Partial Update ```tsx export async function PATCH(req: Request) { const body = await req.json() const patched = await partialUpdateInDatabase(body) return NextResponse.json(patched) } ``` ### DELETE - Remove Resource ```tsx export async function DELETE(req: Request) { await deleteFromDatabase() return NextResponse.json({ success: true }) } ``` ## Best Practices ๐Ÿ’ก ### 1. Always Handle Loading and Error States ```tsx const { data, error, isLoading } = useSWR('/api/endpoint') if (error) return if (isLoading) return return ``` ### 2. Use TypeScript for Type Safety ```tsx interface User { id: string name: string email: string } const { data } = useSWR('/api/app/profile') // data is now typed as User ``` ### 3. Implement Optimistic Updates Make your app feel instant: ```tsx // Update UI first mutate('/api/todos', [...todos, newTodo], false) // Then make API call await createTodo(newTodo) // Finally revalidate mutate('/api/todos') ``` ### 4. Validate Input Server-Side ```tsx export const POST = withAuthRequired(async (req, context) => { const body = await req.json() // Validate input if (!body.name || body.name.length < 3) { return NextResponse.json( { error: 'Name must be at least 3 characters' }, { status: 400 } ) } // Process valid data // ... }) ``` ### 5. Use Proper Error Status Codes ```tsx // 400 - Bad Request (invalid input) return NextResponse.json({ error: 'Invalid data' }, { status: 400 }) // 401 - Unauthorized (not logged in) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) // 403 - Forbidden (logged in but no permission) return NextResponse.json({ error: 'Forbidden' }, { status: 403 }) // 404 - Not Found return NextResponse.json({ error: 'Not found' }, { status: 404 }) // 500 - Server Error return NextResponse.json({ error: 'Server error' }, { status: 500 }) ``` ## Security Checklist ๐Ÿ” * โœ… Use `withAuthRequired` for protected endpoints * โœ… Validate all input data server-side * โœ… Never trust client-side data * โœ… Use proper HTTP methods * โœ… Return appropriate error codes * โœ… Don't expose sensitive data in error messages * โœ… Rate limit API endpoints if needed * โœ… Use HTTPS in production ## Troubleshooting ๐Ÿ”ง ### "useSWR is not defined" Make sure your component has `'use client'` at the top: ```tsx 'use client' import useSWR from 'swr' ``` ### "401 Unauthorized" on Protected Routes Ensure you're logged in and using `withAuthRequired`: ```tsx export const GET = withAuthRequired(async (req, context) => { // Your code here }) ``` ### Data Not Updating Force revalidation after mutations: ```tsx await updateData() mutate('/api/endpoint') // Add this ``` ### CORS Errors Next.js API routes don't have CORS issues since they're same-origin. If calling external APIs, handle CORS server-side. ## Next Steps ๐Ÿš€ Now you know how to: * โœ… Create public and protected API endpoints * โœ… Fetch data with SWR * โœ… Handle loading and error states * โœ… Implement optimistic updates * โœ… Use proper HTTP methods * โœ… Secure your endpoints You're ready to build powerful features with API calls! Start with a simple endpoint and expand from there. ๐ŸŽ‰ ## Related Documentation ๐Ÿ“š * [Protected Pages](/customisation/private-page) - Authentication for pages * [Pre-installed Components](/customisation/installed-components) - UI components * [SWR Documentation](https://swr.vercel.app/) - Data fetching * [Next.js API Routes](https://nextjs.org/docs/app/building-your-application/routing/route-handlers) # Community Components (/customisation/components) # Access 600+ Components ๐ŸŽจ Indie Kit is built on [shadcn/ui](https://ui.shadcn.com/), giving you access to a massive ecosystem of beautiful, customizable components! โœจ ## What is shadcn/ui? ๐Ÿค” shadcn/ui is not a traditional component library. Instead of installing packages, you **copy the code directly into your project**. This means: * โœ… **Full Control**: Components live in your codebase - modify them however you want * โœ… **No Dependencies**: No bloated node\_modules from component libraries * โœ… **Easy Customization**: Change styles, behavior, or structure as needed * โœ… **Type Safe**: Built with TypeScript for better developer experience * โœ… **Accessible**: All components follow accessibility best practices Indie Kit already includes many components out of the box! Check out the [Pre-installed Components](/customisation/installed-components) to see what's available. ## Community Components ๐Ÿš€ Beyond the official shadcn/ui components, there's a thriving community creating amazing additions! ### 21st.dev - 600+ Premium Components Visit [21st.dev](https://21st.dev/) to explore: * ๐ŸŽจ **UI Elements**: Buttons, cards, modals, dropdowns * ๐Ÿ“ฑ **Mobile Components**: Touch-optimized mobile interfaces * ๐Ÿ–ฅ๏ธ **Dashboard Layouts**: Admin panels and analytics views * ๐ŸŽฏ **Marketing Sections**: Hero sections, features, pricing tables * ๐Ÿ“ˆ **Charts & Graphs**: Data visualization components * ๐Ÿ”’ **Authentication**: Login forms, signup flows, OAuth buttons * ๐Ÿ’ณ **Payment Forms**: Stripe-ready checkout components * ๐Ÿ“Š **Tables**: Advanced data tables with sorting and filtering * ๐ŸŽญ **Animations**: Beautiful transitions and effects * ๐Ÿ“ **Forms**: Complex form layouts and validation ## How to Install Components ๐Ÿ’ก ### Step 1: Find a Component 1. Browse [shadcn/ui](https://ui.shadcn.com/) or [21st.dev](https://21st.dev/) 2. Find the component you want to use 3. Click on it to see the installation command ### Step 2: Install with pnpm Most components will show an `npx` command, but since Indie Kit uses **pnpm**, use this instead: ```bash pnpm dlx shadcn@latest add [component-name] ``` **Examples:** ```bash # Add a calendar component pnpm dlx shadcn@latest add calendar # Add a data table pnpm dlx shadcn@latest add table # Add a chart pnpm dlx shadcn@latest add chart ``` Indie Kit uses **pnpm** for package management. Always use `pnpm dlx` instead of `npx` when installing shadcn components. ### Step 3: Import and Use After installation, import the component in your page or component: ```tsx import { Calendar } from "@/components/ui/calendar"; export default function MyPage() { return (
); } ``` ## Practical Examples ๐Ÿ“ ### Adding a Date Picker ```bash # Install calendar component pnpm dlx shadcn@latest add calendar ``` ```tsx "use client"; import { Calendar } from "@/components/ui/calendar"; import { useState } from "react"; export default function BookingPage() { const [date, setDate] = useState(new Date()); return (
); } ``` ### Adding a Data Table ```bash # Install table component pnpm dlx shadcn@latest add table ``` ```tsx import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; export default function UsersPage() { return ( Name Email Status John Doe john@example.com Active
); } ``` ### Adding Charts ```bash # Install chart component pnpm dlx shadcn@latest add chart ``` ```tsx import { Bar, BarChart } from "recharts"; import { ChartContainer } from "@/components/ui/chart"; const data = [ { month: "Jan", revenue: 4000 }, { month: "Feb", revenue: 3000 }, { month: "Mar", revenue: 5000 }, ]; export default function AnalyticsPage() { return ( ); } ``` ## Where Components Are Installed ๐Ÿ“ When you run the install command, components are added to: ``` src/ components/ ui/ calendar.tsx โ† Your new component table.tsx chart.tsx ... ``` All components are added to your project, not as npm packages. This means you can modify them freely to match your exact needs! ## Customizing Components ๐ŸŽจ Since the code is in your project, you can customize anything: ### Change Colors ```tsx // Edit src/components/ui/button.tsx ``` ### Modify Behavior ```tsx // Add custom logic const handleClick = () => { // Your custom behavior console.log("Button clicked!"); }; ``` ### Add Props ```tsx // Extend component props interface CustomButtonProps extends ButtonProps { icon?: React.ReactNode; } ``` ## Best Practices ๐Ÿ’ก 1. **Start with Pre-installed Components**: Check what Indie Kit already includes before adding new ones 2. **Install Only What You Need**: Don't bloat your codebase with unused components 3. **Customize After Installation**: Modify components to match your brand 4. **Keep Components Organized**: Group related components in logical folders 5. **Document Custom Changes**: Add comments if you heavily modify a component ## Troubleshooting ๐Ÿ”ง **"Command not found"** * Make sure you have pnpm installed: `npm install -g pnpm` * Use `pnpm dlx` instead of `npx` **"Component not working"** * Check if all dependencies are installed: `pnpm install` * Verify imports are correct * Make sure Tailwind CSS is configured properly **"Styling looks wrong"** * Check your `tailwind.config.ts` includes the component paths * Verify your `globals.css` has the required CSS variables * Try clearing your build cache: `pnpm run build` With 600+ community components plus the ability to customize everything, you can build any UI you imagine! ๐Ÿš€ ## Related Documentation ๐Ÿ“š * [shadcn/ui Official Docs](https://ui.shadcn.com/) - Browse all official components * [21st.dev Component Library](https://21st.dev/) - 600+ premium components * [Pre-installed Components](/customisation/installed-components) - What's included * [Theme Customization](/customisation/theme-guide) - Style your components # Pre-installed Components (/customisation/installed-components) import { ImageZoom } from 'fumadocs-ui/components/image-zoom'; import { Cards, Card } from 'fumadocs-ui/components/card'; # Pre-installed Components ๐ŸŽจ Indie Kit comes with beautiful, production-ready components that you can use right away! No installation needed - just import and use. โœจ All these components are already installed in your Indie Kit project. Simply import them and start building! ## What's Included? ๐Ÿ“ฆ Your Indie Kit installation includes professionally designed components for: * ๐Ÿš€ **Hero Sections** - Attention-grabbing landing page headers * ๐Ÿ“ข **Call to Actions** - Conversion-focused CTAs * ๐Ÿ’ซ **Feature Displays** - Showcase your product features * ๐Ÿ’ฌ **Testimonials** - Build trust with social proof * ๐Ÿ‘‡ **Footer** - Clean, minimal footer design ## Hero Sections ๐Ÿš€ Hero sections are the first thing visitors see on your website. Choose from these options: `import Hero1 from "@/components/sections/hero-1";` `import Hero2 from "@/components/sections/hero-2";` ## Call to Action (CTA) ๐Ÿ“ข Effective CTAs to drive user engagement: `import CTA1 from "@/components/website/cta-1";` `import { CTA2 } from "@/components/website/cta-2";` ## Feature Displays ๐Ÿ’ซ Showcase your product features: `import FeatureGrid from "@/components/sections/feature-grid";` `import FeatureAccordion from "@/components/sections/feature-accordion";` ## Testimonials ๐Ÿ’ฌ Display social proof with these testimonial layouts: `import TestimonialGrid from "@/components/sections/testimonial-grid";` `import Testimonial1 from "@/components/sections/testimonial-1";` `import Testimonial2 from "@/components/sections/testimonial-2";` ## Footer ๐Ÿ‘‡ Minimal footer design for a clean look: ## How to Use Components ๐Ÿ“ Using these components is simple! Here's a quick example: ### Basic Usage ```tsx import Hero1 from "@/components/sections/hero-1"; export default function HomePage() { return (
); } ``` ### Customizing Components All components accept props for customization: ```tsx import TestimonialGrid from "@/components/sections/testimonial-grid"; export default function AboutPage() { const testimonials = [ { name: "John Doe", role: "CEO at Company", content: "This product is amazing!", avatar: "/images/john.jpg" }, // ... more testimonials ]; return ( ); } ``` ### Combining Components Build complete pages by combining multiple components: ```tsx import Hero1 from "@/components/sections/hero-1"; import FeatureGrid from "@/components/sections/feature-grid"; import TestimonialGrid from "@/components/sections/testimonial-grid"; import CTA1 from "@/components/website/cta-1"; export default function LandingPage() { return (
); } ``` Check each component's file to see available props and customization options. All components are fully typed with TypeScript! ## Component Features โœจ All pre-installed components include: * โœ… **Tailwind CSS** - Easy to customize with utility classes * โœ… **Fully Responsive** - Mobile, tablet, and desktop optimized * โœ… **Dark Mode** - Automatically adapts to your theme * โœ… **TypeScript** - Full type safety and autocomplete * โœ… **Accessible** - Built following ARIA best practices * โœ… **Customizable** - Pass data and customize behavior * โœ… **Production Ready** - Tested and optimized ## Customization Guide ๐ŸŽจ ### Modify Styles Since components use Tailwind CSS, you can easily override styles: ```tsx ``` ### Edit Component Code Want deeper customization? Edit the component files directly: ``` src/ components/ sections/ hero-1.tsx โ† Edit this file feature-grid.tsx testimonial-grid.tsx website/ cta-1.tsx cta-2.tsx ``` ### Create Variants Copy a component and create your own variant: ```bash # Copy an existing component cp src/components/sections/hero-1.tsx src/components/sections/hero-custom.tsx ``` Then modify `hero-custom.tsx` to match your needs! ## Best Practices ๐Ÿ’ก 1. **Start with Pre-built Components**: Use what's included before building custom ones 2. **Customize Gradually**: Start with props, then modify styles, then edit code if needed 3. **Keep Components Reusable**: Extract repeated patterns into new components 4. **Use TypeScript**: Take advantage of type safety for props 5. **Test Responsiveness**: Always check mobile, tablet, and desktop views 6. **Follow Naming Conventions**: Keep your custom components consistently named ## Component Locations ๐Ÿ“ All pre-installed components live in: ``` src/ components/ sections/ โ† Landing page sections hero-1.tsx hero-2.tsx feature-grid.tsx feature-accordion.tsx testimonial-grid.tsx testimonial-1.tsx testimonial-2.tsx website/ โ† Website components cta-1.tsx cta-2.tsx minimal-footer.tsx ``` ## Need More Components? ๐ŸŽ Want access to 600+ additional components? ๐Ÿš€ Access 600+ shadcn/ui Components These components cover everything from charts and data tables to advanced forms and animations! ## Next Steps ๐Ÿš€ Now that you know what components are available: 1. โœ… Browse through the components above 2. โœ… Copy the import statements and use them in your pages 3. โœ… Customize them to match your brand 4. โœ… Combine multiple components to build complete pages 5. โœ… Check out [Community Components](/customisation/components) for even more options You have everything you need to build beautiful pages! Just import, customize, and ship! ๐ŸŽจ ## Related Documentation ๐Ÿ“š * [Community Components](/customisation/components) - 600+ more components * [Theme Customization](/customisation/theme-guide) - Customize colors * [Static Pages](/customisation/static-page) - Build landing pages # Protected Pages (/customisation/private-page) YouTube Video: https://www.youtube.com/watch?v=sswHDbkGXQc Protect your pages and API routes with built-in authentication! Multiple approaches for different scenarios. ๐Ÿ›ก๏ธ Routes in `src/app/(in-app)/app/*` and `src/app/api/app/*` are automatically protected! No extra auth code needed for these paths. ## Three Protection Methods ๐ŸŽฏ Choose the right approach for your use case: | Method | Use For | Where | | ----------------- | ---------------------------- | ----------------- | | Server Components | Initial page load protection | Server components | | Client Components | Dynamic UI with auth state | Client components | | API Routes | Endpoint protection | API routes | ## Server-Side Protection ๐Ÿ–ฅ๏ธ Use the `auth` helper for server components: ```tsx // src/app/dashboard/page.tsx import { auth } from '@/auth' import { forbidden } from 'next/navigation' export default async function DashboardPage() { const session = await auth() if (!session) { forbidden() // Returns 403 } return (

Welcome back, {session.user.name}! ๐Ÿ‘‹

{/* Protected content */}
) } ``` Add `authInterrupts: true` to `experimental` in `next.config.js` to use `forbidden()`. **When to use:** * โœ… Initial page load protection * โœ… SEO-important authenticated pages * โœ… Static user data display * โœ… Better performance (no client hydration) ## Client-Side Protection ๐Ÿ’ป Use the `useUser` hook for dynamic client components: ```tsx 'use client' import useUser from '@/lib/users/useUser' import { redirect } from 'next/navigation' import { Skeleton } from '@/components/ui/skeleton' export default function ProfilePage() { const { user, isLoading } = useUser() if (isLoading) { return (
) } if (!user) { redirect('/login') } return (

Your Profile โœจ

Name: {user.name}

Email: {user.email}

{/* More profile content */}
) } ``` **When to use:** * โœ… Dynamic user interfaces * โœ… Real-time data updates * โœ… Interactive features * โœ… Client-side state management Always show a loading state to prevent flash of unauthenticated content. Use skeleton loaders for better UX! ## API Route Protection ๐Ÿ”Œ Protect API endpoints with `withAuthRequired` middleware: ```ts // src/app/api/app/profile/route.ts import withAuthRequired from '@/lib/auth/withAuthRequired' import { NextResponse } from 'next/server' export const GET = withAuthRequired(async (req, context) => { const { session } = context // Protected API logic const userData = { id: session.user.id, name: session.user.name, email: session.user.email, } return NextResponse.json(userData) }) export const PATCH = withAuthRequired(async (req, context) => { const { session } = context const body = await req.json() // Update user data await updateUser(session.user.id, body) return NextResponse.json({ success: true }) }) ``` **Benefits:** * โœ… Automatic session validation * โœ… User data in context * โœ… Type-safe session object * โœ… Returns 401 if not authenticated Never skip authentication on API routes that access user data! Client-side protection alone is not enough. ## Best Practices โœ… **1. Double Protection** ```tsx // โœ… Good - Protected on both client and server 'use client' const { user } = useUser() if (!user) redirect('/login') // API route also protected export const GET = withAuthRequired(...) // โŒ Bad - Only client-side check 'use client' const { user } = useUser() if (!user) redirect('/login') // API route unprotected โš ๏ธ ``` **2. Loading States** ```tsx // โœ… Good - Smooth loading if (isLoading) return if (!user) redirect('/login') // โŒ Bad - Flash of content if (!user) redirect('/login') ``` **3. Error Handling** ```tsx // โœ… Good - Clear error messages if (!session) { return NextResponse.json( { error: 'Please sign in to continue' }, { status: 401 } ) } // โŒ Bad - Generic errors if (!session) { return NextResponse.json({ error: 'Error' }, { status: 401 }) } ``` **4. Redirect Appropriately** ```tsx // โœ… Good - Redirect to login if (!user) redirect('/login') // โŒ Bad - Redirect to home (confusing) if (!user) redirect('/') ``` ## Quick Reference ๐Ÿ“‹ ### Server Component ```tsx import { auth } from '@/auth' const session = await auth() if (!session) forbidden() ``` ### Client Component ```tsx 'use client' import useUser from '@/lib/users/useUser' const { user, isLoading } = useUser() if (!user) redirect('/login') ``` ### API Route ```tsx import withAuthRequired from '@/lib/auth/withAuthRequired' export const GET = withAuthRequired(async (req, context) => { const { session } = context // Your logic }) ``` You can now secure any page or API route! Always protect sensitive data on both client and server. ๐Ÿ”’ ## Related Documentation ๐Ÿ“š * [Password Authentication](/setup/auth/password-login) - Enable password login * [Authentication Setup](/setup/auth) - Configure auth providers * [API Calls](/customisation/api-calls) - Working with protected APIs # Static Page (/customisation/static-page) # Creating Static Pages ๐Ÿš€ Build SEO-optimized static pages with proper metadata, Open Graph tags, and structured data! Perfect for landing pages, about pages, and marketing content. ๐Ÿš€ Indie Kit includes automatic SEO optimization! Just add metadata and your page is search engine ready. ## Create Your Page ๐ŸŽจ ### Step 1: Create Page File Create a new file in `src/app/` directory, for example `src/app/events/join-us/page.tsx`: ```tsx import { Metadata } from 'next' import { WebPageJsonLd } from 'next-seo' import { appConfig } from '@/lib/config' // SEO metadata export const metadata: Metadata = { title: 'Join Us This Weekend - Special Event', description: 'Join our community for an unforgettable weekend of learning and networking!', openGraph: { title: 'Join Us This Weekend - Special Event', description: 'Join our community for an unforgettable weekend of learning and networking!', url: `${process.env.NEXT_PUBLIC_APP_URL}/events/join-us`, siteName: appConfig.projectName, type: 'website', images: ['/og-event.png'], }, twitter: { card: 'summary_large_image', title: 'Join Us This Weekend', description: 'Join our community event!', images: ['/og-event.png'], }, } export default function JoinUsPage() { return ( <> {/* JSON-LD structured data for SEO */} {/* Page content */}

Join Us This Weekend! ๐ŸŽ‰

Connect with amazing people and create unforgettable memories! โœจ

) } ``` ### Step 2: Add Pre-built Components Combine pre-installed components for beautiful pages: ```tsx import Hero1 from "@/components/sections/hero-1" import FeatureGrid from "@/components/sections/feature-grid" import TestimonialGrid from "@/components/sections/testimonial-grid" import CTA1 from "@/components/website/cta-1" export default function LandingPage() { return ( <> ) } ``` All [pre-installed components](/customisation/installed-components) work together seamlessly! Build your perfect landing page in minutes. ## SEO Features Built-In โšก Indie Kit automatically handles: * ๐Ÿ“ **Metadata** - Title, description, keywords * ๐Ÿ–ผ๏ธ **Open Graph** - Social media previews * ๐Ÿฆ **Twitter Cards** - Twitter-specific previews * ๐Ÿ” **JSON-LD** - Structured data for search engines * ๐Ÿ—บ๏ธ **Sitemap** - Auto-generated from pages * ๐Ÿค– **Robots.txt** - Search engine instructions ### Metadata Configuration ```tsx export const metadata: Metadata = { title: 'Your Page Title', description: 'Compelling description under 160 characters', keywords: ['saas', 'indie', 'startup'], openGraph: { title: 'Your Page Title', description: 'Same or different description', images: ['/og-image.png'], url: 'https://yourapp.com/page', }, twitter: { card: 'summary_large_image', title: 'Your Page Title', description: 'Twitter-specific description', images: ['/twitter-og.png'], }, } ``` ### JSON-LD Structured Data ```tsx ``` JSON-LD helps search engines understand your content. Include it on important pages for better SEO! ## Best Practices โœ… **1. Descriptive Metadata** ```tsx // โœ… Good - Specific and compelling title: "Ship Your SaaS in Days with Indie Kit" description: "Production-ready Next.js boilerplate with auth, payments, and more. Launch your startup faster." // โŒ Bad - Generic title: "Home" description: "Welcome to our website" ``` **2. Optimize Images** ```tsx import Image from 'next/image' // โœ… Good - Next.js Image with alt Dashboard preview showing analytics // โŒ Bad - Regular img without alt ``` **3. Semantic HTML** ```tsx // โœ… Good - Proper structure

Main Heading

Content
// โŒ Bad - All divs
Heading
Content
``` **4. Mobile Responsive** ```tsx // โœ… Good - Responsive classes

Responsive Heading

// โŒ Bad - Fixed size

Too Big on Mobile

``` ## Page Templates ๐Ÿ“ ### Marketing Landing Page ```tsx import { Metadata } from 'next' import Hero2 from "@/components/sections/hero-2" import FeatureGrid from "@/components/sections/feature-grid" import CTA1 from "@/components/website/cta-1" export const metadata: Metadata = { title: 'Product Name - Solve X Problem', description: 'Help customers achieve Y goal in Z time', } export default function ProductPage() { return ( <> ) } ``` ### About Page ```tsx export const metadata: Metadata = { title: 'About Us - Our Mission', description: 'Learn about our story and what drives us', } export default function AboutPage() { return (

About Us

{/* Your content */}
) } ``` ### Pricing Page ```tsx import MonthlyAnnualPricing from "@/components/website/monthly-annual-pricing" export const metadata: Metadata = { title: 'Pricing - Simple, Transparent Plans', description: 'Choose the perfect plan for your needs. Start free!', } export default function PricingPage() { return (

Simple Pricing

Choose the perfect plan for your needs

) } ``` ## SEO Checklist ๐Ÿ“‹ Before launching your page: * โœ… **Unique title** - Different from other pages * โœ… **Meta description** - 150-160 characters * โœ… **Open Graph image** - 1200x630px recommended * โœ… **JSON-LD data** - For rich search results * โœ… **Alt text on images** - Describe every image * โœ… **Semantic HTML** - Use proper tags (h1, main, section) * โœ… **Mobile responsive** - Test on all devices * โœ… **Fast loading** - Optimize images and code * โœ… **Internal links** - Link to related pages * โœ… **External links** - Add `rel="noopener"` for security Use [Google's Rich Results Test](https://search.google.com/test/rich-results) to verify your structured data works correctly! ## Next Steps ๐Ÿš€ 1. **Create your pages** with proper metadata 2. **Use pre-built components** for quick assembly 3. **Add structured data** for better SEO 4. **Test on mobile** to ensure responsiveness 5. **Check search preview** using SEO tools Your static pages are now search engine ready! Build more pages and watch your traffic grow. ๐Ÿ“ˆ ## Related Documentation ๐Ÿ“š * [Pre-installed Components](/customisation/installed-components) - Available components * [SEO Guide](/seo/seo) - Complete SEO documentation * [Google Search Console](/seo/google-search-console) - Track search performance # Theme Guide (/customisation/theme-guide) YouTube Video: https://www.youtube.com/watch?v=5lBSrSPhjw8 import { Cards, Card } from 'fumadocs-ui/components/card'; Customize your app's look in minutes! Use visual theme builders to create beautiful color schemes with automatic dark mode. ๐ŸŽจ Indie Kit uses CSS variables for theming. Change a few values and your entire app updates instantly! ## Visual Theme Builders ๐ŸŽจ Pick your favorite tool to design your theme: **Recommended** - Most powerful theme editor Alternative with simple interface **Features:** * ๐ŸŽจ **1000+ color combinations** - Explore pre-made palettes * ๐ŸŒ™ **Automatic dark mode** - Light and dark variants generated * ๐ŸŽฏ **Real-time preview** - See changes instantly * ๐Ÿ“ฑ **Accessible colors** - Ensures proper contrast * ๐Ÿš€ **One-click export** - Copy CSS and paste Don't start from scratch! Pick a pre-made palette close to your brand, then tweak it to perfection. ## Apply Your Theme โšก ### Step 1: Generate Theme 1. Open [TweakCN](https://tweakcn.com/editor/theme) 2. Pick a base color for your brand 3. Adjust accent, background, and foreground colors 4. Preview light and dark modes 5. Click "Export" to copy CSS ### Step 2: Update CSS Variables Open `src/app/globals.css` and replace the theme variables: ```css @layer base { :root { --background: 0 0% 100%; --foreground: 222.2 84% 4.9%; --primary: 221.2 83.2% 53.3%; --primary-foreground: 210 40% 98%; --secondary: 210 40% 96.1%; --secondary-foreground: 222.2 47.4% 11.2%; --accent: 210 40% 96.1%; --accent-foreground: 222.2 47.4% 11.2%; --destructive: 0 84.2% 60.2%; --destructive-foreground: 210 40% 98%; --muted: 210 40% 96.1%; --muted-foreground: 215.4 16.3% 46.9%; --border: 214.3 31.8% 91.4%; --radius: 0.5rem; } .dark { --background: 222.2 84% 4.9%; --foreground: 210 40% 98%; --primary: 217.2 91.2% 59.8%; /* ... dark mode variables */ } } ``` ### Step 3: Save and See Changes That's it! Your theme is live. ๐ŸŽ‰ All components automatically use these CSS variables. Change once, update everywhere! ## What You Can Customize ๐ŸŽฏ ### Colors ```css --background /* Page background */ --foreground /* Text color */ --primary /* Brand color (buttons, links) */ --secondary /* Secondary elements */ --accent /* Highlights and accents */ --destructive /* Delete, error states */ --muted /* Disabled states */ --border /* Borders and dividers */ ``` ### Border Radius ```css --radius: 0.5rem; /* Default rounded corners */ --radius: 0; /* Sharp corners */ --radius: 1rem; /* Very rounded */ ``` ### Fonts Update in `tailwind.config.ts`: ```ts export default { theme: { extend: { fontFamily: { sans: ['Inter', 'sans-serif'], heading: ['Poppins', 'sans-serif'], }, }, }, } ``` ## Pre-made Themes ๐ŸŽ Try these popular themes: ### Modern Blue ```css :root { --primary: 221.2 83.2% 53.3%; /* Blue */ --secondary: 210 40% 96.1%; } ``` ### Elegant Purple ```css :root { --primary: 262.1 83.3% 57.8%; /* Purple */ --secondary: 270 40% 96.1%; } ``` ### Bold Orange ```css :root { --primary: 24.6 95% 53.1%; /* Orange */ --secondary: 33 40% 96.1%; } ``` ### Minimal Gray ```css :root { --primary: 224 71% 4%; /* Dark gray */ --secondary: 220 14.3% 95.9%; } ``` ## Dark Mode ๐ŸŒ™ Dark mode is built-in! The theme builder generates both light and dark variants. ### How It Works ```css /* Light mode (default) */ :root { --background: 0 0% 100%; /* White */ } /* Dark mode (automatic) */ .dark { --background: 222.2 84% 4.9%; /* Dark */ } ``` Users can toggle with the theme switcher component included in Indie Kit! All components automatically adapt to dark mode using CSS variables. No extra code needed! ## Advanced Customization ๐Ÿ”ง ### Custom Color Palette Create your own color system: ```css :root { /* Brand colors */ --brand-blue: 221 83% 53%; --brand-green: 142 76% 36%; --brand-red: 0 84% 60%; /* Use in components */ --primary: var(--brand-blue); --success: var(--brand-green); --destructive: var(--brand-red); } ``` ### Per-Component Styles Override specific components: ```tsx // Custom button with brand color ``` ### Gradient Backgrounds ```css :root { --gradient-primary: linear-gradient(to right, #667eea 0%, #764ba2 100%); } ``` ```tsx
Gradient background
``` ## Testing Your Theme ๐Ÿงช After applying a theme: 1. **Check all pages** - Landing, app, admin 2. **Test dark mode** - Toggle and verify contrast 3. **Check accessibility** - Use browser dev tools 4. **Mobile testing** - Colors should work on small screens 5. **Print preview** - Ensure readability if printed Make sure text has enough contrast with backgrounds! Use the theme builder's accessibility checker. ## Tips & Tricks ๐Ÿ’ก **1. Start Simple** * Choose your primary color * Let the theme builder generate the rest * Fine-tune after seeing it in your app **2. Test Both Modes** * Some colors work great in light but not dark * Adjust dark mode separately if needed * Check all UI states (hover, active, disabled) **3. Brand Consistency** ```tsx // Use your brand colors --primary: /* Your brand color */ --secondary: /* Complementary color */ ``` **4. Save Your Theme** * Keep your CSS variables in a separate file * Document custom colors * Save hex codes for reference ## Troubleshooting ๐Ÿ”ง **Colors not applying?** * Check CSS is in `globals.css` * Verify file is imported in root layout * Clear browser cache * Restart dev server **Dark mode broken?** * Ensure `.dark` class variables are set * Check theme switcher is working * Verify all color variables have dark variants **Components look wrong?** * Check Tailwind config includes color variables * Verify you're using HSL format (not hex or RGB) * Make sure no hardcoded colors override theme Your custom theme is applied! All components now use your brand colors. ๐ŸŽจโœจ ## Related Documentation ๐Ÿ“š * [Pre-installed Components](/customisation/installed-components) - Components to style * [Community Components](/customisation/components) - More components * [shadcn/ui Themes](https://ui.shadcn.com/themes) - Explore pre-made themes # Deployment Guide (/deployment) YouTube Video: https://www.youtube.com/watch?v=61yI-Knl1D4 Deploy your Indie Kit app to production in minutes! Choose from multiple platforms based on your needs and budget. โœจ The video tutorial above walks you through the entire deployment process. Follow along for the easiest experience! ## Choose Your Platform ๐ŸŽฏ ### Sherpa.sh ๐Ÿ†• **Perfect for indie hackers!** Affordable, simple, unlimited projects. Sherpa offers unlimited projects and new user discounts! Great for bootstrappers building multiple side projects. ๐Ÿ’ฐ **Why Sherpa?** * ๐Ÿ’ฐ **Affordable pricing** - Perfect for indie hackers * ๐Ÿš€ **Unlimited projects** - Build as many apps as you want * ๐ŸŽ **New user discounts** - Special offers for new users * ๐Ÿค **Helpful creator** - Reach out via email/Discord **Deployment steps:** 1. **Create account** โ†’ [sherpa.sh](https://www.sherpa.sh/?via=Indie%20Kit) 2. **Connect GitHub** โ†’ Authorize repository access 3. **Select repository** โ†’ Choose your Indie Kit project 4. **Add environment variables** โ†’ Copy from `.env.local` 5. **Deploy!** โ†’ Click deploy and you're live! ๐ŸŽ‰ **Need help?** The creator is super helpful! Contact them via: * ๐Ÿ“ง Email from their website * ๐Ÿ’ฌ Discord community ### Vercel โšก **Most popular option!** Fast, easy, great DX. **Deployment steps:** 1. **Push to GitHub** โ†’ Commit and push your code 2. **Visit Vercel** โ†’ [vercel.com](https://vercel.com) 3. **Click "Add New Project"** โ†’ Import repository 4. **Select repository** โ†’ Choose your Indie Kit project 5. **Add environment variables** โ†’ Copy from `.env.local` (see below) 6. **Click "Deploy"** โ†’ Done! ๐Ÿš€ Vercel auto-detects Next.js! No build configuration needed. ### Netlify ๐ŸŒ **Great alternative!** Solid performance and features. **Deployment steps:** 1. **Push to GitHub** โ†’ Commit your code 2. **Visit Netlify** โ†’ [netlify.com](https://netlify.com) 3. **Click "Add new site"** โ†’ Connect to Git 4. **Select repository** โ†’ Choose your project 5. **Add environment variables** โ†’ Site settings โ†’ Environment variables 6. **Deploy!** โ†’ Netlify handles the rest ### Render ๐ŸŽจ **Developer-friendly!** Simple and reliable. **Deployment steps:** 1. **Push to GitHub** โ†’ Save your code 2. **Visit Render** โ†’ [render.com](https://render.com) 3. **New Web Service** โ†’ Connect repository 4. **Select repository** โ†’ Choose your Indie Kit app 5. **Add environment variables** โ†’ Environment tab 6. **Deploy!** โ†’ Watch it build ## Environment Variables ๐Ÿ” All platforms need these environment variables. Copy from your `.env.local`: ### Required Variables **Database:** ```bash DATABASE_URL=your_database_connection_string ``` **Authentication:** ```bash NEXTAUTH_URL=https://your-domain.com NEXTAUTH_SECRET=your_secret_key ``` **OAuth (Google):** ```bash GOOGLE_CLIENT_ID=your_google_client_id GOOGLE_CLIENT_SECRET=your_google_client_secret ``` **Email (AWS SES):** ```bash AWS_ACCESS_KEY_ID=your_aws_access_key AWS_SECRET_ACCESS_KEY=your_aws_secret_key AWS_SES_REGION=us-east-1 ``` **Payments (Stripe):** ```bash STRIPE_SECRET_KEY=sk_live_... STRIPE_WEBHOOK_SECRET=whsec_... NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_... ``` Make sure `NEXTAUTH_URL` matches your production domain! Don't use localhost. ### Optional Variables **Background Jobs (Inngest):** ```bash INNGEST_EVENT_KEY=your_inngest_event_key INNGEST_SIGNING_KEY=your_inngest_signing_key ``` **Analytics:** ```bash NEXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXX ``` ## Post-Deployment Checklist โœ… After deploying, verify everything works: ### 1. Test Core Features **Authentication:** * โœ… Sign up with email * โœ… Sign in with Google * โœ… Password reset works **Database:** * โœ… Data persists correctly * โœ… No connection errors **Emails:** * โœ… Welcome emails arrive * โœ… Magic link emails work * โœ… Check spam folder first! **Payments:** * โœ… Checkout flows work * โœ… Webhooks receive events * โœ… Test with Stripe test mode first! ### 2. Configure Webhooks **Stripe webhooks:** 1. Go to Stripe Dashboard โ†’ Webhooks 2. Add endpoint: `https://your-domain.com/api/webhooks/stripe` 3. Copy webhook secret 4. Add to environment variables: `STRIPE_WEBHOOK_SECRET` **Inngest webhooks (if using background jobs):** 1. Go to Inngest Dashboard 2. Add production app URL 3. Copy signing key 4. Add to environment variables ### 3. Monitor & Debug **Check logs:** * Platform dashboard shows build/runtime logs * Monitor for errors * Set up error tracking (Sentry recommended) **Performance:** * Test page load speeds * Check mobile responsiveness * Verify all routes work Test your app thoroughly on the production URL before sharing publicly! ## Troubleshooting ๐Ÿ”ง **Build failing?** * Check Node.js version (should be 18+) * Verify all dependencies in `package.json` * Review build logs for errors **Environment variables not working?** * Redeploy after adding new variables * Check for typos in variable names * Ensure no extra spaces or quotes **Database connection errors?** * Verify `DATABASE_URL` is correct * Check if database allows production connections * Add platform's IP to database allowlist if needed **OAuth not working?** * Update authorized redirect URIs in Google Console * Add: `https://your-domain.com/api/auth/callback/google` * Make sure `NEXTAUTH_URL` matches your domain **Stripe webhooks failing?** * Test webhook endpoint manually * Verify webhook secret is correct * Check if webhook events are enabled ## Best Practices ๐ŸŒŸ **1. Use Different Stripe Keys** ```bash # Development: Test mode STRIPE_SECRET_KEY=sk_test_... # Production: Live mode STRIPE_SECRET_KEY=sk_live_... ``` **2. Secure Your Secrets** * Never commit `.env.local` to Git * Use different secrets for production * Rotate secrets periodically **3. Set Up Monitoring** * Error tracking (Sentry, LogRocket) * Uptime monitoring (Better Uptime) * Analytics (Google Analytics, Plausible) **4. Plan for Scale** * Monitor database usage * Optimize slow queries * Consider CDN for assets ## Next Steps ๐Ÿš€ 1. **Set up custom domain** - Add your domain in platform settings 2. **Configure SSL** - Usually automatic on all platforms 3. **Set up monitoring** - Track errors and performance 4. **Launch!** - Share your app with the world ๐ŸŽ‰ Congratulations! Your Indie Kit app is now in production. Time to get users! ๐Ÿš€ ## Related Documentation ๐Ÿ“š * [Database Setup](/setup/database) - Configure your database * [Stripe Setup](/setup/payments/stripe) - Payment configuration * [Email Setup](/setup/email) - Email provider setup # Inngest Deployment (/deployment/vercel) YouTube Video: https://www.youtube.com/watch?v=NYbJ6EMY5rA import { Cards, Card } from 'fumadocs-ui/components/card'; Create an Inngest account to get started. Learn more about deploying on Vercel. # Docs (/docs) # Documentation System ๐Ÿ“š Indie Kit comes with a powerful, production-ready documentation system. Write MDX files, and everything else is handled automatically! โœจ ## What You Get Out of the Box ๐ŸŽ Your docs come with incredible features built-in: * ๐Ÿค– **Automatic llms.txt** - AI-ready content for ChatGPT and other platforms * ๐Ÿ” **Built-in Search** - Lightning-fast fuzzy search * ๐Ÿ–ผ๏ธ **Auto OG Images** - Social media cards for every page * ๐Ÿ“‹ **Copy as Markdown** - One-click copy for sharing * ๐Ÿ—บ๏ธ **Automatic Sitemap** - SEO handled automatically * ๐ŸŽจ **Icon Support** - Add icons to pages effortlessly * ๐Ÿ“ฑ **Mobile Responsive** - Perfect on all devices Built on [Fumadocs](https://fumadocs.vercel.app/) - a modern documentation framework optimized for performance and developer experience. ## AI Integration ๐Ÿค– Your docs are automatically LLM-ready: ### llms.txt Generation Every time you add content, `llms.txt` is automatically generated with: * Structured content for AI consumption * Proper formatting for ChatGPT, Claude, and other LLMs * Optimized for context window efficiency ### Open in ChatGPT Users can open any doc page directly in ChatGPT with one click! The **"Open in ChatGPT"** button: * Shares the page content with proper context * Works with Claude and other AI platforms * Includes markdown formatting ### Copy as Markdown The **"Copy as Markdown"** feature lets users: * Copy entire pages with formatting intact * Share in Slack, Discord, or anywhere * Preserve code blocks and structure Your docs are optimized for both humans AND AI! Users can chat with your docs using their favorite AI assistant. ## Adding Content ๐Ÿ“ Adding new documentation is super simple: ### Step 1: Create Your MDX File Drop your `.mdx` file in `content/docs/`: ``` content/docs/ โ”œโ”€โ”€ getting-started.mdx โ”œโ”€โ”€ your-new-page.mdx โ””โ”€โ”€ advanced/ โ””โ”€โ”€ custom-feature.mdx ``` ### Step 2: Add Frontmatter Every page needs frontmatter: ```mdx --- title: Your Page Title description: A helpful description for SEO icon: Rocket --- # Your Content Here Write your documentation in MDX format! ``` ### Step 3: That's It! โœจ Your page is now: * โœ… Searchable * โœ… Has an OG image * โœ… In the sitemap * โœ… Available to AI * โœ… Mobile responsive No build steps, no config files! Just add your `.mdx` file and everything works automatically. ## Customization ๐ŸŽจ ### Custom OG Images Want to customize social media preview images? Edit the OG image generator: ``` src/app/api/og/route.tsx ``` Modify the template to match your branding: ```tsx // Customize colors, fonts, layout
{/* Your custom OG image template */}
``` ### Icons Add icons to any page using [Lucide icons](https://lucide.dev): ```mdx --- title: My Page icon: Rocket # Any Lucide icon name --- ``` Common icons: * ๐Ÿš€ `Rocket` - Getting started * ๐Ÿ“š `Book` - Documentation * โš™๏ธ `Settings` - Configuration * ๐Ÿ” `Lock` - Security * ๐Ÿ’ณ `CreditCard` - Payments ### Navigation Structure Control sidebar navigation in `meta.json`: ```json { "title": "Section Name", "pages": ["page-1", "page-2"], "icon": "Folder" } ``` ## Features in Detail โšก ### Search Implementation Fuzzy search is automatically enabled: * Search across all content * Keyboard shortcuts (`Cmd+K` or `Ctrl+K`) * Instant results * No configuration needed ### Automatic Sitemap Your `sitemap.xml` is generated automatically: * All documentation pages included * Proper priority and change frequency * Submitted to search engines ### Mobile Navigation Responsive by default: * Collapsible sidebar * Touch-friendly navigation * Optimized for small screens Every page automatically gets meta tags, OG images, and proper semantic HTML for maximum search engine visibility! ## Best Practices ๐Ÿ’ก ### Writing Great Docs 1. **Clear Titles** - Descriptive and searchable 2. **Use Callouts** - Highlight important information 3. **Code Examples** - Show, don't just tell 4. **Short Sections** - Break up long content 5. **Emojis** - Make it friendly and scannable ### Organizing Content ``` content/docs/ โ”œโ”€โ”€ getting-started.mdx # First steps โ”œโ”€โ”€ setup/ # Installation guides โ”œโ”€โ”€ features/ # Feature documentation โ”œโ”€โ”€ guides/ # How-to guides โ””โ”€โ”€ api/ # API reference ``` ### Using MDX Features MDX supports React components: ```mdx This is a callout component! ``` ## Next Steps ๐Ÿš€ Now that you understand the docs system: 1. **Add Your Content** - Start writing in `content/docs/` 2. **Customize Branding** - Update OG images and colors 3. **Organize Navigation** - Structure your `meta.json` files 4. **Test Search** - Make sure content is discoverable Your documentation system is production-ready! Just add content and watch the magic happen. โœจ # Analytics (/analytics) # Analytics Let's set up analytics in your Indie Kit application! ๐Ÿš€ # Create Email Sequence (/background-jobs/create-email-sequence) # Create Email Sequence ๐Ÿ“ง Build powerful automated email sequences! Perfect for onboarding new users, nurturing leads, and running drip campaigns. โœจ Make sure you have [email provider](/setup/email) and [background jobs](/setup/background-jobs) configured first. ## What Are Email Sequences? ๐Ÿค” Email sequences are automated series of emails sent over time: **Common types:** * ๐ŸŽ‰ **Welcome sequences** - Onboard new users * ๐Ÿ“š **Educational drips** - Teach product features * ๐Ÿ’ฐ **Trial sequences** - Convert free users to paid * ๐Ÿ”„ **Re-engagement** - Win back inactive users * ๐ŸŽ“ **Course delivery** - Send lessons over time **Benefits:** * Automate user onboarding * Increase engagement * Improve conversions * Save time on manual emails * Personalize at scale Create sequences once and they run automatically for every user! No manual work needed. ## Build Your First Sequence ๐Ÿ› ๏ธ ### Step 1: Create Sequence Function Create `src/lib/inngest/functions/welcome-sequence.ts`: ```ts import { inngest } from "@/lib/inngest/client" import { sendEmail } from "@/lib/email/send" export const welcomeSequence = inngest.createFunction( { id: "welcome-sequence" }, { event: "user/signup" }, async ({ event, step }) => { const { email, name } = event.data // Day 0: Welcome (immediate) await step.run("send-welcome", async () => { await sendEmail({ to: email, template: "welcome", data: { name } }) }) // Day 1: Getting started tips await step.sleep("wait-1-day", "1d") await step.run("send-getting-started", async () => { await sendEmail({ to: email, template: "getting-started", data: { name } }) }) // Day 3: Feature highlights await step.sleep("wait-2-more-days", "2d") await step.run("send-features", async () => { await sendEmail({ to: email, template: "feature-highlights", data: { name } }) }) // Day 7: Upgrade offer await step.sleep("wait-4-more-days", "4d") await step.run("send-upgrade-offer", async () => { await sendEmail({ to: email, template: "upgrade-offer", data: { name } }) }) } ) ``` **What this does:** * โœ‰๏ธ Sends welcome email immediately * โฐ Waits 1 day, sends getting started tips * โฐ Waits 2 more days, sends feature highlights * โฐ Waits 4 more days, sends upgrade offer Use `1d` (day), `1h` (hour), `1m` (minute), or `1s` (second). Inngest handles the scheduling automatically! ### Step 2: Register Function Add to `src/lib/inngest/functions/index.ts`: ```ts import { welcomeSequence } from "./welcome-sequence" export const functions = [ welcomeSequence, // ... other functions ] ``` ### Step 3: Trigger the Sequence Send an event when user signs up: ```ts import { inngest } from "@/lib/inngest/client" // In your signup handler export async function handleSignup(user) { // ... create user in database // Trigger welcome sequence await inngest.send({ name: "user/signup", data: { email: user.email, name: user.name, userId: user.id, }, }) } ``` That's it! The sequence starts automatically and runs over 7 days. Inngest handles all the timing and retries! ## More Sequence Examples ๐Ÿ’ก ### Trial Reminder Sequence Convert trial users before they expire: ```ts export const trialReminders = inngest.createFunction( { id: "trial-reminders" }, { event: "subscription/trial.started" }, async ({ event, step }) => { const { email, name, trialEndsAt } = event.data // 3 days before expiry await step.sleepUntil("3-days-before", new Date(trialEndsAt - 3 * 24 * 60 * 60 * 1000) ) await step.run("send-3-day-warning", async () => { await sendEmail({ to: email, template: "trial-ending-soon", data: { name, daysLeft: 3 } }) }) // Last day await step.sleepUntil("last-day", new Date(trialEndsAt - 24 * 60 * 60 * 1000) ) await step.run("send-last-chance", async () => { await sendEmail({ to: email, template: "trial-last-day", data: { name } }) }) } ) ``` ### Re-engagement Sequence Win back inactive users: ```ts export const reengagementSequence = inngest.createFunction( { id: "reengage-inactive-users" }, { event: "user/inactive.detected" }, async ({ event, step }) => { const { email, name, lastActiveAt } = event.data // Email 1: We miss you await step.run("send-we-miss-you", async () => { await sendEmail({ to: email, template: "we-miss-you", data: { name, lastActiveAt } }) }) // Wait 3 days await step.sleep("wait-3-days", "3d") // Email 2: New features await step.run("send-whats-new", async () => { await sendEmail({ to: email, template: "whats-new", data: { name } }) }) // Wait 4 more days await step.sleep("wait-4-days", "4d") // Email 3: Special offer await step.run("send-special-offer", async () => { await sendEmail({ to: email, template: "comeback-offer", data: { name, discountCode: "COMEBACK20" } }) }) } ) ``` ### Product Update Sequence Announce new features to all users: ```ts export const productUpdateSequence = inngest.createFunction( { id: "product-update" }, { event: "product/feature.launched" }, async ({ event, step }) => { const { featureName, featureDescription } = event.data // Get all active users const users = await step.run("get-users", async () => { return await getAllActiveUsers() }) // Send to all users await step.run("send-announcements", async () => { for (const user of users) { await sendEmail({ to: user.email, template: "feature-announcement", data: { name: user.name, featureName, featureDescription } }) } }) } ) ``` For large user lists, use Inngest's fan-out pattern to send emails in parallel and avoid timeouts! ## Advanced Features โšก ### Conditional Logic Send different emails based on user behavior: ```ts export const smartOnboarding = inngest.createFunction( { id: "smart-onboarding" }, { event: "user/signup" }, async ({ event, step }) => { const { email, name, plan } = event.data // Welcome email await step.run("send-welcome", async () => { await sendEmail({ to: email, template: "welcome" }) }) await step.sleep("wait-1-day", "1d") // Different content based on plan if (plan === "free") { await step.run("send-upgrade-tips", async () => { await sendEmail({ to: email, template: "why-upgrade", data: { name } }) }) } else { await step.run("send-pro-features", async () => { await sendEmail({ to: email, template: "pro-features", data: { name } }) }) } } ) ``` ### Stop Sequence Early Cancel sequence if user takes action: ```ts export const trialSequence = inngest.createFunction( { id: "trial-sequence" }, { event: "trial/started" }, async ({ event, step }) => { const { email, userId } = event.data await step.sleep("wait-5-days", "5d") // Check if user already upgraded const user = await step.run("check-status", async () => { return await getUser(userId) }) if (user.isPaid) { return { message: "User already upgraded, stopping sequence" } } // Send reminder only if still on trial await step.run("send-reminder", async () => { await sendEmail({ to: email, template: "upgrade-reminder" }) }) } ) ``` ### Track Opens & Clicks ```ts await step.run("send-with-tracking", async () => { const result = await sendEmail({ to: email, template: "welcome", data: { name }, tracking: { campaign: "welcome-sequence", source: "inngest" } }) return result // Track delivery status }) ``` ## Best Practices โœ… **1. Timing Strategy** ```tsx // โœ… Good - Natural spacing Day 0: Welcome Day 1: Getting started tips Day 3: Feature highlights Day 7: Upgrade offer // โŒ Bad - Too aggressive Day 0: Welcome Day 0.5: Buy now! Day 1: Last chance! Day 1.5: Final offer! ``` **2. Email Content** * Keep each email focused on one topic * Include clear CTAs * Make it easy to unsubscribe * Personalize with user data * Mobile-friendly design **3. Error Handling** ```ts // โœ… Good - Retry on failures await step.run("send-email", async () => { try { await sendEmail({ to: email, template: "welcome" }) } catch (error) { console.error("Failed to send:", error) throw error // Inngest will retry } }) // โŒ Bad - Swallow errors try { await sendEmail(...) } catch { // Ignored } ``` **4. Testing** * Test locally with Inngest dev server * Use your own email to verify timing * Check all templates render correctly * Monitor delivery rates * Verify unsubscribe links work ## Sequence Ideas ๐ŸŽฏ **Onboarding (First 30 days):** * Day 0: Welcome ๐Ÿ‘‹ * Day 1: Quick start guide * Day 3: Feature showcase * Day 7: Check-in & support offer * Day 14: Success stories * Day 30: Upgrade invitation **Trial Conversion (14-day trial):** * Day 0: Trial started * Day 3: Feature deep dive * Day 7: Midpoint check-in * Day 11: Trial ending reminder * Day 13: Last day warning * Day 14: Trial expired (offer discount) **Lead Nurturing:** * Week 1: Problem awareness * Week 2: Solution introduction * Week 3: Social proof & case studies * Week 4: Special offer **Educational Course:** * Send 1 lesson per day * Include exercises * Track completion * Offer certification Plan your sequence on paper first! Map out the user journey and what value each email provides. ## Monitoring & Analytics ๐Ÿ“Š ### View Sequence Status Check Inngest dashboard: 1. Go to [app.inngest.com](https://app.inngest.com) 2. Click on your function 3. See all running sequences 4. Check completion rates 5. View any errors ### Track Performance ```ts export const trackedSequence = inngest.createFunction( { id: "tracked-welcome" }, { event: "user/signup" }, async ({ event, step }) => { const startTime = Date.now() // ... your sequence await step.run("track-completion", async () => { await analytics.track({ event: "sequence_completed", userId: event.data.userId, duration: Date.now() - startTime, }) }) } ) ``` ## Troubleshooting ๐Ÿ”ง **Emails not sending?** * Check email provider is configured * Verify email templates exist * Look at Inngest execution logs * Test email function separately **Timing issues?** * Check sleep duration format * Verify server timezone * Look at Inngest dashboard for delays * Test with shorter durations first **Sequence not starting?** * Check event name matches exactly * Verify function is registered * Look for errors in Inngest dashboard * Check if event was sent successfully **Users getting multiple sequences?** * Check for duplicate event sends * Use idempotency keys * Verify function registration * Check database for duplicate users Use Inngest dev server at `localhost:8288` to test sequences with accelerated timing (seconds instead of days)! ## Next Steps ๐Ÿš€ 1. **Create email templates** in `src/emails/` 2. **Build your first sequence** with the examples above 3. **Test locally** with dev server 4. **Monitor in production** via Inngest dashboard 5. **Iterate based on metrics** - Open rates, conversions You can now automate your email marketing! Start with a simple welcome sequence and expand from there. ๐Ÿ“งโœจ ## Related Documentation ๐Ÿ“š * [Background Jobs Setup](/setup/background-jobs) - Configure Inngest * [Email Setup](/setup/email) - Configure email provider * [Running Scheduled Jobs](/background-jobs/running-scheduled-jobs) - Cron patterns * [Inngest Docs](https://www.inngest.com/docs) - Advanced features # Cron Jobs (/background-jobs/cron) YouTube Video: https://www.youtube.com/watch?v=C5ag3lfL3BQ Run tasks on a schedule! Clean up data daily, send weekly reports, or process monthly billing automatically. โฐ Indie Kit supports Vercel Cron, Inngest scheduled functions, and external cron services. Pick what works best for your hosting! ## What Are Cron Jobs? ๐Ÿค” Cron jobs are tasks that run on a schedule: **Common use cases:** * ๐Ÿงน **Daily cleanup** - Delete old data, expire sessions * ๐Ÿ“Š **Weekly reports** - Send analytics summaries * ๐Ÿ’ณ **Monthly billing** - Process subscriptions * ๐Ÿ”„ **Hourly sync** - Update external data * ๐Ÿ“ง **Email digests** - Send daily/weekly digests ## Using Inngest (Recommended) โšก Inngest provides the easiest way to schedule jobs: ### Create Scheduled Function ```ts // src/lib/inngest/functions/daily-cleanup.ts import { inngest } from "@/lib/inngest/client" export const dailyCleanup = inngest.createFunction( { id: "daily-cleanup" }, { cron: "0 0 * * *" }, // Every day at midnight async ({ step }) => { await step.run("delete-old-sessions", async () => { await deleteExpiredSessions() }) await step.run("cleanup-temp-files", async () => { await cleanupTempFiles() }) return { cleaned: true } } ) ``` **Register it:** ```ts // src/lib/inngest/functions/index.ts import { dailyCleanup } from "./daily-cleanup" export const functions = [ dailyCleanup, // ... other functions ] ``` That's it! Inngest handles the scheduling automatically. No deployment config needed! ### Cron Pattern Examples ```ts // Every minute { cron: "* * * * *" } // Every hour { cron: "0 * * * *" } // Every day at midnight { cron: "0 0 * * *" } // Every day at 9 AM { cron: "0 9 * * *" } // Every Monday at 9 AM { cron: "0 9 * * 1" } // First day of month at midnight { cron: "0 0 1 * *" } // Every weekday at 6 PM { cron: "0 18 * * 1-5" } ``` Format: `minute hour day month weekday` - Use [Crontab Guru](https://crontab.guru/) to build complex patterns! ## Using Vercel Cron ๐ŸŸข For apps deployed on Vercel: ### Step 1: Create API Route ```ts // src/app/api/cron/daily-cleanup/route.ts import { NextResponse } from 'next/server' export async function GET(request: Request) { // Verify cron secret const authHeader = request.headers.get('authorization') if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) } // Run cleanup await deleteExpiredSessions() await cleanupTempFiles() return NextResponse.json({ success: true }) } ``` ### Step 2: Add to vercel.json ```json { "crons": [ { "path": "/api/cron/daily-cleanup", "schedule": "0 0 * * *" } ] } ``` ### Step 3: Set Environment Variable ```bash CRON_SECRET="your-random-secret-key" ``` Vercel Cron only works on Vercel hosting! Use Inngest for platform-agnostic scheduling. ## Using External Cron Service ๐ŸŒ For any hosting platform, use [cron-job.org](https://cron-job.org): ### Step 1: Create Protected API Route ```ts // src/app/api/cron/cleanup/route.ts export async function GET(request: Request) { // Basic auth protection const authHeader = request.headers.get('authorization') const [username, password] = Buffer.from( authHeader?.split(' ')[1] || '', 'base64' ).toString().split(':') if ( username !== process.env.CRON_USERNAME || password !== process.env.CRON_PASSWORD ) { return new Response('Unauthorized', { status: 401 }) } // Run your task await performCleanup() return Response.json({ success: true }) } ``` ### Step 2: Setup on cron-job.org 1. Create free account 2. Add new cron job 3. **URL:** `https://yourapp.com/api/cron/cleanup` 4. **Schedule:** Choose timing (daily, weekly, etc.) 5. **Authentication:** Add HTTP Basic Auth with credentials 6. **Save** and activate ### Step 3: Add Credentials to .env ```bash CRON_USERNAME="secure-username" CRON_PASSWORD="secure-strong-password" ``` Works with any hosting platform! Great for Render, Railway, or custom servers. ## Common Scheduled Tasks ๐Ÿ“‹ ### Daily Cleanup ```ts export const dailyCleanup = inngest.createFunction( { id: "daily-cleanup" }, { cron: "0 2 * * *" }, // 2 AM daily async ({ step }) => { // Delete expired sessions await step.run("sessions", async () => { await db.delete(sessions) .where(lt(sessions.expiresAt, new Date())) }) // Delete old logs await step.run("logs", async () => { const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000) await db.delete(logs) .where(lt(logs.createdAt, thirtyDaysAgo)) }) } ) ``` ### Weekly Reports ```ts export const weeklyReport = inngest.createFunction( { id: "weekly-report" }, { cron: "0 9 * * 1" }, // Mondays at 9 AM async ({ step }) => { const stats = await step.run("get-stats", async () => { return await getWeeklyStats() }) await step.run("send-report", async () => { await sendEmail({ to: "admin@yourapp.com", template: "weekly-report", data: stats }) }) } ) ``` ### Monthly Billing ```ts export const monthlyBilling = inngest.createFunction( { id: "monthly-billing" }, { cron: "0 0 1 * *" }, // First day of month async ({ step }) => { const users = await step.run("get-users", async () => { return await getActiveSubscriptions() }) await step.run("process-billing", async () => { for (const user of users) { await processMonthlyCharge(user) } }) } ) ``` ## Best Practices โœ… **1. Use UTC Timezone** * All cron times are in UTC * Convert local time to UTC * Document timezone in comments **2. Idempotency** ```ts // โœ… Good - Can run multiple times safely await deleteExpiredSessions() // Safe to run multiple times // โŒ Bad - Duplicate data on re-run await sendEmailToEveryone() // Might send duplicates ``` **3. Error Handling** ```ts await step.run("task", async () => { try { await riskyOperation() } catch (error) { console.error("Task failed:", error) throw error // Trigger retry } }) ``` **4. Monitor Execution** * Set up alerts for failures * Check execution logs regularly * Monitor duration and performance * Track success rates ## Testing Cron Jobs ๐Ÿงช ### Test Locally ```ts // Call your cron function directly import { dailyCleanup } from "@/lib/inngest/functions/daily-cleanup" // In test file or API route const result = await dailyCleanup.handler({ event: { name: "cron", data: {} }, // ... mock context }) ``` ### Test Timing Use shorter intervals for testing: ```ts // Production { cron: "0 0 * * *" } // Daily // Testing { cron: "*/5 * * * *" } // Every 5 minutes ``` Change back before deploying! Always test cron jobs in development with shorter intervals before deploying to production! ## Troubleshooting ๐Ÿ”ง **Job not running?** * Verify cron pattern is correct * Check function is registered * Look at Inngest/Vercel logs * Ensure environment variables set **Running multiple times?** * Check for duplicate registrations * Verify cron pattern * Look for multiple deployments * Check Inngest dashboard **Timing off?** * Remember UTC timezone * Convert from local time * Check server timezone settings * Test pattern on crontab.guru ## Next Steps ๐Ÿš€ 1. **Create your first cron job** - Start with daily cleanup 2. **Test locally** - Use Inngest dev server 3. **Deploy** - Push and verify in dashboard 4. **Monitor** - Set up alerts for failures 5. **Optimize** - Adjust timing based on needs Your cron jobs are ready to run! Automate repetitive tasks and save yourself time. โฐ ## Related Documentation ๐Ÿ“š * [Background Jobs Setup](/setup/background-jobs) - Configure Inngest * [Running Scheduled Jobs](/background-jobs/running-scheduled-jobs) - More examples * [Email Sequences](/background-jobs/create-email-sequence) - Automated emails * [Inngest Cron Docs](https://www.inngest.com/docs/guides/scheduled-functions) - Advanced scheduling # Running Scheduled Jobs (/background-jobs/running-scheduled-jobs) YouTube Video: https://www.youtube.com/watch?v=gz2331T2sAw Run reliable background jobs! Execute tasks on events, schedules, or delays with automatic retries and monitoring. โšก Background jobs handle async tasks like sending emails, processing data, generating reports, and cleaning up databases! ## Event-Driven Jobs ๐ŸŽฏ Jobs that run when events happen in your app. ### Create Your First Job Create `src/lib/inngest/functions/hello-world.ts`: ```ts import { inngest } from "@/lib/inngest/client" export const helloWorld = inngest.createFunction( { id: "hello-world" }, { event: "test/hello.world" }, async ({ event, step }) => { // Wait 1 minute await step.sleep("wait-a-bit", "1m") // Do something await step.run("say-hello", async () => { console.log("Hello, World!", event.data) return { message: "Hello, World!" } }) } ) ``` **Register it in** `src/lib/inngest/functions/index.ts`: ```ts import { helloWorld } from "./hello-world" export const functions = [ helloWorld, // ... other functions ] ``` Inngest automatically discovers and registers your functions. No manual setup needed! ### Trigger Jobs with Events Send events to trigger your functions: ```ts import { inngest } from "@/lib/inngest/client" // Trigger background job await inngest.send({ name: "test/hello.world", data: { message: "Test data", userId: "123", }, }) ``` **Real-world example:** ```ts // src/app/api/checkout/route.ts export async function POST(req: Request) { const order = await createOrder(await req.json()) // Trigger background job to process order await inngest.send({ name: "order/created", data: { orderId: order.id, userId: order.userId, total: order.total, items: order.items, }, }) return Response.json({ success: true }) } ``` Events are sent asynchronously! Your API responds immediately while the job processes in the background. ## Scheduled Jobs (Cron) โฐ Run jobs on a recurring schedule: ```ts // src/lib/inngest/functions/daily-report.ts export const dailyReport = inngest.createFunction( { id: "daily-report" }, { cron: "0 9 * * *" }, // Every day at 9 AM async ({ step }) => { const stats = await step.run("get-stats", async () => { return await getDailyStats() }) await step.run("send-email", async () => { await sendEmail({ to: "admin@yourapp.com", template: "daily-report", data: stats }) }) } ) ``` **Common schedules:** ```ts { cron: "0 0 * * *" } // Daily at midnight { cron: "0 */6 * * *" } // Every 6 hours { cron: "0 9 * * 1" } // Mondays at 9 AM { cron: "0 0 1 * *" } // First of month ``` ## Monitoring Jobs ๐Ÿ“Š ### Local Development Visit `http://localhost:8288` to: * ๐Ÿ‘€ See all registered functions * ๐Ÿƒ View running jobs * ๐Ÿ“ Check execution logs * ๐Ÿ”„ Replay failed jobs * ๐Ÿ› Debug step-by-step ### Production Use [Inngest Cloud Dashboard](https://app.inngest.com): * ๐Ÿ“Š Real-time execution monitoring * ๐Ÿšจ Set up failure alerts * ๐Ÿ“ˆ View performance metrics * ๐Ÿ” Search execution history * ๐Ÿ”„ Retry failed jobs manually Inngest provides complete visibility into your background jobs. No extra monitoring tools needed! ## Advanced Patterns ๐Ÿš€ ### Step Functions Break jobs into retryable steps: ```ts export const complexJob = inngest.createFunction( { id: "complex-job" }, { event: "job/complex" }, async ({ event, step }) => { // Step 1: Fetch data (retries if fails) const data = await step.run("fetch-data", async () => { return await fetchExternalData() }) // Step 2: Process (retries independently) const processed = await step.run("process", async () => { return await processData(data) }) // Step 3: Save (retries independently) await step.run("save", async () => { await saveToDatabase(processed) }) } ) ``` **Benefits:** * โœ… Each step retries independently * โœ… Resume from failed step * โœ… No duplicate work * โœ… Better debugging ### Parallel Execution Process items in parallel: ```ts export const processUsers = inngest.createFunction( { id: "process-users" }, { event: "users/process.batch" }, async ({ event, step }) => { const users = event.data.users // Process all users in parallel await Promise.all( users.map((user) => step.run(`process-${user.id}`, async () => { await processUser(user) }) ) ) } ) ``` ### Conditional Execution Run steps based on conditions: ```ts export const conditionalJob = inngest.createFunction( { id: "conditional-job" }, { event: "user/action" }, async ({ event, step }) => { const user = await step.run("get-user", async () => { return await getUser(event.data.userId) }) // Only send email if user is premium if (user.isPremium) { await step.run("send-premium-email", async () => { await sendEmail({ to: user.email, template: "premium-content" }) }) } else { await step.run("send-upgrade-prompt", async () => { await sendEmail({ to: user.email, template: "upgrade-now" }) }) } } ) ``` ## Best Practices โœ… **1. Use Step Functions** ```ts // โœ… Good - Retryable steps await step.run("fetch", async () => fetch()) await step.run("process", async () => process()) // โŒ Bad - All or nothing await fetch() await process() ``` **2. Name Things Clearly** ```ts // โœ… Good - Descriptive { id: "send-welcome-email-sequence" } { event: "user/signup.completed" } // โŒ Bad - Vague { id: "job1" } { event: "event" } ``` **3. Handle Errors** ```ts await step.run("risky-operation", async () => { try { await riskyOperation() } catch (error) { console.error("Failed:", error) throw error // Triggers automatic retry } }) ``` **4. Keep Jobs Idempotent** * Jobs might run multiple times * Use unique IDs to prevent duplicates * Check state before performing actions * Make operations safe to retry ## Common Use Cases ๐ŸŽฏ **User Actions:** * ๐Ÿ“ง Send welcome emails * ๐ŸŽ‰ Trigger onboarding sequences * ๐Ÿ“Š Track user activity * ๐ŸŽ Give signup bonuses **Scheduled Maintenance:** * ๐Ÿงน Daily data cleanup * ๐Ÿ“ˆ Weekly analytics reports * ๐Ÿ’ณ Monthly billing processing * ๐Ÿ”„ Hourly data syncing **Processing:** * ๐Ÿ–ผ๏ธ Image optimization * ๐Ÿ“„ PDF generation * ๐Ÿ“น Video transcoding * ๐Ÿ” Search index updates Retries, delays, parallelization, and monitoring are all built-in. Focus on your business logic! ## Troubleshooting ๐Ÿ”ง **Function not appearing?** * Check it's exported from `functions/index.ts` * Restart dev server * Look at Inngest dashboard * Verify function syntax **Event not triggering job?** * Check event name matches exactly * Verify event was sent successfully * Look at Inngest event stream * Test with Inngest dev server **Job failing?** * Check error logs in dashboard * Verify all dependencies available * Test step-by-step locally * Check environment variables **Steps taking too long?** * Optimize slow operations * Break into smaller steps * Use parallel execution * Check for infinite loops ## Next Steps ๐Ÿš€ 1. **Create your first job** - Start simple with hello world 2. **Send test event** - Trigger it and watch execution 3. **Add error handling** - Make it production-ready 4. **Monitor execution** - Check logs and timing 5. **Build real features** - Email sequences, reports, cleanup You can now build reliable async features! Start with a simple job and expand from there. โšก ## Related Documentation ๐Ÿ“š * [Background Jobs Setup](/setup/background-jobs) - Configure Inngest * [Email Sequences](/background-jobs/create-email-sequence) - Automated emails * [Cron Jobs](/background-jobs/cron) - Scheduled tasks * [Inngest Docs](https://www.inngest.com/docs) - Full platform documentation # IDE Setup (/codebase/ide-setup) import { Tabs, Tab } from 'fumadocs-ui/components/tabs' # IDE Setup ๐Ÿ’ป Set up your development environment with AI-powered editors for maximum productivity! Code faster and smarter. โœจ AI code editors can 3-10x your coding speed! We highly recommend Cursor or Windsurf. ## Setting up Cursor โญ **Best AI coding experience!** ### Why Cursor? * ๐Ÿค– **AI that understands your project** - Chat with your entire codebase * โšก **AI autocomplete** - Write code 10x faster * ๐Ÿง  **Context-aware** - AI knows your project structure * ๐Ÿ”ง **VS Code compatible** - All extensions work perfectly ### Installation Steps 1. **Download Cursor** ๐Ÿ“ฅ ๐Ÿ‘‰ [Download Cursor](https://cursor.sh) 2. **Install the app** ๐Ÿ’ป * Open the downloaded file * Follow installation prompts * Launch Cursor 3. **Open Indie Kit project** ๐Ÿ“‚ * Click "Open Folder" * Select your Indie Kit project directory * Click "Select Folder" 4. **Install extensions** ๐Ÿ”Œ * Cursor will detect recommended extensions * Click "Install All" when prompted * Done! ๐ŸŽ‰ * `Cmd+K` (Mac) / `Ctrl+K` (Win) - AI command * `Cmd+L` (Mac) / `Ctrl+L` (Win) - Chat with codebase ### Using Cursor AI ๐Ÿค– **Ask questions:** * "How does authentication work in this project?" * "Create a new API route for user profile" * "Fix this TypeScript error" **AI autocomplete:** Just start typing - AI suggests complete code blocks! **Chat with codebase:** Open chat (`Cmd+L`) and ask anything about your project. ## Setting up Windsurf ๐ŸŒŠ **Another excellent AI editor!** ### Why Windsurf? * ๐Ÿค– **AI-powered coding** - Smart code suggestions * ๐Ÿš€ **Fast and lightweight** - Quick startup * ๐Ÿ’ก **Intelligent assistance** - Context-aware help * ๐Ÿ”ง **VS Code compatible** - Extensions work here too ### Installation Steps 1. **Download Windsurf** ๐Ÿ“ฅ ๐Ÿ‘‰ [Download Windsurf](https://codeium.com/windsurf) 2. **Install the app** ๐Ÿ’ป * Open the downloaded installer * Complete the installation * Launch Windsurf 3. **Open Indie Kit project** ๐Ÿ“‚ * Click "Open Folder" * Navigate to your Indie Kit directory * Click "Select Folder" 4. **Install extensions** ๐Ÿ”Œ * Look for notification about recommended extensions * Click "Install All" * All set! โœ… Windsurf is a powerful AI code editor worth trying if Cursor doesn't fit your workflow! ### Using Windsurf AI ๐Ÿค– Similar to Cursor, use AI features to: * ๐Ÿง  Generate code * ๐Ÿ› Debug issues * ๐Ÿ” Understand patterns * โ™ป๏ธ Refactor code ## Setting up VS Code ๐Ÿ“ **Traditional editor, still great!** ### Installation Steps 1. **Download VS Code** ๐Ÿ“ฅ ๐Ÿ‘‰ [Download VS Code](https://code.visualstudio.com) 2. **Install the app** ๐Ÿ’ป * Open the downloaded installer * Complete installation * Launch VS Code 3. **Open Indie Kit project** ๐Ÿ“‚ * Click "Open Folder" * Select your Indie Kit directory * Click "Select Folder" 4. **Install extensions** ๐Ÿ”Œ * VS Code will suggest recommended extensions * Click "Install All" * Done! ๐ŸŽ‰ Install AI extensions to supercharge VS Code: * **GitHub Copilot** - AI code completion * **Codeium** - Free AI assistant * **Tabnine** - AI autocomplete ### Recommended AI Extensions **GitHub Copilot** (Paid) ๐Ÿ‘‰ [Install GitHub Copilot](https://marketplace.visualstudio.com/items?itemName=GitHub.copilot) **Codeium** (Free) ๐Ÿ‘‰ [Install Codeium](https://marketplace.visualstudio.com/items?itemName=Codeium.codeium) ## Install Extensions ๐Ÿ”Œ These extensions make Indie Kit development much easier! ### Automatic Installation (Easy Way) โœจ 1. **Open your editor** in the Indie Kit project folder 2. **Look for notification** - "Do you want to install recommended extensions?" 3. **Click "Install All"** - Done! ๐ŸŽ‰ Your editor will automatically detect the recommended extensions from the project. After installing extensions, restart your editor to activate them all! ### Recommended Extensions ๐Ÿ“ฆ #### Essential Extensions **Tailwind CSS IntelliSense** - CSS autocomplete โœจ ๐Ÿ‘‰ [Install Tailwind CSS IntelliSense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) **ESLint** - Code quality ๐Ÿ” ๐Ÿ‘‰ [Install ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) **Prettier** - Code formatting ๐ŸŽจ ๐Ÿ‘‰ [Install Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) **TypeScript Latest** - TypeScript support ๐Ÿ“˜ ๐Ÿ‘‰ [Install TypeScript Latest](https://marketplace.visualstudio.com/items?itemName=ms-vscode.vscode-typescript-next) **ES7+ React Snippets** - React shortcuts โš›๏ธ ๐Ÿ‘‰ [Install ES7+ React Snippets](https://marketplace.visualstudio.com/items?itemName=dsznajder.es7-react-js-snippets) #### Helpful Extensions **Tailwind Snippets** - Quick Tailwind classes ๐ŸŽฏ ๐Ÿ‘‰ [Install Tailwind Snippets](https://marketplace.visualstudio.com/items?itemName=zarifprogrammer.tailwind-snippets) **Simple React Snippets** - More React shortcuts ๐Ÿš€ ๐Ÿ‘‰ [Install Simple React Snippets](https://marketplace.visualstudio.com/items?itemName=burkeholland.simple-react-snippets) **npm Intellisense** - Package imports ๐Ÿ“ฆ ๐Ÿ‘‰ [Install npm Intellisense](https://marketplace.visualstudio.com/items?itemName=christian-kohler.npm-intellisense) **Better Comments** - Colorful comments ๐Ÿ’ฌ ๐Ÿ‘‰ [Install Better Comments](https://marketplace.visualstudio.com/items?itemName=aaron-bond.better-comments) **Import Cost** - See package sizes ๐Ÿ’ฐ ๐Ÿ‘‰ [Install Import Cost](https://marketplace.visualstudio.com/items?itemName=wix.vscode-import-cost) ### Manual Installation ๐Ÿ”ง If automatic installation doesn't work: 1. Press `Cmd+Shift+X` (Mac) or `Ctrl+Shift+X` (Windows) 2. Click the extension link above 3. Click "Install" in your browser 4. Repeat for each extension ## Verify Your Setup โœ… After setup, test that everything works: **1. Check Tailwind IntelliSense** ```tsx
// Type "flex" - you should see autocomplete!
``` **2. Check ESLint** Open any `.ts` or `.tsx` file - errors should show red underlines. **3. Check Prettier** Save any file - it should auto-format! **4. Check TypeScript** Hover over any variable - you should see type information. If you see autocomplete, linting, and formatting - you're all set! ๐ŸŽ‰ ## Troubleshooting ๐Ÿ”ง **Extensions not appearing?** * Check you're in the project folder * Restart your editor * Look for notification about recommended extensions **Tailwind autocomplete not working?** * Install "Tailwind CSS IntelliSense" extension * Restart editor * Verify `tailwind.config.ts` exists **ESLint errors everywhere?** * Run `pnpm install` to install dependencies * Restart editor * Wait a few seconds for ESLint to initialize **Prettier not formatting?** * Install Prettier extension * Set Prettier as default formatter * Enable "Format on Save" in editor settings **AI features not working? (Cursor/Windsurf)** * Check you're logged into the AI service * Verify internet connection * Restart the editor ## Next Steps ๐Ÿš€ 1. **Start coding** - Open `src/app/page.tsx` 2. **Test AI features** - Try Cursor (`Cmd+K`) or Windsurf AI 3. **Explore codebase** - Use AI to understand structure 4. **Build your app** - Start customizing! Your IDE is set up with AI superpowers! Start building. ๐Ÿ’ช ## Related Documentation ๐Ÿ“š * [Formatting & Linting](/codebase/formatting-and-linting) - Code quality tools * [Keeping Things Updated](/codebase/keeping-things-updated) - Update your project * [Getting Started](/getting-started) - Installation guide # Keeping Your Project Updated (/codebase/keeping-things-updated) YouTube Video: https://www.youtube.com/watch?v=LLx143cDyhs Stay in sync with the latest Indie Kit features, fixes, and improvements! Get new updates without losing your customizations. โœจ The video tutorial above walks you through the entire process. Follow along for the easiest experience! ## Quick Update Process ๐Ÿš€ ### Step 1: Add Upstream Remote Add Indie Kit as a remote source (one-time setup): **For Indie Kit:** ```bash git remote add ik https://github.com/indie-kit/indie-kit ``` **For B2B Kit:** ```bash git remote add ik https://github.com/indie-kit/b2b-boilerplate ``` You only need to do this once! Skip to Step 2 if you've already added the remote. ### Step 2: Configure Git Set merge strategy for pulls: ```bash git config pull.rebase false ``` **What this does:** Tells git to merge updates instead of rebasing. Safer for keeping your changes! ### Step 3: Pull Latest Updates Get the latest changes: **For Indie Kit:** ```bash git pull ik main ``` **For B2B Kit:** ```bash git pull ik b2b ``` ๐ŸŽฏ Git will automatically merge new features into your project! ### Step 4: Handle Conflicts If you get merge conflicts, don't panic! ๐Ÿ˜Œ **What are conflicts?** Git doesn't know which version to keep when you and Indie Kit both changed the same code. **How to fix:** 1. Open conflicted files in VS Code 2. You'll see conflict markers: `<<<<<<<`, `=======`, `>>>>>>>` 3. Choose which version to keep (yours, theirs, or both) 4. Delete the conflict markers 5. Save the file Check the video tutorial above for visual examples of resolving conflicts! ### Step 5: Commit & Push Finalize the update: ```bash git add . git commit -m "Merge latest Indie Kit updates" git push ``` Done! ๐ŸŽ‰ Your project is now up to date! ## What Gets Updated? ๐Ÿ”„ When you pull updates, you'll get: * โœจ **New features** - Latest components and functionality * ๐Ÿ› **Bug fixes** - Stability improvements * ๐Ÿ” **Security patches** - Important security updates * ๐Ÿ“š **Documentation** - Updated guides * โšก **Performance** - Speed optimizations Your customizations stay intact! Only core Indie Kit files get updated. ## Best Practices โœ… **1. Update Regularly** ```bash # Check for updates weekly git pull ik main # or 'ik b2b' for B2B Kit ``` **2. Commit Before Updating** Always commit your changes first: ```bash git add . git commit -m "Save my changes" git pull ik main ``` **3. Test After Updates** Run your app locally to verify everything works: ```bash pnpm dev ``` **4. Review Changes** Check what was updated: ```bash git log --oneline -10 # See last 10 commits ``` ## Troubleshooting ๐Ÿ”ง **"Please commit your changes before merging"** * Save your work first: `git add . && git commit -m "Save changes"` * Then pull updates: `git pull ik main` **Too many conflicts?** * Watch the video tutorial for conflict resolution tips * Focus on one file at a time * When in doubt, keep your customized code **"Remote 'ik' not found"** * You need to add the remote first (Step 1) * Run: `git remote add ik ` **Updates broke something?** * Revert to previous version: `git revert HEAD` * Check what changed: `git diff HEAD~1` * Ask for help in Discord! ## Next Steps ๐Ÿš€ 1. **Set a reminder** - Update monthly or quarterly 2. **Join Discord** - Get notified of important updates 3. **Check changelog** - See what's new in each release 4. **Test thoroughly** - Verify everything works after updating Regular updates keep your project secure, fast, and feature-rich! ๐ŸŽฏ ## Related Documentation ๐Ÿ“š * [IDE Setup](/codebase/ide-setup) - Set up your editor * [Formatting & Linting](/codebase/formatting-and-linting) - Code quality tools # Multi Tenant Kit (/multi-tenancy/b2b-kit) # Multi Tenant Kit ๐Ÿข Build powerful multi-tenant B2B SaaS applications with organization management, role-based access control, and subscription billing! โœจ ## What is Multi Tenant Kit? ๐Ÿค” The Multi Tenant Kit transforms Indie Kit into a complete B2B SaaS platform with: * ๐Ÿข **Multi-Organization Support**: Users can belong to multiple organizations * ๐Ÿ‘ฅ **Role-Based Access Control**: Owner, Admin, and User roles with permissions * ๐Ÿ’ณ **Subscription Management**: Integrate with Stripe or LemonSqueezy * ๐Ÿ‘‘ **Super Admin Dashboard**: Manage all organizations and plans * ๐Ÿ“Š **Plan Quotas**: Limit features based on subscription tiers * โœ‰๏ธ **Team Invitations**: Invite members to your organization * ๐Ÿ” **Secure API Routes**: Built-in authentication wrappers Multi Tenant Kit is a separate boilerplate that extends Indie Kit with multi-tenancy features. Perfect for building tools like Slack, Notion, or Figma! ## Installation ๐Ÿš€ If you don't have the Multi Tenant Kit yet: ```bash git clone https://github.com/Indie-Kit/b2b-boilerplate cd b2b-boilerplate pnpm install ``` You need a valid Indie Kit B2B license to access this repository. [Get your license here](https://indiekit.pro/app/pricing)! ## Quick Navigation ๐Ÿ—บ๏ธ Jump to what you need: * [Database Structure](#database-structure) - Understanding the data models * [Organizations](#organizations) - How organizations work * [Authentication](#authentication--security) - Securing your routes * [Roles & Permissions](#roles--permissions) - Managing access control * [Invitations](#invitations--memberships) - Team management * [Plans & Billing](#plans--subscriptions) - Subscription management * [Hooks & Utilities](#hooks--utilities) - Developer tools ## Database Structure ๐Ÿ—„๏ธ The Multi Tenant Kit uses a relational database structure designed for multi-tenancy: ### Key Relationships ๐Ÿ”— * **Users โ†” Organizations**: Many-to-many through `OrganizationMembership` * **Organizations โ†’ Plan**: Each organization has one plan (or null for free tier) * **Organizations โ†’ Invites**: Organizations can send multiple invitations * **Organizations โ†’ Billing**: Connected to Stripe/LemonSqueezy for payments All tables use Drizzle ORM. Check `src/db/schema/` for the complete type-safe schema definitions! ## Organizations ๐Ÿข Organizations are the core of the Multi Tenant Kit! Users can create and join multiple organizations, each with its own team, subscription, and settings. ### How Organizations Work ๐Ÿ”„ Think of organizations like workspaces in Slack or teams in Figma: * ๐Ÿ‘ค **Multiple Users**: An organization can have many team members * ๐Ÿ‘ฅ **Multiple Organizations Per User**: Users can belong to many organizations * ๐ŸŽญ **Different Roles**: Each user has a specific role in each organization * ๐Ÿ’ณ **Independent Billing**: Each organization has its own subscription * โš™๏ธ **Isolated Data**: Organization data is completely separated ### Organization Features โœจ **1. Unique Identity** * ID, name, and slug for URL routing * Optional organization image/logo * Timestamps for tracking **2. Role-Based Access Control** * Owner: Full control (billing, deletion, everything) * Admin: Manage members and settings * User: Basic access to features **3. Onboarding System** * Track if onboarding is complete * Store onboarding data (JSON) * Custom onboarding flows **4. Billing Integration** * Stripe or LemonSqueezy support * Customer and subscription IDs stored * Automatic plan assignment **5. Plan Management** * Each organization has a plan (or null for free) * Plan quotas control feature access * Easy plan upgrades/downgrades The current organization is stored in a **cookie session**! When you log in, your first organization is automatically selected. Use `switchOrganization()` to change the active organization without navigating away. ## Authentication & Security ๐Ÿ” The Multi Tenant Kit includes powerful authentication wrappers to protect your API routes. Choose the right wrapper for your security needs! ### Three Levels of Protection ๐Ÿ›ก๏ธ | Wrapper | Protection Level | Use Case | | ------------------------------ | -------------------------- | -------------------------------------- | | `withAuthRequired` | User must be logged in | Personal data, user settings | | `withOrganizationAuthRequired` | User + Organization + Role | Organization resources, team features | | `withSuperAdminAuthRequired` | Super admin only | Platform management, all organizations | ### 1. Basic Authentication: `withAuthRequired` ๐Ÿ‘ค Requires user to be logged in. Perfect for personal features: ```typescript // src/app/api/app/profile/route.ts import withAuthRequired from "@/lib/auth/withAuthRequired"; export const GET = withAuthRequired(async (req, context) => { const user = await context.session.user; // Fetch user's personal data const profile = await getUserProfile(user.id); return NextResponse.json(profile); }); ``` **Use this for:** * User profile endpoints * Personal settings * User-specific data (not org-specific) ### 2. Organization Authentication: `withOrganizationAuthRequired` ๐Ÿข Requires user to belong to the organization + optional role check: ```typescript // src/app/api/app/org/[slug]/settings/route.ts import withOrganizationAuthRequired from "@/lib/auth/withOrganizationAuthRequired"; import { OrganizationRole } from "@/db/schema/organization"; export const PATCH = withOrganizationAuthRequired( async (req, context) => { const organization = await context.session.organization; const body = await req.json(); // Update organization settings await updateSettings(organization.id, body); return NextResponse.json({ success: true }); }, OrganizationRole.enum.admin // Only admin or owner ); ``` **Use this for:** * Organization resources (projects, documents, etc.) * Team settings * Member management * Anything scoped to an organization [Learn more about Organization Auth โ†’](/multi-tenancy/with-organization-auth-required) ### 3. Super Admin Authentication: `withSuperAdminAuthRequired` ๐Ÿ‘‘ Requires super admin privileges. For platform management only: ```typescript // src/app/api/super-admin/organizations/route.ts import withSuperAdminAuthRequired from "@/lib/auth/withSuperAdminAuthRequired"; export const GET = withSuperAdminAuthRequired(async (req, context) => { // Fetch all organizations (admin view) const allOrgs = await getAllOrganizations(); return NextResponse.json(allOrgs); }); ``` **Use this for:** * Managing all organizations * Creating/editing plans * Platform analytics * User impersonation Always use the most restrictive wrapper for your use case! If an endpoint needs organization context, use `withOrganizationAuthRequired`, not just `withAuthRequired`. ## Roles & Permissions ๐Ÿ‘ฅ The Multi Tenant Kit uses a simple but powerful three-tier role system: ```typescript export const roleEnum = pgEnum("role", ["owner", "admin", "user"]); ``` ### Role Hierarchy ๐Ÿ“Š ``` ๐Ÿ‘‘ Owner (Level 2) โ†“ Can do everything ๐Ÿ‘ค Admin (Level 1) โ†“ Can do most things ๐Ÿ™‚ User (Level 0) โ†“ Can use features ``` ### Role Permissions ๐Ÿ”‘ | Permission | Owner | Admin | User | | --------------------- | ----- | ----- | ---- | | Delete organization | โœ… | โŒ | โŒ | | Manage billing | โœ… | โŒ | โŒ | | Invite/remove members | โœ… | โœ… | โŒ | | Change member roles | โœ… | โœ… | โŒ | | Update org settings | โœ… | โœ… | โŒ | | Use features | โœ… | โœ… | โœ… | ### Role Checking in Code ๐Ÿ’ป The `hasHigherOrEqualRole` function compares roles: ```typescript const roleHierarchy = { user: 0, admin: 1, owner: 2, }; // Check if user can perform admin actions if (hasHigherOrEqualRole({ currentRole: userRole, requiredRole: 'admin' })) { // Allow action } ``` This means: * **Owners** can do everything Admins and Users can do * **Admins** can do everything Users can do * **Users** have only their own permissions [Learn more about managing roles โ†’](/multi-tenancy/roles) ## Invitations & Memberships โœ‰๏ธ Build your team by inviting members to your organization! ### How Memberships Work ๐Ÿ‘ฅ Organization memberships connect users to organizations: ```typescript OrganizationMembership { organizationId: string // Which organization userId: string // Which user role: 'owner' | 'admin' | 'user' // Their role createdAt: timestamp updatedAt: timestamp } ``` **Key Features:** * One user can belong to multiple organizations * Each membership has a specific role * Composite primary key prevents duplicates * Timestamps track when they joined ### The Invitation System ๐Ÿ“จ Admins and Owners can invite new members: ```typescript OrganizationInvite { id: string // Unique invite ID email: string // Invitee's email organizationId: string // Target organization role: 'owner' | 'admin' | 'user' // Assigned role token: string // Verification token expiresAt: timestamp // When invite expires createdAt: timestamp } ``` **How it Works:** 1. Admin/Owner sends invite with email and role 2. System creates invite with unique token 3. Email sent to invitee with link 4. Invitee clicks link, creates account (if needed) 5. Membership created automatically 6. Invite marked as used or deleted Invitations expire after a set period (default: 7 days). This prevents unused invites from piling up and improves security! [Learn more about inviting members โ†’](/multi-tenancy/inviting-team-members) ## Plans & Subscriptions ๐Ÿ’ณ Monetize your B2B SaaS with flexible subscription plans! ### Understanding Plans ๐Ÿ“Š Plans define what organizations can do and how much they pay: **Plan Structure:** * Name and codename (for URLs) * Default flag (free tier) * Pricing tiers (monthly, yearly, one-time) * Payment provider integration * Feature quotas **Supported Pricing Models:** * ๐Ÿ’ฐ **Monthly**: Recurring monthly charge * ๐Ÿ“… **Yearly**: Annual subscription (usually discounted) * ๐ŸŽฏ **One-time**: Lifetime access or credits **Payment Providers:** * Stripe (recommended) * LemonSqueezy ### Creating Plans (Super Admin) ๐Ÿ‘‘ Plans are managed through the super admin dashboard: 1. Navigate to `/super-admin/plans` 2. Click **"Create Plan"** 3. Fill in the details: ``` Plan Details: โ”œโ”€ Name: "Professional" โ”œโ”€ Codename: "pro" โ”œโ”€ Default: false โ”œโ”€ Monthly Price: $49 โ”œโ”€ Yearly Price: $490 (save $98!) โ”œโ”€ Stripe Price IDs โ””โ”€ Quotas: { projects: 100, users: 50 } ``` 4. Save and it's live! Quotas are stored as JSON and can be anything: `{ projects: 100, storage: "50GB", apiCalls: 10000 }`. Check quotas in your code to limit features! ### Generating Subscription Links ๐Ÿ”— Use `getSubscribeUrl` to create subscription links: ```tsx import Link from "next/link"; import getSubscribeUrl from "@/lib/plans/getSubscribeUrl"; import { PlanType, PlanProvider } from "@/lib/plans/getSubscribeUrl"; export function PricingCard() { return (

Pro Plan

$49/month

Start Free Trial
); } ``` **getSubscribeUrl Parameters:** * `codename`: Plan identifier (e.g., "pro", "enterprise") * `type`: MONTHLY, YEARLY, or ONETIME * `provider`: STRIPE or LEMON\_SQUEEZY * `trialPeriodDays`: Optional trial period ### Checking Plan Quotas in Code ๐Ÿ“ Enforce plan limits in your application: ```tsx 'use client' import { useOrganization } from '@/lib/organizations/useOrganization' export function CreateProjectButton() { const { organization } = useOrganization() // Check if they can create more projects const currentProjects = 45 // From your database const maxProjects = organization.plan?.quotas.projects || 10 const canCreate = currentProjects < maxProjects return ( ) } ``` ## Hooks & Utilities ๐Ÿช The Multi Tenant Kit provides powerful React hooks and utility functions for your application! ### `useOrganization` Hook ๐Ÿข Your go-to hook for accessing organization data in client components: ```tsx 'use client' import { useOrganization } from '@/lib/organizations/useOrganization' export default function Dashboard() { const { organization, // Current org data with plan and role isLoading, // Loading state error, // Error state mutate, // Refresh data switchOrganization // Switch to another org } = useOrganization() if (isLoading) return
Loading...
if (error) return
Error loading organization
return (

{organization.name}

Plan: {organization.plan?.name}

Your Role: {organization.role}

) } ``` **What you get:** * Organization name, slug, image * Current user's role in the organization * Plan details and quotas * Loading and error states * Functions to refresh or switch organizations [Full useOrganization documentation โ†’](/multi-tenancy/use-organization) ### Organization Utility Functions ๐Ÿ› ๏ธ Server-side utilities for working with organizations: #### `getUserOrganizations(userId)` Fetches all organizations a user belongs to: ```typescript const userOrgs = await getUserOrganizations(userId); // Returns: Array<{id, name, slug, image, role, onboardingDone}> ``` #### `getUserOrganizationById(userId, organizationId)` Fetches a specific organization with plan: ```typescript const org = await getUserOrganizationById(userId, organizationId); // Returns: {id, name, slug, role, plan: {id, name, quotas, ...}} ``` #### `createOrganization(name, creatorId)` Creates a new organization: ```typescript const newOrg = await createOrganization("Acme Corp", userId); // Creator automatically becomes owner ``` #### `hasHigherOrEqualRole({currentRole, requiredRole})` Checks if a role meets requirements: ```typescript if (hasHigherOrEqualRole({ currentRole: 'admin', requiredRole: 'admin' })) { // Allow action } ``` #### `userBelongsToOrganization(userId, organizationId)` Verifies membership: ```typescript const isMember = await userBelongsToOrganization(userId, orgId); // Returns: boolean ``` All utilities are fully typed with TypeScript! Your IDE will autocomplete everything. ## Quick Start Guide ๐Ÿš€ Ready to build? Here's how to get started: ### 1. Install Multi Tenant Kit โœ… ```bash git clone https://github.com/Indie-Kit/b2b-boilerplate cd b2b-boilerplate pnpm install ``` ### 2. Set Up Database ๐Ÿ—„๏ธ ```bash pnpm db:push # Push schema to database ``` ### 3. Create Your First Plan ๐Ÿ’ณ 1. Run the app: `pnpm dev` 2. Make yourself super admin (check docs) 3. Go to `/super-admin/plans` 4. Create a "Free" plan with default: true 5. Create paid plans (Pro, Enterprise, etc.) ### 4. Build Organization Features ๐Ÿข Use the provided hooks and wrappers: ```tsx // Client component with organization data 'use client' import { useOrganization } from '@/lib/organizations/useOrganization' // API route with organization auth import { withOrganizationAuthRequired } from '@/lib/auth/withOrganizationAuthRequired' ``` ### 5. Test Everything โœจ * Create an organization * Invite team members * Subscribe to a plan * Test role permissions * Verify quota enforcement ## Related Documentation ๐Ÿ“š Deep dive into specific topics: * [Organization Roles](/multi-tenancy/roles) - Managing permissions * [useOrganization Hook](/multi-tenancy/use-organization) - Client-side hook * [withOrganizationAuthRequired](/multi-tenancy/with-organization-auth-required) - API security * [Inviting Team Members](/multi-tenancy/inviting-team-members) - Team management Remember: The current organization is managed via **cookie session**, not URL routing! This means users stay on the same URL when switching organizations - the context changes seamlessly in the background. You now have everything you need to build a powerful B2B SaaS application! Start with the Quick Start Guide above and refer back to specific sections as needed. ๐Ÿš€ # Inviting Team Members (/multi-tenancy/inviting-team-members) # Inviting Team Members ๐Ÿ‘ฅ Invite members to your organization! The complete team management system is built-in and ready to use. โœจ Only **Admins** and **Owners** can invite members. Regular users can view the team but cannot send invitations. ## Using Team Management (Built-In) ๐Ÿš€ The team management UI is already implemented! Just use it: ### Navigate to Team Page Go to **Settings** โ†’ **Team** in your organization. ### Invite New Members 1. Click **"Invite Member"** button 2. Enter their email address 3. Select role: **User** or **Admin** 4. Click **"Send Invitation"** Done! The invitation email is sent automatically. ๐Ÿ“ง The team page includes everything: member list, pending invites, role management, and removal. No code needed! ### Manage Your Team From the Team page: * ๐Ÿ‘ฅ **View members** - See all team members with avatars and roles * ๐Ÿ“ง **Pending invites** - Track sent invitations and expiry dates * ๐Ÿ”„ **Change roles** - Click menu โ†’ "Change Role" * โŒ **Remove members** - Click menu โ†’ "Remove Member" * ๐Ÿšซ **Cancel invites** - Revoke pending invitations ## How Invitations Work ๐ŸŽฏ **Complete flow:** 1. **Admin sends invite** โ†’ Email + role specified 2. **Email sent** โ†’ Unique link with 7-day expiry 3. **User accepts** โ†’ Clicks link, creates account if needed 4. **Membership created** โ†’ User joins with assigned role 5. **Invite deleted** โ†’ Cleanup after acceptance **Security features:** * โœ… Invitations expire after 7 days * โœ… One-time use tokens * โœ… Email verification required * โœ… Role-based access control Email sending, token generation, expiry handling, and membership creation are all automatic! Just use the UI. ## Customization (Advanced) ๐ŸŽจ Want to customize the invitation system? You can modify these files: ### Email Templates **Invitation email:** ``` src/emails/InvitationEmail.tsx ``` Customize the design, content, and branding of invitation emails. **Access revoked email:** ``` src/emails/AccessRevokedEmail.tsx ``` Customize the email sent when a member is removed. ### Team Page UI **Complete team management page:** ``` src/app/(in-app)/(organization)/app/settings/team/page.tsx ``` The page includes: * `` - Main team page layout * `` - Member list with table * `` - Pending invitations list * `` - Invite member modal * `` - Cancel invite modal * `` - Change role modal * `` - Remove member modal **What you can customize:** * Table layout and styling * Dialog designs * Add custom fields (department, title, etc.) * Bulk invite actions * Custom validation rules All team management code is in your project! Customize the UI, emails, and logic to match your needs. ## Example Customizations ๐Ÿ’ก ### Custom Invitation Email Edit `src/emails/InvitationEmail.tsx`: ```tsx export default function InvitationEmail({ organizationName, inviterName, role, inviteUrl, }) { return (

๐ŸŽ‰ Join {organizationName}!

{inviterName} has invited you to join{' '} {organizationName} as a {role}.

Accept Invitation โ†’

This invitation expires in 7 days.

) } ``` ### Add Department Field Extend the invitation to include department: 1. **Update invite dialog** - Add department input field 2. **Update API** - Include department in request 3. **Store in database** - Add department to memberships table 4. **Display in UI** - Show department in member list ## Troubleshooting ๐Ÿ”ง **Can't see Invite button?** * Check your role (must be Admin or Owner) * Verify you're on the Team page * Look in Settings โ†’ Team **Invitation not sending?** * Check [email provider](/setup/email) is configured * Verify email template exists * Test with your own email first * Check server logs for errors **Member not receiving email?** * Check spam/junk folder * Verify email address is correct * Resend the invitation * Check email provider logs **Can't remove member?** * Must be Admin or Owner * Can't remove the organization owner * Check for role permissions Always test invitations with your own email before inviting real team members! ## Next Steps ๐Ÿš€ 1. **Test the system** - Invite yourself to see the flow 2. **Customize emails** - Update templates to match your brand 3. **Customize UI** - Modify team page if needed 4. **Understand roles** - Learn about [role permissions](/multi-tenancy/roles) Your team management system is production-ready! Start inviting members and building your team. ๐Ÿ‘ฅ ## Related Documentation ๐Ÿ“š * [Organization Roles](/multi-tenancy/roles) - Understanding role permissions * [useOrganization Hook](/multi-tenancy/use-organization) - Client-side org data * [withOrganizationAuthRequired](/multi-tenancy/with-organization-auth-required) - API security * [Multi Tenant Kit Overview](/multi-tenancy/b2b-kit) - Complete B2B guide # Roles (/multi-tenancy/roles) # Organization Roles ๐Ÿ‘ฅ The Multi Tenant Kit includes a powerful role-based permission system to control what members can do in your organization! โœจ ## Understanding Roles ๐ŸŽฏ Roles determine what actions members can perform within an organization. Each member has exactly one role that defines their level of access. Multi Tenant Kit includes three pre-configured roles: **Owner**, **Admin**, and **User**. These cover most B2B SaaS use cases! Need more roles? You can customize the role system by updating the `roleEnum` in `src/db/schema/organization.ts`. Add roles like **Manager**, **Viewer**, or **Contributor** to match your use case! ## The Three Roles ๐Ÿ† ### Owner (Highest Permission) The organization creator and ultimate authority: * โœ… **Full Access**: Can do everything in the organization * โœ… **Billing Management**: Control subscriptions and payments * โœ… **Delete Organization**: Can permanently delete the organization * โœ… **Transfer Ownership**: Can assign ownership to another member * โœ… **All Admin Permissions**: Everything an admin can do, plus more **Use Case**: Typically the person who created the organization or pays for the subscription. ### Admin (Management Permission) Trusted team members who help manage the organization: * โœ… **Member Management**: Invite, remove, and change member roles * โœ… **Settings Management**: Update organization settings and preferences * โœ… **Resource Management**: Create, update, and delete organization resources * โœ… **View Billing**: Can see billing information (but not modify) * โŒ **Cannot Delete Organization**: Only owners can delete * โŒ **Cannot Transfer Ownership**: Only owners can change ownership **Use Case**: Team leads, managers, or trusted employees who help run the organization. ### User (Basic Access) Standard members with basic permissions: * โœ… **Use Features**: Access and use the application's features * โœ… **View Organization Info**: See organization details * โœ… **Manage Own Profile**: Update their own profile * โŒ **Cannot Invite Members**: Cannot invite new people * โŒ **Cannot Change Settings**: Cannot modify organization settings * โŒ **Cannot Access Billing**: Cannot see billing information **Use Case**: Regular team members, employees, or collaborators. ## Role Hierarchy ๐Ÿ“Š Roles have a hierarchical structure where higher roles have all permissions of lower roles: ```typescript const roleHierarchy = { user: 0, // Lowest admin: 1, // Middle owner: 2, // Highest } ``` This means: * **Owners** can do everything Admins and Users can do * **Admins** can do everything Users can do * **Users** have only their own permissions ## Protecting API Routes with Roles ๐Ÿ”’ Use `withOrganizationAuthRequired` to protect routes and enforce role requirements: ### Require Minimum Role ```tsx // src/app/api/app/org/[slug]/settings/route.ts import { withOrganizationAuthRequired } from '@/lib/auth/withOrganizationAuthRequired' import { OrganizationRole } from '@/db/schema/organization' import { NextResponse } from 'next/server' // Only admins and owners can access this route export const PATCH = withOrganizationAuthRequired( async (req, context) => { const { organization } = context.session const body = await req.json() // Update organization settings await updateOrganizationSettings(organization.id, body) return NextResponse.json({ success: true }) }, OrganizationRole.enum.admin // Requires admin role or higher ) ``` ### Owner-Only Routes ```tsx // src/app/api/app/org/[slug]/delete/route.ts import { withOrganizationAuthRequired } from '@/lib/auth/withOrganizationAuthRequired' import { OrganizationRole } from '@/db/schema/organization' // Only owners can delete organizations export const DELETE = withOrganizationAuthRequired( async (req, context) => { const { organization } = context.session // Delete organization await deleteOrganization(organization.id) return NextResponse.json({ success: true }) }, OrganizationRole.enum.owner // Requires owner role ) ``` ### User-Level Routes (No Minimum Role) ```tsx // src/app/api/app/org/[slug]/projects/route.ts import { withOrganizationAuthRequired } from '@/lib/auth/withOrganizationAuthRequired' // Any organization member can access export const GET = withOrganizationAuthRequired( async (req, context) => { const { organization } = context.session // Fetch projects const projects = await getProjects(organization.id) return NextResponse.json(projects) } // No role parameter = any member can access ) ``` When you specify a minimum role, the wrapper automatically allows users with that role **or higher**. For example, `admin` allows both admins and owners! ## Checking Roles in Components ๐ŸŽจ ### Using useOrganization Hook ```tsx 'use client' import { useOrganization } from '@/lib/organizations/useOrganization' export default function OrganizationSettings() { const { organization } = useOrganization() // Get current user's role const userRole = organization.membership?.role const isOwner = userRole === 'owner' const isAdmin = userRole === 'admin' || userRole === 'owner' return (

Organization Settings

{/* Show delete button only to owners */} {isOwner && ( )} {/* Show invite button to admins and owners */} {isAdmin && ( )} {/* Everyone sees this */}
Organization Name: {organization.name}
) } ``` ### Checking Role Hierarchy Use the `hasHigherOrEqualRole` utility: ```tsx import { hasHigherOrEqualRole } from '@/lib/organizations/roles' // Check if user has sufficient role const canManageMembers = hasHigherOrEqualRole( currentUserRole, 'admin' ) if (canManageMembers) { // Show member management UI } ``` ## Assigning Roles to Members ๐Ÿ‘ค ### When Inviting New Members Specify the role when creating an invitation: ```tsx // src/app/api/app/org/[slug]/invites/route.ts import { withOrganizationAuthRequired } from '@/lib/auth/withOrganizationAuthRequired' import { OrganizationRole } from '@/db/schema/organization' export const POST = withOrganizationAuthRequired( async (req, context) => { const { organization } = context.session const { email, role } = await req.json() // Create invitation with specified role await createInvitation({ organizationId: organization.id, email, role: role || 'user', // Default to 'user' if not specified }) return NextResponse.json({ success: true }) }, OrganizationRole.enum.admin // Only admins can invite ) ``` ### Changing Member Roles Update an existing member's role: ```tsx // src/app/api/app/org/[slug]/members/[userId]/route.ts import { withOrganizationAuthRequired } from '@/lib/auth/withOrganizationAuthRequired' import { OrganizationRole } from '@/db/schema/organization' export const PATCH = withOrganizationAuthRequired( async (req, context) => { const { organization } = context.session const { userId } = context.params const { role } = await req.json() // Update member role await updateMemberRole(organization.id, userId, role) return NextResponse.json({ success: true }) }, OrganizationRole.enum.admin // Only admins can change roles ) ``` ## Role-Based UI Examples ๐ŸŽฏ ### Conditional Rendering ```tsx 'use client' import { useOrganization } from '@/lib/organizations/useOrganization' export function MembersList() { const { organization } = useOrganization() const isAdmin = ['admin', 'owner'].includes(organization.membership?.role) return (

Team Members

{/* List all members */} {organization.members.map(member => (

{member.name}

{member.role}

{/* Only admins see action buttons */} {isAdmin && (
)}
))} {/* Only admins see invite button */} {isAdmin && ( )}
) } ``` ### Navigation Based on Roles ```tsx 'use client' import Link from 'next/link' import { useOrganization } from '@/lib/organizations/useOrganization' export function OrgNavigation() { const { organization } = useOrganization() const role = organization.membership?.role const isOwner = role === 'owner' const isAdmin = ['admin', 'owner'].includes(role) return ( ) } ``` ## Best Practices ๐Ÿ’ก ### 1. Always Protect API Routes ```tsx // โŒ Bad - No role check export async function DELETE(req: Request) { await deleteOrganization() } // โœ… Good - Protected with role requirement export const DELETE = withOrganizationAuthRequired( async (req, context) => { await deleteOrganization() }, OrganizationRole.enum.owner ) ``` ### 2. Use Role Hierarchy ```tsx // โŒ Bad - Checking each role individually if (role === 'admin' || role === 'owner') { // Allow action } // โœ… Good - Using hasHigherOrEqualRole if (hasHigherOrEqualRole(role, 'admin')) { // Allow action } ``` ### 3. Default to Least Privilege ```tsx // โœ… Default to 'user' role when inviting const role = invitationData.role || 'user' ``` ### 4. Hide UI for Unauthorized Actions ```tsx // โœ… Don't show buttons users can't use {isAdmin && ( )} ``` ### 5. Validate on Backend ```tsx // โœ… Always validate permissions on the server export const DELETE = withOrganizationAuthRequired( async (req, context) => { // Server-side validation if (context.session.membership.role !== 'owner') { return NextResponse.json( { error: 'Unauthorized' }, { status: 403 } ) } // Proceed with deletion } ) ``` ## Common Use Cases ๐Ÿ“š ### Creating a Members Page ```tsx 'use client' import { useOrganization } from '@/lib/organizations/useOrganization' import { useState } from 'react' export default function MembersPage() { const { organization } = useOrganization() const [inviteEmail, setInviteEmail] = useState('') const [inviteRole, setInviteRole] = useState('user') const isAdmin = ['admin', 'owner'].includes( organization.membership?.role ) const handleInvite = async () => { await fetch(`/api/app/org/${organization.slug}/invites`, { method: 'POST', body: JSON.stringify({ email: inviteEmail, role: inviteRole }), }) } return (

Team Members

{/* Invite form - only for admins */} {isAdmin && (

Invite New Member

setInviteEmail(e.target.value)} placeholder="email@example.com" className="border px-3 py-2 rounded" />
)} {/* Members list */}
{organization.members?.map(member => (

{member.name}

{member.email}

{member.role} {isAdmin && member.role !== 'owner' && ( )}
))}
) } ``` ## Troubleshooting ๐Ÿ”ง ### "Unauthorized" Error Make sure you're using the correct role wrapper: ```tsx // Check that you have the required role export const PATCH = withOrganizationAuthRequired( async (req, context) => { // Your code }, OrganizationRole.enum.admin // Make sure you have admin role ) ``` ### Role Not Updating After changing roles, refresh the session: ```tsx import { mutate } from 'swr' // After updating role await updateMemberRole(userId, newRole) mutate('/api/app/org/current') // Refresh organization data ``` ### Can't Access Organization Data Make sure you're inside an organization context: ```tsx 'use client' import { useOrganization } from '@/lib/organizations/useOrganization' export default function MyComponent() { const { organization, isLoading } = useOrganization() if (isLoading) return
Loading...
if (!organization) return
No organization selected
// Now you can safely access organization data } ``` ## Summary ๐Ÿ“‹ You now know how to: * โœ… Understand the three role types (Owner, Admin, User) * โœ… Protect API routes with role requirements * โœ… Check roles in components * โœ… Assign and change member roles * โœ… Build role-based UIs * โœ… Follow security best practices With roles properly configured, you can build secure multi-user organizations! Remember to always validate permissions on the backend. ๐ŸŽ‰ ## Related Documentation ๐Ÿ“š * [Inviting Team Members](/multi-tenancy/inviting-team-members) * [Using useOrganization Hook](/multi-tenancy/use-organization) * [Organization Auth Required](/multi-tenancy/with-organization-auth-required) * [Multi Tenant Kit Overview](/multi-tenancy/b2b-kit) # useOrganization (/multi-tenancy/use-organization) # useOrganization Hook ๐Ÿข The `useOrganization` hook is your primary way to access and manage organization data in client components! โœจ ## What It Does ๐ŸŽฏ The hook provides: * ๐Ÿข Current organization data (name, slug, plan, etc.) * ๐Ÿ‘ฅ User's membership info (role, permissions) * ๐Ÿ”„ Loading and error states * ๐Ÿ” Ability to refresh data * ๐Ÿ”€ Function to switch organizations This hook uses SWR and must be used in client components. Add `'use client'` at the top of your file! ## Basic Usage ๐Ÿ“ ```tsx 'use client' import { useOrganization } from '@/lib/organizations/useOrganization' export default function Dashboard() { const { organization, isLoading, error } = useOrganization() if (isLoading) return
Loading...
if (error) return
Error loading organization
return (

Welcome to {organization.name}

Your role: {organization.role}

) } ``` ## Return Values ๐Ÿ“ฆ The hook returns an object with the following properties: ```typescript { organization: { id: string name: string slug: string image?: string onboardingDone: boolean role: 'owner' | 'admin' | 'user' plan: { id: string name: string codename: string default: boolean quotas: object requiredCouponCount: number } | null } isLoading: boolean error: Error | null mutate: () => void switchOrganization: (organizationId: string) => Promise } ``` The `plan` property can be `null` if the organization doesn't have a plan assigned yet. Always check for null before accessing plan properties! ## Common Use Cases ๐Ÿš€ ### Display Organization Info ```tsx 'use client' import { useOrganization } from '@/lib/organizations/useOrganization' export function OrganizationHeader() { const { organization, isLoading } = useOrganization() if (isLoading) return
Loading...
return (
{organization.image && ( {organization.name} )}

{organization.name}

{organization.plan?.name || 'No Plan'} Plan

) } ``` ### Check User Role ```tsx 'use client' import { useOrganization } from '@/lib/organizations/useOrganization' export function MembersButton() { const { organization } = useOrganization() const isAdmin = ['admin', 'owner'].includes(organization.role) if (!isAdmin) return null return ( ) } ``` ### Access Plan Quotas ```tsx 'use client' import { useOrganization } from '@/lib/organizations/useOrganization' export function ProjectsPage() { const { organization } = useOrganization() if (!organization.plan) { return
No plan assigned
} const { projects } = organization.plan.quotas const currentProjects = 5 // Fetch from your data const canCreateMore = currentProjects < projects return (

Projects: {currentProjects} / {projects}

{!canCreateMore && (

Upgrade your plan to create more projects

)}
) } ``` ### Refresh Organization Data ```tsx 'use client' import { useOrganization } from '@/lib/organizations/useOrganization' import { toast } from 'sonner' export function OrganizationSettings() { const { organization, mutate } = useOrganization() const updateSettings = async (settings: any) => { try { await fetch(`/api/app/org/${organization.slug}/settings`, { method: 'PATCH', body: JSON.stringify(settings) }) // Refresh organization data mutate() toast.success('Settings updated!') } catch (error) { toast.error('Failed to update settings') } } return (
) } ``` ### Switch Organizations ```tsx 'use client' import { useOrganization } from '@/lib/organizations/useOrganization' import { useRouter } from 'next/navigation' export function OrganizationSwitcher({ organizations }) { const { organization, switchOrganization } = useOrganization() const handleSwitch = async (organizationId: string) => { await switchOrganization(organizationId) // Redirects to /app automatically } return ( ) } ``` ## Loading States ๐Ÿ”„ Always handle loading and error states: ```tsx 'use client' import { useOrganization } from '@/lib/organizations/useOrganization' import { Skeleton } from '@/components/ui/skeleton' export default function Dashboard() { const { organization, isLoading, error } = useOrganization() // Loading state with skeleton if (isLoading) { return (
) } // Error state if (error) { return (

Failed to load organization

) } // Success state return (

{organization.name}

{/* Your content */}
) } ``` ## Real-World Example ๐Ÿ’ผ Complete dashboard component with organization data: ```tsx 'use client' import { useOrganization } from '@/lib/organizations/useOrganization' import { Skeleton } from '@/components/ui/skeleton' export default function OrganizationDashboard() { const { organization, isLoading, error, mutate } = useOrganization() if (isLoading) { return (
) } if (error) { return (
Error loading organization
) } const isOwner = organization.role === 'owner' const isAdmin = ['admin', 'owner'].includes(organization.role) return (
{/* Header */}

{organization.name}

{organization.plan?.name || 'No Plan'} ยท {organization.role}

{/* Stats */}

Plan

{organization.plan?.name || 'No Plan'}

Your Role

{organization.role}

Onboarding

{organization.onboardingDone ? 'โœ“' : 'Pending'}

{/* Actions */}
{isAdmin && ( <> )} {isOwner && ( )}
) } ``` ## Best Practices ๐Ÿ’ก ### 1. Always Handle Loading States ```tsx if (isLoading) return ``` ### 2. Handle Errors Gracefully ```tsx if (error) return ``` ### 3. Check Role Before Actions ```tsx const canInvite = ['admin', 'owner'].includes(organization.role) if (!canInvite) return null ``` ### 4. Refresh After Mutations ```tsx await updateOrganization() mutate() // Refresh data ``` ### 5. Use TypeScript ```tsx import type { Organization } from '@/lib/organizations/types' const { organization }: { organization: Organization } = useOrganization() ``` ## Troubleshooting ๐Ÿ”ง ### "organization is undefined" Make sure the component is inside an organization route: ```tsx // โœ… Good - Inside /app/org/[slug]/ 'use client' import { useOrganization } from '@/lib/organizations/useOrganization' export default function Page() { const { organization } = useOrganization() // organization will be available } ``` ### "Must be used in client component" Add `'use client'` at the top: ```tsx 'use client' // Add this! import { useOrganization } from '@/lib/organizations/useOrganization' ``` ### Data Not Updating Call `mutate()` after making changes: ```tsx const { mutate } = useOrganization() await updateOrganization() mutate() // Force refresh ``` ### Switch Organization Not Working The function automatically redirects to `/app`: ```tsx const { switchOrganization } = useOrganization() // This will switch and redirect automatically await switchOrganization(newOrganizationId) // No need to manually navigate - it's handled for you! ``` The `switchOrganization` function shows toast notifications automatically for loading, success, and error states! ## API Reference ๐Ÿ“š ### Parameters The hook takes no parameters. ### Returns | Property | Type | Description | | -------------------- | ------------------------------------------- | -------------------------------------------------------------- | | `organization` | `UserOrganizationWithPlan` | Current organization data with plan and role | | `isLoading` | `boolean` | True while loading organization data | | `error` | `Error \| null` | Error object if request failed | | `mutate` | `() => void` | Function to refresh organization data | | `switchOrganization` | `(organizationId: string) => Promise` | Switch to a different organization (shows toast and redirects) | ## Summary ๐Ÿ“‹ You now know how to: * โœ… Use the useOrganization hook in components * โœ… Access organization and membership data * โœ… Handle loading and error states * โœ… Check user roles and permissions * โœ… Refresh organization data * โœ… Switch between organizations * โœ… Build role-based UIs The useOrganization hook gives you everything you need to build organization-aware components! ๐ŸŽ‰ ## Related Documentation ๐Ÿ“š * [Organization Roles](/multi-tenancy/roles) * [withOrganizationAuthRequired](/multi-tenancy/with-organization-auth-required) * [Inviting Team Members](/multi-tenancy/inviting-team-members) * [Multi Tenant Kit Overview](/multi-tenancy/b2b-kit) # withOrganizationAuthRequired (/multi-tenancy/with-organization-auth-required) # withOrganizationAuthRequired ๐Ÿ”’ Protect your API routes with organization-level authentication and role-based access control! โœจ ## What It Does ๐ŸŽฏ The `withOrganizationAuthRequired` wrapper: * ๐Ÿ” Verifies user is authenticated * ๐Ÿข Checks user belongs to the organization * ๐Ÿ‘ฎ Enforces minimum role requirements * ๐Ÿ“ฆ Provides organization data in context * โšก Returns 401/403 errors automatically This wrapper is for API routes only. For client components, use the `useOrganization` hook! ## Basic Usage ๐Ÿ“ ### Without Role Requirement Any organization member can access: ```tsx // src/app/api/app/org/[slug]/projects/route.ts import { withOrganizationAuthRequired } from '@/lib/auth/withOrganizationAuthRequired' import { NextResponse } from 'next/server' export const GET = withOrganizationAuthRequired( async (req, context) => { const organization = await context.session.organization // Fetch projects for this organization const projects = await getProjects(organization.id) return NextResponse.json(projects) } ) ``` ### With Role Requirement Only admins and owners can access: ```tsx // src/app/api/app/org/[slug]/settings/route.ts import { withOrganizationAuthRequired } from '@/lib/auth/withOrganizationAuthRequired' import { OrganizationRole } from '@/db/schema/organization' import { NextResponse } from 'next/server' export const PATCH = withOrganizationAuthRequired( async (req, context) => { const organization = await context.session.organization const body = await req.json() // Update organization settings await updateOrganizationSettings(organization.id, body) return NextResponse.json({ success: true }) }, OrganizationRole.enum.admin // Requires admin or owner ) ``` ## Context Object ๐Ÿ“ฆ The wrapper provides a `context` object with: ```typescript { session: { expires: string user: Promise<{ id: string email: string name: string image?: string // ... other user fields }> organization: Promise<{ id: string name: string slug: string image?: string role: 'owner' | 'admin' | 'user' plan: { id: string name: string codename: string default: boolean quotas: object requiredCouponCount: number } | null }> } params: Promise<{ slug?: string // ... other route params }> } ``` The `user` and `organization` properties are async getters (Promises). You must await them before accessing their properties! ## Common Patterns ๐Ÿš€ ### GET - Fetch Organization Data ```tsx // src/app/api/app/org/[slug]/projects/route.ts import { withOrganizationAuthRequired } from '@/lib/auth/withOrganizationAuthRequired' import { NextResponse } from 'next/server' import { db } from '@/db' import { projects } from '@/db/schema' import { eq } from 'drizzle-orm' export const GET = withOrganizationAuthRequired( async (req, context) => { const organization = await context.session.organization // Fetch projects for this organization const orgProjects = await db .select() .from(projects) .where(eq(projects.organizationId, organization.id)) return NextResponse.json(orgProjects) } ) ``` ### POST - Create Resource ```tsx // src/app/api/app/org/[slug]/projects/route.ts import { withOrganizationAuthRequired } from '@/lib/auth/withOrganizationAuthRequired' import { OrganizationRole } from '@/db/schema/organization' import { NextResponse } from 'next/server' import { db } from '@/db' import { projects } from '@/db/schema' export const POST = withOrganizationAuthRequired( async (req, context) => { const organization = await context.session.organization const user = await context.session.user const body = await req.json() // Validate input if (!body.name || body.name.length < 3) { return NextResponse.json( { error: 'Name must be at least 3 characters' }, { status: 400 } ) } // Create project const newProject = await db.insert(projects).values({ organizationId: organization.id, name: body.name, createdBy: user.id, }).returning() return NextResponse.json(newProject[0]) }, OrganizationRole.enum.admin // Only admins can create ) ``` ### PATCH - Update Resource ```tsx // src/app/api/app/org/[slug]/settings/route.ts import { withOrganizationAuthRequired } from '@/lib/auth/withOrganizationAuthRequired' import { OrganizationRole } from '@/db/schema/organization' import { NextResponse } from 'next/server' import { db } from '@/db' import { organizations } from '@/db/schema' import { eq } from 'drizzle-orm' export const PATCH = withOrganizationAuthRequired( async (req, context) => { const organization = await context.session.organization const body = await req.json() // Update organization await db .update(organizations) .set({ name: body.name, image: body.image, updatedAt: new Date(), }) .where(eq(organizations.id, organization.id)) return NextResponse.json({ success: true }) }, OrganizationRole.enum.admin ) ``` ### DELETE - Remove Resource ```tsx // src/app/api/app/org/[slug]/delete/route.ts import { withOrganizationAuthRequired } from '@/lib/auth/withOrganizationAuthRequired' import { OrganizationRole } from '@/db/schema/organization' import { NextResponse } from 'next/server' export const DELETE = withOrganizationAuthRequired( async (req, context) => { const organization = await context.session.organization // Delete organization and all related data await deleteOrganization(organization.id) return NextResponse.json({ success: true }) }, OrganizationRole.enum.owner // Only owners can delete ) ``` ## Role-Based Examples ๐ŸŽญ ### Owner-Only Routes ```tsx // src/app/api/app/org/[slug]/billing/route.ts import { withOrganizationAuthRequired } from '@/lib/auth/withOrganizationAuthRequired' import { OrganizationRole } from '@/db/schema/organization' import { NextResponse } from 'next/server' export const GET = withOrganizationAuthRequired( async (req, context) => { const organization = await context.session.organization // Fetch billing information const billing = await getBillingInfo(organization.id) return NextResponse.json(billing) }, OrganizationRole.enum.owner // Owner only ) ``` ### Admin-Level Routes ```tsx // src/app/api/app/org/[slug]/members/[userId]/route.ts import { withOrganizationAuthRequired } from '@/lib/auth/withOrganizationAuthRequired' import { OrganizationRole } from '@/db/schema/organization' import { NextResponse } from 'next/server' // Remove member (admin or owner) export const DELETE = withOrganizationAuthRequired( async (req, context) => { const organization = await context.session.organization const params = await context.params const { userId } = params // Remove member await removeMember(organization.id, userId) return NextResponse.json({ success: true }) }, OrganizationRole.enum.admin ) // Change member role (admin or owner) export const PATCH = withOrganizationAuthRequired( async (req, context) => { const organization = await context.session.organization const params = await context.params const { userId } = params const { role } = await req.json() // Update member role await updateMemberRole(organization.id, userId, role) return NextResponse.json({ success: true }) }, OrganizationRole.enum.admin ) ``` ### User-Level Routes ```tsx // src/app/api/app/org/[slug]/profile/route.ts import { withOrganizationAuthRequired } from '@/lib/auth/withOrganizationAuthRequired' import { NextResponse } from 'next/server' // Any member can view their profile export const GET = withOrganizationAuthRequired( async (req, context) => { const { user, organization } = context.session // Get user's profile in this organization const profile = await getUserProfile(user.id, organization.id) return NextResponse.json(profile) } // No role requirement = any member can access ) ``` ## Advanced Use Cases ๐Ÿ’ก ### Check Plan Quotas ```tsx // src/app/api/app/org/[slug]/projects/route.ts import { withOrganizationAuthRequired } from '@/lib/auth/withOrganizationAuthRequired' import { OrganizationRole } from '@/db/schema/organization' import { NextResponse } from 'next/server' export const POST = withOrganizationAuthRequired( async (req, context) => { const organization = await context.session.organization const body = await req.json() // Check quota const currentProjects = await getProjectCount(organization.id) const maxProjects = organization.plan.quotas.projects if (currentProjects >= maxProjects) { return NextResponse.json( { error: 'Project limit reached. Please upgrade your plan.' }, { status: 403 } ) } // Create project const project = await createProject({ organizationId: organization.id, ...body, }) return NextResponse.json(project) }, OrganizationRole.enum.admin ) ``` ### Custom Role Logic ```tsx // src/app/api/app/org/[slug]/projects/[projectId]/route.ts import { withOrganizationAuthRequired } from '@/lib/auth/withOrganizationAuthRequired' import { NextResponse } from 'next/server' export const DELETE = withOrganizationAuthRequired( async (req, context) => { const organization = await context.session.organization const user = await context.session.user const params = await context.params const { projectId } = params // Get project const project = await getProject(projectId) // Check permissions: Owner, Admin, or project creator const canDelete = organization.role === 'owner' || organization.role === 'admin' || project.createdBy === user.id if (!canDelete) { return NextResponse.json( { error: 'Insufficient permissions' }, { status: 403 } ) } // Delete project await deleteProject(projectId) return NextResponse.json({ success: true }) } ) ``` ### Validate Organization Ownership ```tsx // src/app/api/app/org/[slug]/resources/[resourceId]/route.ts import { withOrganizationAuthRequired } from '@/lib/auth/withOrganizationAuthRequired' import { NextResponse } from 'next/server' export const GET = withOrganizationAuthRequired( async (req, context) => { const organization = await context.session.organization const params = await context.params const { resourceId } = params // Fetch resource const resource = await getResource(resourceId) // Verify resource belongs to this organization if (resource.organizationId !== organization.id) { return NextResponse.json( { error: 'Resource not found' }, { status: 404 } ) } return NextResponse.json(resource) } ) ``` ## Best Practices ๐Ÿ’ก ### 1. Always Validate Input ```tsx export const POST = withOrganizationAuthRequired( async (req, context) => { const body = await req.json() // โœ… Validate input if (!body.name || body.name.length < 3) { return NextResponse.json( { error: 'Invalid input' }, { status: 400 } ) } // Process valid data } ) ``` ### 2. Use Appropriate Roles ```tsx // โŒ Bad - No role check for sensitive operation export const DELETE = withOrganizationAuthRequired(async (req, context) => { await deleteOrganization() }) // โœ… Good - Require owner for deletion export const DELETE = withOrganizationAuthRequired( async (req, context) => { await deleteOrganization() }, OrganizationRole.enum.owner ) ``` ### 3. Check Resource Ownership ```tsx // โœ… Verify resource belongs to organization const resource = await getResource(resourceId) if (resource.organizationId !== organization.id) { return NextResponse.json({ error: 'Not found' }, { status: 404 }) } ``` ### 4. Return Proper Status Codes ```tsx // 400 - Bad Request return NextResponse.json({ error: 'Invalid data' }, { status: 400 }) // 403 - Forbidden (authenticated but no permission) return NextResponse.json({ error: 'Forbidden' }, { status: 403 }) // 404 - Not Found return NextResponse.json({ error: 'Not found' }, { status: 404 }) ``` ### 5. Check Plan Quotas ```tsx // โœ… Enforce plan limits if (currentUsage >= plan.quotas.limit) { return NextResponse.json( { error: 'Plan limit reached' }, { status: 403 } ) } ``` ## Error Responses ๐Ÿšจ The wrapper automatically handles these errors: ### 401 Unauthorized User is not logged in: ```json { "error": "Unauthorized", "message": "You must be logged in to access this resource" } ``` ### 403 Forbidden User doesn't have required role: ```json { "error": "Forbidden", "message": "Insufficient permissions" } ``` ### 404 Not Found Organization not found or user not a member: ```json { "error": "Not Found", "message": "Organization not found" } ``` ## Troubleshooting ๐Ÿ”ง ### "Unauthorized" Error Make sure user is logged in and has session: ```tsx // Test with a valid session token const response = await fetch('/api/app/org/my-org/projects', { headers: { 'Cookie': 'session=...' } }) ``` ### "Forbidden" Error Check if user has the required role: ```tsx // Verify user's role const { membership } = await getOrganization(slug) console.log('User role:', membership.role) // Make sure role meets requirements export const PATCH = withOrganizationAuthRequired( async (req, context) => { // ... }, OrganizationRole.enum.admin // Requires admin or owner ) ``` ### "Organization not found" Verify the slug in the URL matches an existing organization: ```tsx // Check URL: /api/app/org/[slug]/... // Slug must match an organization the user belongs to ``` ### Context is Undefined Make sure you're accessing context correctly: ```tsx // โœ… Correct export const GET = withOrganizationAuthRequired( async (req, context) => { const organization = await context.session.organization } ) // โŒ Wrong - missing context parameter export const GET = withOrganizationAuthRequired( async (req) => { // context is missing! } ) ``` ## API Reference ๐Ÿ“š ### Function Signature ```typescript withOrganizationAuthRequired( handler: (req: Request, context: Context) => Promise, requiredRole: OrganizationRole ): (req: Request, context: RouteContext) => Promise ``` ### Parameters | Parameter | Type | Required | Description | | -------------- | ------------------ | -------- | -------------------------------------------------------------- | | `handler` | `Function` | Yes | Your route handler function | | `requiredRole` | `OrganizationRole` | Yes | Minimum role required (OrganizationRole.enum.user/admin/owner) | ### Context Type ```typescript { session: { expires: string user: Promise organization: Promise } params: Promise> } ``` Both `session.user`, `session.organization`, and `params` are Promises! Always `await` them before use. ## Summary ๐Ÿ“‹ You now know how to: * โœ… Protect API routes with organization authentication * โœ… Enforce role-based access control * โœ… Access organization data in routes * โœ… Handle different HTTP methods * โœ… Validate input and check quotas * โœ… Return proper error responses * โœ… Build secure multi-tenant APIs Always use `withOrganizationAuthRequired` for routes that access organization data. Security first! ๐Ÿ”’ ## Related Documentation ๐Ÿ“š * [Organization Roles](/multi-tenancy/roles) * [useOrganization Hook](/multi-tenancy/use-organization) * [API Calls](/customisation/api-calls) * [Multi Tenant Kit Overview](/multi-tenancy/b2b-kit) # One-Time Payments (/payments/create-one-time-payment) YouTube Video: https://www.youtube.com/watch?v=kUEaiBS4z0c Sell lifetime access, digital products, or courses with one-time payments! Perfect for LTD campaigns and premium features. ๐Ÿ’ฐ Set up [Stripe](/setup/payments/stripe) or [LemonSqueezy](/setup/payments/lemonsqueezy) first, then create a plan with one-time pricing enabled. ## Setup Your Plan ๐ŸŽฏ ### Step 1: Create Plan Navigate to `/super-admin/plans` and create a new plan: 1. **Add plan details** - Name, description, features 2. **Enable one-time pricing** - Check `hasOnetimePricing: true` 3. **Set price** - Amount in cents (e.g., 49900 = $499) 4. **Add price ID** - From Stripe/LemonSqueezy dashboard 5. **Configure quotas** - Define what users get One-time payments are perfect for lifetime deals, but you can also use them for courses, templates, or any digital product! ### Step 2: Generate Payment Link Use the `getSubscribeUrl` helper with `PlanType.ONETIME`: ```tsx import getSubscribeUrl, { PlanType, PlanProvider } from '@/lib/plans/getSubscribeUrl' // Stripe one-time payment const stripeUrl = getSubscribeUrl({ codename: "lifetime", type: PlanType.ONETIME, provider: PlanProvider.STRIPE, }) // LemonSqueezy one-time payment const lemonUrl = getSubscribeUrl({ codename: "lifetime", type: PlanType.ONETIME, provider: PlanProvider.LEMON_SQUEEZY, }) ``` **Parameters:** * `codename` - Your plan's unique identifier * `type` - `PlanType.ONETIME` for one-time payments * `provider` - Payment provider (Stripe, LemonSqueezy, PayPal, DodoPayments, Polar) One-time payments work with Stripe, LemonSqueezy, PayPal, DodoPayments, and Polar! Choose what works best for you. ## Complete Example ๐Ÿ“ Build a lifetime access purchase button: ```tsx import Link from 'next/link' import { Button } from '@/components/ui/button' import getSubscribeUrl, { PlanType, PlanProvider } from '@/lib/plans/getSubscribeUrl' function LifetimeAccessCard({ plan }) { if (!plan.hasOnetimePricing) return null const price = plan.onetimePrice / 100 const savings = (plan.monthlyPrice * 12) - plan.onetimePrice const savingsPercent = Math.round((savings / (plan.monthlyPrice * 12)) * 100) return (

{plan.name}

Save {savingsPercent}%
${price} one-time
    {plan.featuresList.map((feature, i) => (
  • {feature}
  • ))}

โœจ One-time payment โ€ข Lifetime access โ€ข No recurring fees

) } ``` ## Common Use Cases ๐ŸŽฏ **Perfect for:** * ๐Ÿš€ **Lifetime Deals** - LTD campaigns on AppSumo, ProductHunt * ๐Ÿ“š **Course Access** - One-time purchase for educational content * ๐ŸŽจ **Digital Products** - Templates, themes, design assets * ๐Ÿ”“ **Premium Unlocks** - One-time upgrade to premium tier * ๐Ÿ’Ž **Early Bird Pricing** - Special pricing for early adopters ## Pricing Strategies ๐Ÿ’ก ### 1. Lifetime vs Subscription Math ```tsx // Calculate break-even point const monthlyPrice = 29 const lifetimePrice = 299 const breakEvenMonths = lifetimePrice / monthlyPrice // 10.3 months ``` **Strategy:** Price lifetime at 10-12 months of subscription value. ### 2. Show Savings ```tsx // Highlight the value
Regular: ${monthlyPrice * 12}/year Lifetime: ${lifetimePrice} (Save ${(monthlyPrice * 12) - lifetimePrice}!)
``` ### 3. Limited Time Urgency ```tsx Get lifetime access at this price before it increases! Only available during our launch week. ``` ## Best Practices โœ… **1. Clear Pricing Display** ```tsx // โœ… Good - Clear and upfront

$499 - Lifetime Access

One-time payment, no recurring fees

// โŒ Bad - Confusing

$499

``` **2. Highlight Value** * Show what they get forever * Compare to subscription cost * Emphasize "no recurring fees" * Display savings percentage **3. Feature Comparison** ```tsx

Monthly: $29/mo

    All features

= $348/year

Lifetime: $299

    Same features

Pay once, use forever! ๐ŸŽ‰

``` **4. Test Everything** * โœ… Test payment flow end-to-end * โœ… Verify instant access after payment * โœ… Check webhook handling * โœ… Test with test cards/sandbox mode * โœ… Confirm plan assignment ## Troubleshooting ๐Ÿ”ง **Payment not processing?** * Check price ID matches payment provider * Verify `hasOnetimePricing` is true * Ensure webhooks are configured * Test in provider's test mode first **User not getting access?** * Check webhook received successfully * Verify plan assignment logic * Check database for subscription record * Look at webhook logs in provider dashboard One-time payments rely on webhooks to grant access! Make sure your webhook endpoint is configured in your payment provider dashboard. ## Next Steps ๐Ÿš€ 1. **Set up provider** - Configure Stripe/LemonSqueezy webhooks 2. **Create plan** - Enable one-time pricing in super admin 3. **Build pricing page** - Display lifetime option 4. **Test thoroughly** - Use test mode before going live 5. **Launch** - Start selling lifetime access! Your one-time payment system is ready! Perfect for LTD campaigns, courses, and lifetime deals. ๐Ÿ’ฐ ## Related Documentation ๐Ÿ“š * [Managing Plans](/payments/managing-plans) - Create and edit plans * [Running LTD Campaigns](/payments/running-ltd-campaigns) - Coupon-based LTDs * [Stripe Setup](/setup/payments/stripe) - Configure Stripe * [Subscription Payments](/payments/create-subscription) - Recurring billing # Subscriptions (/payments/create-subscription) YouTube Video: https://www.youtube.com/watch?v=twzpdNvl9DI Start earning recurring revenue with subscriptions! Support monthly, yearly, and trial periods out of the box. ๐Ÿ’ฐ Set up [Stripe](/setup/payments/stripe) or [LemonSqueezy](/setup/payments/lemonsqueezy), then create subscription plans in super admin. ## Setup Your Plans ๐ŸŽฏ ### Step 1: Create Subscription Plans Navigate to `/super-admin/plans` and configure: 1. **Enable subscription types** - Monthly, yearly, or both 2. **Set prices** - Amount in cents (2900 = $29/month) 3. **Add price IDs** - From your payment provider 4. **Configure features** - What subscribers get 5. **Set quotas** - Usage limits per plan Enable both monthly and yearly pricing! Offer 15-20% discount on yearly plans to incentivize annual subscriptions. ### Step 2: Generate Subscription Links Use the `getSubscribeUrl` helper: ```tsx import getSubscribeUrl, { PlanType, PlanProvider } from '@/lib/plans/getSubscribeUrl' // Monthly subscription const monthlyUrl = getSubscribeUrl({ codename: "pro", type: PlanType.MONTHLY, provider: PlanProvider.STRIPE, }) // Yearly subscription with discount const yearlyUrl = getSubscribeUrl({ codename: "pro", type: PlanType.YEARLY, provider: PlanProvider.STRIPE, }) // With free trial const trialUrl = getSubscribeUrl({ codename: "pro", type: PlanType.MONTHLY, provider: PlanProvider.STRIPE, trialPeriodDays: 14, // 14-day free trial }) ``` **Parameters:** * `codename` - Plan identifier from your database * `type` - `PlanType.MONTHLY` or `PlanType.YEARLY` * `provider` - Stripe, LemonSqueezy, PayPal, DodoPayments, or Polar * `trialPeriodDays` - Optional free trial (e.g., 7, 14, 30 days) Adding a 7-14 day trial can increase conversions by 20-30%! Let users experience value before paying. ## Complete Pricing Page Example ๐Ÿ“ Build a beautiful pricing page with toggles: ```tsx 'use client' import { useState } from 'react' import Link from 'next/link' import { Button } from '@/components/ui/button' import { Check } from 'lucide-react' import getSubscribeUrl, { PlanType, PlanProvider } from '@/lib/plans/getSubscribeUrl' function PricingPage({ plans }) { const [billingPeriod, setBillingPeriod] = useState<'monthly' | 'yearly'>('monthly') return (
{/* Billing toggle */}
{/* Pricing cards */}
{plans.map(plan => ( ))}
) } function PricingCard({ plan, billingPeriod }) { const isMonthly = billingPeriod === 'monthly' const price = isMonthly ? plan.monthlyPrice : plan.yearlyPrice const priceDisplay = price / 100 const period = isMonthly ? 'month' : 'year' return (

{plan.name}

${priceDisplay} /{period}
    {plan.featuresList.map((feature, i) => (
  • {feature}
  • ))}

No credit card required for trial

) } ``` ## Pricing Strategies ๐Ÿ’ก ### 1. Monthly vs Yearly Discount ```tsx // Calculate savings const monthlyPrice = 29 const yearlyPrice = 290 // Instead of $348 ($29 ร— 12) const savings = (monthlyPrice * 12) - yearlyPrice // $58 saved const discountPercent = Math.round((savings / (monthlyPrice * 12)) * 100) // 17% ``` **Best practice:** Offer 15-20% discount on annual plans. ### 2. Free Trial Strategy ```tsx - **7 days**: For simple, low-cost products ($10-20/mo) - **14 days**: Sweet spot for most SaaS ($20-50/mo) - **30 days**: For complex or expensive products ($50+/mo) ``` ### 3. Display Annual Savings ```tsx {billingPeriod === 'yearly' && (
Save ${(monthlyPrice * 12) - yearlyPrice}/year
)} ``` ## Subscription Lifecycle ๐Ÿ”„ ### What Happens After Subscription 1. **Payment processed** - User charged immediately (or after trial) 2. **Webhook received** - Your app gets notified 3. **Plan assigned** - User gets access to features 4. **Recurring billing** - Auto-charged monthly/yearly 5. **Access managed** - Features gated by subscription status ### Handling Different States ```tsx 'use client' import { useCurrentPlan } from '@/lib/subscription/useCurrentPlan' function FeatureGate({ children }) { const { currentPlan, isLoading } = useCurrentPlan() if (isLoading) return
Loading...
if (!currentPlan) { return (

Subscribe to Access

) } return <>{children} } ``` ## Best Practices โœ… **1. Clear Pricing Display** ```tsx // โœ… Good - Clear and transparent

$29/month

Billed monthly, cancel anytime

// โŒ Bad - Hidden information

$29

``` **2. Free Trial Transparency** ```tsx // โœ… Good - Clear trial terms

No credit card required

Cancel anytime during trial

// โŒ Bad - Unclear terms ``` **3. Show What They Get** * List all features clearly * Highlight popular plan * Show quota limits * Compare plans side-by-side **4. Easy Cancellation** * Make cancellation accessible * No dark patterns * Clear in billing settings * One-click cancellation ## Testing Your Subscriptions ๐Ÿงช **1. Test Mode Setup** ```bash # Use test keys STRIPE_SECRET_KEY="sk_test_..." STRIPE_PUBLISHABLE_KEY="pk_test_..." ``` **2. Test Scenarios** * โœ… New subscription * โœ… Free trial expiration * โœ… Successful renewal * โœ… Failed payment * โœ… Upgrade/downgrade * โœ… Cancellation * โœ… Reactivation **3. Use Test Cards** ``` Successful: 4242 4242 4242 4242 Declined: 4000 0000 0000 0002 ``` Always test the complete subscription flow in test mode before going live! Verify webhooks work correctly. ## Troubleshooting ๐Ÿ”ง **Subscription not activating?** * Check webhook is configured * Verify price IDs match * Look at webhook logs * Test in provider dashboard **User not seeing features?** * Check plan assignment logic * Verify subscription status * Check database records * Review webhook processing **Renewals failing?** * Customer payment method expired * Card declined * Webhook not processed * Check provider dashboard ## Next Steps ๐Ÿš€ 1. **Configure plans** in `/super-admin/plans` 2. **Set up webhooks** in payment provider 3. **Build pricing page** with toggle 4. **Add feature gates** based on plans 5. **Test thoroughly** before launch Your subscription system is ready to generate recurring revenue! Start with a free trial to boost conversions. ๐Ÿ’ฐ ## Related Documentation ๐Ÿ“š * [Managing Plans](/payments/managing-plans) - Create and edit plans * [Plan-Based Rendering](/payments/current-plan-based-rendering) - Gate features * [Stripe Setup](/setup/payments/stripe) - Configure Stripe * [One-Time Payments](/payments/create-one-time-payment) - Lifetime deals # Credits System (/payments/credits-system) # Credits System ๐Ÿ’ฐ Charge users based on actual usage! Perfect for AI applications where you want to bill per image generated, video created, or API call made. โœจ Credits provide a flexible pay-as-you-go model that works alongside your subscription plans. Users buy credits and consume them as they use your features! ## Why Use Credits? ๐Ÿค” **Perfect for:** * ๐ŸŽจ **AI Image Generation** - Charge per image created * ๐ŸŽฅ **Video Processing** - Bill based on video length or quality * ๐Ÿค– **API Calls** - Metered usage for AI services * ๐Ÿ“Š **Data Processing** - Usage-based pricing for compute-heavy tasks * โœจ **Freemium Models** - Give free credits, upsell more **Benefits:** * Fair pricing (users pay for what they use) * Revenue from non-subscription users * Incentivize trials with free credits * Flexible monetization options * Volume discounts made easy ## Enabling Credits To enable the credits system in your application: 1. Open `src/lib/credits/config.ts` 2. Set `enableCredits` to `true` ```ts export const enableCredits = true; // Enable or disable credits ``` 3. The system will now be active and ready to configure! โœจ That's it! The entire credits system is now enabled. Next, configure your credit types and pricing below. ## Adding New Credit Types Credit types define what users can purchase and use (e.g., "image\_generation", "video\_generation"). **Where to configure:** `src/lib/credits/config.ts` **Example:** ```ts import { z } from "zod"; // 1. Define your credit types in the schema export const creditTypeSchema = z.enum([ "image_generation", "video_generation", // Add more types here ]); // 2. Configure pricing for each type export const creditsConfig: CreditsConfig = { image_generation: { name: "Image Generation Credits", currency: "USD", minimumAmount: 1, slabs: [ { from: 0, to: 1000, pricePerUnit: 0.01 }, { from: 1001, to: 5000, pricePerUnit: 0.008 }, // Volume discount ], }, video_generation: { name: "Video Generation Credits", currency: "USD", minimumAmount: 1, priceCalculator: (amount, userPlan) => { // Custom pricing based on user's plan return amount * 0.05; }, }, }; ``` **Pricing Models:** * **Slabs** - Simple tiered pricing with volume discounts (recommended for most cases) * **priceCalculator** - Advanced dynamic pricing based on user's subscription plan ## Auto Adding Credits on Signup Reward new users with free credits when they register! ๐ŸŽ **Where to configure:** `onRegisterCredits` in `src/lib/credits/config.ts` **What to specify:** * Credit type to grant * Amount of credits to give * Expiry duration (optional - credits won't expire if not set) Free credits are perfect for letting users try your AI features before purchasing! Hook them with value, then convert to paid. ๐Ÿš€ ## Adding Credits on Plan Change Automatically grant credits when users upgrade or change their subscription plan. **Where to configure:** `onPlanChangeCredits` in `src/lib/credits/config.ts` **How it works:** * Map plan codenames to credit grants * Each plan can give different amounts of different credit types * Set expiry periods per credit type * Credits are added automatically when the plan change is detected Include bonus credits with paid plans to incentivize upgrades! "Pro plan includes 1000 free credits per month" ๐Ÿ’Ž ## Expiring Credits Credits can automatically expire after a set duration (e.g., 30 days from grant). **Configuration:** Set `expiryAfter` (in days) when defining credits in: * `onRegisterCredits` (signup bonuses) * `onPlanChangeCredits` (plan upgrade bonuses) **Cron Job Setup (if not using Inngest):** 1. Add to your `.env`: ```bash CRON_USERNAME="your-secure-username" CRON_PASSWORD="your-secure-password" ``` 2. Setup on [cron-job.org](https://cron-job.org/en/): * **URL**: `https://yourdomain.com/api/cron/expire-credits` * **Auth**: HTTP Basic Authentication with your credentials * **Schedule**: Daily at midnight (recommended) The cron job runs daily and automatically expires credits based on your configuration. Set it once and forget it! โฐ ## Usage in Frontend Two powerful hooks make it easy to work with credits: ### `useCredits` Hook Fetch and display user's current credit balances. **Returns:** * `credits` - Object with all credit types and amounts * `isLoading` - Loading state * `error` - Any errors * `mutate` - Function to refresh credits **Example:** ```tsx import useCredits from "@/lib/credits/useCredits"; function CreditBalance() { const { credits, isLoading } = useCredits(); if (isLoading) return
Loading...
; return (

Image Credits: {credits?.image_generation || 0}

Video Credits: {credits?.video_generation || 0}

); } ``` ### `useBuyCredits` Hook Handle credit purchasing with automatic price calculation. **Parameters:** * Credit type * Amount to purchase **Returns:** * `price` - Calculated price (considers user's plan for personalized pricing) * `currency` - Currency for transaction * `isLoading` - Loading state * `error` - Any errors * `getBuyCreditsUrl(provider)` - Generate payment URL **Example:** ```tsx import useBuyCredits from "@/lib/credits/useBuyCredits"; import { PlanProvider } from "@/lib/plans/getSubscribeUrl"; import { Button } from "@/components/ui/button"; function BuyCreditButton() { const { price, isLoading, getBuyCreditsUrl } = useBuyCredits( "image_generation", 100 // Buy 100 credits ); const handlePurchase = () => { const url = getBuyCreditsUrl(PlanProvider.STRIPE); window.location.href = url; }; return ( ); } ``` The `useBuyCredits` hook automatically calculates personalized pricing based on the user's current plan. Pro users might get better rates than free users! **Benefits:** * โšก Automatic price calculation based on user's plan * ๐Ÿ’ณ Support for multiple payment providers (Stripe, PayPal, DodoPayments, Polar) * ๐Ÿ“Š Real-time pricing updates * ๐ŸŽฏ Seamless checkout experience ## Backend Functions On the server side, you have access to powerful functions for managing credits programmatically. ### `deductCredits` Function Deducts credits from a user's account with automatic balance checking. **Parameters:** * `userId` - User ID * `creditType` - Type of credit to deduct * `amount` - Amount to deduct * `metadata` - Optional metadata for tracking **Throws:** Error if user has insufficient credits **Example:** ```ts import { deductCredits } from "@/lib/credits/credits"; // In your API route or server action async function generateImage(userId: string) { try { // Deduct credits before processing await deductCredits(userId, "image_generation", 1, { action: "generate_image", prompt: "A beautiful sunset", }); // Process the image generation const image = await aiService.generateImage(); return { success: true, image }; } catch (error) { // Handle insufficient credits return { success: false, error: "Insufficient credits" }; } } ``` ### `addCredits` Function Adds credits to a user's account with duplicate prevention. **Parameters:** * `userId` - User ID * `creditType` - Type of credit to add * `amount` - Amount to add * `paymentId` - Payment ID (prevents duplicate credits) * `metadata` - Optional metadata * `expirationDate` - Optional expiration date **Example:** ```ts import { addCredits } from "@/lib/credits/credits"; // Grant bonus credits to a user async function grantBonusCredits(userId: string) { const result = await addCredits( userId, "image_generation", 50, `bonus-${userId}-${Date.now()}`, // Unique payment ID { reason: "promotional_bonus" }, new Date(Date.now() + 30 * 24 * 60 * 60 * 1000) // Expires in 30 days ); return result; } ``` Both functions include built-in safety checks: `deductCredits` prevents negative balances, and `addCredits` prevents duplicate credits using payment IDs! **Use these functions for:** * ๐ŸŽจ Deducting credits when users consume AI services * ๐ŸŽ Granting bonus or promotional credits * ๐Ÿ”„ Refunding credits after failed operations * ๐Ÿ“Š Implementing custom credit workflows ## Payment Providers Credits work with Stripe, PayPal, DodoPayments, Paddle, and Polar. LemonSqueezy doesn't support custom credit purchases yet. **Supported payment providers:** * โœ… **Stripe** - Full support with checkout * โœ… **PayPal** - Full support * โœ… **DodoPayments** - Full support with pay-as-you-go * โœ… **Paddle** - Full support with automatic price creation * โœ… **Polar** - Full support with pay-as-you-go pricing ### DodoPayments Setup For DodoPayments credit purchases: 1. Create a product in DodoPayments dashboard 2. Set pricing model to **"Pay as you go"** 3. Add to `.env`: ```bash DODO_CREDITS_PRODUCT_ID="pdt_xxxxxxxxxxxxx" ``` DodoPayments offers great rates and supports pay-as-you-go pricing natively. Perfect for credit systems! ๐Ÿ’ณ ### Paddle Setup For Paddle credit purchases: 1. Go to Dashboard โ†’ **Catalogue** โ†’ **Products** 2. Create a product named **"Credits"** 3. **No need to create prices** - Indie Kit automatically creates them 4. Copy the Product ID and add to `.env`: ```bash PADDLE_CREDITS_PRODUCT_ID="pro_xxxxxxxxxxxxx" ``` Paddle integrates seamlessly with the credits system. Indie Kit automatically handles price creation, so you only need to create the product itself! See [Paddle Setup](/setup/payments/paddle) for complete integration details. ๐Ÿ’ณ ### Polar Setup For Polar credit purchases: 1. Create a product in Polar dashboard 2. Set pricing model to **"Pay what you want"** 3. Add to `.env`: ```bash POLAR_CREDITS_PRODUCT_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" ``` Polar.sh supports pay-what-you-want pricing natively. Perfect for dynamic credit purchases! ๐Ÿป ## Super Admin Administrators have full control over user credits from the admin panel. **Features:** * โž• Add credits to any user * โž– Remove/deduct credits from users * ๐Ÿ“Š View credit transaction history * ๐Ÿ” Monitor credit usage patterns **Access:** Navigate to user detail page in the super admin section to manage credits. Super admins can manually adjust credits for customer support, refunds, or promotions. Great for handling edge cases! ๐Ÿ‘‘ **Common use cases:** * ๐Ÿ’ฌ Customer support (issuing refunds or bonuses) * ๐Ÿ”ง Fixing billing issues * ๐ŸŽ‰ Running promotions and giveaways * ๐Ÿงช Testing features with test users ## Quick Recap ๐ŸŽฏ With the credits system enabled, you can: โœ… **Create multiple credit types** for different features\ โœ… **Auto-grant credits** on signup and plan changes\ โœ… **Set credit expiry periods** to create urgency\ โœ… **Offer personalized pricing** based on subscription tiers\ โœ… **Use frontend hooks** (`useCredits`, `useBuyCredits`) for UI\ โœ… **Use backend functions** (`deductCredits`, `addCredits`) for logic\ โœ… **Manage credits via admin panel** for support\ โœ… **Accept payments** through Stripe, PayPal, DodoPayments, Paddle, or Polar You now have a complete usage-based pricing system! Perfect for AI SaaS, API platforms, or any service where you want to charge based on actual usage. ๐Ÿš€ ## Related Documentation ๐Ÿ“š * [Stripe Setup](/setup/payments/stripe) - Configure Stripe payments * [Paddle Setup](/setup/payments/paddle) - Configure Paddle payments * [Create One-Time Payment](/payments/create-one-time-payment) - One-off purchases * [Subscription Plans](/payments/create-subscription) - Recurring billing * [Managing Plans](/payments/managing-plans) - Create and edit payment plans * [Super Admin](/setup/super-admin) - Platform administration # Plan-Based Rendering (/payments/current-plan-based-rendering) YouTube Video: https://www.youtube.com/watch?v=JdlB2nwoUuM Show different features to different plans! Gate premium features, enforce quotas, and create upgrade prompts based on user subscriptions. ๐ŸŽฏ Use the `useCurrentPlan` hook to check user plans and quotas in client components, or `withAuthRequired` for API routes! ## Client-Side: useCurrentPlan Hook ๐ŸŽจ Access user plan data in any client component: ```tsx 'use client' import { useCurrentPlan } from '@/lib/subscription/useCurrentPlan' import { Button } from '@/components/ui/button' import { CreditCard, Crown } from 'lucide-react' import Link from 'next/link' function Dashboard() { const { currentPlan, isLoading } = useCurrentPlan() if (isLoading) return
Loading...
return (
{/* Plan status badge */}

Dashboard

{currentPlan ? ( <> {currentPlan.name} ) : ( 'Free Plan' )}
{/* Conditional CTA */} {currentPlan ? ( ) : (

Unlock premium features!

)}
) } ``` `useCurrentPlan` provides `currentPlan` (plan object with quotas), `isLoading` (loading state), and `error` (if any). ## Configure Plan Quotas โš™๏ธ Quotas let you limit features per plan. Define them in your schema: ```ts // src/db/schema/plans.ts import { z } from 'zod' export const quotaSchema = z.object({ canUseApp: z.boolean().default(true), projects: z.number(), apiCalls: z.number(), storage: z.string(), advancedFeatures: z.boolean(), }) ``` **Example quotas:** ```ts // Free Plan { canUseApp: true, projects: 3, apiCalls: 100, storage: "1GB", advancedFeatures: false } // Pro Plan { canUseApp: true, projects: 100, apiCalls: 10000, storage: "100GB", advancedFeatures: true } ``` Use any quota structure that fits your app! Boolean flags, numbers, strings - anything that defines plan limits. ### Enforce Quotas in Components ๐Ÿ“Š Gate features based on quotas: ```tsx 'use client' import { useCurrentPlan } from '@/lib/subscription/useCurrentPlan' import Link from 'next/link' import { Button } from '@/components/ui/button' import { Lock } from 'lucide-react' function AdvancedFeature() { const { currentPlan } = useCurrentPlan() // Check boolean quota if (!currentPlan?.quotas.advancedFeatures) { return (

Premium Feature

Upgrade to Pro to access advanced features

) } return
{/* Premium feature content */}
} function ProjectsList({ userProjects }) { const { currentPlan } = useCurrentPlan() // Check numerical quota const projectLimit = currentPlan?.quotas.projects ?? 3 const canCreateMore = userProjects.length < projectLimit return (

Projects ({userProjects.length}/{projectLimit})

{canCreateMore ? ( ) : (

Limit reached!

)}
{/* Project list */}
) } ``` ## Best Practices โœ… **1. Graceful Degradation** ```tsx // โœ… Good - Clear message with upgrade path if (!hasAccess) { return (

This feature requires Pro plan

) } // โŒ Bad - Just hiding with no explanation if (!hasAccess) return null ``` **2. Show Limits Proactively** ```tsx // โœ… Good - Show before they hit limit

Projects: {current}/{limit}

{current >= limit * 0.8 && (

Almost at limit! Consider upgrading.

)} // โŒ Bad - Only show after hitting limit ``` **3. Consistent UI** * Keep layout same across plans * Show locked features (with lock icon) * Make upgrade CTAs clear * Don't frustrate free users **4. Performance** * Use SWR/React Query for plan data * Cache plan checks * Avoid re-fetching on every render * Show loading states Always show users what they're missing and how to get it! Clear upgrade paths convert better than hidden features. ## Common Patterns ๐ŸŽฏ ### 1. Feature Gates ```tsx // Reusable feature gate component function FeatureGate({ children, requiredPlan = 'pro', featureName = 'this feature' }) { const { currentPlan } = useCurrentPlan() const hasAccess = currentPlan?.codename === requiredPlan if (!hasAccess) { return (

{requiredPlan.charAt(0).toUpperCase() + requiredPlan.slice(1)} Feature

Upgrade to {requiredPlan} to access {featureName}

) } return <>{children} } // Usage ``` ### 2. Usage Limits with Progress ```tsx function UsageTracker({ used, type = 'projects' }) { const { currentPlan } = useCurrentPlan() const limit = currentPlan?.quotas[type] ?? 0 const percent = (used / limit) * 100 const isNearLimit = percent >= 80 return (

{type.charAt(0).toUpperCase() + type.slice(1)}

{used} / {limit}
{isNearLimit && (

Almost at limit!{' '} Upgrade

)}
) } ``` ### 3. Plan Comparison Badge ```tsx function PlanBadge() { const { currentPlan } = useCurrentPlan() if (!currentPlan) return Free return ( {currentPlan.name} ) } function getPlanColor(codename: string) { switch (codename) { case 'pro': return 'bg-blue-100 text-blue-800' case 'enterprise': return 'bg-purple-100 text-purple-800' default: return 'bg-gray-100 text-gray-800' } } ``` ## Server-Side: API Routes ๐Ÿ”’ Enforce plan limits in API routes using `withAuthRequired`: ```ts // src/app/api/app/advanced-feature/route.ts import withAuthRequired from '@/lib/auth/withAuthRequired' import { NextResponse } from 'next/server' export const POST = withAuthRequired(async (req, context) => { const currentPlan = await context.getCurrentPlan() // Check boolean quota if (!currentPlan?.quotas.advancedFeatures) { return NextResponse.json( { error: 'Upgrade to Pro to access this feature' }, { status: 403 } ) } // Your feature logic const data = await req.json() // Process... return NextResponse.json({ success: true }) }) ``` **Check numerical quotas:** ```ts export const POST = withAuthRequired(async (req, context) => { const currentPlan = await context.getCurrentPlan() const userId = context.session.user.id // Get current usage const currentProjects = await getProjectCount(userId) const projectLimit = currentPlan?.quotas.projects ?? 3 if (currentProjects >= projectLimit) { return NextResponse.json( { error: 'Project limit reached', current: currentProjects, limit: projectLimit, upgradeUrl: '/pricing' }, { status: 403 } ) } // Create project... return NextResponse.json({ success: true }) }) ``` Never rely only on client-side checks! Always validate plan access in API routes to prevent bypassing restrictions. ## Next Steps ๐Ÿš€ 1. **Define quotas** in `src/db/schema/plans.ts` 2. **Set quota values** for each plan in super admin 3. **Add feature gates** in your components 4. **Enforce server-side** in API routes 5. **Test all plans** to ensure proper gating You can now gate features by plan! Always show users what they're missing and how to upgrade. ๐Ÿ’Ž ## Related Documentation ๐Ÿ“š * [Managing Plans](/payments/managing-plans) - Create and configure plans * [Create Subscription](/payments/create-subscription) - Implement subscriptions * [Credits System](/payments/credits-system) - Usage-based pricing * [Super Admin](/setup/super-admin) - Manage plans as admin # Running LTD Campaigns (/payments/running-ltd-campaigns) YouTube Video: https://www.youtube.com/watch?v=oczhPugwt2c Launch successful lifetime deal (LTD) campaigns with coupon-based access! Perfect for AppSumo, ProductHunt, and other deal platforms. ๐Ÿš€ LTDs let you sell lifetime access at discounted prices. Users redeem coupon codes to unlock plans - great for rapid user acquisition! ## How It Works ๐ŸŽฏ The coupon system allows tiered access: 1. **Create coupon codes** with campaign prefix (e.g., `APPSUMO_ABC123`) 2. **Configure plans** to require X number of codes 3. **Users redeem codes** to unlock features 4. **Stack codes** for higher tiers (1 code = Basic, 2 codes = Pro, etc.) **Example flow:** * Basic Plan: Requires 1 code * Pro Plan: Requires 2 codes * Enterprise Plan: Requires 3 codes Users buy multiple codes on deal platforms to unlock higher tiers! This increases your revenue per customer. ## Creating Coupon Codes โšก ### Step 1: Generate Codes Navigate to `/super-admin/coupons` โ†’ **Create Coupon Codes** **Configure:** 1. **Number of codes** - How many to generate (e.g., 1000 codes) 2. **Campaign prefix** - Identifier (e.g., `APPSUMO`, `BLACKFRIDAY`, `PH2024`) 3. **Generate** - Creates unique codes **Example codes generated:** ``` APPSUMO_A1B2C3D4 APPSUMO_E5F6G7H8 APPSUMO_I9J0K1L2 ... ``` Use descriptive prefixes to track which platform/campaign codes came from. Helps with analytics and support! ### Step 2: Export & Distribute 1. **Export codes** - Download CSV of all codes 2. **Send to platform** - Upload to AppSumo, ProductHunt, etc. 3. **Platform distributes** - Customers receive codes after purchase ## Configuring Plan Tiers ๐ŸŽฏ ### Set Code Requirements Navigate to `/super-admin/plans` and edit each plan: **Configure code requirements:** ``` Basic Plan: - Codes required: 1 - Price on platform: $49 Pro Plan: - Codes required: 2 - Price on platform: $98 ($49 ร— 2) Enterprise Plan: - Codes required: 3 - Price on platform: $147 ($49 ร— 3) ``` Set code requirements to create natural upgrade tiers. Users buy more codes on the platform to unlock better plans! ### Example Configuration ```tsx // Basic Tier { name: "LTD Basic", codesRequired: 1, features: [ "10 projects", "Basic support", "Lifetime access" ] } // Pro Tier { name: "LTD Pro", codesRequired: 2, features: [ "100 projects", "Priority support", "Advanced features", "Lifetime access" ] } // Enterprise Tier { name: "LTD Enterprise", codesRequired: 3, features: [ "Unlimited projects", "24/7 support", "All features", "Lifetime access" ] } ``` ## User Redemption Flow ๐ŸŽซ ### How Users Redeem 1. **Purchase on platform** - Buy 1-3 codes on AppSumo/etc 2. **Receive codes** - Get unique coupon codes 3. **Redeem in app** - Enter codes at `/redeem` or `/app/billing` 4. **Instant access** - Plan activated immediately 5. **Stack for upgrades** - Buy more codes to upgrade tier ### Redemption Page Users visit your redemption page: ```tsx // app/redeem/page.tsx 'use client' import { useState } from 'react' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' export default function RedeemPage() { const [code, setCode] = useState('') const [loading, setLoading] = useState(false) const handleRedeem = async () => { setLoading(true) const res = await fetch('/api/redeem-coupon', { method: 'POST', body: JSON.stringify({ code }), }) if (res.ok) { alert('Code redeemed! Plan activated.') window.location.href = '/app' } else { alert('Invalid or expired code') } setLoading(false) } return (

Redeem Your Code

Enter your LTD coupon code below:

setCode(e.target.value.toUpperCase())} />
) } ``` ## Managing Active Campaigns ๐Ÿ“Š ### View Campaign Stats Navigate to `/super-admin/coupons` to see: * **Total codes generated** * **Codes redeemed** * **Codes remaining** * **Revenue per campaign** * **Active users per tier** ### Expire Campaigns When campaign ends: 1. Navigate to `/super-admin/coupons` 2. Find your campaign 3. Click **"Expire Coupon Codes"** 4. Confirm action When codes expire, users are automatically downgraded to free/default plan! Warn users before expiring if needed. **What happens:** * โœ… Codes become invalid * โœ… New redemptions blocked * โœ… Existing users keep access (or downgraded, configurable) * โœ… Campaign closed ## LTD Campaign Best Practices ๐Ÿ’ก ### 1. Pricing Strategy ```tsx // Calculate your LTD pricing const monthlyPrice = 29 const yearlyValue = monthlyPrice * 12 // $348 const ltdPrice = 49 // Per code // Break-even analysis const breakEven = ltdPrice / monthlyPrice // 1.7 months ``` **Strategy:** Price LTD at 1-3 months of subscription value. ### 2. Tier Structure **Option A: Linear Pricing** * 1 code: $49 (Basic) * 2 codes: $98 (Pro) * 3 codes: $147 (Enterprise) **Option B: Volume Discount** * 1 code: $49 (Basic) * 2 codes: $89 (Pro) - Save $9! * 3 codes: $119 (Enterprise) - Save $28! ### 3. Feature Differentiation Make tier upgrades valuable: ```tsx Basic (1 code): - Core features - 10 projects - Email support Pro (2 codes): - All Basic features - 100 projects - Priority support - Advanced features Enterprise (3 codes): - All Pro features - Unlimited projects - 24/7 support - API access - Custom integrations ``` ### 4. Communication **Before campaign:** * Announce on social media * Email existing users * Prepare support docs * Test redemption flow **During campaign:** * Monitor redemptions * Provide quick support * Share success stories * Encourage upgrades **After campaign:** * Thank participants * Share user wins * Close redemptions * Plan next campaign ## Troubleshooting ๐Ÿ”ง **Code not working?** * Check if code exists in database * Verify code hasn't been used * Check if campaign expired * Look for typos (codes are case-sensitive) **User can't upgrade?** * Check how many codes they've redeemed * Verify plan code requirements * Check if they have remaining codes * Review redemption history **Need to refund?** * Mark code as unused in admin * Code becomes available again * User can redeem elsewhere * Or generate replacement code ## Platform-Specific Tips ๐ŸŽฏ ### AppSumo * Use prefix `APPSUMO_` * Typical: 3-tier structure * Price per code: $49-79 * Campaign duration: 2-4 weeks ### ProductHunt * Use prefix `PH_` or `PRODUCTHUNT_` * Launch day special * Limited quantity creates urgency * Price per code: $39-59 ### Your Own Site * Use prefix `LAUNCH_` or `EARLY_` * Control everything * Email delivery * Custom tiers Always generate 20-30% more codes than you expect to sell. Better to have extras than run out during a campaign! ## Next Steps ๐Ÿš€ 1. **Create first campaign** - Generate codes with prefix 2. **Configure plan tiers** - Set code requirements 3. **Test redemption** - Use test code to verify flow 4. **Launch campaign** - Submit to AppSumo/PH 5. **Monitor & support** - Track redemptions, help users Your coupon system is ready for LTD campaigns! Start with AppSumo or ProductHunt to acquire users fast. ๐Ÿš€ ## Related Documentation ๐Ÿ“š * [One-Time Payments](/payments/create-one-time-payment) - Alternative LTD approach * [Managing Plans](/payments/managing-plans) - Configure plans * [Super Admin](/setup/super-admin) - Admin access # Blog Posts (/seo/create-blog-post) YouTube Video: https://www.youtube.com/watch?v=cIDhoIN_Dn0 Indie Kit comes with a built-in blog system that supports MDX, tags, featured images, and automatic routing. Let's learn how to create beautiful blog posts! โœจ ## Quick Start ๐Ÿš€ 1. Rename the sample blog post file from: ``` src/content/blog/add-button-to-any-website.mdx.donotuse ``` to: ``` src/content/blog/add-button-to-any-website.mdx ``` 2. Your blog will automatically appear at `/blog/add-button-to-any-website`! ## Blog Post Structure ๐Ÿ“„ Each blog post is an MDX file with frontmatter metadata. Here's the structure: ```mdx --- title: Your Blog Title tags: ["tag1", "tag2", "tag3"] featuredImage: '/assets/images/your-image.png' createdDate: '2024-03-20' description: 'A brief description of your blog post' --- # Your Content Here Write your blog content using Markdown and MDX! ``` ### Frontmatter Fields ๐Ÿ“‹ * `title`: The title of your blog post * `tags`: Array of tags for categorizing your post * `featuredImage`: Path to the featured image (stored in `public` directory) * `createdDate`: Publication date (YYYY-MM-DD format) * `description`: Brief description for SEO and previews ## Writing Content โœ๏ธ Your blog posts support: * ๐Ÿ“ Full Markdown syntax * ๐ŸŽจ MDX components * ๐Ÿ“Š Code blocks with syntax highlighting * ๐Ÿ–ผ๏ธ Images and media * ๐Ÿ“‘ Automatic table of contents * ๐Ÿ”— Internal and external links ### Example Content Structure ```mdx # Main Title ## Section 1 Content for section 1... ### Subsection 1.1 More detailed content... ## Section 2 Another section with content... ``` ## Best Practices ๐Ÿ’ก 1. **Images** * Store images in `public/assets/images/` * Use descriptive file names * Optimize images for web 2. **Tags** * Use relevant, consistent tags * Keep tags lowercase * Use hyphen for multi-word tags 3. **SEO** * Write descriptive titles * Include meaningful descriptions * Use relevant tags * Add alt text to images ## Viewing Your Blog ๐Ÿ‘€ * Individual posts: `/blog/[slug]` * Blog listing: `/blog` * Tags are automatically grouped * Featured images appear in previews The blog system will automatically: * Generate SEO metadata * Create a table of contents * Handle routing * Show related posts * Enable social sharing Now you're ready to start blogging! Create engaging content and share it with your audience! ๐Ÿš€ # Google Search Console (/seo/google-search-console) # Google Search Console ๐Ÿ” Google Search Console is essential for monitoring your site's performance in Google Search! Let's get you set up. ๐Ÿš€ ## Why You Need This ๐ŸŽฏ Google Search Console helps you: * ๐Ÿ“Š **Monitor Search Performance**: See how many people find your site through Google * ๐Ÿ”Ž **Track Keywords**: Discover which search terms bring visitors to your site * ๐Ÿ› **Fix Issues**: Get alerts about errors, broken links, or indexing problems * ๐Ÿ“ˆ **Submit Sitemaps**: Help Google discover and index all your pages * โšก **Improve Rankings**: Get insights to optimize your content for better visibility It's completely free and takes just 5 minutes to set up! ## Prerequisites โœ… Before you start: * โœ… Your Indie Kit site must be deployed and live * โœ… You need a Google account * โœ… You should be able to modify your site's code or DNS settings ## Step 1: Add Your Property ๐ŸŒ 1. Go to [Google Search Console](https://search.google.com/search-console) 2. Click **"Start now"** or **"Add property"** if you already have an account 3. You'll see two options - choose **URL prefix**: ### Choosing Property Type ๐Ÿค” **URL-prefix property** (Recommended) โœจ * Example: `https://yourdomain.com` * Includes only URLs with the exact prefix you specify * Multiple verification methods available * **Best for most users!** **Domain property** * Example: `yourdomain.com` * Includes all subdomains (www, m, etc.) and protocols (http, https) * Requires DNS verification (more technical) * Good if you have multiple subdomains For most Indie Kit apps, use **URL-prefix property** with your full URL including `https://` 4. Enter your site URL: `https://yourdomain.com` 5. Click **Continue** ## Step 2: Verify Ownership โœ… Google needs to confirm you own the site. Choose one of these verification methods: ### Option 1: HTML Tag (Easiest for Indie Kit) ๐Ÿท๏ธ 1. Google will show you a meta tag like: ```html ``` 2. Add it to your Indie Kit app's `src/app/layout.tsx`: ```tsx export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( {children} ); } ``` 3. Deploy your changes 4. Go back to Google Search Console and click **Verify** ### Option 2: HTML File Upload ๐Ÿ“„ 1. Download the HTML file Google provides 2. Upload it to your `public/` folder 3. Deploy your changes 4. Click **Verify** ### Option 3: DNS Record (For Domain Property) ๐ŸŒ 1. Copy the TXT record Google provides 2. Add it to your domain's DNS settings (in your domain registrar) 3. Wait a few minutes for DNS to propagate 4. Click **Verify** Verification may take a few minutes after deployment. If it fails, wait 5-10 minutes and try again! ## Step 3: Submit Your Sitemap ๐Ÿ—บ๏ธ Once verified, submit your sitemap so Google can discover all your pages: 1. In Google Search Console, go to **Sitemaps** (left sidebar) 2. Enter your sitemap URL: `sitemap.xml` 3. Click **Submit** Indie Kit automatically generates your sitemap! It's available at `https://yourdomain.com/sitemap.xml`. Learn more in the [Sitemap documentation](/seo/sitemap). ## What Happens Next? โฐ * **Verification**: Takes a few minutes to a few hours * **Data Collection**: Starts appearing in 2-3 days * **Full Data**: Takes about a week to show complete statistics Don't worry if you don't see data immediately - this is normal! ๐Ÿ“Š ## Monitoring Your Site ๐Ÿ“ˆ Once data starts coming in, check these sections regularly: ### Performance Report * See clicks, impressions, and average position * Discover which keywords bring traffic * Identify top-performing pages ### Coverage Report * Check which pages are indexed * Find and fix errors * Submit new pages for indexing ### Enhancements * Check mobile usability * Monitor Core Web Vitals * Fix structured data issues ## Troubleshooting ๐Ÿ”ง **"Verification failed"** * Make sure your site is deployed with the verification code * Wait 5-10 minutes and try again * Clear your browser cache **"No data available"** * Normal for new sites - wait 2-3 days * Ensure your sitemap is submitted * Check that your site is publicly accessible **"Sitemap couldn't be read"** * Verify your sitemap URL: `https://yourdomain.com/sitemap.xml` * Check that your site is live * Wait a few minutes and resubmit ## Best Practices ๐Ÿ’ก 1. **Check Weekly**: Review your performance report weekly 2. **Fix Errors**: Address coverage errors promptly 3. **Submit New Content**: Manually submit important new pages via "URL Inspection" 4. **Monitor Core Web Vitals**: Keep an eye on performance metrics 5. **Track Keywords**: Identify opportunities to create more content ## Next Steps ๐Ÿš€ * โœ… Set up email notifications for critical issues * โœ… Connect Google Analytics (if you use it) * โœ… Review your performance report weekly * โœ… Submit your sitemap * โœ… Use "URL Inspection" tool to check specific pages Congratulations! Your site is now connected to Google Search Console. Data will start appearing in a few days! ๐ŸŽ‰ ## Useful Resources ๐Ÿ“š * [Google Search Console Official Guide](https://support.google.com/webmasters/answer/34592) * [Understanding Performance Report](https://support.google.com/webmasters/answer/7576553) * [Sitemap Documentation](/seo/sitemap) * [SEO Best Practices](/seo/seo) # SEO Best Practices (/seo/seo) # SEO Best Practices ๐Ÿ” Indie Kit comes with built-in SEO optimization! Here's how to add metadata and structured data to your pages. โœจ ## Adding Metadata to Pages ๐Ÿ“ Add this to any page to optimize it for search engines and social sharing: ```tsx import { Metadata } from "next"; import { appConfig } from "@/lib/config"; export const metadata: Metadata = { title: `About Us | ${appConfig.projectName}`, description: "Learn more about our company, mission, and values.", openGraph: { title: `About Us | ${appConfig.projectName}`, description: "Learn more about our company, mission, and values.", images: [`${process.env.NEXT_PUBLIC_APP_URL}/images/og.png`], }, twitter: { card: "summary_large_image", title: `About Us | ${appConfig.projectName}`, description: "Learn more about our company, mission, and values.", }, }; ``` * Keep titles under 60 characters * Keep descriptions between 150-160 characters * Always set Open Graph images (1200x630px) ## Structured Data (JSON-LD) ๐Ÿท๏ธ Help search engines understand your content better with JSON-LD: ### Article/Blog Post ```tsx import { ArticleJsonLd } from "next-seo"; ``` ### Product ```tsx import { ProductJsonLd } from "next-seo"; ``` ### FAQ ```tsx import { FAQPageJsonLd } from "next-seo"; ``` ## Sitemap & Robots.txt ๐Ÿ—บ๏ธ Your sitemap is automatically generated! Learn more about [customizing your sitemap](/seo/sitemap). ## Quick SEO Checklist โœ… Before launching, make sure you: * โœ… Add metadata to all public pages * โœ… Set Open Graph images (1200x630px) * โœ… Submit sitemap to [Google Search Console](https://search.google.com/search-console) * โœ… Test your pages with [Rich Results Test](https://search.google.com/test/rich-results) * โœ… Verify social previews with [OpenGraph.xyz](https://www.opengraph.xyz/) * โœ… Check mobile-friendliness ## Testing Tools ๐Ÿ› ๏ธ **Essential Tools:** * [Google Search Console](https://search.google.com/search-console) - Submit sitemap & monitor indexing * [Rich Results Test](https://search.google.com/test/rich-results) - Validate structured data * [OpenGraph.xyz](https://www.opengraph.xyz/) - Preview social cards * [Detailed SEO Extension](https://chromewebstore.google.com/detail/detailed-seo-extension/pfjdepjjfjjahkjfpkcgfmfhmnakjfba) - Chrome extension for quick audits That's it! Your Indie Kit app is now SEO-ready! ๐Ÿš€ # Sitemap (/seo/sitemap) # Sitemap Great news! Indie Kit automatically handles sitemap generation for your application! ๐ŸŽ‰ Your sitemap is automatically created and accessible at `/sitemap.xml`. It includes all your static pages, blog posts, policy pages, and documentation pages without any setup required. ## Why Sitemaps Matter ๐ŸŽฏ Having a sitemap is crucial for SEO because it: * ๐Ÿ” **Helps Search Engines Discover Content**: Ensures all your pages are found and indexed by search engines like Google, Bing, and others * โšก **Speeds Up Indexing**: New pages and updates are discovered faster, improving your site's visibility * ๐Ÿ“Š **Improves Crawl Efficiency**: Tells search engines which pages are most important and how often they change * ๐Ÿ—บ๏ธ **Provides Structure**: Gives search engines a clear map of your site's architecture and content hierarchy * ๐Ÿ“ˆ **Boosts Rankings**: Better indexing means better chances of ranking for relevant searches * ๐Ÿ†• **Prioritizes Fresh Content**: Helps search engines know when content is updated, especially for blogs and dynamic pages Without a sitemap, search engines might miss important pages, leading to lower visibility and reduced organic traffic. ## What's Included by Default ๐Ÿ“ฆ Indie Kit's sitemap automatically includes: * โœ… Static pages (home, about, contact, pricing, etc.) * โœ… Policy pages (privacy, terms, refund, cookie) * โœ… Blog posts (with creation dates) * โœ… Documentation pages * โœ… Proper metadata (lastModified, changeFrequency, priority) ## How It Works ๐Ÿ”ง Indie Kit's sitemap generation is handled automatically through `src/app/sitemap.ts`. The system: * ๐Ÿ“„ Collects all static pages (home, about, contact, etc.) with appropriate priority levels * ๐Ÿ“‹ Gathers policy pages (privacy, terms, refund, cookie) with standard metadata * ๐Ÿ“ Fetches all blog posts with their creation dates * ๐Ÿ“š Includes all documentation pages from your docs directory * โš™๏ธ Combines everything with proper metadata (lastModified, changeFrequency, priority) * ๐Ÿš€ Automatically regenerates when you deploy or add new content Everything works out of the box! Your sitemap is automatically generated and kept up-to-date without any manual intervention. ## Customizing Your Sitemap ๐ŸŽจ Want to add your own pages? Simply update `src/app/sitemap.ts`: ```typescript // Add your custom pages to the staticPages array const staticPages = [ "", "/about", "/contact", "/join-waitlist", "/blog", "/pricing", "/features", // โœจ Your new page "/testimonials", // โœจ Your new page "/case-studies", // โœจ Your new page ].map((route) => ({ url: `${baseUrl}${route}`, lastModified: new Date(), changeFrequency: "monthly" as const, priority: route === "" ? 1 : 0.8, })); ``` ## Programmatic SEO (pSEO) ๐Ÿš€ Want to include database items in your sitemap? This is perfect for programmatic SEO! Here's an example with a "projects" database: ```typescript import { MetadataRoute } from "next"; import { getAllBlogs } from "@/lib/mdx/blogs"; import { db } from "@/db"; // Your database instance import { projects } from "@/db/schema"; // Your projects table export default async function sitemap(): Promise { const baseUrl = process.env.NEXT_PUBLIC_APP_URL!; const blogs = await getAllBlogs(); // Fetch all projects from database const allProjects = await db.select().from(projects); // Static pages const staticPages = [ "", "/about", "/contact", "/projects", // Projects listing page ].map((route) => ({ url: `${baseUrl}${route}`, lastModified: new Date(), changeFrequency: "monthly" as const, priority: route === "" ? 1 : 0.8, })); // Blog pages const blogPages = blogs.map((blog) => ({ url: `${baseUrl}/blog/${blog.slug}`, lastModified: new Date(blog.frontmatter.createdDate), changeFrequency: "weekly" as const, priority: 0.6, })); // ๐ŸŽฏ Project pages from database const projectPages = allProjects.map((project) => ({ url: `${baseUrl}/projects/${project.slug}`, lastModified: project.updatedAt || project.createdAt, changeFrequency: "weekly" as const, priority: 0.7, })); return [...staticPages, ...blogPages, ...projectPages]; } ``` ## Generating Multiple Sitemaps ๐Ÿ“š For large sites with 50,000+ URLs, you can generate multiple sitemaps: ```typescript import type { MetadataRoute } from 'next' import { BASE_URL } from '@/app/lib/constants' export async function generateSitemaps() { // Fetch the total number of products and calculate the number of sitemaps needed return [{ id: 0 }, { id: 1 }, { id: 2 }, { id: 3 }] } export default async function sitemap({ id, }: { id: number }): Promise { // Google's limit is 50,000 URLs per sitemap const start = id * 50000 const end = start + 50000 const products = await getProducts( `SELECT id, date FROM products WHERE id BETWEEN ${start} AND ${end}` ) return products.map((product) => ({ url: `${BASE_URL}/product/${product.id}`, lastModified: product.date, })) } ``` This generates multiple sitemaps like: * `/sitemap/0.xml` * `/sitemap/1.xml` * `/sitemap/2.xml` * etc. ## Customizing Robots.txt ๐Ÿค– To control search engine crawlers, update `src/app/robots.ts`: ```typescript import { MetadataRoute } from 'next' export default function robots(): MetadataRoute.Robots { return { rules: [ { userAgent: '*', allow: '/', disallow: ['/admin/', '/api/'], }, { userAgent: 'Googlebot', allow: '/', crawlDelay: 2, }, ], sitemap: `${process.env.NEXT_PUBLIC_APP_URL}/sitemap.xml`, } } ``` You can customize rules for specific user agents. This is useful for controlling how different search engines crawl your site! ## Best Practices ๐Ÿ’ก 1. **Priority Values** * Homepage: `1.0` (highest priority) * Main pages: `0.8` * Blog posts: `0.6-0.7` * Policy pages: `0.5` 2. **Change Frequency** * Static pages: `monthly` * Blog posts: `weekly` * Dynamic content: `daily` or `weekly` 3. **Last Modified** * Use actual creation/update dates for blog posts * Use `new Date()` for static pages * Keep database items updated with their modification dates 4. **Testing** * Visit `/sitemap.xml` to verify your sitemap * Check that all expected URLs are included * Validate with [Google Search Console](https://search.google.com/search-console) ## Resources ๐Ÿ“š Learn more about sitemaps in Next.js: * [Next.js Sitemap Documentation](https://nextjs.org/docs/app/api-reference/file-conventions/metadata/sitemap) * [Generating Multiple Sitemaps](https://nextjs.org/docs/app/api-reference/file-conventions/metadata/sitemap#generating-multiple-sitemaps) * [Robots.txt Configuration](https://nextjs.org/docs/app/api-reference/file-conventions/metadata/robots#customizing-specific-user-agents) Your sitemap is automatically generated and ready to help search engines discover your content! ๐ŸŽ‰ # Background Jobs (/setup/background-jobs) YouTube Video: https://www.youtube.com/watch?v=gz2331T2sAw Run background tasks reliably! Perfect for email sequences, scheduled jobs, and async processing powered by Inngest. โšก Background jobs work out of the box in development! No configuration needed for local testing. ## What You Can Build ๐ŸŽฏ With background jobs, you can: * โฐ **Scheduled tasks** - Cron jobs, cleanup tasks * ๐Ÿ“ง **Email sequences** - Onboarding flows, drip campaigns * ๐Ÿ”„ **Retry logic** - Automatic retry on failures * ๐Ÿ“Š **Analytics** - Process data in background * ๐Ÿค– **Webhooks** - Handle external events ## Local Development ๐Ÿ’ป Your local Inngest server runs automatically at: ``` http://localhost:8288/stream ``` **Monitor your jobs:** 1. Start your dev server: `pnpm dev` 2. Open `http://localhost:8288` 3. See all jobs and their status 4. Test and debug locally The local Inngest UI shows execution history, logs, and errors. Perfect for debugging! ๐Ÿ› ## Production Setup ๐Ÿš€ ### Step 1: Create Inngest Account 1. Sign up at [Inngest.com](https://www.inngest.com/) (free tier available!) 2. Create a new app in dashboard 3. Copy your `INNGEST_EVENT_KEY` ### Step 2: Add to Environment Variables ```bash INNGEST_EVENT_KEY="your-key-here" ``` Add this to your hosting platform's environment variables, not to `.env` in your repo! ### Step 3: Deploy (Platform-Specific) Choose your hosting platform: **๐ŸŸข Vercel (Recommended)** 1. Add `INNGEST_EVENT_KEY` to environment variables 2. Deploy your app 3. Install [Inngest integration](https://www.inngest.com/docs/deploy/vercel) from Vercel marketplace 4. Jobs automatically sync! Vercel + Inngest integration handles everything automatically. Recommended for easiest setup! โญ **๐ŸŸฃ Render** 1. Add `INNGEST_EVENT_KEY` to environment variables 2. Deploy your app 3. Configure webhook URL in Inngest dashboard 4. [Full Render guide โ†’](https://www.inngest.com/docs/deploy/render) **๐Ÿ”ต Netlify** 1. Add `INNGEST_EVENT_KEY` to environment variables 2. Enable Netlify Functions 3. Configure webhook URL in Inngest dashboard 4. [Full Netlify guide โ†’](https://www.inngest.com/docs/deploy/netlify) ## Verify Setup โœ… After deploying, confirm everything works: 1. **Check Inngest Dashboard** * Visit [app.inngest.com](https://app.inngest.com) * See your functions listed under "Functions" * All jobs from your code appear automatically 2. **Test a Job** * Trigger a test job (e.g., send email) * Watch execution in dashboard * Check logs and status Inngest automatically discovers your functions from your code. No manual registration needed! โœจ ## Example: Email Sequence ๐Ÿ“ง Here's a quick example of a background job: ```ts // src/lib/inngest/functions/welcome-email.ts import { inngest } from "../client"; import { sendEmail } from "@/lib/email"; export const welcomeEmailSequence = inngest.createFunction( { id: "welcome-email-sequence" }, { event: "user/signup" }, async ({ event, step }) => { // Send immediately await step.run("send-welcome", async () => { await sendEmail({ to: event.data.email, subject: "Welcome! ๐Ÿ‘‹", template: "welcome" }); }); // Wait 3 days await step.sleep("wait-3-days", "3d"); // Send follow-up await step.run("send-tips", async () => { await sendEmail({ to: event.data.email, subject: "Here are some tips ๐Ÿ’ก", template: "tips" }); }); } ); ``` **Trigger the job:** ```ts // When user signs up await inngest.send({ name: "user/signup", data: { email: user.email } }); ``` ## Built-in Features ๐ŸŽ Inngest handles complex job management: * โฐ **Scheduling** - Run jobs at specific times * ๐Ÿ”„ **Retries** - Automatic retry on failures * ๐Ÿ“Š **Monitoring** - Real-time execution tracking * ๐Ÿšจ **Error alerts** - Get notified of failures * ๐Ÿ”€ **Throttling** - Control job execution rates * โธ๏ธ **Delays** - Wait between steps * ๐ŸŽฏ **Conditional logic** - Branch based on results ## Best Practices ๐Ÿ’ก **1. Test Locally First** ```bash pnpm dev # Inngest dev server starts automatically ``` **2. Use Step Functions** ```ts // โœ… Good - Each step is retryable await step.run("fetch-data", async () => fetchData()); await step.run("process", async () => process()); // โŒ Bad - All-or-nothing await fetchData(); await process(); ``` **3. Handle Errors Gracefully** ```ts try { await step.run("send-email", async () => { await sendEmail(); }); } catch (error) { // Log and handle console.error("Failed to send:", error); throw error; // Triggers retry } ``` **4. Keep Jobs Idempotent** * Jobs might run multiple times * Use unique IDs to prevent duplicates * Check state before performing actions ## Next Steps ๐Ÿš€ 1. **Explore examples** in `src/lib/inngest/functions/` 2. **Create your first job** - Email sequence or scheduled task 3. **Test locally** - Use the dev UI at localhost:8288 4. **Deploy** - Push and see jobs in Inngest dashboard You can now run reliable background tasks! Start with a simple email sequence and build from there. โšก ## Related Documentation ๐Ÿ“š * [Running Scheduled Jobs](/background-jobs/running-scheduled-jobs) - Cron patterns * [Create Email Sequence](/background-jobs/create-email-sequence) - Drip campaigns * [Inngest Docs](https://www.inngest.com/docs) - Full platform docs # Crisp Chat (/setup/chat) # Crisp Chat Support ๐Ÿ’ฌ Indie Kit includes seamless integration with Crisp Chat, allowing you to provide real-time support to your users. ## Configuration ๐Ÿ› ๏ธ To enable Crisp Chat, add your website ID to your `.env` file: ```bash NEXT_PUBLIC_CRISP_WEBSITE_ID=your_website_id_here ``` ## Features โœจ ### Automatic Setup Indie Kit automatically initializes the chat widget when the environment variable is present. ### User Authentication The integration automatically handles user authentication. When a user is logged in, their name and email will be automatically synced with Crisp, so you know exactly who you're talking to. ## Customization ๐ŸŽจ If you need to customize the chat behavior or appearance further, you can modify the component at: `src/components/chat/crisp.tsx` Feel free to update this file to match your specific requirements or to add advanced Crisp features. # Database (/setup/database) import { Tabs, Tab } from 'fumadocs-ui/components/tabs' import Image from 'next/image' # Setting Up Your Database โšก๏ธ Indie Kit supports multiple database providers. Choose the one that best fits your needs! ๐Ÿš€ ## Setting up Neon (PostgreSQL) ๐ŸŽฏ 1. **Create a Neon Account** ๐Ÿ‘ค * Go to [neon.tech](https://neon.tech) * Click "Sign Up" and create your account * You can use GitHub for quick signup 2. **Create a New Project** ๐Ÿ“ฆ * Click "New Project" in your Neon dashboard * Choose a name for your project * Select the region closest to your users * Click "Create Project" 3. **Get Your Connection String** ๐Ÿ”— ```bash # Your connection string will look like this: postgresql://[user]:[password]@[neon-host]/[database] ``` * Find the connection string in your project dashboard * Make sure to use the full connection string with your credentials Neon offers a generous free tier perfect for development and small projects. You can always upgrade as your project grows! ## Setting up Supabase (PostgreSQL) ๐ŸŽฏ 1. **Create a Supabase Account** ๐Ÿ‘ค * Go to [supabase.com](https://supabase.com) * Click "Start your project" * Sign up with GitHub for easier setup 2. **Create a New Project** ๐Ÿ“ฆ * Click "New Project" in your dashboard * Fill in your project details * Choose a region and database password * Wait for project creation (usually 1-2 minutes) 3. **Get Your Connection String** ๐Ÿ”— * Click "Connect" Supabase Tutorial ```bash postgresql://postgres:[YOUR-PASSWORD]@db.[project-ref].supabase.co:5432/postgres ``` Supabase provides additional features like Auth, Storage, and Edge Functions! Perfect if you need a full backend platform. ## Setting up PlanetScale (MySQL) ๐ŸŽฏ 1. **Create a PlanetScale Account** ๐Ÿ‘ค * Visit [planetscale.com](https://planetscale.com) * Click "Sign Up" * Create your account (GitHub recommended) 2. **Create a New Database** ๐Ÿ“ฆ * Click "Create a new database" * Choose a name and region * Select "Hobby" plan for development * Click "Create database" 3. **Get Your Connection String** ๐Ÿ”— * Click "Connect" * Choose "Connect with" > "Prisma" * Copy the connection string ```bash mysql://[username]:[password]@[host]/[database]?sslaccept=strict ``` PlanetScale's branching feature is great for schema changes! Perfect for teams with complex database workflows. ## Configuring Your Project ๐Ÿ› ๏ธ 1. **Set Up Environment Variables** ๐Ÿ“ * Open your `.env` file * Add your database connection string: ```bash DATABASE_URL="your-connection-string-here" ``` ## Syncing Database Schema ๐Ÿ”„ 2. **Push Schema to Database** ๐Ÿš€ ```bash npx drizzle-kit push ``` This command will: * Connect to your database * Create all necessary tables * Sync your schema changes 3. **Verify Setup** โœ… ```bash npm run dev ``` * Start your development server * Your app should now connect to the database * Check the console for any connection messages Indie Kit uses "JWT" session strategy by default, though you can use "Database" session strategy if you want to. Read more about it [here](https://authjs.dev/concepts/session-strategies). If you want to use your own hosted PostgreSQL database, you may need to set the following in `db.ts` file: ```typescript import { drizzle } from 'drizzle-orm/postgres-js'; export const db = drizzle(process.env.DATABASE_URL!) ``` ## Troubleshooting ๐Ÿ” If you encounter any issues: 1. **Connection Issues** ๐ŸŒ * Verify your connection string is correct * Check if your IP is allowed in database settings * Ensure your database user has proper permissions 2. **Schema Sync Issues** ๐Ÿ“Š * Check if `drizzle.config.ts` is properly configured * Verify your schema files are correctly exported * Look for any console errors during schema push ## Next Steps ๐ŸŽฏ * Set up your database indexes for better performance * Configure database pooling if needed * Consider setting up database backups * Explore your database provider's dashboard for monitoring Now your Indie Kit app is connected to your chosen database! Ready to start building? ๐Ÿš€ # Legal (/setup/policies) YouTube Video: https://www.youtube.com/watch?v=E2bse4u4D58 Let's set up policies and contact form for your Indie Kit application! ๐Ÿš€ # Sentry (/setup/sentry) # Sentry Support ๐Ÿ›ก๏ธ Indie Kit comes with built-in support for Sentry to help you track errors and monitor your application's performance. ## Configuration ๐Ÿ› ๏ธ To enable Sentry, you simply need to add the following environment variables to your `.env` file: ```bash NEXT_PUBLIC_SENTRY_DSN=your_dsn_here SENTRY_PROJECT=your_project_name SENTRY_AUTH_TOKEN=your_auth_token ``` Once these are set, Indie Kit will automatically configure Sentry for your application. ## Inngest Integration โšก๏ธ Indie Kit also automatically sets up Sentry for Inngest. No additional configuration is required! ## Troubleshooting ๐Ÿ” If Sentry isn't reporting errors: 1. Verify your `NEXT_PUBLIC_SENTRY_DSN` is correct. 2. Check your Sentry project settings to ensure the domain is allowed. 3. Make sure you've rebuilt your application after adding the environment variables. # Super Admin (/setup/super-admin) YouTube Video: https://www.youtube.com/watch?v=fOOcvlVc7Sc Get admin access to your platform! Manage users, plans, waitlists, and more from the super admin dashboard. ๐Ÿ‘‘ Make sure you have [database](/setup/database) and [authentication](/setup/auth) configured first. ## Super Admin Powers ๐Ÿฆธ With super admin access, you can: * ๐Ÿ‘ฅ **Manage Users** - View, edit, impersonate users * ๐Ÿ’ณ **Manage Plans** - Create and edit subscription plans * ๐Ÿ“‹ **Waitlist Management** - View and export waitlist signups * ๐Ÿ“Š **Analytics** - Platform-wide stats and insights * ๐ŸŽซ **User Credits** - Add or remove credits manually * โš™๏ธ **Platform Settings** - Configure app-wide settings ## Setup (30 seconds) โšก ### Step 1: Add Admin Emails Add to `.env.local`: ```bash # Single admin SUPER_ADMIN_EMAILS=admin@yourapp.com # Multiple admins (comma-separated) SUPER_ADMIN_EMAILS=admin@yourapp.com,manager@yourapp.com,support@yourapp.com # Enable signin NEXT_PUBLIC_SIGNIN_ENABLED=true ``` Make sure to use the email address you'll sign in with! Case-sensitive matching. ### Step 2: Restart Server ```bash # Stop and restart pnpm dev ``` ### Step 3: Sign In 1. Sign in with your admin email 2. Visit `/super-admin` 3. See the admin dashboard! ๐ŸŽ‰ You now have full admin access to your platform! ๐Ÿ‘‘ ## Admin Dashboard Features ๐ŸŽฏ ### User Management **What you can do:** * View all users with search and filters * See user activity and subscription status * Impersonate users (see app from their perspective) * Manually adjust user data * Add/remove credits **Access:** `/super-admin/users` ### Plan Management **What you can do:** * Create new subscription plans * Edit existing plans (pricing, features, quotas) * Set default plans for new signups * Configure payment provider IDs * Archive old plans **Access:** `/super-admin/plans` Existing subscriptions aren't affected when you edit plans. Users on old plans keep their pricing until they change plans. ### Waitlist Management **What you can do:** * View all waitlist signups * Export to CSV * Send bulk emails * Track signup sources **Access:** `/super-admin/waitlist` ### Contact Form Submissions **What you can do:** * View all contact form submissions * Mark as resolved * Respond to inquiries * Export submissions **Access:** `/super-admin/contact` ## Security Best Practices ๐Ÿ”’ **1. Use Strong Emails** ```bash # โœ… Good - Work emails you control SUPER_ADMIN_EMAILS=founder@startup.com,cto@startup.com # โŒ Bad - Personal emails that might change SUPER_ADMIN_EMAILS=myemail@gmail.com ``` **2. Limit Admin Access** * Only add trusted team members * Remove admin emails when people leave * Review admin list regularly **3. Monitor Admin Actions** * Check audit logs (if enabled) * Monitor for unusual activity * Set up alerts for critical changes **4. Production Environment** ```bash # In production .env SUPER_ADMIN_EMAILS=secure@yourdomain.com NEXT_PUBLIC_SIGNIN_ENABLED=true ``` Remember to set `SUPER_ADMIN_EMAILS` in your production environment variables! Don't rely on `.env` file. ## User Impersonation ๐ŸŽญ One powerful feature is user impersonation: **How to use:** 1. Go to `/super-admin/users` 2. Find the user you want to impersonate 3. Click "Impersonate" 4. See the app exactly as they see it 5. Click "Exit Impersonation" when done **Use cases:** * ๐Ÿ› Debug user-reported issues * ๐Ÿ‘€ See what users experience * ๐Ÿ†˜ Help users navigate the app * ๐Ÿงช Test permissions and roles Always inform users if you'll be accessing their account. Respect privacy and use only for support/debugging! ## Common Tasks ๐Ÿ“ ### Add a New Admin ```bash # Edit .env.local SUPER_ADMIN_EMAILS=existing@admin.com,new@admin.com ``` Restart server. New admin can access `/super-admin` immediately. ### Remove Admin Access ```bash # Edit .env.local - remove their email SUPER_ADMIN_EMAILS=remaining@admin.com ``` Restart server. Removed admin loses access. ### Check Who Has Access ```bash # View current admins echo $SUPER_ADMIN_EMAILS ``` ### Give Temporary Access For contractors or temporary help: 1. Add their email to `SUPER_ADMIN_EMAILS` 2. Set a reminder to remove later 3. Remove when contract ends ## Troubleshooting ๐Ÿ”ง **Can't access /super-admin?** * โœ… Check your email is in `SUPER_ADMIN_EMAILS` * โœ… Verify email matches exactly (case-sensitive) * โœ… Restart dev server after adding email * โœ… Sign out and back in **Changes not reflecting?** * Restart your dev server * Clear browser cache * Check environment variables loaded correctly **Multiple admins not working?** * Use commas, no spaces: `admin1@app.com,admin2@app.com` * Check for typos in email addresses * Verify `.env.local` is being read ## Next Steps ๐Ÿš€ Now that you're a super admin: 1. **Create your plans** at `/super-admin/plans` 2. **Review waitlist** at `/super-admin/waitlist` 3. **Check user activity** at `/super-admin/users` 4. **Explore features** in the admin dashboard You have full control over your platform! Use your powers wisely. ๐Ÿ‘‘โœจ ## Related Documentation ๐Ÿ“š * [User Impersonation](/super-admin/user-impersonation) - Detailed guide * [Managing Plans](/payments/managing-plans) - Plan configuration * [Waitlist Management](/super-admin/waitlist) - Waitlist features # Contact Form Management (/super-admin/contact-us) YouTube Video: https://www.youtube.com/watch?v=fOOcvlVc7Sc # Contact Form Management ๐Ÿ’ฌ View all contact form submissions from your super admin dashboard! Simple and straightforward. โœจ Indie Kit includes a complete contact form with admin view. No setup needed! ## How It Works ๐ŸŽฏ **Simple flow:** 1. User fills contact form on your site 2. Submission saved to database 3. View in super admin dashboard 4. Reply to user via email That's it! ๐Ÿš€ ## Viewing Submissions ๐Ÿ“‹ ### Navigate to Contact Dashboard Go to `/super-admin/messages` in your admin panel. **What you'll see:** * ๐Ÿ“ง All contact form submissions * ๐Ÿ‘ค User name and email * ๐Ÿ’ฌ Message content * ๐Ÿ“… Submission date ### Example Submission ``` Name: John Doe Email: john@example.com Message: Can I upgrade my plan mid-month? Submitted: Oct 16, 2025, 2:30 PM ``` Refresh the page to see new submissions! ## Responding to Users ๐Ÿ’ฌ To reply to a contact submission: 1. **Note the user's email** from the dashboard 2. **Send email** using your email client 3. **Reply directly** to help them out **Quick tip:** ``` Copy email โ†’ Open Gmail/Outlook โ†’ Reply ``` ## Best Practices โœ… **1. Check Regularly** * Visit `/super-admin/contact` daily * Respond within 24 hours * Keep track of who you've replied to **2. Use Templates** Save time with email templates for: * Password reset help * Billing questions * Feature requests * Common questions **3. Track Insights** Notice patterns in submissions: * Common questions โ†’ Update docs * Feature requests โ†’ Roadmap ideas * Bug reports โ†’ Fix priorities ## Customization ๐ŸŽจ ### Customize Contact Form Edit the contact form: ``` src/app/(website-layout)/contact/page.tsx ``` Add custom fields like: * Subject dropdown * Category selection * Phone number * Company name ### Customize Admin View Modify the admin view: ``` src/app/super-admin/messages/page.tsx ``` All contact form code is in your project! Customize as needed. ## Next Steps ๐Ÿš€ 1. **Test it** - Submit a test contact 2. **Check dashboard** - View at `/super-admin/messages` 3. **Reply to users** - Use your email client 4. **Customize** - Add fields if needed ## Related Documentation ๐Ÿ“š * [Super Admin Setup](/setup/super-admin) - Configure admin access * [Waitlist Management](/super-admin/waitlist) - Similar feature * [Email Setup](/setup/email) - Configure email # User Impersonation (/super-admin/user-impersonation) # User Impersonation ๐ŸŽญ See your app exactly as your users see it! Perfect for debugging and support. โœจ Only super admins can impersonate users! ## How to Impersonate ๐Ÿš€ Super simple! Just 3 steps: 1. **Go to super admin** - Navigate to `/super-admin/users` 2. **Click impersonate** - Find user and click the button 3. **Open in incognito** - Copy link and open in incognito/private tab That's it! You're now seeing the app as that user. ๐ŸŽ‰ Always open the impersonation link in incognito to avoid session conflicts! ## What You Can Do ๐ŸŽฏ While impersonating: * โœ… **See their dashboard** - Exactly as they see it * โœ… **Access their data** - Projects, settings, etc. * โœ… **Test features** - Use the app as them * โœ… **Reproduce bugs** - See errors they're experiencing * โœ… **Check permissions** - Verify role-based access ## When to Use It ๐Ÿ’ก **Perfect for:** * ๐Ÿ› **Debugging issues** - See what they see * ๐Ÿ†˜ **Customer support** - Help users navigate * ๐Ÿงช **Testing permissions** - Verify access works * ๐Ÿ” **Reproducing bugs** - See errors in context **Example:** ``` User: "I can't see my projects!" โ†’ Impersonate โ†’ See issue โ†’ Fix bug โœ… ``` ## Exit Impersonation ๐Ÿšช To exit: **Just close the incognito tab!** ๐ŸŽฏ Your admin session in the normal browser stays safe. ## Best Practices โœ… **1. Use for Support Only** * Debug reported issues * Reproduce bugs * Verify feature access **2. Keep Sessions Short** * Exit as soon as you're done * Close incognito tab when finished **3. Respect Privacy** * Use only for legitimate support * Don't browse data without reason Use impersonation only for support and debugging! ## Next Steps ๐Ÿš€ 1. **Test it** - Impersonate a test user 2. **Use for support** - Debug user issues 3. **Close tab** - Exit by closing incognito ## Related Documentation ๐Ÿ“š * [Super Admin Setup](/setup/super-admin) - Configure admin access * [Organization Roles](/multi-tenancy/roles) - Understanding permissions # Waitlist Management (/super-admin/waitlist) # Waitlist Management ๐Ÿ“‹ View all waitlist signups from your super admin dashboard! Perfect for pre-launch campaigns. โœจ Indie Kit includes a complete waitlist system with signup page and admin view. Ready to use! ## Viewing Waitlist Entries ๐Ÿ‘€ ### Navigate to Waitlist Dashboard Go to `/super-admin/waitlist` in your admin panel. **What you'll see:** * ๐Ÿ“ง All waitlist signups * ๐Ÿ‘ค Name and email * ๐Ÿ“… Signup date * ๐Ÿ“Š Total signup count ### Entry Details Each signup shows: ``` Name: Sarah Johnson Email: sarah@example.com Signed up: Oct 15, 2025, 3:45 PM ``` Refresh the page to see new signups! ## Email Your Waitlist ๐Ÿ“ง To send updates to your waitlist: 1. **Copy emails** from the dashboard 2. **Use your email provider** (Gmail, Mailchimp, ConvertKit, etc.) 3. **Send updates** about your launch **What to send:** * ๐ŸŽ‰ **Welcome** - Thanks for joining! * ๐Ÿ“ฐ **Updates** - Development progress * ๐Ÿš€ **Launch announcement** - We're live! * ๐ŸŽŸ๏ธ **Early access** - Beta invites Use [email sequences](/background-jobs/create-email-sequence) to automate waitlist communications! ## Waitlist Page ๐ŸŒ Users sign up at: ``` https://yourapp.com/join-waitlist ``` **Simple form:** * Name field * Email field * Submit button * Success message ### Customize Waitlist Page Edit the page: ``` src/app/join-waitlist/page.tsx ``` Customize: * Form fields * Styling * Success message * Auto-response email ## Best Practices โœ… **1. Send Welcome Email** Set up auto-welcome email on signup to thank them! **2. Keep Them Engaged** * Send weekly/monthly updates * Share progress milestones * Build anticipation for launch **3. Launch Strategy** ``` Week -4: Progress update Week -2: Launch date announcement Week -1: Final preview Day 0: Launch! ๐Ÿš€ ``` ## Customization ๐ŸŽจ ### Add Custom Fields Edit the form to collect more data: ```tsx // src/app/join-waitlist/page.tsx
{/* Add custom fields */}
``` All waitlist code is in your project - customize as needed! ## Next Steps ๐Ÿš€ 1. **Test it** - Sign up yourself 2. **Check dashboard** - View at `/super-admin/waitlist` 3. **Plan launch** - Prepare email campaigns 4. **Customize** - Add fields if needed ## Related Documentation ๐Ÿ“š * [Launch with Waitlist](/launch-with-waitlist) - Setup guide * [Super Admin Setup](/setup/super-admin) - Admin access * [Email Sequences](/background-jobs/create-email-sequence) - Automate emails # Google Login (/setup/auth/google-login) # Setting Up Google Login ๐Ÿ”‘ Let's add Google authentication to your Indie Kit app. This guide will walk you through the setup process. ๐Ÿš€ ## Prerequisites ๐Ÿ“‹ Before starting, make sure you've completed the [Authentication Setup](/setup/auth). ## Getting Google Credentials ๐Ÿ” 1. **Create Google Cloud Project** โ˜๏ธ * Follow this detailed video tutorial: [Google OAuth Setup Guide](https://www.youtube.com/watch?v=OKMgyF5ezFs\&pp=ygUSZ29vZ2xlIG9hdXRoIHNldHVw) * Go to [Google Cloud Console](https://console.cloud.google.com) * Create a new project or select an existing one 2. **Configure OAuth Consent Screen** ๐Ÿ“ * Go to "APIs & Services" > "OAuth consent screen" * Choose "External" user type * Fill in the required information: * App name * User support email * Developer contact information 3. **Create OAuth Credentials** ๐Ÿ”‘ * Go to "APIs & Services" > "Credentials" * Click "Create Credentials" > "OAuth client ID" * Choose "Web application" * Add authorized redirect URIs: ```bash # Development http://localhost:3000/api/auth/callback/google # Production (replace with your domain) https://yourdomain.com/api/auth/callback/google ``` 4. **Add Authorized Domains** ๐ŸŒ * In the OAuth consent screen * Add your domains: ```bash localhost:3000 yourdomain.com ``` ## Configuration โš™๏ธ 5. **Add Environment Variables** ๐Ÿ“ Add these to your `.env.local`: ```bash GOOGLE_CLIENT_ID=your_client_id_here GOOGLE_CLIENT_SECRET=your_client_secret_here ``` * Client ID: Credentials page > Your OAuth 2.0 Client > Client ID * Client Secret: Credentials page > Your OAuth 2.0 Client > Client Secret 6. **Production Setup** ๐Ÿš€ Add these environment variables in your Vercel dashboard: * Go to Project Settings > Environment Variables * Add the same variables: ```bash GOOGLE_CLIENT_ID=your_client_id_here GOOGLE_CLIENT_SECRET=your_client_secret_here ``` * Redeploy your application ## Verification ๐Ÿ” 7. **Test Local Setup** ```bash npm run dev ``` * Visit `http://localhost:3000/sign-in` * Click "Continue with Google" * You should be able to sign in 8. **Test Production Setup** * Deploy your changes * Visit your production URL * Verify Google login works ## Troubleshooting ๐Ÿ”ง Common issues and solutions: 1. **Invalid Redirect URI** * Double-check the exact redirect URI in Google Console * Make sure it matches your domain exactly * Include the full path: `/api/auth/callback/google` 2. **Unauthorized Domain** * Verify your domain is listed in authorized domains * Include both with and without `www` if needed 3. **Production Issues** * Verify environment variables in Vercel * Check if the production redirect URI is authorized * Clear browser cache and try again ## Security Best Practices ๐Ÿ›ก๏ธ 1. Never commit `.env.local` to version control 2. Regularly rotate client secrets 3. Limit OAuth scope to necessary permissions 4. Monitor OAuth usage in Google Console Your users can now sign in with their Google accounts! ## Next Steps ๐Ÿš€ Want to add more authentication options? * [Add more OAuth providers](/setup/auth/z-adding-more-providers) - Facebook, GitHub, and more * [Enable Password Login](/setup/auth/password-login) - Traditional email/password auth # Authentication (/setup/auth) YouTube Video: https://www.youtube.com/watch?v=2fLoibmOsVo Setting up authentication in your Indie Kit application is quick and easy. Let's get started! ๐Ÿš€ ## Quick Setup โšก 1. Add these environment variables to your `.env` file: ```bash # Authentication SUPER_ADMIN_EMAILS=your.email@example.com NEXT_PUBLIC_SIGNIN_ENABLED=true ``` 2. Run the authentication setup command: ```bash npx auth secret ``` That's it! Your authentication system is now ready to use. ๐ŸŽ‰ ## Next Steps ๐Ÿ”Œ Add more authentication features to your app: * [Set up Google Login](/setup/auth/google-login) * [Configure Email Provider](/setup/email) ## Verifying Setup โœ… To verify your authentication is working: 1. Start your development server 2. Visit `/sign-in` page 3. Try signing in with the configured provider 4. You should be redirected to the dashboard after successful login Now your Indie Kit application is ready to handle user authentication! ๐ŸŽฏ # Password Login (/setup/auth/password-login) # Password-Based Authentication ๐Ÿ” Let users sign in with email and password - the classic authentication method everyone knows and trusts! โœจ ## What is Password Auth? ๐Ÿค” Password authentication allows users to: * โœ… Create accounts with email + password * โœ… Sign in using their credentials * โœ… Reset forgotten passwords via email * โœ… No magic links or OAuth required Indie Kit supports multiple auth methods! You can enable password auth alongside magic links and social logins (Google, GitHub, etc.) - or use them independently. ## When to Use Password Auth ๐ŸŽฏ **Perfect for:** * ๐Ÿข **Enterprise/B2B apps** - Many businesses prefer passwords * ๐Ÿ”’ **Security-conscious users** - Some users trust passwords more * ๐Ÿ“ฑ **Mobile-first apps** - Easier than checking email for magic links * ๐ŸŒ **International users** - Works without reliable email access **Consider alternatives for:** * Quick signups (magic links are faster) * Less tech-savvy users (OAuth is simpler) * Apps targeting developers (they love passwordless) ## Enable Password Authentication โšก Super simple - just flip a switch! ### Step 1: Update Config Open `src/lib/config.ts` and enable password auth: ```ts auth: { enablePasswordAuth: true, // Enable password login }, ``` ### Step 2: That's It! โœจ Your app now supports password authentication! Users will see: * **Sign Up** - Email + password fields * **Sign In** - Email + password login * **Forgot Password** - Password reset flow Everything is handled automatically! The UI, validation, password hashing, and reset flows are all built-in. Just enable and go! ๐Ÿš€ ## What Happens Behind the Scenes ๐Ÿ”ง When you enable password auth: ### Security Features ๐Ÿ›ก๏ธ * โœ… **bcrypt Hashing** - Passwords are securely hashed, never stored plain * โœ… **Salt + Pepper** - Extra security layers * โœ… **Secure Reset Flow** - Time-limited password reset tokens ### User Experience โœจ * Clean, modern login UI * Helpful validation messages * Password confirmation on signup * Auto-focus and keyboard shortcuts ### Email Flows ๐Ÿ“ง * Welcome email on signup * Password reset emails * Email verification on signup ## Password Reset Flow ๐Ÿ”„ Built-in password reset is fully functional: **How it works:** 1. User clicks "Forgot Password" 2. Enters their email 3. Receives reset link via email 4. Clicks link, sets new password 5. Automatically logged in **Security:** * Reset links expire after 30 minutes * One-time use tokens * Email verification on signup # User Authentication Guide (/setup/auth/user-authentication) # User Authentication Guide ๐Ÿ” Indie Kit provides powerful authentication tools that you can use throughout your application. Let's explore how to implement authentication in different contexts! ๐Ÿš€ ## Client-Side Authentication with useUser ๐Ÿ’ป The `useUser` hook is perfect for client components. It provides user data and loading states: ```tsx 'use client' import { useUser } from '@/lib/users/useUser' export default function ProfileButton() { const { user, isLoading } = useUser() if (isLoading) { return
Loading...
} if (!user) { return } return (
{user.name} {user.name}
) } ``` ### Features of useUser ๐ŸŽฏ * ๐Ÿ”„ Automatic revalidation * ๐ŸŽญ Type-safe user data * โŒ› Loading states * ๐Ÿšซ Error handling * ๐Ÿ”Œ Offline support ## Server-Side Authentication with auth() ๐Ÿ”’ For server components and API routes, use the `auth()` function: ```tsx // src/app/profile/page.tsx import { auth } from '@/auth' import { redirect } from 'next/navigation' export default async function ProfilePage() { const session = await auth() if (!session) { redirect('/login') } return (

Welcome {session.user.name}!

Email: {session.user.email}

) } ``` ### When to Use auth() ๐Ÿ“‹ * โœ… Server Components * โœ… API Routes * โœ… Server Actions * โœ… Middleware * โœ… Layout Components ## Protected API Routes with withAuthRequired ๐Ÿ›ก๏ธ Secure your API routes using the `withAuthRequired` middleware: ```tsx // src/app/api/app/user-settings/route.ts import { withAuthRequired } from '@/lib/auth/withAuthRequired' import { NextResponse } from 'next/server' export const GET = withAuthRequired(async (req, context) => { const { session } = context // Access user data from session const userId = session.user.id // Your protected API logic here const settings = await getUserSettings(userId) return NextResponse.json(settings) }) export const POST = withAuthRequired(async (req, context) => { const { session } = context const data = await req.json() // Update user settings await updateUserSettings(session.user.id, data) return NextResponse.json({ success: true }) }) ``` ### Benefits of withAuthRequired ๐ŸŽฏ * ๐Ÿ”’ Automatic authentication checking * ๐ŸŽญ Type-safe session data * ๐Ÿšซ Automatic error responses * ๐Ÿ“ Session context in handler * โšก Zero-config setup ## Authentication Flow Examples ๐ŸŒŠ ### Protected Dashboard Page ```tsx // src/app/(in-app)/dashboard/page.tsx import { auth } from '@/auth' import { DashboardMetrics } from '@/components/dashboard/metrics' export default async function DashboardPage() { const session = await auth() return (

Dashboard

) } ``` ### User Settings Component ```tsx 'use client' import { useUser } from '@/lib/users/useUser' import { useState } from 'react' export function UserSettings() { const { user, isLoading } = useUser() const [name, setName] = useState(user?.name) if (isLoading) return
Loading settings...
if (!user) return
Please sign in
return (
setName(e.target.value)} className="input" />
) } ``` ## Best Practices ๐Ÿ’ซ 1. **Choose the Right Method** * ๐Ÿ–ฅ๏ธ `useUser` for client components * ๐Ÿ”’ `auth()` for server components * ๐Ÿ›ก๏ธ `withAuthRequired` for API routes 2. **Error Handling** * Always handle loading states * Provide clear error messages * Implement proper redirects 3. **Security Tips** * Validate on both client and server * Never expose sensitive data * Use HTTPS in production * Implement proper CORS policies 4. **Performance** * Cache authentication state * Use loading skeletons * Implement proper revalidation Now you're ready to implement authentication throughout your Indie Kit application! Remember to always validate user permissions and handle edge cases appropriately. ๐Ÿš€ # Adding More Providers (/setup/auth/z-adding-more-providers) # Adding More Auth Providers ๐Ÿ”Œ Indie Kit supports 80+ authentication providers through Auth.js! Let's add more options for your users. ๐Ÿš€ ## Popular Providers โœจ You can easily add any of these providers: * ๐Ÿ™ **GitHub** - Perfect for developer tools * ๐Ÿ“˜ **Facebook** - Great for social apps * ๐Ÿฆ **Twitter** - Social media integration * ๐Ÿ’ผ **LinkedIn** - B2B and professional apps * ๐ŸŽ **Apple** - iOS app requirement * ๐Ÿ”ท **Microsoft** - Enterprise applications * ๐Ÿ“ง **Email (Magic Links)** - Passwordless auth ## How to Add a Provider โšก The process is similar for all providers: ### 1. Get Provider Credentials ๐Ÿ”‘ Each provider requires: * Client ID * Client Secret * Redirect URI configuration ### 2. Add Environment Variables ๐Ÿ“ ```bash # Example for GitHub GITHUB_CLIENT_ID=your_client_id GITHUB_CLIENT_SECRET=your_client_secret # Example for Facebook FACEBOOK_CLIENT_ID=your_client_id FACEBOOK_CLIENT_SECRET=your_client_secret ``` ### 3. Configure in Auth.js ๐Ÿ› ๏ธ Providers are pre-configured in your `src/auth.ts` file. Just add your credentials to `.env` and they'll work automatically! Most popular providers are already set up in Indie Kit. Just add the environment variables and you're good to go! ๐ŸŽ‰ ## Provider Documentation ๐Ÿ“š For detailed setup guides for each provider: * **Official Docs**: [Auth.js Providers](https://authjs.dev/getting-started/providers) * **Provider List**: [All 80+ Providers](https://authjs.dev/reference/core/providers) Each provider has specific setup instructions, callback URLs, and required scopes. ## Example: Adding GitHub Auth ๐Ÿ™ Quick example of adding GitHub: 1. **Create OAuth App** on GitHub * Go to GitHub Settings > Developer Settings > OAuth Apps * Click "New OAuth App" * Set callback URL: `https://yourdomain.com/api/auth/callback/github` 2. **Add Credentials** ```bash GITHUB_CLIENT_ID=your_github_client_id GITHUB_CLIENT_SECRET=your_github_client_secret ``` 3. **Done!** Users can now sign in with GitHub โœจ Each provider has unique requirements. Check the [Auth.js documentation](https://authjs.dev/getting-started/providers) for specific setup instructions. ## Testing Your Provider ๐Ÿงช After adding a provider: 1. Restart your dev server 2. Visit `/sign-in` page 3. You should see the new provider button 4. Test the authentication flow ## Next Steps ๐ŸŽฏ * [User Authentication Guide](/setup/auth/user-authentication) - Use auth in your app * [Password Login](/setup/auth/password-login) - Add traditional auth # Cloudflare Email Setup (/setup/email/cloudflare) # Setting Up Cloudflare Email Sending ๐Ÿ“ง This guide will help you set up Cloudflare Email Sending for reliable email delivery in your application using their REST API. ## Prerequisites ๐Ÿ“‹ 1. Sign up for a [Cloudflare account](https://dash.cloudflare.com/sign-up/workers-and-pages). 2. Your domain must be managed by Cloudflare DNS. ## Domain Verification ๐Ÿ” Before using Email Sending, you need to configure your domain. 1. In the Cloudflare dashboard, go to **Email Sending** ([direct link](https://dash.cloudflare.com/?to=/:account/email-service/sending)). 2. Select **Onboard Domain**. 3. Choose a domain from your Cloudflare account. 4. Select **Continue** to proceed with DNS configuration. 5. Select **Add records and onboard**. This will automatically add the following DNS records on the `cf-bounce` subdomain of your domain: * **MX records** for bounce handling. * **TXT record** for SPF to authorize sending emails. * **TXT record** for DKIM to provide authentication for emails sent from your domain. * **TXT record** for DMARC on `_dmarc.yourdomain.com`. DNS changes can take up to 24 hours to propagate globally, but usually complete within 5-15 minutes for domains using Cloudflare DNS. Once your domain is onboarded, you can start sending emails. ## Environment Variables ๐Ÿ” Add these to your `.env` or `.env.local` file: ```bash CLOUDFLARE_ACCOUNT_ID="your_account_id" CLOUDFLARE_EMAIL_API_TOKEN="your_api_token" ``` *You can find your Account ID in the Cloudflare dashboard URL or on the overview page. Generate an API Token with Email Sending permissions.* ## Implementation ๐Ÿ’ป Create or update `src/lib/email/sendMail.ts` to use the Cloudflare REST API: ```typescript import { appConfig } from "../config"; const sendMail = async (to: string, subject: string, html: string) => { if (process.env.NODE_ENV !== "production") { console.log( "Sending email to", to, "with subject", subject, "and html", html ); return; } const accountId = process.env.CLOUDFLARE_ACCOUNT_ID; const apiToken = process.env.CLOUDFLARE_EMAIL_API_TOKEN; if (!accountId || !apiToken) { console.error("Cloudflare email credentials are not set."); return; } try { const response = await fetch( `https://api.cloudflare.com/client/v4/accounts/${accountId}/email/sending/send`, { method: "POST", headers: { Authorization: `Bearer ${apiToken}`, "Content-Type": "application/json", }, body: JSON.stringify({ to: to, from: `${appConfig.email.senderName} <${appConfig.email.senderEmail}>`, subject: subject, html: html, // Optional: Add plain text version // text: html.replace(/<[^>]*>?/gm, ''), }), } ); const data = await response.json(); if (!response.ok || !data.success) { console.error("Email sending failed:", data.errors || data); } else { if (process.env.NODE_ENV === "development") { console.info("Email sent successfully", data); } } } catch (error) { console.error("Error sending email via Cloudflare:", error); } }; export default sendMail; ``` ## Testing Email Setup ๐Ÿงช 1. Send a test email using a simple `curl` command to verify your token: ```bash curl "https://api.cloudflare.com/client/v4/accounts//email/sending/send" \ --header "Authorization: Bearer " \ --header "Content-Type: application/json" \ --data '{ "to": "recipient@example.com", "from": "welcome@yourdomain.com", "subject": "Welcome to our service!", "html": "

Welcome!

Thanks for signing up.

", "text": "Welcome! Thanks for signing up." }' ``` 2. Trigger an email through your application flow (e.g., sign up or password reset). 3. Check the recipient inbox and verify the email headers for proper DKIM and SPF alignment. ## Important Notes โš ๏ธ 1. Ensure your `appConfig.email.senderEmail` matches the domain you onboarded in Cloudflare. 2. Keep an eye on your bounce rates and spam complaints to maintain a healthy sender reputation. 3. Cloudflare Email Sending is currently expanding its features, so check their dashboard for analytics and logs. # Email Setup (/setup/email) YouTube Video: https://www.youtube.com/watch?v=ZNeJHxaxG_4 Let's set up and customize your application's email system! ๐Ÿš€ ## Email Authentication & Deliverability ๐Ÿ“ซ Before setting up your email provider, understand these critical email authentication protocols: ### DKIM (DomainKeys Identified Mail) ๐Ÿ”‘ * Digital signature that proves email authenticity * Prevents email spoofing and tampering * Consists of: 1. Public key (published in DNS) 2. Private key (used to sign emails) * Most providers auto-generate DKIM keys * Example DNS record: ```txt selector._domainkey.yourdomain.com TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4..." ``` ### SPF (Sender Policy Framework) ๐Ÿ›ก๏ธ * Specifies authorized servers to send emails * Prevents unauthorized sending from your domain * Single DNS TXT record listing allowed IPs/servers * Example record: ```txt yourdomain.com TXT "v=spf1 include:_spf.google.com include:_spf.mailgun.org ~all" ``` ### DMARC (Domain-based Message Authentication) ๐Ÿ“Š * Enforces SPF and DKIM policies * Provides reporting on authentication failures * Helps prevent domain spoofing * Example record: ```txt _dmarc.yourdomain.com TXT "v=DMARC1; p=quarantine; rua=mailto:dmarc@yourdomain.com" ``` * Policy options: * `p=none`: Monitor only * `p=quarantine`: Send to spam folder * `p=reject`: Reject emails ### Additional Deliverability Tips ๐Ÿ’ก 1. **Warm Up Your Domain** ๐ŸŒก๏ธ * Start with low volume * Gradually increase sending * Monitor engagement metrics * Use consistent sending patterns 2. **List Hygiene** ๐Ÿงน * Remove bounced emails * Honor unsubscribe requests * Validate email addresses * Segment by engagement 3. **Technical Setup** โš™๏ธ * Set up reverse DNS (PTR records) * Use dedicated IP (for high volume) * Monitor IP/domain reputation * Implement feedback loops 4. **Content Best Practices** ๐Ÿ“ * Balance text-to-image ratio * Avoid spam trigger words * Test with spam checkers ## Choose Your Email Provider ๐Ÿ“ฎ Select your preferred email provider: Set up Amazon Simple Email Service for production-ready email sending. Modern email API with great deliverability and simple setup. Send emails through Cloudflare's REST API with built-in DNS configuration. Powerful email service with excellent analytics and tracking. Popular email platform with HTML email support. ## Email Designer โœจ 1. Start the email designer: ```bash npm run dev ``` This will launch the email designer at `http://localhost:3001` 2. Use the designer to: * Preview email templates * Test responsive layouts ## Customizing Email Layout ๐ŸŽจ Customize your email branding in `src/emails/components/layout.tsx`: ```tsx // src/emails/components/layout.tsx // ...Existing code... ``` ## Creating Email Templates ๐Ÿ“ Customize welcome email in `src/emails/components/Welcome.tsx`: ```tsx export default function Welcome({ userName, dashboardUrl }: WelcomeEmailProps) { return ( Welcome to {appConfig.projectName}! ๐Ÿ‘‹ Hi {userName}, We're excited to have you on board! Get started by visiting your dashboard: If you have any questions, just reply to this email - we're here to help! ); } ``` ## Testing Emails ๐Ÿงช 1. **Local Testing** * Use email designer at `localhost:3001` * Preview all templates * Test with different data * Check responsive design 2. **Production Testing** * Send test emails to real addresses * Verify deliverability * Check spam scores * Monitor open rates ## Best Practices ๐Ÿ’ก 1. **Design** * Keep emails responsive * Use brand colors consistently * Include plain text versions * Optimize images 2. **Content** * Clear subject lines * Personalize content * Include unsubscribe links * Follow email regulations 3. **Technical** * Set up SPF/DKIM * Monitor bounce rates * Handle email errors * Track delivery status Now your Indie Kit application is ready to send beautifully designed emails! ๐ŸŽ‰ # Mailchimp Setup (/setup/email/mailchimp) # Setting Up Mailchimp ๐Ÿ“ง This guide will help you set up Mailchimp for sending HTML emails in your application. ## Prerequisites ๐Ÿ“‹ 1. A Mailchimp account 2. Access to your domain's DNS settings 3. Domain verified in Mailchimp 4. API key with send permissions ## Domain Verification ๐Ÿ” 1. Go to Mailchimp Dashboard 2. Navigate to Account > Domains 3. Add and verify your domain 4. Add the provided DNS records to your domain settings ## Implementation ๐Ÿ’ป Install required dependencies: ```bash pnpm add @mailchimp/mailchimp_transactional ``` Create `src/lib/email/sendMail.ts`: ```typescript import mailchimp from "@mailchimp/mailchimp_transactional"; import { appConfig } from "../config"; const sendMail = async (to: string, subject: string, html: string) => { if (process.env.NODE_ENV !== "production") { console.log( "Sending email to", to, "with subject", subject, "and html", html ); return; } const client = mailchimp(process.env.MAILCHIMP_API_KEY); const message = { html: html, subject: subject, from_email: appConfig.email.senderEmail, from_name: appConfig.email.senderName, to: [ { email: to, type: "to" } ], headers: { "Reply-To": appConfig.email.senderEmail } }; const response = await client.messages.send({ message }); console.log("Email sent successfully", response); }; export default sendMail; ``` ## Environment Variables ๐Ÿ” Add these to your `.env.local`: ```bash MAILCHIMP_API_KEY=your_api_key ``` ## Testing Email Setup ๐Ÿงช 1. Send a test email using Mailchimp dashboard 2. Monitor delivery in Mailchimp Activity Feed 3. Check email headers for proper authentication ## Important Notes โš ๏ธ 1. DNS propagation takes time (24-48 hours) 2. Monitor email reputation in Mailchimp dashboard 3. Keep bounce rate below recommended threshold 4. Use production API key in production 5. Only HTML emails are supported Remember to wait for DNS propagation before testing your email setup. Rushing this process can lead to delivery issues! ๐Ÿš€ # Mailgun Setup (/setup/email/mailgun) # Setting Up Mailgun ๐Ÿ“ง This guide will help you set up Mailgun for reliable email delivery in your application. ## Prerequisites ๐Ÿ“‹ 1. A Mailgun account 2. Access to your domain's DNS settings 3. Domain verified in Mailgun 4. API key with sending permissions ## Domain Verification ๐Ÿ” 1. Go to Mailgun Dashboard 2. Navigate to Sending > Domains > Add New Domain 3. Follow the DNS verification steps 4. Add the provided DNS records (SPF, DKIM, DMARC) to your domain ## Implementation ๐Ÿ’ป Install required dependencies: ```bash pnpm add form-data mailgun.js ``` Create `src/lib/email/sendMail.ts`: ```typescript import formData from "form-data"; import Mailgun from "mailgun.js"; import { appConfig } from "../config"; const sendMail = async (to: string, subject: string, html: string) => { if (process.env.NODE_ENV !== "production") { console.log( "Sending email to", to, "with subject", subject, "and html", html ); return; } const mailgun = new Mailgun(formData); const client = mailgun.client({ username: "api", key: process.env.MAILGUN_API_KEY!, }); const response = await client.messages.create( process.env.MAILGUN_DOMAIN!, { from: `${appConfig.email.senderName} <${appConfig.email.senderEmail}>`, to: [to], subject: subject, html: html, "h:Reply-To": appConfig.email.senderEmail, } ); console.log("Email sent successfully", response); }; export default sendMail; ``` ## Environment Variables ๐Ÿ” Add these to your `.env.local`: ```bash MAILGUN_API_KEY=your_api_key MAILGUN_DOMAIN=your_verified_domain ``` ## Testing Email Setup ๐Ÿงช 1. Send a test email using Mailgun dashboard 2. Monitor delivery in Mailgun logs 3. Check email headers for proper authentication 4. Use the Mailgun Email Test feature ## Important Notes โš ๏ธ 1. DNS propagation takes time (24-48 hours) 2. Start with sandbox domain for testing 3. Monitor email reputation in Mailgun dashboard 4. Keep bounce rate below 5% 5. Use production API key in production Remember to wait for DNS propagation before testing your email setup. Rushing this process can lead to delivery issues! ๐Ÿš€ # Resend Setup (/setup/email/resend) # Setting Up Resend ๐Ÿ“ง This guide will help you set up Resend for reliable email delivery in your application. ## Prerequisites ๐Ÿ“‹ 1. A Resend account (sign up at [resend.com](https://resend.com)) 2. Access to your domain's DNS settings 3. Domain verified in Resend ## Domain Verification ๐Ÿ” 1. Go to Resend Dashboard 2. Navigate to Domains > Add Domain 3. Follow the DNS verification steps 4. Add the provided DNS records to your domain ## Implementation ๐Ÿ’ป Install required dependencies: ```bash pnpm add resend ``` Create `src/lib/email/sendMail.ts`: ```typescript import { Resend } from "resend"; import { appConfig } from "../config"; const sendMail = async (to: string, subject: string, html: string) => { if (process.env.NODE_ENV !== "production") { console.log( "Sending email to", to, "with subject", subject, "and html", html ); return; } const resend = new Resend(process.env.RESEND_API_KEY); const response = await resend.emails.send({ from: `${appConfig.email.senderName} <${appConfig.email.senderEmail}>`, to: [to], subject: subject, html: html, replyTo: appConfig.email.senderEmail, }); if(response.error) { console.error("Email sent failed", response.error); } else { if(process.env.NODE_ENV === "development") { console.info("Email sent successfully", response); } } }; export default sendMail; ``` ## Environment Variables ๐Ÿ” Add these to your `.env.local`: ```bash RESEND_API_KEY=your_api_key ``` ## Testing Email Setup ๐Ÿงช 1. Send a test email using Resend dashboard 2. Monitor delivery in Resend logs 3. Check email headers for proper authentication ## Important Notes โš ๏ธ 1. DNS propagation takes time (24-48 hours) 2. Monitor email reputation in Resend dashboard 3. Keep bounce rate low 4. Use production API key in production Remember to wait for DNS propagation before testing your email setup. Rushing this process can lead to delivery issues! ๐Ÿš€ # Amazon SES Setup (/setup/email/ses) # Setting Up Amazon SES ๐Ÿ“ง This guide will help you set up Amazon Simple Email Service (SES) with proper DNS records to ensure reliable email delivery and prevent your emails from being marked as spam. ## Prerequisites ๐Ÿ“‹ 1. An AWS account 2. Access to your domain's DNS settings 3. Domain verified in AWS SES 4. A production SES account (out of sandbox) ## Domain Verification ๐Ÿ” 1. Go to AWS SES Console 2. Click "Verified Identities" > "Create Identity" 3. Choose "Domain" and enter your domain name 4. **Important**: Use AWS-provided DKIM, DMARC, and SPF records 5. Copy the DNS records from AWS dashboard 6. Add them to your domain's DNS settings ## Requesting Production Access ๐Ÿš€ When applying for production access, use this template: ```txt Use Case Description: I am developing a SaaS platform that requires email functionality for essential user communications. Our email sending will be strictly limited to registered and verified users only. Key email use cases include: 1. User Authentication & Security - Account verification emails - Password reset notifications - Security alerts 2. User Engagement - Waitlist notifications - Welcome emails - Product updates - Service notifications 3. Email Management - We have implemented proper bounce and complaint handling - All recipients are verified users who have explicitly opted in - Unsubscribe links are included in all marketing communications - We follow email best practices and maintain clean recipient lists 4. Security Measures - DMARC, SPF, and DKIM are properly configured - Regular monitoring of bounce rates and complaints - Immediate removal of hard bounces - Compliance with anti-spam regulations ``` ## Implementation ๐Ÿ’ป Install required dependencies: ```bash pnpm add @aws-sdk/client-ses ``` Create `src/lib/email/sendMail.ts`: ```typescript import { SESClient, SendEmailCommand } from "@aws-sdk/client-ses"; import { appConfig } from "../config"; const sendMail = async (to: string, subject: string, html: string) => { if (process.env.NODE_ENV !== "production") { console.log( "Sending email to", to, "with subject", subject, "and html", html ); return; } const ses = new SESClient({ region: process.env.AWS_SES_REGION!, credentials: { accessKeyId: process.env.AWS_ACCESS_KEY_ID!, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!, }, }); const command = new SendEmailCommand({ Destination: { ToAddresses: [to], }, Message: { Body: { Html: { Data: html, }, }, Subject: { Data: subject, }, }, Source: `${appConfig.email.senderName} <${appConfig.email.senderEmail}>`, ReplyToAddresses: [appConfig.email.senderEmail], }); const response = await ses.send(command); console.log("Email sent successfully", response); }; export default sendMail; ``` ## Environment Variables ๐Ÿ” Add these to your `.env.local`: ```bash AWS_SES_REGION=us-east-1 AWS_ACCESS_KEY_ID=your_access_key AWS_SECRET_ACCESS_KEY=your_secret_key ``` ## Testing Email Setup ๐Ÿงช 1. Send a test email using AWS SES console 2. Check email headers for proper DKIM alignment 3. Use AWS SES dashboard to track bounces and complaints ## Important Notes โš ๏ธ 1. DNS propagation takes time (24-48 hours) 2. Start in SES sandbox for testing 3. Request production access before sending bulk emails 4. Keep bounce rate below 5% 5. Monitor email reputation regularly Remember to wait for DNS propagation before testing your email setup. Rushing this process can lead to delivery issues! ๐Ÿš€ # Dodo Payments Integration (/setup/payments/dodo) # Dodo Payments Integration ๐Ÿ’ณ Let's set up Dodo Payments for your Indie Kit application! ๐Ÿš€ ## Initial Setup โšก 1. Create a [Dodo Payments account](https://app.dodopayments.com/signup) 2. Set up your business details 3. Add required legal documents: * Privacy Policy * Terms of Service 4. Add these environment variables to your `.env` file: ```bash # Dodo Payments Configuration DODO_PAYMENTS_API_KEY=xxx... # From Dodo Payments API keys DODO_PAYMENTS_WEBHOOK_SECRET=whsec_xxx... # From Dodo Payments webhook settings DODO_PAYMENTS_API_URL="https://live.dodopayments.com" # Or test.dodopayments.com for testing ``` Never commit these keys to your repository. In production, add them securely to your hosting platform's environment variables. Make sure to use the correct API URL for your environment. ## Product Configuration ๐Ÿ›๏ธ In your Dodo Payments Dashboard: 1. Create Products with pricing: * Monthly subscriptions * Yearly subscriptions * One-time payments 2. Customer Portal: * Already enabled by default ## Webhook Setup ๐Ÿ”Œ 1. Add your webhook endpoint in Dodo Payments Dashboard: ``` https://your-domain.com/api/webhooks/dodo ``` 2. Enable these webhook events: * payment.succeeded * payment.failed * payment.processing * payment.cancelled * refund.succeeded * refund.failed * dispute.opened * dispute.expired * dispute.accepted * dispute.cancelled * dispute.challenged * dispute.won * dispute.lost * subscription.created * subscription.active * subscription.on\_hold * subscription.renewed * subscription.paused * subscription.cancelled * subscription.failed * license\_key.created * customer.created All these events are required for proper subscription management. They handle: * Customer creation and management * Subscription lifecycle (creation, updates, deletion) * Invoice payments 3. For local testing, use `ngrok`: ```bash # Forward webhooks to your local server ngrok http 3000 ``` ## Plan Mapping ๐Ÿ—บ๏ธ 1. Go to your super admin dashboard: `/super-admin/plans` 2. For each plan, add the corresponding Dodo Payments Price IDs: * `monthlyDodoPriceId` * `yearlyDodoPriceId` * `onetimeDodoPriceId` Find Price IDs in Dodo Payments Dashboard under Products โ†’ Select Product ## Adding Subscribe Buttons ๐Ÿ”˜ Use the `getSubscribeUrl` helper to create subscription links: ```tsx import getSubscribeUrl, { PlanType, PlanProvider } from '@/lib/plans/getSubscribeUrl' function PricingCard({ plan }) { // Monthly subscription with 7-day trial const monthlyUrl = getSubscribeUrl({ codename: plan.codename, type: PlanType.MONTHLY, provider: PlanProvider.DODO, trialPeriodDays: 7 }) // Yearly subscription with 14-day trial const yearlyUrl = getSubscribeUrl({ codename: plan.codename, type: PlanType.YEARLY, provider: PlanProvider.DODO, trialPeriodDays: 14 }) // One-time payment const onetimeUrl = getSubscribeUrl({ codename: plan.codename, type: PlanType.ONETIME, provider: PlanProvider.DODO }) return (

{plan.name}

{plan.hasMonthlyPricing && ( )} {plan.hasYearlyPricing && ( )} {plan.hasOnetimePricing && ( )}
) } ``` ## Features Available ๐ŸŽฏ * ๐Ÿ”„ Automatic plan upgrades/downgrades * ๐Ÿ’ณ Dodo Payments-managed billing * ๐Ÿช Customer portal for subscription management * โšก Webhook handling * ๐Ÿ” Payment tracking * ๐Ÿ“Š Usage monitoring ## Best Practices ๐Ÿ’ก 1. **Testing** * Use `ngrok` for local webhook testing * Test all subscription flows * Verify upgrade/downgrade paths 2. **Production** * Add all required legal documents * Configure proper webhook security * Monitor webhook events * Set up alerts for failed payments 3. **Customer Experience** * Clear pricing information * Smooth upgrade flow * Easy access to billing portal * Clear trial period information Now your Indie Kit application is ready to accept payments and manage subscriptions through Dodo Payments! ๐ŸŽ‰ # Payments (/setup/payments) YouTube Video: https://www.youtube.com/watch?v=9keJnstTID0 Set up flexible pricing plans to monetize your SaaS! Support for multiple payment providers and pricing models. ๐Ÿ’ฐ Indie Kit supports Stripe, LemonSqueezy, PayPal, DodoPayments, and Polar.sh. Choose the one that works best for your region and needs! ## Managing Plans โšก Access the super admin dashboard at `/super-admin/plans` to create and manage your plans. You need super admin access to manage plans. Set `SUPER_ADMIN_EMAILS` in your `.env` file. ### Plan Structure ๐Ÿ“‹ Each plan includes: ```typescript { name: "Pro Plan", // Display name codename: "pro", // Unique identifier (used in URLs) default: false, // Auto-assigned to new users? // Pricing Options hasMonthlyPricing: true, // Enable monthly subscription hasYearlyPricing: true, // Enable yearly subscription hasOnetimePricing: false, // Enable lifetime/one-time payment // Prices (in cents) monthlyPrice: 2900, // $29/month yearlyPrice: 29000, // $290/year (save $58!) onetimePrice: 50000, // $500 lifetime // Features featuresList: [ "Unlimited projects", "Priority support", "Advanced analytics" ], quotas: { canUseApp: true, projects: 100, apiCalls: 10000 } } ``` **Key fields:** * ๐Ÿ’ฐ **Prices in cents** - $29.00 = 2900 cents * ๐Ÿท๏ธ **Codename** - Used in URLs and code (lowercase, no spaces) * โœจ **Features list** - Shows on pricing page * ๐Ÿ“Š **Quotas** - Limits enforced in your app ### Default Plan ๐ŸŽฏ One plan should be marked as default: ```typescript { default: true, // This plan for new signups } ``` **Default plan behavior:** * โœ… Automatically assigned to new users * โœ… No expiration date * โœ… Users keep it until they upgrade * โœ… Should be your free/basic tier Set your free tier as the default plan to let users try your product before upgrading! Convert them later with upgrade prompts. ## Configuring Quotas โš™๏ธ Quotas control what users can do based on their plan. Define them in `src/db/schema/plans.ts`: ```typescript export const quotaSchema = z.object({ canUseApp: z.boolean().default(true), projects: z.number(), apiCalls: z.number(), storage: z.string(), }) ``` **Quota types:** * ๐Ÿšฆ **Boolean flags** - Feature on/off (e.g., `canExport: true`) * ๐Ÿ”ข **Numerical limits** - Usage caps (e.g., `projects: 10`) * ๐Ÿ“ **String values** - Tier levels (e.g., `support: "priority"`) ### Example Quotas ๐Ÿ“ ```typescript // Free Plan { canUseApp: true, projects: 3, apiCalls: 100, storage: "1GB" } // Pro Plan { canUseApp: true, projects: 100, apiCalls: 10000, storage: "100GB" } // Enterprise Plan { canUseApp: true, projects: -1, // -1 = unlimited apiCalls: -1, storage: "unlimited" } ``` Check quotas in your app logic to gate features. Example: `if (user.plan.quotas.projects >= maxProjects) { showUpgradePrompt() }` ## Pricing Models ๐Ÿ’ณ Indie Kit supports three pricing models: ### 1. Monthly Subscriptions ```typescript { hasMonthlyPricing: true, monthlyPrice: 2900, // $29/month monthlyStripePriceId: "price_xxx", } ``` **Best for:** SaaS products with ongoing value ### 2. Yearly Subscriptions ```typescript { hasYearlyPricing: true, yearlyPrice: 29000, // $290/year yearlyStripePriceId: "price_yyy", } ``` **Best for:** Offering discounts for annual commitment ### 3. One-Time Payments ```typescript { hasOnetimePricing: true, onetimePrice: 50000, // $500 lifetime onetimeStripePriceId: "price_zzz", } ``` **Best for:** Lifetime deals, LTD campaigns, course access Enable multiple pricing models per plan! Let users choose between monthly, yearly, or lifetime access. ## Payment Provider Setup ๐Ÿ”ง Choose and configure your payment provider: **Popular options:** * ๐ŸŸฆ [Stripe](/setup/payments/stripe) - Global, feature-rich (recommended) * ๐Ÿ’ณ [Dodo Payments](/setup/payments/dodo) - Merchant of record, handles tax * ๐Ÿ’™ [PayPal](/setup/payments/paypal) - Trusted, worldwide * ๐Ÿป [Polar.sh](/setup/payments/polar) - Great for developers and open source Each provider guide includes API key setup and webhook configuration. ## Next Steps ๐Ÿš€ 1. **Create your plans** in `/super-admin/plans` 2. **Connect payment provider** - Pick from guides above 3. **Test checkout flow** - Use provider test cards 4. **Display pricing** on your site using [plan-based rendering](/payments/current-plan-based-rendering) Your payment system is ready! Start creating plans and connecting your payment provider. ๐Ÿ’ฐ ## Related Documentation ๐Ÿ“š * [Managing Plans](/payments/managing-plans) - Create, edit, and organize plans * [Create Subscription](/payments/create-subscription) - Implement checkout flow * [Credits System](/payments/credits-system) - Usage-based pricing * [Running LTD Campaigns](/payments/running-ltd-campaigns) - Lifetime deal strategies # LemonSqueezy Integration (/setup/payments/lemonsqueezy) # LemonSqueezy Integration ๐Ÿ‹ Let's set up LemonSqueezy payments for your Indie Kit application! ๐Ÿš€ ## Initial Setup โšก 1. Create a [LemonSqueezy account](https://app.lemonsqueezy.com/register) 2. Set up your store details 3. Add these environment variables to your `.env` file: ```bash # LemonSqueezy Configuration LEMON_SQUEEZY_API_KEY=your-api-key LEMON_SQUEEZY_STORE_ID=your-store-id LEMON_SQUEEZY_WEBHOOK_SECRET=your-webhook-secret ``` Never commit these keys to your repository. In production, add them securely to your hosting platform's environment variables. ## Product Configuration ๐Ÿ›๏ธ In your LemonSqueezy Dashboard: 1. Create Products with variants: * Monthly subscriptions * Yearly subscriptions * One-time payments 2. For each product: * Set up pricing * Configure billing cycles * Add product descriptions * Set up trial periods (if needed) ## Webhook Setup ๐Ÿ”Œ 1. Add your webhook endpoint in LemonSqueezy Dashboard: ``` https://your-domain.com/api/webhooks/lemonsqueezy ``` 2. Enable these webhook events: * `subscription_created` * `subscription_updated` * `subscription_cancelled` * `subscription_resumed` * `order_created` ## Plan Mapping ๐Ÿ—บ๏ธ 1. Go to your super admin dashboard: `/super-admin/plans` 2. For each plan, add the corresponding LemonSqueezy Product IDs: * `monthlyLemonSqueezyProductId` * `yearlyLemonSqueezyProductId` * `onetimeLemonSqueezyProductId` Find Product IDs in LemonSqueezy Dashboard under Products โ†’ Select Product โ†’ Variants ## Adding Subscribe Buttons ๐Ÿ”˜ Use the `getSubscribeUrl` helper to create subscription links: ```tsx import getSubscribeUrl, { PlanType, PlanProvider } from '@/lib/plans/getSubscribeUrl' function PricingCard({ plan }) { // Monthly subscription with 7-day trial const monthlyUrl = getSubscribeUrl({ codename: plan.codename, type: PlanType.MONTHLY, provider: PlanProvider.LEMON_SQUEEZY, trialPeriodDays: 7 }) // Yearly subscription with 14-day trial const yearlyUrl = getSubscribeUrl({ codename: plan.codename, type: PlanType.YEARLY, provider: PlanProvider.LEMON_SQUEEZY, trialPeriodDays: 14 }) // One-time payment const onetimeUrl = getSubscribeUrl({ codename: plan.codename, type: PlanType.ONETIME, provider: PlanProvider.LEMON_SQUEEZY }) return (

{plan.name}

{plan.hasMonthlyPricing && ( )} {plan.hasYearlyPricing && ( )} {plan.hasOnetimePricing && ( )}
) } ``` ## Features Available ๐ŸŽฏ * ๐Ÿ”„ Automatic plan upgrades/downgrades * ๐Ÿ’ณ LemonSqueezy-managed billing * ๐Ÿช Customer portal access * โšก Webhook handling * ๐Ÿ” Payment tracking * ๐Ÿ“Š Usage monitoring ## Best Practices ๐Ÿ’ก 1. **Testing** * Use test mode in LemonSqueezy * Test all subscription flows * Verify webhook events * Test upgrade/downgrade paths 2. **Production** * Configure proper webhook security * Monitor webhook events * Set up email notifications * Keep product IDs in sync 3. **Customer Experience** * Clear pricing information * Smooth checkout flow * Easy access to billing portal * Clear trial period information ## Advantages of LemonSqueezy ๐ŸŒŸ * Simple setup process * Built-in EU VAT handling * Modern checkout experience * Lower transaction fees * Excellent developer experience * Quick payouts Now your Indie Kit application is ready to accept payments and manage subscriptions through LemonSqueezy! ๐ŸŽ‰ # Paddle Payments Integration (/setup/payments/paddle) # Paddle Payments Integration ๐Ÿ’ณ Let's set up Paddle Payments for your Indie Kit application! ๐Ÿš€ ## Initial Setup โšก 1. Create a [Paddle test account](https://sandbox-vendors.paddle.com/) 2. Set up your business details 3. **Important First Step**: Request domain approval: * Go to [Request Domain Approval](https://sandbox-vendors.paddle.com/request-domain-approval) * Sandbox approval is immediate, but production can take time * **Recommended**: Do this first as you build your application Production domain approval can take time. Request it early in your development process! 4. Add these environment variables to your `.env` file: ```bash # Paddle Payments Configuration PADDLE_API_KEY=pdl_sdbx_apikey_xxx... # From Developer Tools > Authentication > API Keys NEXT_PUBLIC_PADDLE_CLIENT_TOKEN=test_xxx... # From Developer Tools > Authentication > Client Side Tokens PADDLE_WEBHOOK_SECRET=pdl_ntfset_xxx... # From Developer Tools > Notifications > Webhook Settings PADDLE_CREDITS_PRODUCT_ID=pro_xxx... # Your credits product ID ``` Never commit these keys to your repository. In production, add them securely to your hosting platform's environment variables. ## Getting Your Credentials ๐Ÿ”‘ ### API Key 1. Go to Dashboard โ†’ **Developer Tools** โ†’ **Authentication** โ†’ **API Keys** 2. Copy your API key ### Client Token 1. Go to Dashboard โ†’ **Developer Tools** โ†’ **Authentication** โ†’ **Client Side Tokens** 2. Copy your client token (use test token for development) ### Webhook Secret 1. Go to Dashboard โ†’ **Developer Tools** โ†’ **Notifications** 2. Click **Add Webhook** or **Edit Webhook** 3. Copy the webhook secret ## Domain & Checkout Configuration ๐ŸŒ 1. **Set up your domain** in Paddle Dashboard (if not done during approval) 2. **Configure Default Payment Link**: * Go to [Checkout Settings](https://sandbox-vendors.paddle.com/checkout-settings) * Set **Default payment link**: * For localhost: `http://localhost:3000/app/subscribe/paddle` * For production: `https://your-domain.com/app/subscribe/paddle` Make sure your domain is already set up in the Paddle Dashboard before configuring the payment link. ## Product Configuration ๐Ÿ›๏ธ In your Paddle Dashboard: 1. Create Products with pricing: * Monthly subscriptions * Yearly subscriptions * One-time payments 2. **Credits Product Setup** (if using credits system): * Go to Dashboard โ†’ **Catalogue** โ†’ **Products** * Click **Create Product** * Name it **"Credits"** for better understanding * **No need to create prices** - Indie Kit will create them automatically * Copy the Product ID and add it to your `.env` as `PADDLE_CREDITS_PRODUCT_ID` Indie Kit automatically creates prices for the Credits product. You only need to create the product itself in Paddle Dashboard. 3. Customer Portal: * Already enabled by default ## Webhook Setup ๐Ÿ”Œ 1. Go to Dashboard โ†’ **Developer Tools** โ†’ **Notifications** 2. Click **Add Webhook** or **Edit Webhook** 3. Add your webhook endpoint: ``` https://your-domain.com/api/webhooks/paddle ``` 4. Copy the webhook secret and add it to your `.env` file 5. For local testing, use `ngrok`: ```bash # Forward webhooks to your local server ngrok http 3000 ``` Then use the ngrok URL in your webhook configuration: ``` https://your-ngrok-url.ngrok.io/api/webhooks/paddle ``` Use ngrok to test webhooks locally. Update the webhook URL in Paddle Dashboard with your ngrok URL. ## Plan Mapping ๐Ÿ—บ๏ธ 1. Go to your super admin dashboard: `/super-admin/plans` 2. For each plan, add the corresponding Paddle Price IDs: * `monthlyPaddlePriceId` * `yearlyPaddlePriceId` * `onetimePaddlePriceId` Find Price IDs in Paddle Dashboard under Products โ†’ Select Product โ†’ Pricing ## Adding Subscribe Buttons ๐Ÿ”˜ Use the `getSubscribeUrl` helper to create subscription links: ```tsx import getSubscribeUrl, { PlanType, PlanProvider } from '@/lib/plans/getSubscribeUrl' function PricingCard({ plan }) { // Monthly subscription with 7-day trial const monthlyUrl = getSubscribeUrl({ codename: plan.codename, type: PlanType.MONTHLY, provider: PlanProvider.PADDLE, trialPeriodDays: 7 }) // Yearly subscription with 14-day trial const yearlyUrl = getSubscribeUrl({ codename: plan.codename, type: PlanType.YEARLY, provider: PlanProvider.PADDLE, trialPeriodDays: 14 }) // One-time payment const onetimeUrl = getSubscribeUrl({ codename: plan.codename, type: PlanType.ONETIME, provider: PlanProvider.PADDLE }) return (

{plan.name}

{plan.hasMonthlyPricing && ( )} {plan.hasYearlyPricing && ( )} {plan.hasOnetimePricing && ( )}
) } ``` ## Features Available ๐ŸŽฏ * ๐Ÿ”„ Automatic plan upgrades/downgrades * ๐Ÿ’ณ Paddle-managed billing * ๐Ÿช Customer portal for subscription management * โšก Webhook handling * ๐Ÿ” Payment tracking * ๐Ÿ“Š Usage monitoring * ๐Ÿ’ฐ Credits system support ## Best Practices ๐Ÿ’ก 1. **Testing** * Use sandbox environment for testing * Use `ngrok` for local webhook testing * Test all subscription flows * Verify upgrade/downgrade paths * Test trial periods 2. **Production** * Request domain approval early (can take time) * Configure proper webhook security * Monitor webhook events * Set up alerts for failed payments * Keep Price IDs in sync with your plans 3. **Customer Experience** * Clear pricing information * Smooth checkout flow * Easy access to billing portal * Clear trial period information * Responsive checkout experience Before going live: * โœ… Domain approved in production * โœ… Production API keys configured * โœ… Webhook endpoint secured * โœ… Default payment link set correctly * โœ… All Price IDs mapped in super admin Now your Indie Kit application is ready to accept payments and manage subscriptions through Paddle Payments! ๐ŸŽ‰ # PayPal Integration (/setup/payments/paypal) import { Tabs, Tab } from 'fumadocs-ui/components/tabs' # Setting Up PayPal Payments ๐Ÿ’ฐ Indie Kit supports PayPal for both one-time purchases and recurring subscriptions. Follow this guide to get started! ๐Ÿš€ ## Initial PayPal Setup ๐Ÿ› ๏ธ Before configuring payment types, you'll need to set up your PayPal developer environment: ### 1. Create a PayPal Developer Account ๐Ÿ‘ค * Go to [developer.paypal.com](https://developer.paypal.com) * Click "Log in to Dashboard" and create your account * Complete the developer verification process ### 2. Create Test Accounts ๐Ÿงช * In your PayPal Developer Dashboard, go to "Sandbox" > "Accounts" * Create **one Test Business Account** (this will be your merchant account) * Create **one Test Personal Account** (for testing purchases) * Note down the credentials for both accounts ### 3. Get API Credentials ๐Ÿ”‘ * Go to "My Apps & Credentials" in your developer dashboard * Click "Create App" under the "Sandbox" section * Choose your business account and give your app a name * Copy your **Client ID** and **Secret Key** ### 4. Set Up Environment Variables ๐Ÿ“ Add these to your `.env.local` file: ```bash # PayPal Sandbox Configuration NEXT_PUBLIC_PAYPAL_CLIENT_ID='AQLowgpUG45c3c6WpgPvkaCAqYh36dvtwjzeuMoJZnQydqnXgRHgF8HE3-jD26aCW8-9_2lmsWm5Dfzo' PAYPAL_SECRET_KEY='EP01knzIEI8C8jGGOvRsVj41uX_AYouvCgnrB1JpFhSiUk6szFDYSrUXKkT4xdzSxKs33qg-00q1NqJw' NEXT_PUBLIC_PAYPAL_IS_SANDBOX=true # This is for test payments, set to false for production ``` ### 5. Configure Webhooks ๐Ÿช For local development, you'll need ngrok: ```bash # Install ngrok (if not already installed) npm install -g ngrok # Start your Next.js app npm run dev # In another terminal, expose your local server ngrok http 3000 ``` Then in PayPal Developer Dashboard: * Go to "Webhooks" section * Click "Create Webhook" * Use your ngrok URL: `https://your-ngrok-url.ngrok.io/api/webhooks/paypal` * Select relevant events (payments, subscriptions) * Copy the **Webhook ID** and add to your environment: ```bash PAYPAL_WEBHOOK_ID=1VC691815S403080G ``` Testing locally with ngrok is highly recommended before deploying to production. This ensures your webhook handling works correctly! ## Setting up One-time Payments ๐Ÿ’ธ Perfect for products, courses, or any single purchase! ### 6. Create Plan in Database ๐Ÿ“Š * Go to `/super-admin/plans/` in your app * Create a new plan and add setup the onetime price. ### 7. Using the Subscribe Function ๐Ÿ”— Your existing `getSubscribeUrl` function already supports PayPal: ```tsx // Usage for one-time payment const subscribeUrl = getSubscribeUrl({ codename: "pro", // From database type: PlanType.ONETIME, provider: PlanProvider.PAYPAL, }); // Use in components/pages Checkout with PayPal ``` ### 8. Test Your Setup โœ… * Use your test personal account to make a purchase * Verify the webhook receives payment confirmations * Check that the payment status updates correctly - Digital products - Course purchases - One-time software licenses - Ebooks and resources ## Setting up Recurring Payments ๐Ÿ”„ Great for SaaS subscriptions and membership sites! ### 6. Create Subscription Plan in PayPal ๐Ÿ“‹ * Log into your **Sandbox Business Account** * Go to "Business Tools" > "Subscriptions" > "Get Started" * Click "Create Plan" * You might need to create a **Product** first, then the plan * Configure your plan: * **Billing cycle**: Monthly or Yearly * **Setup fee**: Optional initial charge * **Trial period**: If desired * **Copy the Plan ID** after creation ### 7. Add Plan ID to Database ๐Ÿ—„๏ธ * Go to `/super-admin/plans/` in your app * Create or edit your plan and add the paypal plan id in monthly or yearly price section. No need to specify the price in cents here as it's managed by paypal. ### 8. Using the Subscribe Function ๐Ÿ”— ```tsx // Usage for monthly subscription const monthlyUrl = getSubscribeUrl({ codename: "pro", // From database type: PlanType.MONTHLY, provider: PlanProvider.PAYPAL, }); // Usage for yearly subscription const yearlyUrl = getSubscribeUrl({ codename: "pro", // From database type: PlanType.YEARLY, provider: PlanProvider.PAYPAL, }); // Use in components/pages Subscribe monthly with PayPal Subscribe yearly with PayPal ``` ### 9. Test Subscription Flow ๐Ÿงช * Subscribe using your test personal account * Verify subscription creation and billing * Test subscription cancellation * Check webhook notifications for all events - SaaS applications - Membership sites - Monthly/yearly services - Content subscriptions ## Production Configuration ๐ŸŒŸ When you're ready to go live: ### Update Environment Variables ๐Ÿ”„ Replace your sandbox credentials with production ones: ```bash # Production PayPal Configuration NEXT_PUBLIC_PAYPAL_CLIENT_ID='your-production-client-id' PAYPAL_SECRET_KEY='your-production-secret-key' NEXT_PUBLIC_PAYPAL_IS_SANDBOX=false PAYPAL_WEBHOOK_ID='your-production-webhook-id' ``` ### Production Webhook Setup ๐ŸŒ * Create a new webhook in PayPal's production dashboard * Use your actual domain: `https://yourdomain.com/api/webhooks/paypal` * Update the webhook ID in your environment variables ## Troubleshooting ๐Ÿ” Common issues and solutions: ### 1. Webhook Not Receiving Events ๐Ÿ“ก * Verify your ngrok tunnel is running for local development * Check that webhook URL is correctly set in PayPal dashboard * Ensure your webhook endpoint is properly implemented * Look at PayPal webhook logs for delivery attempts ### 2. Payment Authentication Errors ๐Ÿ” * Double-check your Client ID and Secret Key * Verify you're using the correct sandbox/production environment * Ensure your PayPal app has the necessary permissions ### 3. Subscription Creation Fails ๐Ÿ“‹ * Verify the PayPal Plan ID is correct * Check that the plan is active in your PayPal dashboard * Ensure your business account has subscription features enabled ### 4. Environment Variable Issues โš™๏ธ * Restart your development server after changing `.env.local` * Verify variable names match exactly (case-sensitive) * Check for typos in your PayPal credentials ## Next Steps ๐ŸŽฏ Now that PayPal is configured: * Test thoroughly with sandbox accounts before going live * Set up proper error handling for failed payments * Implement subscription management features * Configure email notifications for payment events * Set up analytics to track payment conversions Your PayPal integration is ready! Start accepting payments with confidence! ๐Ÿ’ช Always test your payment flows thoroughly in sandbox mode before switching to production. PayPal's sandbox environment closely mimics production behavior. # Polar.sh Integration (/setup/payments/polar) # Polar.sh Integration ๐Ÿป Let's set up Polar.sh payments for your Indie Kit application! ๐Ÿš€ ## Initial Setup โšก 1. Create a [Polar.sh sandbox account](https://sandbox.polar.sh/dashboard/) for development. 2. Add these environment variables to your `.env` file: ```bash # Polar (https://polar.sh/docs/features/checkout/session) โ€” use sandbox for dev POLAR_ACCESS_TOKEN="" POLAR_WEBHOOK_SECRET="" # Server environment: production | sandbox POLAR_SERVER="sandbox" # One catalog product used as the line item for dynamic credit purchases (ad-hoc price set in checkout) POLAR_CREDITS_PRODUCT_ID="" ``` Never commit these keys to your repository. In production, add them securely to your hosting platform's environment variables. ## Product Configuration ๐Ÿ›๏ธ In your Polar.sh Dashboard: 1. Create Products with pricing: * Monthly subscriptions * Yearly subscriptions * One-time payments 2. Go to your super admin dashboard (`/super-admin/plans`) and add the corresponding Polar Product IDs to your plans. 3. **For Credits System:** Create a "Pay what you want" product and add its ID to the `POLAR_CREDITS_PRODUCT_ID` environment variable. ## Webhook Setup ๐Ÿ”Œ For local testing, you can use the Polar CLI to forward webhooks to your local server. 1. Install the Polar CLI: ```bash curl -fsSL https://polar.sh/install.sh | bash ``` 2. Start a tunnel to relay webhooks automatically to your specified URL: ```bash polar listen http://localhost:3000/api/webhooks/polar ``` You will see output similar to this: ```bash โœ” Select Organization โ€ฆ My Organization Connected My Organization Secret 6t3c8ce2247c493a3ade20uea4484d64 Forwarding http://localhost:3000/api/webhooks/polar Waiting for events... ``` 3. Copy the `Secret` from the output and set it as your `POLAR_WEBHOOK_SECRET` in your `.env` file. For production, you'll need to add your webhook endpoint (`https://your-domain.com/api/webhooks/polar`) directly in the Polar.sh Dashboard and use the production webhook secret. ## Plan Mapping ๐Ÿ—บ๏ธ 1. Go to your super admin dashboard: `/super-admin/plans` 2. For each plan, add the corresponding Polar Product IDs: * `monthlyPolarPriceId` (or equivalent field) * `yearlyPolarPriceId` * `onetimePolarPriceId` ## Best Practices ๐Ÿ’ก 1. **Testing** * Always use the `sandbox` environment (`POLAR_SERVER="sandbox"`) during development. * Use the Polar CLI for local webhook testing. * Test all subscription flows and credit purchases. 2. **Production** * Switch `POLAR_SERVER` to `"production"`. * Update your access token and webhook secret with production credentials. * Configure proper webhook security. Now your Indie Kit application is ready to accept payments and manage subscriptions through Polar.sh! ๐ŸŽ‰ # Stripe Integration (/setup/payments/stripe) # Stripe Integration ๐Ÿ’ณ Let's set up Stripe payments for your Indie Kit application! ๐Ÿš€ ## Initial Setup โšก 1. Create a [Stripe account](https://dashboard.stripe.com/register) 2. Set up your business details 3. Add required legal documents: * Privacy Policy * Terms of Service 4. Add these environment variables to your `.env` file: ```bash # Stripe Configuration STRIPE_WEBHOOK_SECRET=whsec_xxx... # From Stripe webhook settings STRIPE_PUBLISHABLE_KEY=pk_test_xxx... # From Stripe API keys STRIPE_SECRET_KEY=sk_test_xxx... # From Stripe API keys ``` Never commit these keys to your repository. In production, add them securely to your hosting platform's environment variables. Keys starting with `pk_test_` and `sk_test_` are for testing. Use `pk_live_` and `sk_live_` in production. ## Product Configuration ๐Ÿ›๏ธ In your Stripe Dashboard: 1. Create Products with pricing: * Monthly subscriptions * Yearly subscriptions * One-time payments 2. Enable Customer Portal: * Go to Settings โ†’ Customer Portal * Add products for upgrade/downgrade flows * Configure branding ## Webhook Setup ๐Ÿ”Œ 1. Add your webhook endpoint in Stripe Dashboard: ``` https://your-domain.com/api/webhooks/stripe ``` 2. Enable these webhook events: * `invoice.paid` * `customer.created` * `customer.subscription.created` * `customer.subscription.updated` * `customer.subscription.deleted` All these events are required for proper subscription management. They handle: * Customer creation and management * Subscription lifecycle (creation, updates, deletion) * Invoice payments 3. For local testing, use Stripe CLI: ```bash # Login to Stripe stripe login # Forward webhooks to your local server stripe listen --forward-to http://localhost:3000/api/webhooks/stripe ``` ## Plan Mapping ๐Ÿ—บ๏ธ 1. Go to your super admin dashboard: `/super-admin/plans` 2. For each plan, add the corresponding Stripe Price IDs: * `monthlyStripePriceId` * `yearlyStripePriceId` * `onetimeStripePriceId` Find Price IDs in Stripe Dashboard under Products โ†’ Select Product โ†’ Pricing ## Adding Subscribe Buttons ๐Ÿ”˜ Use the `getSubscribeUrl` helper to create subscription links: ```tsx import getSubscribeUrl, { PlanType, PlanProvider } from '@/lib/plans/getSubscribeUrl' function PricingCard({ plan }) { // Monthly subscription with 7-day trial const monthlyUrl = getSubscribeUrl({ codename: plan.codename, type: PlanType.MONTHLY, provider: PlanProvider.STRIPE, trialPeriodDays: 7 }) // Yearly subscription with 14-day trial const yearlyUrl = getSubscribeUrl({ codename: plan.codename, type: PlanType.YEARLY, provider: PlanProvider.STRIPE, trialPeriodDays: 14 }) // One-time payment const onetimeUrl = getSubscribeUrl({ codename: plan.codename, type: PlanType.ONETIME, provider: PlanProvider.STRIPE }) return (

{plan.name}

{plan.hasMonthlyPricing && ( )} {plan.hasYearlyPricing && ( )} {plan.hasOnetimePricing && ( )}
) } ``` ## Features Available ๐ŸŽฏ * ๐Ÿ”„ Automatic plan upgrades/downgrades * ๐Ÿ’ณ Stripe-managed billing * ๐Ÿช Customer portal for subscription management * โšก Webhook handling * ๐Ÿ” Payment tracking * ๐Ÿ“Š Usage monitoring ## Best Practices ๐Ÿ’ก 1. **Testing** * Use Stripe CLI for local webhook testing * Test all subscription flows * Verify upgrade/downgrade paths 2. **Production** * Add all required legal documents * Configure proper webhook security * Monitor webhook events * Set up alerts for failed payments 3. **Customer Experience** * Clear pricing information * Smooth upgrade flow * Easy access to billing portal * Clear trial period information Now your Indie Kit application is ready to accept payments and manage subscriptions through Stripe! ๐ŸŽ‰ # Storage (/setup/storage/s3) YouTube Video: https://www.youtube.com/watch?v=p1XN64AWnNk Upload and store files with AWS S3! Perfect for user avatars, documents, images, and any file uploads. ๐Ÿ“ฆ Configure AWS S3 bucket with proper permissions for secure file uploads. Includes three upload methods to fit any use case! ## AWS S3 Configuration ๐Ÿ”ง Already have your S3 bucket? [Jump to usage โ†’](#using-s3-in-indie-kit-) ### Step 1: Bucket Policy Allow public read access to files in your `public` folder. Navigate to your bucket's **Permissions** โ†’ **Bucket Policy** and add: Replace `nanobananai` with your actual bucket name in the Resource ARN below! ```json { "Version": "2012-10-17", "Statement": [ { "Sid": "PublicReadAccess", "Effect": "Allow", "Principal": "*", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::YOUR-BUCKET-NAME/public/*" } ] } ``` **What this does:** * โœ… Allows public read access to files in `/public/*` * โœ… Keeps other files private * โœ… Users can view uploaded images/files ### Step 2: CORS Policy Enable cross-origin requests from your app. Navigate to **Permissions** โ†’ **Cross-origin resource sharing (CORS)**: ```json [ { "AllowedHeaders": ["*"], "AllowedMethods": ["GET", "PUT", "POST", "DELETE"], "AllowedOrigins": ["*"], "ExposeHeaders": [] } ] ``` Your bucket is now configured! Let's integrate it with Indie Kit below. ๐ŸŽ‰ ## Using S3 in Indie Kit ๐Ÿš€ Indie Kit provides **three upload methods** for different scenarios: ### Method 1: S3Uploader Component ๐ŸŽจ Pre-built UI component for instant file uploads! **Perfect for:** * ๐Ÿ“ธ User profile pictures * ๐Ÿ–ผ๏ธ Image galleries * ๐Ÿ“„ Document uploads * โšก Quick implementation ```tsx import { S3Uploader } from "@/components/ui/s3-uploader"; export default function ProfilePage() { return ( { console.log("Uploaded:", fileUrls); // Save URLs to database, show preview, etc. }} /> ); } ``` **Props:** * `presignedRouteProvider` - Your API route for signed URLs * `variant` - "button" or "dropzone" * `maxFiles` - Maximum number of files * `accept` - File types (e.g., "image/\*", ".pdf") * `onUpload` - Callback with uploaded file URLs The component handles everything: drag & drop, progress bars, error states, and file validation! ๐ŸŽ‰ ### Method 2: ClientS3Uploader Class ๐Ÿ”ง Programmatic uploads with custom UI control. **Perfect for:** * ๐ŸŽจ Custom upload interfaces * ๐Ÿ”„ Batch uploads * โš™๏ธ Complex upload logic * ๐ŸŽฏ Programmatic triggers ```tsx import { ClientS3Uploader } from "@/lib/s3/clientS3Uploader"; import { useMemo, useCallback } from "react"; export default function CustomUpload() { const uploader = useMemo( () => new ClientS3Uploader({ presignedRouteProvider: "/api/app/upload-input-images" }), [] ); const handleUpload = useCallback( async (file: File) => { try { const url = await uploader.uploadFile(file); console.log("Uploaded:", url); // Save to database, update state, etc. return url; } catch (error) { console.error("Upload failed:", error); } }, [uploader] ); return ( { const file = e.target.files?.[0]; if (file) handleUpload(file); }} /> ); } ``` **Use this when you need:** * Full control over upload UI * Custom validation logic * Multiple upload sources * Integration with existing forms ### Method 3: Server-Side Upload ๐Ÿ–ฅ๏ธ Upload files directly from API routes or server actions. **Perfect for:** * ๐ŸŽจ Image optimization before upload * ๐Ÿ“„ PDF generation * ๐Ÿ”„ File processing * ๐Ÿค– Automated uploads ```ts import { uploadFromServer } from "@/lib/s3/uploadFromServer"; // In your API route export async function POST(request: Request) { const formData = await request.formData(); const file = formData.get("file") as File; // Convert to base64 const buffer = await file.arrayBuffer(); const base64 = Buffer.from(buffer).toString("base64"); // Upload to S3 const fileUrl = await uploadFromServer({ file: base64, path: "uploads/documents/my-file.pdf", contentType: "application/pdf" }); return Response.json({ url: fileUrl }); } ``` **Common use cases:** ```ts // Optimize image before upload const optimizedImage = await sharp(buffer).resize(800).toBuffer(); const url = await uploadFromServer({ file: optimizedImage.toString("base64"), path: "images/optimized.jpg", contentType: "image/jpeg" }); // Generate and upload PDF const pdf = await generatePDF(data); const url = await uploadFromServer({ file: pdf.toString("base64"), path: "invoices/inv-123.pdf", contentType: "application/pdf" }); ``` Process files before uploading! Resize images, compress PDFs, add watermarks, or generate files dynamically. ## Environment Setup ๐Ÿ”‘ Add your AWS credentials to `.env.local`: ```bash AWS_ACCESS_KEY_ID="your-access-key" AWS_SECRET_ACCESS_KEY="your-secret-key" AWS_REGION="us-east-1" AWS_S3_BUCKET_NAME="your-bucket-name" ``` Never commit AWS credentials to git! Use `.env.local` for local development and set environment variables in your hosting platform. ## Next Steps ๐Ÿš€ 1. **Test uploads** - Try all three methods 2. **Create API routes** - For presigned URLs 3. **Handle file cleanup** - Delete old files 4. **Add file validation** - Size limits, file types Your S3 integration is complete! Choose the upload method that fits your needs and start handling files. ๐Ÿ“ฆ