clipon / frontend /src /components /BrandKitEditor.jsx
yonagush
Fix YouTube DNS failure via Invidious proxy + retheme to chainstreet gold
a72d248
import { useState } from 'react'
import { X, Upload } from 'lucide-react'
import clsx from 'clsx'
/**
* Brand Kit editor modal
* Props: { isOpen, onClose, clipId, jobId }
*/
function BrandKitEditor({ isOpen, onClose, clipId, jobId }) {
const [primaryColor, setPrimaryColor] = useState('#E8A020')
const [secondaryColor, setSecondaryColor] = useState('#FFB800')
const [watermarkPos, setWatermarkPos] = useState('bottom-right')
const [watermarkOpacity, setWatermarkOpacity] = useState(0.8)
const [font, setFont] = useState('Inter')
const [logoFile, setLogoFile] = useState(null)
if (!isOpen) return null
const positions = [
{ id: 'top-left', label: 'TL', x: 'left', y: 'top' },
{ id: 'top-center', label: 'T', x: 'center', y: 'top' },
{ id: 'top-right', label: 'TR', x: 'right', y: 'top' },
{ id: 'middle-left', label: 'L', x: 'left', y: 'center' },
{ id: 'center', label: 'C', x: 'center', y: 'center' },
{ id: 'middle-right', label: 'R', x: 'right', y: 'center' },
{ id: 'bottom-left', label: 'BL', x: 'left', y: 'bottom' },
{ id: 'bottom-center', label: 'B', x: 'center', y: 'bottom' },
{ id: 'bottom-right', label: 'BR', x: 'right', y: 'bottom' },
]
const fonts = ['Inter', 'Poppins', 'Oswald', 'Montserrat', 'Roboto', 'Playfair Display']
const handleSave = () => {
console.log({
primaryColor,
secondaryColor,
watermarkPos,
watermarkOpacity,
font,
logoFile,
})
onClose()
}
return (
<>
{/* Overlay */}
<div
className="fixed inset-0 bg-black/60 backdrop-blur-sm z-40 animate-fade-in"
onClick={onClose}
/>
{/* Modal */}
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 animate-scale-in">
<div className="glass-lg max-w-3xl w-full max-h-[90vh] overflow-y-auto rounded-3xl">
{/* Header */}
<div className="flex items-center justify-between p-8 border-b border-white/10 sticky top-0 bg-dark-900/50 backdrop-blur">
<h2 className="text-2xl font-bold text-white">Brand Kit Editor</h2>
<button
onClick={onClose}
className="p-2 hover:bg-white/10 rounded-lg transition-colors"
>
<X className="w-5 h-5 text-white/70" />
</button>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8 p-8">
{/* Left Panel - Settings */}
<div className="space-y-8">
{/* Logo Upload */}
<div>
<label className="block text-sm font-semibold text-white mb-4">Logo</label>
<label className="upload-zone cursor-pointer">
<div className="flex flex-col items-center gap-3">
<Upload className="w-6 h-6 text-primary-500" />
<div>
<p className="text-sm font-semibold text-white">
{logoFile ? 'Change logo' : 'Upload logo'}
</p>
<p className="text-xs text-white/60">PNG or SVG, max 2MB</p>
</div>
</div>
<input
type="file"
accept="image/png,image/svg+xml"
onChange={(e) => setLogoFile(e.target.files?.[0] || null)}
className="hidden"
/>
</label>
{logoFile && (
<p className="text-xs text-white/60 mt-2">Selected: {logoFile.name}</p>
)}
</div>
{/* Primary Color */}
<div>
<label className="block text-sm font-semibold text-white mb-3">Primary Color</label>
<div className="flex gap-3 items-center">
<input
type="color"
value={primaryColor}
onChange={(e) => setPrimaryColor(e.target.value)}
className="w-16 h-12 rounded-lg cursor-pointer border border-white/10"
/>
<input
type="text"
value={primaryColor}
onChange={(e) => setPrimaryColor(e.target.value)}
className="input-field flex-1 text-sm font-mono"
placeholder="#E8A020"
/>
</div>
</div>
{/* Secondary Color */}
<div>
<label className="block text-sm font-semibold text-white mb-3">Secondary Color</label>
<div className="flex gap-3 items-center">
<input
type="color"
value={secondaryColor}
onChange={(e) => setSecondaryColor(e.target.value)}
className="w-16 h-12 rounded-lg cursor-pointer border border-white/10"
/>
<input
type="text"
value={secondaryColor}
onChange={(e) => setSecondaryColor(e.target.value)}
className="input-field flex-1 text-sm font-mono"
placeholder="#FFB800"
/>
</div>
</div>
{/* Font */}
<div>
<label className="block text-sm font-semibold text-white mb-3">Font Family</label>
<select
value={font}
onChange={(e) => setFont(e.target.value)}
className="input-field-lg w-full bg-white/5"
>
{fonts.map((f) => (
<option key={f} value={f}>
{f}
</option>
))}
</select>
</div>
{/* Watermark Opacity */}
<div>
<div className="flex justify-between items-center mb-3">
<label className="text-sm font-semibold text-white">Watermark Opacity</label>
<span className="text-primary-500 font-bold">{Math.round(watermarkOpacity * 100)}%</span>
</div>
<input
type="range"
min="0"
max="1"
step="0.1"
value={watermarkOpacity}
onChange={(e) => setWatermarkOpacity(parseFloat(e.target.value))}
className="w-full h-2 bg-white/10 rounded-full accent-primary-500"
/>
</div>
</div>
{/* Right Panel - Preview */}
<div>
<p className="text-sm font-semibold text-white mb-4">Live Preview</p>
<div className="glass-lg p-6 rounded-2xl aspect-9-16 flex flex-col relative overflow-hidden bg-dark-850">
{/* Gradient background based on colors */}
<div
className="absolute inset-0 opacity-20"
style={{
background: `linear-gradient(135deg, ${primaryColor}, ${secondaryColor})`,
}}
/>
{/* Content */}
<div className="relative z-10 flex flex-col h-full">
{/* Logo at top-left if watermarkPos involves top */}
{(watermarkPos.includes('top') || watermarkPos.includes('center')) && (
<div
className="p-3"
style={{ opacity: watermarkOpacity }}
>
<div
className="w-12 h-12 rounded-lg"
style={{ backgroundColor: primaryColor }}
/>
</div>
)}
{/* Center content */}
<div className="flex-1 flex flex-col items-center justify-center px-4">
<h3
className="text-xl font-bold text-center mb-2"
style={{ color: primaryColor, fontFamily: font }}
>
Your Brand
</h3>
<p className="text-xs text-white/60 text-center">
This is how your clips will look with your brand kit applied
</p>
</div>
{/* Watermark at bottom if positioned there */}
{(watermarkPos.includes('bottom') || watermarkPos.includes('center')) && (
<div
className="p-3 flex items-center justify-center"
style={{ opacity: watermarkOpacity }}
>
<div
className="text-xs font-bold"
style={{ color: primaryColor }}
>
WATERMARK
</div>
</div>
)}
</div>
</div>
{/* Watermark Position */}
<div className="mt-6">
<p className="text-sm font-semibold text-white mb-4">Watermark Position</p>
<div className="grid grid-cols-3 gap-2">
{positions.map((pos) => (
<button
key={pos.id}
onClick={() => setWatermarkPos(pos.id)}
className={clsx(
'p-3 rounded-lg font-bold text-sm transition-all',
watermarkPos === pos.id
? 'glass-lg bg-primary-500/20 border-primary-500/50 text-primary-400'
: 'glass hover:bg-white/10 text-white/70'
)}
>
{pos.label}
</button>
))}
</div>
</div>
</div>
</div>
{/* Actions */}
<div className="flex gap-3 p-8 border-t border-white/10">
<button onClick={onClose} className="btn-secondary-lg flex-1">
Cancel
</button>
<button onClick={handleSave} className="btn-primary-lg flex-1">
Save Brand Kit
</button>
</div>
</div>
</div>
</>
)
}
export default BrandKitEditor