215 lines
11 KiB
TypeScript
215 lines
11 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import axios from 'axios';
|
|
import { Upload, Trash2, Save, Image as ImageIcon, Store, Link as LinkIcon, Lock } from 'lucide-react';
|
|
|
|
interface BrandKitProps {
|
|
}
|
|
|
|
export default function BrandKit({ }: BrandKitProps) {
|
|
const [logoPreview, setLogoPreview] = useState<string | null>(null);
|
|
const [loading, setLoading] = useState(false);
|
|
const [uploading, setUploading] = useState(false);
|
|
|
|
// Shop Settings State
|
|
const [shopName, setShopName] = useState("");
|
|
const [shopLink, setShopLink] = useState("");
|
|
const [apiKey, setApiKey] = useState("");
|
|
const [savingDetails, setSavingDetails] = useState(false);
|
|
|
|
useEffect(() => {
|
|
fetchBrandData();
|
|
}, []);
|
|
|
|
const fetchBrandData = async () => {
|
|
setLoading(true);
|
|
try {
|
|
// 1. Fetch Logo Status
|
|
const logoRes = await axios.get('/api/brand/logo');
|
|
if (logoRes.data.exists) {
|
|
setLogoPreview(logoRes.data.logo);
|
|
}
|
|
|
|
// 2. Fetch User Settings (Shop Name, Link, API Key)
|
|
const userRes = await axios.get('/api/auth/me');
|
|
if (userRes.data) {
|
|
setShopName(userRes.data.etsyShopName || "");
|
|
setShopLink(userRes.data.etsyShopLink || "");
|
|
setApiKey(userRes.data.apiKey || "");
|
|
}
|
|
|
|
} catch (err) {
|
|
console.error("Failed to fetch brand data", err);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleFileUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const file = e.target.files?.[0];
|
|
if (!file) return;
|
|
|
|
setUploading(true);
|
|
const formData = new FormData();
|
|
formData.append('logo', file);
|
|
|
|
try {
|
|
const res = await axios.post('/api/brand/logo', formData, {
|
|
headers: { 'Content-Type': 'multipart/form-data' }
|
|
});
|
|
|
|
// Refresh preview
|
|
fetchBrandData();
|
|
} catch (err: any) {
|
|
alert('Failed to upload logo: ' + (err.response?.data?.error || err.message));
|
|
} finally {
|
|
setUploading(false);
|
|
}
|
|
};
|
|
|
|
const handleSaveDetails = async () => {
|
|
setSavingDetails(true);
|
|
try {
|
|
await axios.put('/api/auth/me', {
|
|
etsyShopName: shopName,
|
|
etsyShopLink: shopLink,
|
|
apiKey: apiKey // Allow updating API Key here too if needed
|
|
});
|
|
alert("Brand settings saved successfully!");
|
|
} catch (err: any) {
|
|
alert("Failed to save settings: " + (err.response?.data?.error || err.message));
|
|
} finally {
|
|
setSavingDetails(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="bg-white border border-stone-200 shadow-sm rounded-2xl p-6 mb-8">
|
|
<div className="flex justify-between items-start mb-6">
|
|
<div>
|
|
<h3 className="text-lg font-bold text-stone-900 flex items-center gap-2">
|
|
<ImageIcon className="w-5 h-5 text-purple-600" />
|
|
Brand Kit & Shop Settings
|
|
</h3>
|
|
<p className="text-sm text-stone-500 mt-1">
|
|
Manage your shop identity. Your logo will be used for mockups, and shop details for SEO & Branding.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex flex-col md:flex-row gap-8 items-start">
|
|
|
|
{/* 1. Logo Section */}
|
|
<div className="w-full md:w-auto flex flex-col items-center gap-3">
|
|
<span className="text-xs font-bold text-stone-500 uppercase tracking-wider">Store Logo</span>
|
|
<div className="w-48 h-48 bg-stone-100 rounded-xl border-2 border-dashed border-stone-300 flex flex-col items-center justify-center relative overflow-hidden group">
|
|
{loading ? (
|
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-stone-900"></div>
|
|
) : logoPreview ? (
|
|
<>
|
|
<img src={logoPreview} alt="Brand Logo" className="w-full h-full object-contain p-4" />
|
|
<div className="absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center">
|
|
<label className="cursor-pointer bg-white text-stone-900 px-3 py-1.5 rounded-lg text-xs font-bold hover:bg-stone-50 shadow-lg transform translate-y-2 group-hover:translate-y-0 transition-all">
|
|
Change Logo
|
|
<input type="file" className="hidden" accept="image/png" onChange={handleFileUpload} />
|
|
</label>
|
|
</div>
|
|
</>
|
|
) : (
|
|
<label className="cursor-pointer flex flex-col items-center gap-2 text-stone-400 hover:text-stone-600 transition-colors w-full h-full justify-center">
|
|
<Upload className="w-8 h-8" />
|
|
<span className="text-xs font-bold">Upload PNG</span>
|
|
<input type="file" className="hidden" accept="image/png" onChange={handleFileUpload} />
|
|
</label>
|
|
)}
|
|
</div>
|
|
{uploading && <span className="text-xs text-purple-600 animate-pulse">Uploading...</span>}
|
|
<p className="text-[10px] text-stone-400 max-w-[12rem] text-center">
|
|
Supports transparent PNG. Max 5MB. Used for watermarks on mockups.
|
|
</p>
|
|
</div>
|
|
|
|
{/* 2. Shop Details Section */}
|
|
<div className="flex-1 w-full space-y-6">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
|
|
{/* Shop Name */}
|
|
<div className="space-y-1">
|
|
<label className="text-xs font-bold text-stone-700 uppercase flex items-center gap-1.5">
|
|
<Store className="w-3.5 h-3.5" /> Etsy Shop Name
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={shopName}
|
|
onChange={(e) => setShopName(e.target.value)}
|
|
placeholder="e.g. MyArtPrintStudio"
|
|
className="w-full px-3 py-2 bg-stone-50 border border-stone-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-purple-500/20 focus:border-purple-500 transition-all"
|
|
/>
|
|
</div>
|
|
|
|
{/* Shop Link */}
|
|
<div className="space-y-1">
|
|
<label className="text-xs font-bold text-stone-700 uppercase flex items-center gap-1.5">
|
|
<LinkIcon className="w-3.5 h-3.5" /> Shop URL
|
|
</label>
|
|
<input
|
|
type="text"
|
|
value={shopLink}
|
|
onChange={(e) => setShopLink(e.target.value)}
|
|
placeholder="https://etsy.com/shop/..."
|
|
className="w-full px-3 py-2 bg-stone-50 border border-stone-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-purple-500/20 focus:border-purple-500 transition-all"
|
|
/>
|
|
</div>
|
|
|
|
{/* API Key (Optional Display) */}
|
|
<div className="col-span-1 md:col-span-2 space-y-1">
|
|
<label className="text-xs font-bold text-stone-700 uppercase flex items-center gap-1.5">
|
|
<Lock className="w-3.5 h-3.5" /> Gemini API Key (Optional Override)
|
|
</label>
|
|
<input
|
|
type="password"
|
|
value={apiKey}
|
|
onChange={(e) => setApiKey(e.target.value)}
|
|
placeholder="Enter specific key for this project..."
|
|
className="w-full px-3 py-2 bg-stone-50 border border-stone-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-purple-500/20 focus:border-purple-500 transition-all font-mono"
|
|
/>
|
|
<p className="text-[10px] text-stone-400">
|
|
Leave blank to use system default.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Branding Info Box */}
|
|
<div className="bg-purple-50 border border-purple-100 rounded-xl p-4">
|
|
<h4 className="text-sm font-bold text-purple-900 mb-2">How this data is used</h4>
|
|
<ul className="text-xs text-purple-800 space-y-1.5 list-disc list-inside">
|
|
<li><strong>Shop Name:</strong> Used in SEO Titles and Descriptions generated by AI.</li>
|
|
<li><strong>Shop URL:</strong> Included in 'Printing Guide' PDFs for customer reference.</li>
|
|
<li><strong>Logo:</strong> Applied as a watermark to generated mockups (never on masters).</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div className="flex justify-end pt-2">
|
|
<button
|
|
onClick={handleSaveDetails}
|
|
disabled={savingDetails}
|
|
className="flex items-center gap-2 px-6 py-2.5 bg-stone-900 hover:bg-black text-white rounded-xl font-medium text-sm transition-all shadow-lg shadow-stone-900/10 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
>
|
|
{savingDetails ? (
|
|
<>
|
|
<div className="w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin" />
|
|
Saving...
|
|
</>
|
|
) : (
|
|
<>
|
|
<Save className="w-4 h-4" />
|
|
Save Changes
|
|
</>
|
|
)}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|