4) Brand Kit page — style pack, reroll, favorite/lock

src/pages/BrandKit/BrandKitPage.tsx (full file)

import { useMemo, useState } from "react";
import { generateBrandKit } from "@/services/ai/brand";
import { downloadBrandKitZip } from "@/services/kits/export";
import { saveBrandKit } from "@/services/kits/persist";
import { BrandKit, BrandPersonality } from "@/types/brand";
import { auth } from "@/lib/firebase";
import { generateAiLogos, StylePack } from "@/services/ai/logos";
import { sanitizeSvg } from "@/utils/svg-sanitize"; // if you placed it elsewhere, adjust import

// Minimal inline sanitizer fallback
function sanitizeInline(svg: string) {
  return svg
    .replace(/<script[\s\S]*?<\/script>/gi, "")
    .replace(/\son\w+="[^"]*"/gi, "")
    .replace(/xlink:href="javascript:[^"]*"/gi, "")
    .replace(/<foreignObject[\s\S]*?<\/foreignObject>/gi, "");
}

export default function BrandKitPage() {
  const [businessName, setBusinessName] = useState("");
  const [industry, setIndustry] = useState("");
  const [personality, setPersonality] = useState<BrandPersonality>("Modern");
  const [stylePack, setStylePack] = useState<StylePack>("balanced");

  const [kit, setKit] = useState<BrandKit | null>(null);
  const [saving, setSaving] = useState(false);

  // UI state for favorites/locks
  const [favorites, setFavorites] = useState<Record<number, boolean>>({});
  const [locks, setLocks] = useState<Record<number, boolean>>({});

  const palettePreview = useMemo(() => kit?.palette, [kit]);

  const handleGenerateKit = () => {
    const result = generateBrandKit({ businessName, industry, personality });
    setKit(result);
    setFavorites({});
    setLocks({});
  };

  const handleSave = async () => {
    if (!kit) return;
    const user = auth.currentUser;
    if (!user) { alert("Please sign in first."); return; }
    setSaving(true);
    try {
      const id = await saveBrandKit(user.uid, kit);
      setKit({ ...kit, id });
      alert("Brand Kit saved!");
    } finally {
      setSaving(false);
    }
  };

  const handleGenerateAI = async () => {
    if (!businessName) { alert("Enter a business name first"); return; }
    const variants = await generateAiLogos({
      businessName,
      personality,
      palette: {
        primary: kit?.palette.primary || "#4274b9",
        secondary: kit?.palette.secondary || "#6fc282",
        accent: kit?.palette.accent || "#FF8800",
      },
      keywords: industry ? [industry] : [],
      stylePack,
    });
    setKit(prev => prev ? { ...prev, logos: variants.map(v => ({ filename: v.filename, svg: v.svg })) } : null);
    setFavorites({});
    setLocks({});
  };

  const rerollOne = async (index: number) => {
    if (!kit) return;
    if (locks[index]) return; // do nothing if locked

    // Detect type from filename
    const fn = kit.logos[index]?.filename || "";
    let variantType: "geometric" | "monogram" | "badge" = "geometric";
    if (fn.includes("monogram")) variantType = "monogram";
    else if (fn.includes("badge")) variantType = "badge";

    const variants = await generateAiLogos({
      businessName,
      personality,
      palette: {
        primary: kit.palette.primary,
        secondary: kit.palette.secondary,
        accent: kit.palette.accent,
      },
      keywords: industry ? [industry] : [],
      variantType,
    });

    // Expect one variant back; replace only that index
    const v = variants[0];
    if (!v) return;

    setKit(prev => {
      if (!prev) return prev;
      const next = [...prev.logos];
      if (!locks[index]) {
        next[index] = { filename: v.filename, svg: v.svg };
      }
      return { ...prev, logos: next };
    });
  };

  const toggleFavorite = (i: number) =>
    setFavorites(prev => ({ ...prev, [i]: !prev[i] }));

  const toggleLock = (i: number) =>
    setLocks(prev => ({ ...prev, [i]: !prev[i] }));

  return (
    <div className="p-6">
      <h1 className="text-2xl font-semibold mb-2">Brand Kit Generator</h1>
      <p className="text-sm text-gray-600 mb-6">
        Answer a few questions, generate your kit, then refine with AI logos.
      </p>

      {/* Quick Start Form */}
      <div className="grid grid-cols-1 md:grid-cols-3 gap-4 bg-white rounded-xl p-4 shadow">
        <input
          className="border rounded-lg px-3 py-2"
          placeholder="Business name"
          value={businessName}
          onChange={(e)=>setBusinessName(e.target.value)}
        />
        <input
          className="border rounded-lg px-3 py-2"
          placeholder="Industry (optional)"
          value={industry}
          onChange={(e)=>setIndustry(e.target.value)}
        />
        <select
          className="border rounded-lg px-3 py-2"
          value={personality}
          onChange={(e)=>setPersonality(e.target.value as BrandPersonality)}
        >
          <option>Modern</option>
          <option>Natural</option>
          <option>Luxury</option>
          <option>Friendly</option>
        </select>

        <div className="md:col-span-3 flex flex-wrap items-center gap-3 mt-1">
          <label className="text-sm text-gray-600">Style Pack:</label>
          <select
            className="border rounded-lg px-3 py-2"
            value={stylePack}
            onChange={e => setStylePack(e.target.value as StylePack)}
          >
            <option value="balanced">Balanced (Geometric + Monogram + Badge)</option>
            <option value="monogram">Monogram-focused (3x)</option>
            <option value="badge">Badge-focused (3x)</option>
          </select>

          <button onClick={handleGenerateKit} className="px-4 py-2 rounded-lg bg-primary text-white hover:opacity-90">
            Generate Brand Kit
          </button>

          {kit && (
            <>
              <button onClick={handleGenerateAI} className="px-4 py-2 rounded-lg bg-dark text-white hover:opacity-90">
                Generate AI Logos (SVG)
              </button>
              <button onClick={handleSave} disabled={saving} className="px-4 py-2 rounded-lg bg-emerald-600 text-white hover:opacity-90 disabled:opacity-50">
                {saving ? "Saving..." : "Save Kit"}
              </button>
              <button onClick={()=>kit && downloadBrandKitZip(kit)} className="px-4 py-2 rounded-lg bg-accent text-white hover:opacity-90">
                Download Assets (.zip)
              </button>
            </>
          )}
        </div>
      </div>

      {/* Preview */}
      {palettePreview && (
        <section className="mt-8">
          <h2 className="text-xl font-semibold mb-3">Palette</h2>
          <div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 gap-3">
            {[
              ["Primary", palettePreview.primary],
              ["Secondary", palettePreview.secondary],
              ["Accent", palettePreview.accent],
              ["Neutral", palettePreview.neutral],
              ["Surface", palettePreview.surface],
              ["Text", palettePreview.textPrimary],
            ].map(([label, hex]) => (
              <div key={label as string} className="rounded-lg overflow-hidden border">
                <div className="h-16" style={{ background: hex as string }} />
                <div className="p-2 text-sm">
                  <div className="font-medium">{label}</div>
                  <div className="text-gray-500">{hex}</div>
                </div>
              </div>
            ))}
          </div>
        </section>
      )}

      {kit && (
        <section className="mt-8">
          <h2 className="text-xl font-semibold mb-3">Logos</h2>
          <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
            {kit.logos.map((v, i) => (
              <div key={i} className={`bg-white rounded-xl p-4 shadow border ${favorites[i] ? "ring-2 ring-amber-400" : ""}`}>
                <div className="flex items-center justify-between mb-2">
                  <div className="text-sm font-medium truncate">{v.filename}</div>
                  <div className="flex items-center gap-2">
                    <button
                      title={favorites[i] ? "Unfavorite" : "Favorite"}
                      className="text-xl"
                      onClick={() => toggleFavorite(i)}
                    >{favorites[i] ? "⭐" : "☆"}</button>

                    <button
                      title={locks[i] ? "Unlock" : "Lock (freeze this variant)"}
                      className="text-xl"
                      onClick={() => toggleLock(i)}
                    >{locks[i] ? "🔒" : "🔓"}</button>

                    <button
                      title="Reroll this variant"
                      className="text-xl"
                      onClick={() => rerollOne(i)}
                      disabled={!!locks[i]}
                    >↻</button>
                  </div>
                </div>
                <div className="aspect-[3/1] md:aspect-[1/1] border rounded-lg flex items-center justify-center overflow-hidden bg-gray-50">
                  <div
                    dangerouslySetInnerHTML={{ __html: (sanitizeSvg?.(v.svg) || sanitizeInline(v.svg)) }}
                  />
                </div>
              </div>
            ))}
          </div>
        </section>
      )}
    </div>
  );
}


If you don’t already have sanitizeSvg, keep the sanitizeInline provided (or move it to src/utils/svg-sanitize.ts and export).