diff --git a/apps/web/src/components/studio/GoLiveView.stories.tsx b/apps/web/src/components/studio/GoLiveView.stories.tsx index 1a3f902d0..c68e09cc9 100644 --- a/apps/web/src/components/studio/GoLiveView.stories.tsx +++ b/apps/web/src/components/studio/GoLiveView.stories.tsx @@ -1,23 +1,25 @@ import type { Meta, StoryObj } from '@storybook/react'; -import { GoLiveView } from './GoLiveView'; +import { GoLiveView, GoLiveViewSkeleton } from './go-live-view'; const meta: Meta = { - title: 'Components/Features/Studio/GoLiveView', - component: GoLiveView, - parameters: { layout: 'fullscreen' }, - tags: ['autodocs'], - decorators: [ - (Story) => ( -
- -
- ), - ], + title: 'Components/Features/Studio/GoLiveView', + component: GoLiveView, + parameters: { layout: 'fullscreen' }, + tags: ['autodocs'], + decorators: [ + (Story) => ( +
+ +
+ ), + ], }; export default meta; type Story = StoryObj; -export const Setup: Story = { name: 'Configuration' }; -export const Live: Story = { name: 'En direct' }; -export const Ended: Story = { name: 'Terminé' }; +export const Default: Story = {}; + +export const Loading: Story = { + render: () => , +}; diff --git a/apps/web/src/components/studio/GoLiveView.tsx b/apps/web/src/components/studio/GoLiveView.tsx index 7376cc431..4146c687d 100644 --- a/apps/web/src/components/studio/GoLiveView.tsx +++ b/apps/web/src/components/studio/GoLiveView.tsx @@ -1,255 +1 @@ -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 '../../components/feedback/ToastProvider'; - -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 ( -
- {/* Header */} -
-
-

- - BROADCAST STUDIO -

-

- Configure your stream and go live. -

-
-
-
-
- {isLive ? 'LIVE' : 'OFFLINE'} -
- -
-
- -
- {/* Left: Preview & Info */} -
- {/* Preview Player */} -
- {isLive ? ( -
- {/* Mock live feed */} -
- -

Receiving Stream Data...

-
-
- ) : ( -
-
- -

Stream Offline

-

Connect OBS to start preview

-
-
- )} -
- 1080p 60fps -
-
- - {/* Stream Info Form */} - -

Stream Information

-
- setTitle(e.target.value)} - /> -
- - -
- -
- -
-
-
-
- - {/* Right: Setup Keys */} -
- -

- Encoder Setup -

- -
-
- -
- - -
-
- -
- -
- - - -
-

- Never share your stream key! -

-
-
-
- - -

- Quick Instructions -

-
    -
  1. Open OBS or Streamlabs.
  2. -
  3. Go to Settings {'>'} Stream.
  4. -
  5. Select "Custom" service.
  6. -
  7. Paste Server URL and Stream Key.
  8. -
  9. Start Streaming in OBS.
  10. -
-
- -
-
- Microphone - -
-
-
-
-
-
-
-
- ); -}; +export { GoLiveView, GoLiveViewSkeleton } from './go-live-view'; diff --git a/apps/web/src/components/studio/go-live-view/GoLiveView.tsx b/apps/web/src/components/studio/go-live-view/GoLiveView.tsx new file mode 100644 index 000000000..2caa0ddc5 --- /dev/null +++ b/apps/web/src/components/studio/go-live-view/GoLiveView.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import { useGoLiveView } from './useGoLiveView'; +import { GoLiveViewHeader } from './GoLiveViewHeader'; +import { GoLiveViewPreview } from './GoLiveViewPreview'; +import { GoLiveViewStreamInfo } from './GoLiveViewStreamInfo'; +import { GoLiveViewEncoderSetup } from './GoLiveViewEncoderSetup'; +import { GoLiveViewQuickInstructions } from './GoLiveViewQuickInstructions'; +import { GoLiveViewMicLevel } from './GoLiveViewMicLevel'; + +export function GoLiveView() { + const { + streamKeyVisible, + setStreamKeyVisible, + isLive, + title, + setTitle, + category, + setCategory, + streamKey, + serverUrl, + copyToClipboard, + toggleStream, + onUpdateInfo, + } = useGoLiveView(); + + return ( +
+ + +
+
+ + +
+ +
+ setStreamKeyVisible(!streamKeyVisible)} + onCopy={copyToClipboard} + /> + + +
+
+
+ ); +} diff --git a/apps/web/src/components/studio/go-live-view/GoLiveViewEncoderSetup.tsx b/apps/web/src/components/studio/go-live-view/GoLiveViewEncoderSetup.tsx new file mode 100644 index 000000000..cc2649507 --- /dev/null +++ b/apps/web/src/components/studio/go-live-view/GoLiveViewEncoderSetup.tsx @@ -0,0 +1,92 @@ +import { Card } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Settings, Copy, Eye, EyeOff } from 'lucide-react'; + +interface GoLiveViewEncoderSetupProps { + serverUrl: string; + streamKey: string; + streamKeyVisible: boolean; + onToggleStreamKeyVisible: () => void; + onCopy: (text: string, label: string) => void; +} + +export function GoLiveViewEncoderSetup({ + serverUrl, + streamKey, + streamKeyVisible, + onToggleStreamKeyVisible, + onCopy, +}: GoLiveViewEncoderSetupProps) { + return ( + +

+ Encoder Setup +

+ +
+
+ +
+ + +
+
+ +
+ +
+ + + +
+

+ Never share your stream key! +

+
+
+
+ ); +} diff --git a/apps/web/src/components/studio/go-live-view/GoLiveViewHeader.tsx b/apps/web/src/components/studio/go-live-view/GoLiveViewHeader.tsx new file mode 100644 index 000000000..82e8deb3e --- /dev/null +++ b/apps/web/src/components/studio/go-live-view/GoLiveViewHeader.tsx @@ -0,0 +1,53 @@ +import { Button } from '@/components/ui/button'; +import { Radio, Play, Square } from 'lucide-react'; + +interface GoLiveViewHeaderProps { + isLive: boolean; + onToggleStream: () => void; +} + +export function GoLiveViewHeader({ isLive, onToggleStream }: GoLiveViewHeaderProps) { + return ( +
+
+

+ + BROADCAST STUDIO +

+

+ Configure your stream and go live. +

+
+
+
+
+ {isLive ? 'LIVE' : 'OFFLINE'} +
+ +
+
+ ); +} diff --git a/apps/web/src/components/studio/go-live-view/GoLiveViewMicLevel.tsx b/apps/web/src/components/studio/go-live-view/GoLiveViewMicLevel.tsx new file mode 100644 index 000000000..4592e1a33 --- /dev/null +++ b/apps/web/src/components/studio/go-live-view/GoLiveViewMicLevel.tsx @@ -0,0 +1,15 @@ +import { Mic } from 'lucide-react'; + +export function GoLiveViewMicLevel() { + return ( +
+
+ Microphone + +
+
+
+
+
+ ); +} diff --git a/apps/web/src/components/studio/go-live-view/GoLiveViewPreview.tsx b/apps/web/src/components/studio/go-live-view/GoLiveViewPreview.tsx new file mode 100644 index 000000000..e40c3e581 --- /dev/null +++ b/apps/web/src/components/studio/go-live-view/GoLiveViewPreview.tsx @@ -0,0 +1,31 @@ +import { Radio, Monitor } from 'lucide-react'; + +interface GoLiveViewPreviewProps { + isLive: boolean; +} + +export function GoLiveViewPreview({ isLive }: GoLiveViewPreviewProps) { + return ( +
+ {isLive ? ( +
+
+ +

Receiving Stream Data...

+
+
+ ) : ( +
+
+ +

Stream Offline

+

Connect OBS to start preview

+
+
+ )} +
+ 1080p 60fps +
+
+ ); +} diff --git a/apps/web/src/components/studio/go-live-view/GoLiveViewQuickInstructions.tsx b/apps/web/src/components/studio/go-live-view/GoLiveViewQuickInstructions.tsx new file mode 100644 index 000000000..25996a78e --- /dev/null +++ b/apps/web/src/components/studio/go-live-view/GoLiveViewQuickInstructions.tsx @@ -0,0 +1,18 @@ +import { Card } from '@/components/ui/card'; + +export function GoLiveViewQuickInstructions() { + return ( + +

+ Quick Instructions +

+
    +
  1. Open OBS or Streamlabs.
  2. +
  3. Go to Settings {'>'} Stream.
  4. +
  5. Select "Custom" service.
  6. +
  7. Paste Server URL and Stream Key.
  8. +
  9. Start Streaming in OBS.
  10. +
+
+ ); +} diff --git a/apps/web/src/components/studio/go-live-view/GoLiveViewSkeleton.tsx b/apps/web/src/components/studio/go-live-view/GoLiveViewSkeleton.tsx new file mode 100644 index 000000000..c9f76d9e5 --- /dev/null +++ b/apps/web/src/components/studio/go-live-view/GoLiveViewSkeleton.tsx @@ -0,0 +1,52 @@ +/** + * Skeleton for GoLiveView — layout primitives, no arbitrary values. + */ +export function GoLiveViewSkeleton() { + return ( +
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ); +} diff --git a/apps/web/src/components/studio/go-live-view/GoLiveViewStreamInfo.tsx b/apps/web/src/components/studio/go-live-view/GoLiveViewStreamInfo.tsx new file mode 100644 index 000000000..6a5b60c78 --- /dev/null +++ b/apps/web/src/components/studio/go-live-view/GoLiveViewStreamInfo.tsx @@ -0,0 +1,52 @@ +import { Card } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { STREAM_CATEGORIES } from './types'; +import type { StreamCategory } from './types'; + +interface GoLiveViewStreamInfoProps { + title: string; + onTitleChange: (value: string) => void; + category: StreamCategory; + onCategoryChange: (value: StreamCategory) => void; + onUpdateInfo: () => void; +} + +export function GoLiveViewStreamInfo({ + title, + onTitleChange, + category, + onCategoryChange, + onUpdateInfo, +}: GoLiveViewStreamInfoProps) { + return ( + +

Stream Information

+
+ onTitleChange(e.target.value)} /> +
+ + +
+ +
+ +
+
+
+ ); +} diff --git a/apps/web/src/components/studio/go-live-view/index.ts b/apps/web/src/components/studio/go-live-view/index.ts new file mode 100644 index 000000000..768fb99b5 --- /dev/null +++ b/apps/web/src/components/studio/go-live-view/index.ts @@ -0,0 +1,11 @@ +export { GoLiveView } from './GoLiveView'; +export { GoLiveViewSkeleton } from './GoLiveViewSkeleton'; +export { GoLiveViewHeader } from './GoLiveViewHeader'; +export { GoLiveViewPreview } from './GoLiveViewPreview'; +export { GoLiveViewStreamInfo } from './GoLiveViewStreamInfo'; +export { GoLiveViewEncoderSetup } from './GoLiveViewEncoderSetup'; +export { GoLiveViewQuickInstructions } from './GoLiveViewQuickInstructions'; +export { GoLiveViewMicLevel } from './GoLiveViewMicLevel'; +export { useGoLiveView } from './useGoLiveView'; +export { STREAM_CATEGORIES } from './types'; +export type { StreamCategory } from './types'; diff --git a/apps/web/src/components/studio/go-live-view/types.ts b/apps/web/src/components/studio/go-live-view/types.ts new file mode 100644 index 000000000..d095c09f5 --- /dev/null +++ b/apps/web/src/components/studio/go-live-view/types.ts @@ -0,0 +1,8 @@ +export const STREAM_CATEGORIES = [ + 'Production', + 'DJ Set', + 'Listening Party', + 'Q&A / Talk', +] as const; + +export type StreamCategory = (typeof STREAM_CATEGORIES)[number]; diff --git a/apps/web/src/components/studio/go-live-view/useGoLiveView.ts b/apps/web/src/components/studio/go-live-view/useGoLiveView.ts new file mode 100644 index 000000000..ffad3eac5 --- /dev/null +++ b/apps/web/src/components/studio/go-live-view/useGoLiveView.ts @@ -0,0 +1,50 @@ +import { useState, useCallback } from 'react'; +import { useToast } from '@/components/feedback/ToastProvider'; +import type { StreamCategory } from './types'; + +const DEFAULT_STREAM_KEY = 'live_83921_abc123xyz789_secret_key'; +const DEFAULT_SERVER_URL = 'rtmp://live.veza.io/app'; + +export function useGoLiveView() { + 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 copyToClipboard = useCallback( + (text: string, label: string) => { + navigator.clipboard.writeText(text); + addToast(`${label} copied to clipboard`, 'success'); + }, + [addToast], + ); + + const toggleStream = useCallback(() => { + 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'); + } + }, [isLive, addToast]); + + return { + streamKeyVisible, + setStreamKeyVisible, + isLive, + title, + setTitle, + category, + setCategory, + streamKey: DEFAULT_STREAM_KEY, + serverUrl: DEFAULT_SERVER_URL, + copyToClipboard, + toggleStream, + onUpdateInfo, + }; +}