217 lines
7.1 KiB
TypeScript
217 lines
7.1 KiB
TypeScript
import { Button } from "@/components/ui/button";
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { Badge } from "@/components/ui/badge";
|
|
|
|
export type BrandKitBundle = {
|
|
type: "brand-kit-bundle";
|
|
spec: {
|
|
name: string;
|
|
tagline: string;
|
|
primaryColor: string;
|
|
secondaryColor: string;
|
|
fontStyle: string;
|
|
industry: string;
|
|
};
|
|
logoSvgBase64: string;
|
|
avatarPngs: Record<string, string>; // "512"|"256"|"128"|"64"|"32" -> base64
|
|
socialImages: Record<string, string>; // "twitter-profile" etc -> base64
|
|
signatureHtml: string;
|
|
letterheadHtml: string;
|
|
guidelinesPdfBase64: string;
|
|
zipBase64: string;
|
|
};
|
|
|
|
const AVATAR_SIZES = ["512", "256", "128", "64", "32"] as const;
|
|
|
|
const SOCIAL_PLATFORM_LABELS: Record<string, string> = {
|
|
"twitter-profile": "Twitter/X Profile",
|
|
"linkedin-profile": "LinkedIn Profile",
|
|
"linkedin-banner": "LinkedIn Banner",
|
|
"instagram-profile": "Instagram Profile",
|
|
"facebook-cover": "Facebook Cover",
|
|
};
|
|
|
|
interface BrandKitResultProps {
|
|
bundle: BrandKitBundle;
|
|
}
|
|
|
|
export function BrandKitResult({ bundle }: BrandKitResultProps) {
|
|
function downloadBlob(base64: string, mimeType: string, filename: string) {
|
|
const bytes = Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));
|
|
const blob = new Blob([bytes], { type: mimeType });
|
|
const url = URL.createObjectURL(blob);
|
|
const a = document.createElement("a");
|
|
a.href = url;
|
|
a.download = filename;
|
|
a.click();
|
|
URL.revokeObjectURL(url);
|
|
}
|
|
|
|
function handleDownloadZip() {
|
|
downloadBlob(bundle.zipBase64, "application/zip", `${bundle.spec.name || "brand-kit"}.zip`);
|
|
}
|
|
|
|
function handleDownloadGuidelines() {
|
|
downloadBlob(
|
|
bundle.guidelinesPdfBase64,
|
|
"application/pdf",
|
|
`${bundle.spec.name || "brand"}-guidelines.pdf`,
|
|
);
|
|
}
|
|
|
|
const socialEntries = Object.entries(bundle.socialImages);
|
|
|
|
return (
|
|
<div className="flex flex-col gap-6">
|
|
{/* Brand spec summary */}
|
|
<div className="flex flex-wrap items-center gap-2">
|
|
<span className="text-sm font-semibold">{bundle.spec.name}</span>
|
|
{bundle.spec.tagline && (
|
|
<span className="text-sm text-muted-foreground">— {bundle.spec.tagline}</span>
|
|
)}
|
|
<Badge variant="outline">{bundle.spec.industry}</Badge>
|
|
<Badge variant="outline">{bundle.spec.fontStyle}</Badge>
|
|
<span
|
|
className="inline-flex size-5 rounded-full border border-border"
|
|
style={{ backgroundColor: bundle.spec.primaryColor }}
|
|
title={`Primary: ${bundle.spec.primaryColor}`}
|
|
/>
|
|
<span
|
|
className="inline-flex size-5 rounded-full border border-border"
|
|
style={{ backgroundColor: bundle.spec.secondaryColor }}
|
|
title={`Secondary: ${bundle.spec.secondaryColor}`}
|
|
/>
|
|
</div>
|
|
|
|
{/* Logo */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="text-base">Logo</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="flex items-center justify-center rounded-lg bg-muted p-6">
|
|
<img
|
|
src={`data:image/svg+xml;base64,${bundle.logoSvgBase64}`}
|
|
alt={`${bundle.spec.name} logo`}
|
|
className="max-h-32 max-w-full object-contain"
|
|
/>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Avatars */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="text-base">Avatars</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="flex flex-wrap items-end gap-4">
|
|
{AVATAR_SIZES.map((size) => {
|
|
const pngBase64 = bundle.avatarPngs[size];
|
|
if (!pngBase64) return null;
|
|
const px = Number(size);
|
|
const displaySize = Math.min(px, 96);
|
|
return (
|
|
<div key={size} className="flex flex-col items-center gap-1">
|
|
<img
|
|
src={`data:image/png;base64,${pngBase64}`}
|
|
alt={`${size}px avatar`}
|
|
width={displaySize}
|
|
height={displaySize}
|
|
className="rounded-full border border-border object-contain"
|
|
/>
|
|
<span className="text-xs text-muted-foreground">{size}px</span>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Social images */}
|
|
{socialEntries.length > 0 && (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="text-base">Social Images</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3">
|
|
{socialEntries.map(([platform, pngBase64]) => (
|
|
<div key={platform} className="flex flex-col gap-1">
|
|
<div className="rounded-lg bg-muted overflow-hidden">
|
|
<img
|
|
src={`data:image/png;base64,${pngBase64}`}
|
|
alt={`${platform} image`}
|
|
className="w-full object-cover"
|
|
/>
|
|
</div>
|
|
<span className="text-xs text-muted-foreground text-center">
|
|
{SOCIAL_PLATFORM_LABELS[platform] ?? platform}
|
|
</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
|
|
{/* Templates */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="text-base">Templates</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="flex flex-col gap-4">
|
|
<div className="flex flex-col gap-2">
|
|
<span className="text-sm font-medium">Email Signature</span>
|
|
<div className="rounded-lg border border-border overflow-hidden">
|
|
<iframe
|
|
title="Email Signature Preview"
|
|
srcDoc={bundle.signatureHtml}
|
|
sandbox="allow-same-origin"
|
|
className="w-full h-32 bg-white"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="flex flex-col gap-2">
|
|
<span className="text-sm font-medium">Letterhead</span>
|
|
<div className="rounded-lg border border-border overflow-hidden">
|
|
<iframe
|
|
title="Letterhead Preview"
|
|
srcDoc={bundle.letterheadHtml}
|
|
sandbox="allow-same-origin"
|
|
className="w-full h-48 bg-white"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Guidelines */}
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="text-base">Brand Guidelines</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Button
|
|
type="button"
|
|
variant="secondary"
|
|
className="w-full"
|
|
onClick={handleDownloadGuidelines}
|
|
>
|
|
Download Brand Guidelines PDF
|
|
</Button>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Download Brand Kit (ZIP) */}
|
|
<Button
|
|
type="button"
|
|
size="lg"
|
|
className="w-full"
|
|
onClick={handleDownloadZip}
|
|
>
|
|
Download Brand Kit (ZIP)
|
|
</Button>
|
|
</div>
|
|
);
|
|
}
|