---
phase: 14-crm-attio-style-fix
plan: 03
type: execute
wave: 1
depends_on: []
files_modified:
  - src/components/admin/dashboard/FollowUpWidget.tsx
  - src/components/admin/leads/LeadForm.tsx
  - src/components/admin/leads/SendQuoteModal.tsx
autonomous: true
requirements: [CRM-10, CRM-11, CRM-12]

must_haves:
  truths:
    - "FollowUpWidget shows Italian text only: heading 'Richiedi Follow-up', body 'X lead da contattare' (no English words, no pluralized 's')"
    - "EditLeadModal and CreateLeadModal use useForm<CreateLeadInput>() (not useForm<any>()) — form fields are type-checked against the Zod-inferred lead schema"
    - "SendQuoteModal's onSubmit no longer contains an unreachable 'new' tab branch — the only navigation for the 'new' tab happens via the button's own onClick"
  artifacts:
    - path: "src/components/admin/dashboard/FollowUpWidget.tsx"
      provides: "Italian-only follow-up widget copy"
      contains: "Richiedi Follow-up"
    - path: "src/components/admin/leads/LeadForm.tsx"
      provides: "Typed react-hook-form for lead create/edit"
      contains: "useForm<CreateLeadInput>"
    - path: "src/components/admin/leads/SendQuoteModal.tsx"
      provides: "onSubmit without dead 'new' tab branch"
      contains: "onSubmit"
  key_links:
    - from: "src/components/admin/leads/LeadForm.tsx"
      to: "src/lib/lead-validators.ts"
      via: "import type { CreateLeadInput }"
      pattern: "import.*CreateLeadInput.*lead-validators"
---

<objective>
Fix three residual CRM bugs identified in RESEARCH.md, each isolated to a single component file with no shared dependencies:

1. **CRM-10**: `FollowUpWidget.tsx` has 6 hardcoded English strings — translate to Italian per UI-SPEC.md Decision 5 ("X lead da contattare", no pluralization since Italian "lead" is invariant).
2. **CRM-11**: `LeadForm.tsx`'s `CreateLeadModal` and `EditLeadModal` both call `useForm<any>()`, defeating react-hook-form's type safety — replace with `useForm<CreateLeadInput>()` using the Zod-inferred type already exported from `lead-validators.ts`, removing the local type redeclaration and narrowing the one unavoidable `status` cast.
3. **CRM-12**: `SendQuoteModal.tsx`'s `onSubmit` contains a dead `if (tab === "new") {...}` branch that can never execute (the "new" tab's button has its own `onClick` handler and never triggers form submit) — remove the unreachable branch.

Purpose: Closes out the three non-UI residual bugs from Phase 14's scope, completing CRM-10/11/12 alongside the Attio-style table redesign (CRM-08/09 in Plans 14-01/14-02).

Output: Three independently-fixed components, each type-checked and linted.
</objective>

<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/ROADMAP.md
@.planning/REQUIREMENTS.md
@.planning/phases/14-crm-attio-style-fix/14-RESEARCH.md
@.planning/phases/14-crm-attio-style-fix/14-PATTERNS.md
@.planning/phases/14-crm-attio-style-fix/14-UI-SPEC.md
</context>

<interfaces>
<!-- No new interfaces created; these are isolated bug fixes against existing types. -->

From src/lib/lead-validators.ts (existing, unchanged — CreateLeadInput already exported):
```typescript
export const LEAD_STAGES = ["contacted", "qualified", "proposal_sent", "negotiating", "won", "lost"] as const;

export const createLeadSchema = z.object({
  name: z.string().min(1),
  email: z.string().email().nullable().optional(),
  phone: z.string().nullable().optional(),
  company: z.string().nullable().optional(),
  status: z.enum(LEAD_STAGES).optional(),
  next_action: z.string().nullable().optional(),
  // ... etc
});

export type CreateLeadInput = z.infer<typeof createLeadSchema>;
export type UpdateLeadInput = z.infer<typeof updateLeadSchema>;
```

From src/components/admin/dashboard/FollowUpWidget.tsx (current, full 47 lines — to be translated):
```tsx
import Link from "next/link";
import { getLeadsNeedingFollowUp } from "@/lib/lead-service";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";

export async function FollowUpWidget() {
  const leads = await getLeadsNeedingFollowUp(7);
  const count = leads.length;

  return (
    <Card>
      <CardHeader>
        <CardTitle>Require Follow-up</CardTitle>
      </CardHeader>
      <CardContent>
        {count > 0 ? (
          <>
            <p className="text-sm text-gray-600 mb-3">
              {count} lead{count !== 1 ? "s" : ""} to contact
            </p>
            <div className="space-y-2">
              {leads.slice(0, 5).map((lead) => (
                <div key={lead.id} className="flex items-center justify-between">
                  <span className="text-sm font-medium">{lead.name}</span>
                  <Button size="sm" variant="outline" asChild>
                    <Link href={`/admin/leads/${lead.id}`}>Contact</Link>
                  </Button>
                </div>
              ))}
            </div>
            {count > 5 && (
              <Link href="/admin/leads" className="text-sm text-blue-600 hover:underline mt-2 block">
                View All ({count})
              </Link>
            )}
          </>
        ) : (
          <p className="text-sm text-gray-500">All leads are up to date!</p>
        )}
      </CardContent>
    </Card>
  );
}
```

From src/components/admin/leads/LeadForm.tsx (current, lines 1-40 and 202-220 — relevant excerpts):
```tsx
"use client";

import { useState } from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { createLeadSchema } from "@/lib/lead-validators";
import type { z } from "zod";
import type { Lead } from "@/db/schema";
// ... other imports

type CreateLeadInput = z.infer<typeof createLeadSchema>;  // <-- local redeclaration, line 36, to be removed

export function CreateLeadModal() {
  const form = useForm<any>({  // <-- line ~42, to be fixed
    resolver: zodResolver(createLeadSchema),
    defaultValues: { name: "", email: "", phone: "", company: "", status: "contacted", next_action: "" },
  });
  // ...
}

// ... EditLeadModal starts around line 202
export function EditLeadModal({ lead, open, onOpenChange }: { lead: Lead; open: boolean; onOpenChange: (open: boolean) => void }) {
  const form = useForm<any>({  // <-- to be fixed
    resolver: zodResolver(createLeadSchema),
    defaultValues: {
      name: lead.name,
      email: lead.email ?? "",
      phone: lead.phone ?? "",
      company: lead.company ?? "",
      status: lead.status as any,  // <-- line 212, to be narrowed
      next_action: lead.next_action ?? "",
    },
  });
  // ...
}
```

From src/components/admin/leads/SendQuoteModal.tsx (current, lines 31-75 and 120-135 — relevant excerpts):
```tsx
const assignQuoteSchema = z.object({
  tab: z.enum(["existing", "new"]),
  quote_id: z.string().optional(),
  generate_new: z.boolean().optional(),  // <-- dead field, line ~31
});

// ...

const form = useForm<AssignQuoteInput>({
  resolver: zodResolver(assignQuoteSchema),
  defaultValues: { tab: "existing", quote_id: "", generate_new: false },  // <-- line ~43
});

// ...

async function onSubmit(data: AssignQuoteInput) {
  if (data.tab === "new") {
    window.location.href = `/admin/quotes/new?lead_id=${lead.id}`;  // <-- dead branch, lines 51-54, UNREACHABLE
    return;
  }

  setError(null);
  startTransition(async () => {
    try {
      const result = await assignQuoteToLead({ lead_id: lead.id, quote_id: data.quote_id! });
      if (!result.success) {
        setError(result.error ?? "Errore");
        return;
      }
      onOpenChange(false);
      router.refresh();
    } catch (e) {
      setError(e instanceof Error ? e.message : "Errore");
    }
  });
}

// ... around line 126-133, the "new" tab's button:
{tab === "new" && (
  <Button type="button" onClick={() => { window.location.href = `/admin/quotes/new?lead_id=${lead.id}`; }}>
    Crea Nuovo Preventivo
  </Button>
)}
```
</interfaces>

<tasks>

<task type="auto">
  <name>Task 1: Translate FollowUpWidget.tsx to Italian (CRM-10)</name>
  <files>src/components/admin/dashboard/FollowUpWidget.tsx</files>
  <read_first>
    - src/components/admin/dashboard/FollowUpWidget.tsx (current, full 47-line file)
    - .planning/phases/14-crm-attio-style-fix/14-UI-SPEC.md (Design Decision 5 — FollowUpWidget plural handling)
    - .planning/phases/14-crm-attio-style-fix/14-RESEARCH.md (Pitfall 3 — FollowUpWidget i18n scope, confirms lead-service.ts has no English strings, fix is widget-only)
  </read_first>
  <action>
Translate all 6 hardcoded English strings in `FollowUpWidget.tsx` to Italian, per UI-SPEC.md Decision 5 (Italian "lead" is invariant — same word for singular/plural, so no conditional pluralization needed):

1. `"Require Follow-up"` (CardTitle) → `"Richiedi Follow-up"`
2. `{count} lead{count !== 1 ? "s" : ""} to contact` → `{count} lead da contattare` (remove the `{count !== 1 ? "s" : ""}` ternary entirely — "lead" never takes an "s" in Italian)
3. `"Contact"` (Button label) → `"Contatta"`
4. `View All ({count})` → `Vedi Tutto ({count})`
5. `"All leads are up to date!"` (empty state) → `"Tutti i lead sono aggiornati!"`

Apply these as direct string replacements — do not change any logic, imports, component structure, or the `getLeadsNeedingFollowUp(7)` call. Only the JSX text content changes.

The full corrected file:

```tsx
import Link from "next/link";
import { getLeadsNeedingFollowUp } from "@/lib/lead-service";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";

export async function FollowUpWidget() {
  const leads = await getLeadsNeedingFollowUp(7);
  const count = leads.length;

  return (
    <Card>
      <CardHeader>
        <CardTitle>Richiedi Follow-up</CardTitle>
      </CardHeader>
      <CardContent>
        {count > 0 ? (
          <>
            <p className="text-sm text-gray-600 mb-3">
              {count} lead da contattare
            </p>
            <div className="space-y-2">
              {leads.slice(0, 5).map((lead) => (
                <div key={lead.id} className="flex items-center justify-between">
                  <span className="text-sm font-medium">{lead.name}</span>
                  <Button size="sm" variant="outline" asChild>
                    <Link href={`/admin/leads/${lead.id}`}>Contatta</Link>
                  </Button>
                </div>
              ))}
            </div>
            {count > 5 && (
              <Link href="/admin/leads" className="text-sm text-blue-600 hover:underline mt-2 block">
                Vedi Tutto ({count})
              </Link>
            )}
          </>
        ) : (
          <p className="text-sm text-gray-500">Tutti i lead sono aggiornati!</p>
        )}
      </CardContent>
    </Card>
  );
}
```
  </action>
  <verify>
    <automated>grep -ic "Require Follow-up\|to contact\|\"Contact\"\|View All\|up to date" src/components/admin/dashboard/FollowUpWidget.tsx | grep -qx 0 && npx tsc --noEmit</automated>
  </verify>
  <acceptance_criteria>
    - `grep -c "Richiedi Follow-up" src/components/admin/dashboard/FollowUpWidget.tsx` returns 1
    - `grep -c "lead da contattare" src/components/admin/dashboard/FollowUpWidget.tsx` returns 1
    - `grep -c "Contatta" src/components/admin/dashboard/FollowUpWidget.tsx` returns 1
    - `grep -c "Vedi Tutto" src/components/admin/dashboard/FollowUpWidget.tsx` returns 1
    - `grep -c "Tutti i lead sono aggiornati" src/components/admin/dashboard/FollowUpWidget.tsx` returns 1
    - `grep -ic "Require Follow-up\|to contact\|\"Contact\"\|View All\|up to date" src/components/admin/dashboard/FollowUpWidget.tsx` returns 0 (no English strings remain)
    - `grep -c 'count !== 1 ? "s" : ""' src/components/admin/dashboard/FollowUpWidget.tsx` returns 0 (pluralization ternary removed)
    - `npx tsc --noEmit` exits 0
    - `npx eslint src/components/admin/dashboard/FollowUpWidget.tsx` exits 0
  </acceptance_criteria>
  <done>FollowUpWidget.tsx contains only Italian text: "Richiedi Follow-up" heading, "{count} lead da contattare" body (no pluralization), "Contatta" button, "Vedi Tutto ({count})" link, "Tutti i lead sono aggiornati!" empty state. No English strings remain. Type-checks and lints cleanly.</done>
</task>

<task type="auto" tdd="true">
  <name>Task 2: Type LeadForm.tsx's useForm with CreateLeadInput (CRM-11)</name>
  <files>src/components/admin/leads/LeadForm.tsx</files>
  <read_first>
    - src/components/admin/leads/LeadForm.tsx (current, full 366-line file — both CreateLeadModal lines 38-201 and EditLeadModal lines 202-365)
    - src/lib/lead-validators.ts (full file — confirm CreateLeadInput export, createLeadSchema shape, LEAD_STAGES)
    - .planning/phases/14-crm-attio-style-fix/14-RESEARCH.md (relevant Pattern for CRM-11 fix)
  </read_first>
  <behavior>
    Manual-trace behaviors (verified via `npx tsc --noEmit` — TypeScript itself is the test for this type-safety fix):
    - `CreateLeadModal`'s `useForm<any>()` becomes `useForm<CreateLeadInput>()`, where `CreateLeadInput` is imported from `@/lib/lead-validators` (not locally redeclared)
    - `EditLeadModal`'s `useForm<any>()` becomes `useForm<CreateLeadInput>()`
    - The local `type CreateLeadInput = z.infer<typeof createLeadSchema>;` declaration (current line 36) is REMOVED — the type now comes from the import
    - `EditLeadModal`'s `defaultValues.status: lead.status as any` becomes `status: lead.status as CreateLeadInput["status"]` — the only remaining cast, narrowing `Lead.status` (a plain `string` column type from Drizzle's `$inferSelect`) to the Zod-enum-derived `CreateLeadInput["status"]` type (`"contacted" | "qualified" | "proposal_sent" | "negotiating" | "won" | "lost" | undefined`)
    - All `form.register(...)`, `form.handleSubmit(...)`, `form.setValue(...)`, `form.watch(...)` calls (wherever they appear in both modals) continue to type-check against `CreateLeadInput` field names (`name`, `email`, `phone`, `company`, `status`, `next_action`, etc.) — if any currently-untyped `any`-cast field access breaks, the field name is wrong relative to `createLeadSchema` and must be corrected to match the schema (do not re-introduce `any` to suppress the error)
    - If `z` import becomes unused after removing the local `type CreateLeadInput = z.infer<...>` line, remove the now-unused `import type { z } from "zod"` (or equivalent) — but only if `z` is not used elsewhere in the file (grep first)
  </behavior>
  <action>
1. Read the full `LeadForm.tsx` file to locate:
   - The current local type declaration: `type CreateLeadInput = z.infer<typeof createLeadSchema>;`
   - Both `useForm<any>({...})` call sites (one in `CreateLeadModal`, one in `EditLeadModal`)
   - The `status: lead.status as any` cast in `EditLeadModal`'s `defaultValues`
   - The current import block (to determine whether `z` and `createLeadSchema` are already imported, and from where)

2. Add `CreateLeadInput` to the import from `@/lib/lead-validators`:
   ```tsx
   import { createLeadSchema, type CreateLeadInput } from "@/lib/lead-validators";
   ```
   (adjust based on the exact current import statement — if `createLeadSchema` is already imported from `lead-validators`, just add `, type CreateLeadInput` to that same import line)

3. Remove the local redeclaration: delete the line `type CreateLeadInput = z.infer<typeof createLeadSchema>;` entirely.

4. If `import type { z } from "zod"` (or `import { z } from "zod"` used only for this) becomes unused after step 3, remove it. First grep the file for other `z.` usages — if any remain (e.g., inline schema validation elsewhere in the file), keep the import.

5. In `CreateLeadModal`, change:
   ```tsx
   const form = useForm<any>({
   ```
   to:
   ```tsx
   const form = useForm<CreateLeadInput>({
   ```

6. In `EditLeadModal`, change:
   ```tsx
   const form = useForm<any>({
   ```
   to:
   ```tsx
   const form = useForm<CreateLeadInput>({
   ```

7. In `EditLeadModal`'s `defaultValues`, change:
   ```tsx
   status: lead.status as any,
   ```
   to:
   ```tsx
   status: lead.status as CreateLeadInput["status"],
   ```

8. Run `npx tsc --noEmit`. If new type errors surface inside either modal (e.g., a `form.watch("someField")` call referencing a field name not in `createLeadSchema`, or a `form.setValue(...)` with a mismatched type), fix the call site to match the actual `createLeadSchema` shape (from `lead-validators.ts`) — do NOT reintroduce `any` or `as any` to silence the error. The goal is genuine type safety, not error suppression.
  </action>
  <verify>
    <automated>grep -c "useForm<any>\|as any" src/components/admin/leads/LeadForm.tsx | grep -qx 0 && npx tsc --noEmit && npx eslint src/components/admin/leads/LeadForm.tsx</automated>
  </verify>
  <acceptance_criteria>
    - `grep -c "useForm<any>" src/components/admin/leads/LeadForm.tsx` returns 0
    - `grep -c "useForm<CreateLeadInput>" src/components/admin/leads/LeadForm.tsx` returns 2 (CreateLeadModal + EditLeadModal)
    - `grep -c "type CreateLeadInput = z.infer" src/components/admin/leads/LeadForm.tsx` returns 0 (local redeclaration removed)
    - `grep -c "CreateLeadInput" src/components/admin/leads/LeadForm.tsx | grep -v '^0'` returns non-empty (import line + 2 useForm + 1 cast = at least 4 occurrences)
    - `grep -c "status: lead.status as any" src/components/admin/leads/LeadForm.tsx` returns 0
    - `grep -c 'status: lead.status as CreateLeadInput\["status"\]' src/components/admin/leads/LeadForm.tsx` returns 1
    - `grep -c "as any" src/components/admin/leads/LeadForm.tsx` returns 0 (no remaining `any` casts in the file)
    - `npx tsc --noEmit` exits 0
    - `npx eslint src/components/admin/leads/LeadForm.tsx` exits 0
  </acceptance_criteria>
  <done>Both CreateLeadModal and EditLeadModal use useForm<CreateLeadInput>() with CreateLeadInput imported from lead-validators.ts (no local redeclaration). The only remaining type assertion is the narrowing cast `lead.status as CreateLeadInput["status"]` in EditLeadModal's defaultValues. No `as any` remains anywhere in the file. Type-checks and lints cleanly.</done>
</task>

<task type="auto" tdd="true">
  <name>Task 3: Remove dead onSubmit branch in SendQuoteModal.tsx (CRM-12)</name>
  <files>src/components/admin/leads/SendQuoteModal.tsx</files>
  <read_first>
    - src/components/admin/leads/SendQuoteModal.tsx (current, full 140-line file)
    - .planning/phases/14-crm-attio-style-fix/14-RESEARCH.md (Pitfall 4 — SendQuoteModal dead branch analysis)
    - .planning/phases/14-crm-attio-style-fix/14-PATTERNS.md (SendQuoteModal.tsx fixed pattern)
  </read_first>
  <behavior>
    Manual-trace behaviors (verified via `npx tsc --noEmit` + `npx eslint` — no existing test file for this component; the fix is a dead-code removal, behavior for the "existing" tab path is unchanged):
    - `onSubmit`'s body NO LONGER contains an `if (data.tab === "new") { window.location.href = ...; return; }` branch — this branch was unreachable because: (a) the "new" tab's submit button has its own `onClick={() => window.location.href = ...}` handler that navigates directly without calling `form.handleSubmit`, and (b) when `tab === "new"`, the only rendered button is that direct-navigation button — no `type="submit"` button exists for the "new" tab, so `onSubmit` (wired to `form.handleSubmit(onSubmit)`) can never fire with `data.tab === "new"`
    - `onSubmit` now begins directly with the "existing" tab logic: `setError(null); startTransition(async () => { ... assignQuoteToLead({ lead_id: lead.id, quote_id: data.quote_id! }) ... })`
    - The "new" tab's button (with its own `onClick` navigation) is UNCHANGED — it continues to work exactly as before
    - `assignQuoteToLead({ lead_id, quote_id })` call signature is unchanged — only the dead branch above it is removed
  </behavior>
  <action>
1. Read the full `SendQuoteModal.tsx` file.

2. Locate the `onSubmit` function. It currently begins with:
   ```tsx
   async function onSubmit(data: AssignQuoteInput) {
     if (data.tab === "new") {
       window.location.href = `/admin/quotes/new?lead_id=${lead.id}`;
       return;
     }

     setError(null);
     startTransition(async () => {
       // ... existing-tab logic
     });
   }
   ```

3. Remove the dead `if (data.tab === "new") { ... return; }` block entirely, so `onSubmit` begins directly with `setError(null);`:
   ```tsx
   async function onSubmit(data: AssignQuoteInput) {
     setError(null);
     startTransition(async () => {
       // ... existing-tab logic (unchanged)
     });
   }
   ```

4. Confirm the "new" tab's button (its own `onClick={() => { window.location.href = ... }}`, separate from form submission) is untouched — it remains the sole navigation path for the "new" tab.

5. Leave the `generate_new: z.boolean().optional()` field in `assignQuoteSchema` and its `defaultValues: { ..., generate_new: false }` entry AS-IS — RESEARCH.md flags this as optional cleanup, not required for CRM-12. Removing it would require also checking `AssignQuoteInput` usages elsewhere (out of scope for this isolated fix). Do not touch it.

6. After the edit, grep the file for `data.tab` to confirm no other dead `tab === "new"` checks remain inside `onSubmit`. If the `tab` field is still referenced elsewhere (e.g., to conditionally render the "existing" vs "new" tab UI in JSX), that is correct and expected — only the `onSubmit` dead branch is removed.
  </action>
  <verify>
    <automated>grep -A2 "async function onSubmit" src/components/admin/leads/SendQuoteModal.tsx | grep -c 'data.tab === "new"' | grep -qx 0 && npx tsc --noEmit && npx eslint src/components/admin/leads/SendQuoteModal.tsx</automated>
  </verify>
  <acceptance_criteria>
    - `grep -A2 "async function onSubmit" src/components/admin/leads/SendQuoteModal.tsx | grep -c 'data.tab === "new"'` returns 0
    - `grep -c "window.location.href" src/components/admin/leads/SendQuoteModal.tsx` returns 1 (only the "new" tab button's onClick remains, the onSubmit copy is removed)
    - `grep -c "assignQuoteToLead" src/components/admin/leads/SendQuoteModal.tsx` returns at least 1 (existing-tab submit logic preserved)
    - `grep -c "generate_new" src/components/admin/leads/SendQuoteModal.tsx` returns at least 1 (left untouched per action step 5)
    - `npx tsc --noEmit` exits 0
    - `npx eslint src/components/admin/leads/SendQuoteModal.tsx` exits 0
  </acceptance_criteria>
  <done>SendQuoteModal.tsx's onSubmit no longer contains the unreachable `if (data.tab === "new") {...}` branch — onSubmit now starts directly with `setError(null)` and the existing-tab assignQuoteToLead logic. The "new" tab's own onClick-based navigation button is unchanged. generate_new field left as-is (optional cleanup, out of scope). Type-checks and lints cleanly.</done>
</task>

</tasks>

<threat_model>
## Trust Boundaries

| Boundary | Description |
|----------|--------------|
| Admin browser -> FollowUpWidget/LeadForm/SendQuoteModal | Server-rendered (FollowUpWidget) and client components (LeadForm, SendQuoteModal); no new data flows introduced |

## STRIDE Threat Register

| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-14-11 | Tampering | LeadForm.tsx — `useForm<any>()` to `useForm<CreateLeadInput>()` | mitigate | Stronger compile-time typing reduces risk of malformed form payloads reaching `createLead`/`updateLead` server actions; Zod validation at the action boundary (pre-existing, unchanged) remains the actual runtime enforcement — this fix is a defense-in-depth/DX improvement, not a new security control |
| T-14-12 | Tampering | SendQuoteModal.tsx — dead `onSubmit` branch removal | accept | Removing unreachable code has no security effect; `assignQuoteToLead` (existing-tab path) and its `requireAdmin`-equivalent guards (if any, pre-existing) are unchanged |
| T-14-13 | Information Disclosure | FollowUpWidget.tsx — string translation only | accept | No data exposure change; only display copy is translated. `getLeadsNeedingFollowUp(7)` query and its session-protected `/admin` route context are unchanged |
</threat_model>

<verification>
1. `npx tsc --noEmit` — exits 0 across all three modified files
2. `npx eslint src/components/admin/dashboard/FollowUpWidget.tsx src/components/admin/leads/LeadForm.tsx src/components/admin/leads/SendQuoteModal.tsx` — exits 0
3. `grep -ic "Require Follow-up\|to contact\|View All\|up to date" src/components/admin/dashboard/FollowUpWidget.tsx` returns 0 (no English strings)
4. `grep -c "useForm<any>\|as any" src/components/admin/leads/LeadForm.tsx` returns 0
5. `grep -A2 "async function onSubmit" src/components/admin/leads/SendQuoteModal.tsx | grep -c 'data.tab === "new"'` returns 0
</verification>

<success_criteria>
- FollowUpWidget.tsx displays only Italian text ("Richiedi Follow-up", "{count} lead da contattare", "Contatta", "Vedi Tutto ({count})", "Tutti i lead sono aggiornati!") — CRM-10 satisfied
- LeadForm.tsx's CreateLeadModal and EditLeadModal both use `useForm<CreateLeadInput>()` with `CreateLeadInput` imported from `lead-validators.ts`; no `as any` remains — CRM-11 satisfied
- SendQuoteModal.tsx's `onSubmit` no longer contains the unreachable `tab === "new"` branch; existing-tab submission and new-tab button navigation both continue to work — CRM-12 satisfied
- `npx tsc --noEmit` and `npx eslint` both exit 0 across all three files
</success_criteria>

<output>
After completion, create `.planning/phases/14-crm-attio-style-fix/14-03-SUMMARY.md`
</output>
