- Created automated script (scripts/align-8px-grid.py) to align all spacing to 8px grid - Replaced non-8px-aligned spacing: gap-3/p-3/m-3 (12px) → gap-4/p-4/m-4 (16px), gap-5/p-5/m-5 (20px) → gap-6/p-6/m-6 (24px), gap-10/p-10/m-10 (40px) → gap-12/p-12/m-12 (48px), gap-20/p-20/m-20 (80px) → gap-24/p-24/m-24 (96px) - Preserved: 4px values (gap-1, p-1, m-1) as they may be intentional fine-tuning, responsive breakpoints (sm:, md:, lg:), test files, documentation - Modified files across all components to ensure consistent 8px grid alignment - Action 11.2.1.3: Align all elements to 8px grid - COMPLETE
255 lines
9.6 KiB
TypeScript
255 lines
9.6 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { Card } from '../ui/card';
|
|
import { Button } from '../ui/button';
|
|
import { Input } from '../ui/input';
|
|
import {
|
|
Radio,
|
|
Copy,
|
|
Eye,
|
|
EyeOff,
|
|
Play,
|
|
Square,
|
|
Settings,
|
|
Monitor,
|
|
Mic,
|
|
} from 'lucide-react';
|
|
import { useToast } from '../../context/ToastContext';
|
|
|
|
export const GoLiveView: React.FC = () => {
|
|
const { addToast } = useToast();
|
|
const [streamKeyVisible, setStreamKeyVisible] = useState(false);
|
|
const [isLive, setIsLive] = useState(false);
|
|
const [title, setTitle] = useState('My Awesome Stream');
|
|
const [category, setCategory] = useState('Production');
|
|
|
|
const streamKey = 'live_83921_abc123xyz789_secret_key';
|
|
const serverUrl = 'rtmp://live.veza.io/app';
|
|
|
|
const copyToClipboard = (text: string, label: string) => {
|
|
navigator.clipboard.writeText(text);
|
|
addToast(`${label} copied to clipboard`, 'success');
|
|
};
|
|
|
|
const toggleStream = () => {
|
|
if (!isLive) {
|
|
addToast('Starting stream... Waiting for signal...', 'info');
|
|
setTimeout(() => {
|
|
setIsLive(true);
|
|
addToast('You are LIVE!', 'success');
|
|
}, 2000);
|
|
} else {
|
|
setIsLive(false);
|
|
addToast('Stream ended', 'info');
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="animate-fadeIn pb-20 max-w-6xl mx-auto">
|
|
{/* Header */}
|
|
<div className="flex justify-between items-center mb-8 border-b border-kodo-steel/50 pb-6">
|
|
<div>
|
|
<h1 className="text-3xl font-display font-bold text-white mb-2 flex items-center gap-4">
|
|
<Radio
|
|
className={`w-8 h-8 ${isLive ? 'text-kodo-red animate-pulse' : 'text-kodo-content-dim'}`}
|
|
/>
|
|
BROADCAST STUDIO
|
|
</h1>
|
|
<p className="text-kodo-content-dim font-mono text-sm">
|
|
Configure your stream and go live.
|
|
</p>
|
|
</div>
|
|
<div className="flex gap-4">
|
|
<div
|
|
className={`px-4 py-2 rounded-lg border flex items-center gap-2 font-bold ${isLive ? 'bg-kodo-red text-white border-kodo-red' : 'bg-kodo-ink text-kodo-content-dim border-kodo-steel'}`}
|
|
>
|
|
<div
|
|
className={`w-3 h-3 rounded-full ${isLive ? 'bg-white animate-pulse' : 'bg-kodo-steel'}`}
|
|
></div>
|
|
{isLive ? 'LIVE' : 'OFFLINE'}
|
|
</div>
|
|
<Button
|
|
variant={isLive ? 'secondary' : 'primary'}
|
|
className={
|
|
isLive
|
|
? 'border-kodo-red text-kodo-red hover:bg-kodo-red/10'
|
|
: 'bg-kodo-lime text-black hover:bg-white'
|
|
}
|
|
icon={
|
|
isLive ? (
|
|
<Square className="w-4 h-4 fill-current" />
|
|
) : (
|
|
<Play className="w-4 h-4 fill-current" />
|
|
)
|
|
}
|
|
onClick={toggleStream}
|
|
>
|
|
{isLive ? 'END STREAM' : 'START STREAM'}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
|
{/* Left: Preview & Info */}
|
|
<div className="lg:col-span-2 space-y-6">
|
|
{/* Preview Player */}
|
|
<div className="aspect-video bg-black rounded-xl overflow-hidden border border-kodo-steel relative group">
|
|
{isLive ? (
|
|
<div className="w-full h-full flex items-center justify-center bg-kodo-ink">
|
|
{/* Mock live feed */}
|
|
<div className="text-center animate-pulse">
|
|
<Radio className="w-16 h-16 text-kodo-red mx-auto mb-4" />
|
|
<p className="text-kodo-content-dim">Receiving Stream Data...</p>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div className="w-full h-full flex items-col items-center justify-center bg-kodo-ink/50 text-kodo-content-dim">
|
|
<div className="text-center">
|
|
<Monitor className="w-16 h-16 mx-auto mb-4 opacity-50" />
|
|
<p>Stream Offline</p>
|
|
<p className="text-xs mt-2">Connect OBS to start preview</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
<div className="absolute top-4 right-4 bg-black/50 px-2 py-1 rounded text-xs text-white font-mono">
|
|
1080p 60fps
|
|
</div>
|
|
</div>
|
|
|
|
{/* Stream Info Form */}
|
|
<Card variant="default">
|
|
<h3 className="font-bold text-white mb-4">Stream Information</h3>
|
|
<div className="space-y-4">
|
|
<Input
|
|
label="Title"
|
|
value={title}
|
|
onChange={(e) => setTitle(e.target.value)}
|
|
/>
|
|
<div>
|
|
<label className="block text-sm font-medium text-kodo-content-dim mb-2">
|
|
Category
|
|
</label>
|
|
<select
|
|
className="w-full bg-kodo-graphite border border-kodo-steel rounded-lg px-4 py-4 text-white focus:border-kodo-steel outline-none"
|
|
value={category}
|
|
onChange={(e) => setCategory(e.target.value)}
|
|
>
|
|
<option>Production</option>
|
|
<option>DJ Set</option>
|
|
<option>Listening Party</option>
|
|
<option>Q&A / Talk</option>
|
|
</select>
|
|
</div>
|
|
<Input
|
|
label="Notification Text"
|
|
placeholder="Going live with some new beats!"
|
|
/>
|
|
<div className="flex justify-end">
|
|
<Button
|
|
variant="secondary"
|
|
size="sm"
|
|
onClick={() => addToast('Info updated', 'success')}
|
|
>
|
|
Update Info
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
|
|
{/* Right: Setup Keys */}
|
|
<div className="space-y-6">
|
|
<Card variant="gaming">
|
|
<h3 className="font-bold text-white mb-4 text-sm uppercase tracking-wider flex items-center gap-2">
|
|
<Settings className="w-4 h-4 text-kodo-gold" /> Encoder Setup
|
|
</h3>
|
|
|
|
<div className="space-y-4">
|
|
<div>
|
|
<label className="block text-xs font-bold text-kodo-content-dim uppercase mb-2">
|
|
Server URL
|
|
</label>
|
|
<div className="flex gap-2">
|
|
<input
|
|
className="flex-1 bg-kodo-ink border border-kodo-steel rounded px-4 py-2 text-sm text-kodo-text-main font-mono"
|
|
value={serverUrl}
|
|
readOnly
|
|
/>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
className="border border-kodo-steel"
|
|
onClick={() => copyToClipboard(serverUrl, 'Server URL')}
|
|
>
|
|
<Copy className="w-4 h-4" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="block text-xs font-bold text-kodo-content-dim uppercase mb-2">
|
|
Stream Key
|
|
</label>
|
|
<div className="flex gap-2">
|
|
<input
|
|
className="flex-1 bg-kodo-ink border border-kodo-steel rounded px-4 py-2 text-sm text-kodo-text-main font-mono"
|
|
value={
|
|
streamKeyVisible ? streamKey : '•••••••••••••••••••••••••'
|
|
}
|
|
readOnly
|
|
type={streamKeyVisible ? 'text' : 'password'}
|
|
/>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
className="border border-kodo-steel"
|
|
onClick={() => setStreamKeyVisible(!streamKeyVisible)}
|
|
>
|
|
{streamKeyVisible ? (
|
|
<EyeOff className="w-4 h-4" />
|
|
) : (
|
|
<Eye className="w-4 h-4" />
|
|
)}
|
|
</Button>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
className="border border-kodo-steel"
|
|
onClick={() => copyToClipboard(streamKey, 'Stream Key')}
|
|
>
|
|
<Copy className="w-4 h-4" />
|
|
</Button>
|
|
</div>
|
|
<p className="text-[10px] text-kodo-red mt-2 flex items-center gap-1">
|
|
<EyeOff className="w-3 h-3" /> Never share your stream key!
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
|
|
<Card variant="default">
|
|
<h3 className="font-bold text-white mb-4 text-sm uppercase tracking-wider">
|
|
Quick Instructions
|
|
</h3>
|
|
<ol className="text-sm text-kodo-content-dim space-y-2 list-decimal pl-4">
|
|
<li>Open OBS or Streamlabs.</li>
|
|
<li>Go to Settings {'>'} Stream.</li>
|
|
<li>Select "Custom" service.</li>
|
|
<li>Paste Server URL and Stream Key.</li>
|
|
<li>Start Streaming in OBS.</li>
|
|
</ol>
|
|
</Card>
|
|
|
|
<div className="bg-kodo-ink p-4 rounded-xl border border-kodo-steel">
|
|
<div className="flex items-center justify-between mb-2">
|
|
<span className="text-sm text-kodo-text-main">Microphone</span>
|
|
<Mic className="w-4 h-4 text-kodo-lime" />
|
|
</div>
|
|
<div className="w-full bg-kodo-graphite h-2 rounded-full overflow-hidden">
|
|
<div className="h-full bg-kodo-lime w-[60%] animate-pulse"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|