# Free Trial Signup Flow - Complete Fix
## Problem: Complex signup with plan selection; Need: Simple free trial

---

## Current Flow Analysis

### Frontend ([`SignUpForm.tsx`](frontend/src/components/auth/SignUpForm.tsx))
**Problems:**
1. Lines 29-64: Loads all plans from API
2. Lines 257-279: Shows plan selection dropdown (required)
3. Line 85-88: Validates plan is selected
4. Line 101: Passes `plan_id` to register
5. Line 105: Redirects to `/account/plans` after signup (payment/plan page)

### Backend ([`RegisterSerializer`](backend/igny8_core/auth/serializers.py:276))
**Problems:**
1. Line 282-290: If no plan_id, tries to find 'free' plan or cheapest
2. Line 332-337: Creates account but **NO credit seeding**
3. No default status='trial' set
4. No automatic trial period setup

### Current User Journey (Messy)
```
Marketing → Click "Sign Up" 
  → /signup page loads plans API
  → User must select plan from dropdown
  → Submit registration with plan_id
  → Backend creates account (0 credits!)
  → Redirect to /account/plans (payment page)
  → User confused, no clear trial
```

---

## Solution: Simple Free Trial Signup

### Desired User Journey (Clean)
```
Marketing → Click "Sign Up"
  → /signup page (no plan selection!)
  → User fills: name, email, password
  → Submit → Backend auto-assigns "Free Trial" plan
  → Credits seeded automatically
  → Status = 'trial'
  → Redirect to /sites (dashboard)
  → User starts using app immediately
```

---

## Implementation Steps

### Step 1: Create Free Trial Plan (Database)

Run in Django shell or create migration:
```python
from igny8_core.auth.models import Plan

# Create or update Free Trial plan
Plan.objects.update_or_create(
    slug='free-trial',
    defaults={
        'name': 'Free Trial',
        'price': 0.00,
        'billing_cycle': 'monthly',
        'included_credits': 2000,  # Enough for testing
        'max_sites': 1,
        'max_users': 1,
        'max_industries': 3,
        'is_active': True,
        'features': ['ai_writer', 'planner', 'basic_support']
    }
)
```

**Verify:**
```bash
python manage.py shell
>>> from igny8_core.auth.models import Plan
>>> Plan.objects.get(slug='free-trial')
<Plan: Free Trial>
```

---

### Step 2: Update Backend Registration to Auto-Assign Free Trial

**File:** [`backend/igny8_core/auth/serializers.py:276`](backend/igny8_core/auth/serializers.py:276)

**Current code (lines 280-343):** Has issues - no credits, tries to find plan

**Replace with:**
```python
def create(self, validated_data):
    from django.db import transaction
    from igny8_core.business.billing.models import CreditTransaction
    
    with transaction.atomic():
        # ALWAYS assign Free Trial plan for /signup route
        # Ignore plan_id if provided - this route is for free trials only
        try:
            plan = Plan.objects.get(slug='free-trial', is_active=True)
        except Plan.DoesNotExist:
            # Fallback to 'free' if free-trial doesn't exist
            try:
                plan = Plan.objects.get(slug='free', is_active=True)
            except Plan.DoesNotExist:
                raise serializers.ValidationError({
                    "plan": "Free trial plan not configured. Please contact support."
                })
        
        # Generate account name
        account_name = validated_data.get('account_name')
        if not account_name:
            first_name = validated_data.get('first_name', '')
            last_name = validated_data.get('last_name', '')
            if first_name or last_name:
                account_name = f"{first_name} {last_name}".strip() or \
                              validated_data['email'].split('@')[0]
            else:
                account_name = validated_data['email'].split('@')[0]
        
        # Generate username if not provided
        username = validated_data.get('username')
        if not username:
            username = validated_data['email'].split('@')[0]
            base_username = username
            counter = 1
            while User.objects.filter(username=username).exists():
                username = f"{base_username}{counter}"
                counter += 1
        
        # Create user first
        user = User.objects.create_user(
            username=username,
            email=validated_data['email'],
            password=validated_data['password'],
            first_name=validated_data.get('first_name', ''),
            last_name=validated_data.get('last_name', ''),
            account=None,
            role='owner'
        )
        
        # Create account with unique slug
        base_slug = account_name.lower().replace(' ', '-').replace('_', '-')[:50] or 'account'
        slug = base_slug
        counter = 1
        while Account.objects.filter(slug=slug).exists():
            slug = f"{base_slug}-{counter}"
            counter += 1
        
        # Get trial credits from plan
        trial_credits = plan.get_effective_credits_per_month()
        
        account = Account.objects.create(
            name=account_name,
            slug=slug,
            owner=user,
            plan=plan,
            credits=trial_credits,  # CRITICAL: Seed credits
            status='trial'  # CRITICAL: Set as trial account
        )
        
        # Log initial credit transaction
        CreditTransaction.objects.create(
            account=account,
            transaction_type='subscription',
            amount=trial_credits,
            balance_after=trial_credits,
            description=f'Free trial credits from {plan.name}',
            metadata={
                'plan_slug': plan.slug,
                'registration': True,
                'trial': True
            }
        )
        
        # Link user to account
        user.account = account
        user.save()
        
        return user
```

**Changes:**
- Line 283: Force free-trial plan, ignore plan_id
- Line 352: Set `credits=trial_credits` 
- Line 353: Set `status='trial'`
- Lines 356-365: Log credit transaction

---

### Step 3: Update Frontend to Remove Plan Selection

**File:** [`frontend/src/components/auth/SignUpForm.tsx`](frontend/src/components/auth/SignUpForm.tsx)

**Remove lines 29-64** (plan loading logic)
**Remove lines 257-279** (plan selection dropdown)
**Remove lines 85-88** (plan validation)
**Remove line 101** (plan_id from register call)

**Replace handleSubmit (lines 71-115):**
```typescript
const handleSubmit = async (e: React.FormEvent) => {
  e.preventDefault();
  setError("");

  if (!formData.email || !formData.password || !formData.firstName || !formData.lastName) {
    setError("Please fill in all required fields");
    return;
  }

  if (!isChecked) {
    setError("Please agree to the Terms and Conditions");
    return;
  }

  try {
    // Generate username from email if not provided
    const username = formData.username || formData.email.split("@")[0];
    
    // NO plan_id - backend will auto-assign free trial
    await register({
      email: formData.email,
      password: formData.password,
      username: username,
      first_name: formData.firstName,
      last_name: formData.lastName,
      account_name: formData.accountName,
    });
    
    // Redirect to dashboard/sites instead of payment page
    navigate("/sites", { replace: true });
  } catch (err: any) {
    setError(err.message || "Registration failed. Please try again.");
  }
};
```

**Remove state:**
```typescript
// DELETE these lines:
const [plans, setPlans] = useState<Plan[]>([]);
const [selectedPlanId, setSelectedPlanId] = useState<number | null>(null);
const [plansLoading, setPlansLoading] = useState(true);
```

**Remove useEffect** (lines 41-64 - plan loading)

---

### Step 4: Update Auth Store

**File:** [`frontend/src/store/authStore.ts:120`](frontend/src/store/authStore.ts:120)

No changes needed - it already handles registration without plan_id correctly.

---

### Step 5: Update Middleware to Allow 'trial' Status

**File:** [`backend/igny8_core/auth/middleware.py:132`](backend/igny8_core/auth/middleware.py:132)

Ensure trial accounts can login - current code should already allow this.

Check validation logic allows status='trial':
```python
# In validate_account_and_plan helper (to be created)
# Allow 'trial' status along with 'active'
if account.status in ['suspended', 'cancelled']:
    # Block only suspended/cancelled
    # Allow: 'trial', 'active', 'pending_payment'
    return (False, f'Account is {account.status}', 403)
```

---

## Complete Code Changes

### Change 1: Update RegisterSerializer

**File:** `backend/igny8_core/auth/serializers.py`

Replace lines 276-343 with:
```python
def create(self, validated_data):
    from django.db import transaction
    from igny8_core.business.billing.models import CreditTransaction
    
    with transaction.atomic():
        # ALWAYS assign Free Trial plan for simple signup
        # Ignore plan_id parameter - this is for free trial signups only
        try:
            plan = Plan.objects.get(slug='free-trial', is_active=True)
        except Plan.DoesNotExist:
            try:
                plan = Plan.objects.get(slug='free', is_active=True)
            except Plan.DoesNotExist:
                raise serializers.ValidationError({
                    "plan": "Free trial plan not configured. Please contact support."
                })
        
        # Generate account name if not provided
        account_name = validated_data.get('account_name')
        if not account_name:
            first_name = validated_data.get('first_name', '')
            last_name = validated_data.get('last_name', '')
            if first_name or last_name:
                account_name = f"{first_name} {last_name}".strip() or \
                              validated_data['email'].split('@')[0]
            else:
                account_name = validated_data['email'].split('@')[0]
        
        # Generate username if not provided
        username = validated_data.get('username')
        if not username:
            username = validated_data['email'].split('@')[0]
            base_username = username
            counter = 1
            while User.objects.filter(username=username).exists():
                username = f"{base_username}{counter}"
                counter += 1
        
        # Create user first without account
        user = User.objects.create_user(
            username=username,
            email=validated_data['email'],
            password=validated_data['password'],
            first_name=validated_data.get('first_name', ''),
            last_name=validated_data.get('last_name', ''),
            account=None,
            role='owner'
        )
        
        # Generate unique slug for account
        base_slug = account_name.lower().replace(' ', '-').replace('_', '-')[:50] or 'account'
        slug = base_slug
        counter = 1
        while Account.objects.filter(slug=slug).exists():
            slug = f"{base_slug}-{counter}"
            counter += 1
        
        # Get trial credits from plan
        trial_credits = plan.get_effective_credits_per_month()
        
        # Create account with trial status and credits
        account = Account.objects.create(
            name=account_name,
            slug=slug,
            owner=user,
            plan=plan,
            credits=trial_credits,
            status='trial'
        )
        
        # Log initial credit transaction
        CreditTransaction.objects.create(
            account=account,
            transaction_type='subscription',
            amount=trial_credits,
            balance_after=trial_credits,
            description=f'Free trial credits from {plan.name}',
            metadata={
                'plan_slug': plan.slug,
                'registration': True,
                'trial': True
            }
        )
        
        # Update user to reference account
        user.account = account
        user.save()
        
        return user
```

### Change 2: Simplify SignUpForm

**File:** `frontend/src/components/auth/SignUpForm.tsx`

Replace entire component with:
```typescript
import { useState } from "react";
import { Link, useNavigate } from "react-router-dom";
import { ChevronLeftIcon, EyeCloseIcon, EyeIcon } from "../../icons";
import Label from "../form/Label";
import Input from "../form/input/InputField";
import Checkbox from "../form/input/Checkbox";
import { useAuthStore } from "../../store/authStore";

export default function SignUpForm() {
  const [showPassword, setShowPassword] = useState(false);
  const [isChecked, setIsChecked] = useState(false);
  const [formData, setFormData] = useState({
    firstName: "",
    lastName: "",
    email: "",
    password: "",
    username: "",
    accountName: "",
  });
  const [error, setError] = useState("");
  const navigate = useNavigate();
  const { register, loading } = useAuthStore();

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target;
    setFormData((prev) => ({ ...prev, [name]: value }));
  };

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setError("");

    if (!formData.email || !formData.password || !formData.firstName || !formData.lastName) {
      setError("Please fill in all required fields");
      return;
    }

    if (!isChecked) {
      setError("Please agree to the Terms and Conditions");
      return;
    }

    try {
      const username = formData.username || formData.email.split("@")[0];
      
      // No plan_id needed - backend auto-assigns free trial
      await register({
        email: formData.email,
        password: formData.password,
        username: username,
        first_name: formData.firstName,
        last_name: formData.lastName,
        account_name: formData.accountName,
      });
      
      // Redirect to dashboard/sites instead of payment page
      navigate("/sites", { replace: true });
    } catch (err: any) {
      setError(err.message || "Registration failed. Please try again.");
    }
  };

  return (
    <div className="flex flex-col flex-1 w-full overflow-y-auto lg:w-1/2 no-scrollbar">
      <div className="w-full max-w-md mx-auto mb-5 sm:pt-10">
        <Link
          to="/"
          className="inline-flex items-center text-sm text-gray-500 transition-colors hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-300"
        >
          <ChevronLeftIcon className="size-5" />
          Back to dashboard
        </Link>
      </div>
      <div className="flex flex-col justify-center flex-1 w-full max-w-md mx-auto">
        <div>
          <div className="mb-5 sm:mb-8">
            <h1 className="mb-2 font-semibold text-gray-800 text-title-sm dark:text-white/90 sm:text-title-md">
              Start Your Free Trial
            </h1>
            <p className="text-sm text-gray-500 dark:text-gray-400">
              No credit card required. 2,000 AI credits to get started.
            </p>
          </div>
          <div>
            <form onSubmit={handleSubmit}>
              <div className="space-y-5">
                {error && (
                  <div className="p-3 text-sm text-red-600 bg-red-50 border border-red-200 rounded-lg dark:bg-red-900/20 dark:text-red-400 dark:border-red-800">
                    {error}
                  </div>
                )}
                <div className="grid grid-cols-1 gap-5 sm:grid-cols-2">
                  <div className="sm:col-span-1">
                    <Label>
                      First Name<span className="text-error-500">*</span>
                    </Label>
                    <Input
                      type="text"
                      id="firstName"
                      name="firstName"
                      value={formData.firstName}
                      onChange={handleChange}
                      placeholder="Enter your first name"
                      required
                    />
                  </div>
                  <div className="sm:col-span-1">
                    <Label>
                      Last Name<span className="text-error-500">*</span>
                    </Label>
                    <Input
                      type="text"
                      id="lastName"
                      name="lastName"
                      value={formData.lastName}
                      onChange={handleChange}
                      placeholder="Enter your last name"
                      required
                    />
                  </div>
                </div>
                <div>
                  <Label>
                    Email<span className="text-error-500">*</span>
                  </Label>
                  <Input
                    type="email"
                    id="email"
                    name="email"
                    value={formData.email}
                    onChange={handleChange}
                    placeholder="Enter your email"
                    required
                  />
                </div>
                <div>
                  <Label>Account Name (optional)</Label>
                  <Input
                    type="text"
                    id="accountName"
                    name="accountName"
                    value={formData.accountName}
                    onChange={handleChange}
                    placeholder="Workspace / Company name"
                  />
                </div>
                <div>
                  <Label>
                    Password<span className="text-error-500">*</span>
                  </Label>
                  <div className="relative">
                    <Input
                      placeholder="Enter your password"
                      type={showPassword ? "text" : "password"}
                      id="password"
                      name="password"
                      value={formData.password}
                      onChange={handleChange}
                      required
                    />
                    <span
                      onClick={() => setShowPassword(!showPassword)}
                      className="absolute z-30 -translate-y-1/2 cursor-pointer right-4 top-1/2"
                    >
                      {showPassword ? (
                        <EyeIcon className="fill-gray-500 dark:fill-gray-400 size-5" />
                      ) : (
                        <EyeCloseIcon className="fill-gray-500 dark:fill-gray-400 size-5" />
                      )}
                    </span>
                  </div>
                </div>
                <div className="flex items-center gap-3">
                  <Checkbox
                    className="w-5 h-5"
                    checked={isChecked}
                    onChange={setIsChecked}
                  />
                  <p className="inline-block font-normal text-gray-500 dark:text-gray-400">
                    By creating an account means you agree to the{" "}
                    <span className="text-gray-800 dark:text-white/90">
                      Terms and Conditions,
                    </span>{" "}
                    and our{" "}
                    <span className="text-gray-800 dark:text-white">
                      Privacy Policy
                    </span>
                  </p>
                </div>
                <div>
                  <button 
                    type="submit"
                    disabled={loading}
                    className="flex items-center justify-center w-full px-4 py-3 text-sm font-medium text-white transition rounded-lg bg-brand-500 shadow-theme-xs hover:bg-brand-600 disabled:opacity-50 disabled:cursor-not-allowed"
                  >
                    {loading ? "Creating your account..." : "Start Free Trial"}
                  </button>
                </div>
              </div>
            </form>

            <div className="mt-5">
              <p className="text-sm font-normal text-center text-gray-700 dark:text-gray-400 sm:text-start">
                Already have an account?{" "}
                <Link
                  to="/signin"
                  className="text-brand-500 hover:text-brand-600 dark:text-brand-400"
                >
                  Sign In
                </Link>
              </p>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}
```

**Key changes:**
- Removed all plan-related code
- Changed heading to "Start Your Free Trial"
- Added "No credit card required" subtext
- Changed button text to "Start Free Trial"
- Redirect to `/sites` instead of `/account/plans`
- No plan_id sent to backend

---

## Verification Steps

### 1. Create Free Trial Plan
```bash
python manage.py shell
>>> from igny8_core.auth.models import Plan
>>> Plan.objects.create(
    slug='free-trial',
    name='Free Trial',
    price=0.00,
    billing_cycle='monthly',
    included_credits=2000,
    max_sites=1,
    max_users=1,
    max_industries=3,
    is_active=True
)
>>> exit()
```

### 2. Test Registration Flow
```bash
# Visit https://app.igny8.com/signup
# Fill form: name, email, password
# Submit
# Should:
#   1. Create account with status='trial'
#   2. Set credits=2000
#   3. Redirect to /sites
#   4. User can immediately use app
```

### 3. Verify Database
```bash
python manage.py shell
>>> from igny8_core.auth.models import User
>>> u = User.objects.get(email='test@example.com')
>>> u.account.status
'trial'
>>> u.account.credits
2000
>>> u.account.plan.slug
'free-trial'
```

---

## Summary of Changes

| File | Action | Lines |
|------|--------|-------|
| Database | Add free-trial plan | Create via shell/migration |
| `auth/serializers.py` | Force free-trial plan, seed credits | 276-343 (68 lines) |
| `auth/SignUpForm.tsx` | Remove plan selection, simplify | 29-279 (removed ~80 lines) |

**Result:** Clean, simple free trial signup with zero payment friction.

---

## Before vs After

### Before (Messy)
```
User → Signup page
  → Must select plan
  → Submit with plan_id
  → Account created with 0 credits
  → Redirect to /account/plans (payment)
  → Confused user
```

### After (Clean)
```
User → Signup page  
  → Fill name, email, password
  → Submit (no plan selection)
  → Account created with:
      - status='trial'
      - plan='free-trial'
      - credits=2000
  → Redirect to /sites
  → User starts using app immediately
```

---

## Next: Paid Plans (Future)

For users who want paid plans:
- Create separate `/pricing` page
- After selecting paid plan, route to `/signup?plan=growth`
- Backend checks query param and assigns that plan instead
- OR keep /signup as free trial only and create `/subscribe` for paid

**For now: /signup = 100% free trial, zero friction.**