# Custom Domain Settings Implementation Plan

> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

**Goal:** Improve custom domain visibility with dashboard settings section, better UX on domain settings page, and conditional DNS options in registration.

**Architecture:** Add Settings section to dashboard, enhance domain settings page with copy button and DNS checker, modify onboarding form to show DNS method options only when custom domain is entered.

**Tech Stack:** Next.js App Router, React, TypeScript, CSS Modules

---

## Task 1: Update API to Include Grandfathered Flag

**Files:**

- Modify: `app/api/dealer/route.ts`

**Step 1: Add hasGrandfatheredCustomDomain to dealer query**

In `app/api/dealer/route.ts`, add `hasGrandfatheredCustomDomain` to the select object:

```typescript
const dealer = await prisma.dealer.findUnique({
  where: {
    userId: session.user.id,
  },
  select: {
    id: true,
    subdomain: true,
    domain: true,
    domainPrefix: true,
    subscriptionTier: true,
    businessName: true,
    contactName: true,
    phone: true,
    address: true,
    city: true,
    state: true,
    zip: true,
    contactEmail: true,
    status: true,
    updatedAt: true,
    hasLeadForm: true,
    hasGrandfatheredCustomDomain: true, // Add this line
  },
});
```

**Step 2: Verify the change**

Run: `npm run build`
Expected: Build succeeds

**Step 3: Commit**

```bash
git add app/api/dealer/route.ts
git commit -m "feat(api): include hasGrandfatheredCustomDomain in dealer response"
```

---

## Task 2: Add Settings Section CSS

**Files:**

- Modify: `app/dashboard/dashboard.module.css`

**Step 1: Add settings section styles**

Add at the end of `app/dashboard/dashboard.module.css`:

```css
/* Settings Section */
.settingsSection {
  padding-top: 0;
  margin-top: var(--spacing-xl);
}

.settingsCard {
  background: #182033;
  border: 1px solid rgba(148, 163, 184, 0.25);
  border-radius: 20px;
  padding: var(--spacing-xl);
  width: 100%;
  max-width: 1200px;
}

.settingsInnerCard {
  border: 1px dashed rgba(148, 163, 184, 0.4);
  border-radius: 12px;
  padding: 20px;
}

.settingsButtons {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-md);
}

.settingsButton {
  min-width: 180px;
}

.settingsButton:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.upgradeHint {
  font-size: 0.75rem;
  color: var(--color-text-secondary);
  margin-top: var(--spacing-xs);
}
```

**Step 2: Verify styles compile**

Run: `npm run build`
Expected: Build succeeds

**Step 3: Commit**

```bash
git add app/dashboard/dashboard.module.css
git commit -m "style: add settings section styles to dashboard"
```

---

## Task 3: Add Settings Section to Dashboard

**Files:**

- Modify: `app/dashboard/page.tsx`

**Step 1: Import hasCustomDomainAccess**

Add import at top of file:

```typescript
import { hasCustomDomainAccess } from '@/lib/cms/types';
```

**Step 2: Update DealerInfo interface**

Update the interface to include new fields:

```typescript
interface DealerInfo {
  status: string;
  subdomain: string;
  domain: string;
  subscriptionTier: string;
  hasGrandfatheredCustomDomain: boolean;
}
```

**Step 3: Add Settings section JSX**

After the closing `</div>` of `dashboardCard` (around line 178) and before the `leadPages.length > 0` check, add:

```tsx
{
  /* Settings Section */
}
<section className={`${styles.section} ${styles.settingsSection}`}>
  <div className={styles.settingsCard}>
    <div className={styles.settingsInnerCard}>
      <h2 className={styles.sectionTitle}>Settings</h2>
      <div className={styles.settingsButtons}>
        {dealer &&
        hasCustomDomainAccess(dealer.subscriptionTier, dealer.hasGrandfatheredCustomDomain) ? (
          <Button
            variant="primary"
            size="lg"
            onClick={() => router.push('/dashboard/settings/domains')}
            className={styles.settingsButton}
          >
            Domain Settings
          </Button>
        ) : (
          <div>
            <Button variant="primary" size="lg" disabled className={styles.settingsButton}>
              Domain Settings
            </Button>
            <p className={styles.upgradeHint}>Upgrade to Growth to use custom domains</p>
          </div>
        )}
      </div>
    </div>
  </div>
</section>;
```

**Step 4: Verify the change**

Run: `npm run build`
Expected: Build succeeds

**Step 5: Commit**

```bash
git add app/dashboard/page.tsx
git commit -m "feat(dashboard): add Settings section with Domain Settings button"
```

---

## Task 4: Add Copy Button to Domain Settings Page

**Files:**

- Modify: `app/dashboard/settings/domains/page.tsx`

**Step 1: Add copied state**

Add state variable after other useState declarations (around line 46):

```typescript
const [copied, setCopied] = useState(false);
```

**Step 2: Add copy handler function**

Add after fetchDomainConfig function (around line 76):

```typescript
function handleCopySubdomain() {
  if (config?.subdomainUrl) {
    navigator.clipboard.writeText(config.subdomainUrl);
    setCopied(true);
    setTimeout(() => setCopied(false), 2000);
  }
}
```

**Step 3: Update subdomain display section**

Replace the subdomain display section (lines 228-248) with:

```tsx
{
  /* Current Subdomain */
}
<div className="mb-8 p-6 bg-slate-800 rounded-lg">
  <h2 className="text-lg font-semibold mb-4">Your Subdomain</h2>
  {config?.subdomainUrl ? (
    <div>
      <div className="flex items-center gap-3">
        <a
          href={config.subdomainUrl}
          target="_blank"
          rel="noopener noreferrer"
          className="text-cyan-400 hover:text-cyan-300 underline"
        >
          {config.subdomainUrl}
        </a>
        <button
          onClick={handleCopySubdomain}
          className="px-3 py-1 text-sm bg-slate-700 hover:bg-slate-600 rounded transition-colors"
        >
          {copied ? 'Copied!' : 'Copy'}
        </button>
      </div>
      <p className="text-sm text-gray-400 mt-2">
        This is your primary site URL. It cannot be changed.
      </p>
    </div>
  ) : (
    <p className="text-gray-400">No subdomain configured yet.</p>
  )}
</div>;
```

**Step 4: Verify the change**

Run: `npm run build`
Expected: Build succeeds

**Step 5: Commit**

```bash
git add app/dashboard/settings/domains/page.tsx
git commit -m "feat(domains): add copy button for subdomain URL"
```

---

## Task 5: Default to Managed DNS and Show Options Conditionally

**Files:**

- Modify: `app/dashboard/settings/domains/page.tsx`

**Step 1: Add showDnsOptions state**

Add state variable after other useState declarations:

```typescript
const [showDnsOptions, setShowDnsOptions] = useState(false);
```

**Step 2: Update domain input to show options on input**

Replace the form section (the `<form onSubmit={handleConfigureDomain}>` block, approximately lines 357-421) with:

```tsx
<form onSubmit={handleConfigureDomain}>
  <div className="mb-4">
    <label className="block text-sm font-medium mb-2">Domain Name</label>
    <input
      type="text"
      value={newDomain}
      onChange={(e) => {
        setNewDomain(e.target.value);
        setShowDnsOptions(e.target.value.length > 0);
      }}
      placeholder="www.example.com"
      className="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg focus:outline-none focus:border-cyan-500"
      required
    />
    <p className="text-xs text-gray-400 mt-1">Enter your domain without http:// or https://</p>
  </div>

  {showDnsOptions && (
    <div className="mb-6">
      <label className="block text-sm font-medium mb-2">DNS Management</label>
      <div className="space-y-3">
        <label className="flex items-start p-3 bg-slate-700 rounded-lg cursor-pointer hover:bg-slate-600">
          <input
            type="radio"
            name="method"
            value="managed"
            checked={selectedMethod === 'managed'}
            onChange={() => setSelectedMethod('managed')}
            className="mt-1 mr-3"
          />
          <div>
            <p className="font-medium">Managed DNS (Recommended)</p>
            <p className="text-sm text-gray-400">
              We&apos;ll handle your DNS. Just update your nameservers at your registrar to point to
              ours.
            </p>
          </div>
        </label>

        <label className="flex items-start p-3 bg-slate-700 rounded-lg cursor-pointer hover:bg-slate-600">
          <input
            type="radio"
            name="method"
            value="self_managed"
            checked={selectedMethod === 'self_managed'}
            onChange={() => setSelectedMethod('self_managed')}
            className="mt-1 mr-3"
          />
          <div>
            <p className="font-medium">Self-Managed DNS</p>
            <p className="text-sm text-gray-400">
              Point your domain&apos;s A record to our server IP. Best if you want to keep your
              current DNS provider.
            </p>
          </div>
        </label>
      </div>
      <p className="text-sm text-gray-400 mt-3">
        Need help?{' '}
        <a
          href="https://forms.clickup.com/18006137/f/h5g3t-33933/YX9MJRKKCCJ778CL6K"
          target="_blank"
          rel="noopener noreferrer"
          className="text-cyan-400 hover:underline"
        >
          Contact support
        </a>{' '}
        and we&apos;ll walk you through it.
      </p>
    </div>
  )}

  <button
    type="submit"
    disabled={saving || !newDomain}
    className="px-4 py-2 bg-cyan-600 hover:bg-cyan-700 disabled:opacity-50 rounded-lg"
  >
    {saving ? 'Configuring...' : 'Configure Domain'}
  </button>
</form>
```

**Step 3: Change default selectedMethod to 'managed'**

Update the useState for selectedMethod (around line 41):

```typescript
const [selectedMethod, setSelectedMethod] = useState<DomainMethod>('managed');
```

**Step 4: Verify the change**

Run: `npm run build`
Expected: Build succeeds

**Step 5: Commit**

```bash
git add app/dashboard/settings/domains/page.tsx
git commit -m "feat(domains): default to managed DNS, show options conditionally"
```

---

## Task 6: Add DNS Checker and Status Visibility

**Files:**

- Modify: `app/dashboard/settings/domains/page.tsx`

**Step 1: Add checking state**

Add state variable after other useState declarations:

```typescript
const [checking, setChecking] = useState(false);
const [checkResult, setCheckResult] = useState<{ success: boolean; message: string } | null>(null);
```

**Step 2: Add DNS check handler**

Add after handleVerifyDomain function:

```typescript
async function handleCheckDns() {
  setCheckResult(null);
  setChecking(true);

  try {
    const response = await fetch('/api/dealer/custom-domain/verify', {
      method: 'POST',
    });

    const data = await response.json();

    if (data.success) {
      if (data.data.verified) {
        setCheckResult({
          success: true,
          message: 'DNS is configured correctly! You can now verify and activate your domain.',
        });
        await fetchDomainConfig();
      } else {
        setCheckResult({
          success: false,
          message:
            data.data.error || 'DNS not configured yet. Please check your settings and try again.',
        });
      }
    } else {
      setCheckResult({
        success: false,
        message: data.error || 'Could not check DNS configuration.',
      });
    }
  } catch {
    setCheckResult({ success: false, message: 'Failed to check DNS. Please try again.' });
  } finally {
    setChecking(false);
  }
}
```

**Step 3: Update the configured domain display section**

Replace the pending status section (the `{config.customDomainStatus === 'pending' && ...}` block, approximately lines 311-324) with:

```tsx
{
  config.customDomainStatus === 'pending' && (
    <div className="mb-4">
      <div className="p-4 bg-yellow-900/30 border border-yellow-700 rounded-lg mb-4">
        <h4 className="font-medium text-yellow-300 mb-2">Next Step: Configure Your DNS</h4>
        <p className="text-sm text-gray-300 mb-3">
          {config.customDomainMethod === 'managed'
            ? "Update your domain's nameservers at your registrar to point to ours. This usually takes a few minutes to a few hours to propagate."
            : 'Add an A record pointing to our server IP. DNS changes can take a few minutes to a few hours to propagate.'}
        </p>
        <div className="flex gap-3">
          <button
            onClick={handleCheckDns}
            disabled={checking}
            className="px-4 py-2 bg-yellow-600 hover:bg-yellow-700 disabled:opacity-50 rounded-lg"
          >
            {checking ? 'Checking...' : 'Check My DNS'}
          </button>
          <button
            onClick={handleVerifyDomain}
            disabled={verifying}
            className="px-4 py-2 bg-blue-600 hover:bg-blue-700 disabled:opacity-50 rounded-lg"
          >
            {verifying ? 'Verifying...' : 'Verify & Continue'}
          </button>
        </div>
      </div>
      {checkResult && (
        <div
          className={`p-3 rounded-lg text-sm ${checkResult.success ? 'bg-green-900/30 border border-green-700 text-green-300' : 'bg-red-900/30 border border-red-700 text-red-300'}`}
        >
          {checkResult.message}
        </div>
      )}
    </div>
  );
}
```

**Step 4: Update verified status section**

Replace the verified status section (lines 326-339) with:

```tsx
{
  config.customDomainStatus === 'verified' && (
    <div className="mb-4">
      <div className="p-4 bg-blue-900/30 border border-blue-700 rounded-lg">
        <h4 className="font-medium text-blue-300 mb-2">DNS Verified!</h4>
        <p className="text-sm text-gray-300 mb-3">
          Your DNS is configured correctly. Click activate to make your custom domain live.
        </p>
        <button
          onClick={handleActivateDomain}
          disabled={saving}
          className="px-4 py-2 bg-green-600 hover:bg-green-700 disabled:opacity-50 rounded-lg"
        >
          {saving ? 'Activating...' : 'Activate Domain'}
        </button>
      </div>
    </div>
  );
}
```

**Step 5: Update active status section**

Replace the active status section (lines 341-345) with:

```tsx
{
  config.customDomainStatus === 'active' && (
    <div className="mb-4">
      <div className="p-4 bg-green-900/30 border border-green-700 rounded-lg">
        <h4 className="font-medium text-green-300 mb-2">Domain Active</h4>
        <p className="text-sm text-gray-300">
          Your custom domain is live! Visitors can now access your site at{' '}
          <a
            href={`https://${config.customDomain}`}
            target="_blank"
            rel="noopener noreferrer"
            className="text-cyan-400 hover:underline"
          >
            {config.customDomain}
          </a>
        </p>
        {config.customDomainVerifiedAt && (
          <p className="text-xs text-gray-400 mt-2">
            Active since {new Date(config.customDomainVerifiedAt).toLocaleDateString()}
          </p>
        )}
      </div>
    </div>
  );
}
```

**Step 6: Verify the change**

Run: `npm run build`
Expected: Build succeeds

**Step 7: Commit**

```bash
git add app/dashboard/settings/domains/page.tsx
git commit -m "feat(domains): add DNS checker and improve status visibility"
```

---

## Task 7: Update Registration Onboarding Form

**Files:**

- Modify: `app/registration/onboarding/OnboardingForm.tsx`

**Step 1: Add DNS method state**

Add state variable after the customDomain state (around line 51):

```typescript
const [customDomainMethod, setCustomDomainMethod] = useState<'managed' | 'self_managed' | null>(
  null
);
```

**Step 2: Replace custom domain input section**

Replace the custom domain section (lines 292-315) with:

```tsx
{
  /* Custom Domain Input - Optional for Growth+ tiers */
}
{
  supportsCustomDomain && (
    <div className="mt-6">
      <label htmlFor="customDomain" className="block text-sm font-medium text-gray-700 mb-2">
        Custom Domain (Optional)
      </label>
      <input
        type="text"
        id="customDomain"
        value={customDomain}
        onChange={(e) => {
          setCustomDomain(e.target.value.toLowerCase());
          if (!e.target.value) {
            setCustomDomainMethod(null);
          }
        }}
        placeholder="www.yourdomain.com"
        className="w-full px-4 py-3 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-lg"
      />

      {customDomain && (
        <div className="mt-4 space-y-3">
          <p className="text-sm font-medium text-gray-700">DNS Management (Optional)</p>
          <label className="flex items-start p-3 bg-gray-50 rounded-lg cursor-pointer hover:bg-gray-100 border border-gray-200">
            <input
              type="radio"
              name="dnsMethod"
              value="managed"
              checked={customDomainMethod === 'managed'}
              onChange={() => setCustomDomainMethod('managed')}
              className="mt-1 mr-3"
            />
            <div>
              <p className="font-medium text-gray-900">Managed DNS (Recommended)</p>
              <p className="text-sm text-gray-600">
                We&apos;ll handle your DNS. Just update your nameservers at your registrar.
              </p>
            </div>
          </label>

          <label className="flex items-start p-3 bg-gray-50 rounded-lg cursor-pointer hover:bg-gray-100 border border-gray-200">
            <input
              type="radio"
              name="dnsMethod"
              value="self_managed"
              checked={customDomainMethod === 'self_managed'}
              onChange={() => setCustomDomainMethod('self_managed')}
              className="mt-1 mr-3"
            />
            <div>
              <p className="font-medium text-gray-900">Self-Managed DNS</p>
              <p className="text-sm text-gray-600">
                Point your domain&apos;s A record to our server IP.
              </p>
            </div>
          </label>

          <p className="text-xs text-gray-500">
            You can configure DNS settings later in your dashboard.
          </p>
        </div>
      )}

      {!customDomain && (
        <p className="mt-2 text-sm text-gray-600">
          You&apos;ll need to configure DNS settings after registration. Your site will be
          accessible at your subdomain until then.
        </p>
      )}
    </div>
  );
}
```

**Step 3: Update handleCompleteSetup to include customDomainMethod**

Update the API call body in handleCompleteSetup (around line 143) to include customDomainMethod:

```typescript
body: JSON.stringify({
  dealerId,
  sessionId,
  subdomain,
  domainPrefix,
  customDomain,
  customDomainMethod: customDomain ? customDomainMethod : null,
  dealerNumber: formData.dealerNumber,
  name: formData.name,
  phone: formData.phone,
  address: formData.address,
  city: formData.city,
  state: formData.state,
  zip: formData.zip,
}),
```

**Step 4: Verify the change**

Run: `npm run build`
Expected: Build succeeds

**Step 5: Commit**

```bash
git add app/registration/onboarding/OnboardingForm.tsx
git commit -m "feat(onboarding): show DNS options conditionally when custom domain entered"
```

---

## Task 8: Update Registration Helpers to Handle DNS Method

**Files:**

- Modify: `lib/registration-helpers.ts`
- Modify: `app/api/register/complete/route.ts`

**Step 1: Update RegistrationData interface**

In `lib/registration-helpers.ts`, add `customDomainMethod` to the interface (around line 14):

```typescript
export interface RegistrationData {
  dealerId: string;
  subdomain: string;
  domainPrefix?: string;
  customDomain?: string;
  customDomainMethod?: 'managed' | 'self_managed' | null; // Add this line
  dealerNumber?: string;
  name?: string | null;
  phone: string;
  address: string;
  city: string;
  state: string;
  zip: string;
}
```

**Step 2: Update completeRegistration function**

In the `updateData` object (around line 314), add customDomainMethod and customDomainStatus:

```typescript
const updateData: Prisma.DealerUpdateInput = {
  subdomain: normalizedSubdomain,
  domainPrefix: normalizedDomainPrefix,
  customDomain: data.customDomain?.trim() || null,
  customDomainMethod: data.customDomainMethod || null,
  customDomainStatus: data.customDomain && data.customDomainMethod ? 'pending' : 'none',
  dealerNumber,
  phone: data.phone.trim(),
  address: data.address.trim(),
  city: data.city.trim(),
  state: data.state.trim(),
  zip: data.zip.trim(),
  status: 'registration_pending',
  migrationStatus: 'registration_pending',
  hasGrandfatheredCustomDomain,
};
```

**Step 3: Update API route to pass customDomainMethod**

In `app/api/register/complete/route.ts`, update the data object (around line 141):

```typescript
const data: RegistrationData = {
  dealerId: body.dealerId,
  subdomain: body.subdomain || '',
  domainPrefix: body.domainPrefix || 'myamsoil',
  customDomain: body.customDomain || '',
  customDomainMethod: body.customDomainMethod || null,
  dealerNumber: body.dealerNumber || '',
  name: body.name,
  phone: body.phone,
  address: body.address,
  city: body.city,
  state: body.state,
  zip: body.zip,
};
```

**Step 4: Verify the change**

Run: `npm run build`
Expected: Build succeeds

**Step 5: Commit**

```bash
git add lib/registration-helpers.ts app/api/register/complete/route.ts
git commit -m "feat(registration): handle customDomainMethod in registration completion"
```

---

## Task 9: Final Verification and Lint

**Step 1: Run linter**

Run: `npm run lint`
Expected: No errors

**Step 2: Run build**

Run: `npm run build`
Expected: Build succeeds

**Step 3: Run tests**

Run: `npm test`
Expected: All tests pass

**Step 4: Final commit if any lint fixes were needed**

```bash
git add -A
git commit -m "chore: lint fixes for custom domain settings"
```

---

## Summary

**Files Modified:**

1. `app/api/dealer/route.ts` - Include hasGrandfatheredCustomDomain
2. `app/dashboard/dashboard.module.css` - Settings section styles
3. `app/dashboard/page.tsx` - Settings section with Domain Settings button
4. `app/dashboard/settings/domains/page.tsx` - Copy button, DNS checker, status visibility, managed default
5. `app/registration/onboarding/OnboardingForm.tsx` - Conditional DNS options
6. `lib/registration-helpers.ts` - Add customDomainMethod to RegistrationData and completeRegistration
7. `app/api/register/complete/route.ts` - Pass customDomainMethod to registration helper

**Total Tasks:** 9
**Estimated Commits:** 8-9
