314 lines
14 KiB
TypeScript
314 lines
14 KiB
TypeScript
import React, { useState, useEffect } from 'react'
|
|
import {
|
|
Modal,
|
|
ModalRadioGroup,
|
|
ModalSection,
|
|
ModalDestinationButton,
|
|
ModalInput,
|
|
ModalTextarea,
|
|
ModalSelect,
|
|
ModalCheckbox,
|
|
ModalLink,
|
|
ModalDateTimeGroup,
|
|
ModalButton,
|
|
ModalButtonGroup,
|
|
ModalPlatformCard
|
|
} from '.'
|
|
import { MdVideocam, MdVideoLibrary, MdAdd, MdImage, MdAutoAwesome, MdArrowBack } from 'react-icons/md'
|
|
import { FaYoutube, FaFacebook, FaLinkedin, FaTwitch, FaInstagram, FaKickstarterK } from 'react-icons/fa'
|
|
import { FaXTwitter } from 'react-icons/fa6'
|
|
import { BsInfoCircle } from 'react-icons/bs'
|
|
import styles from './NewTransmissionModal.module.css'
|
|
import type { Transmission, Destination } from '@shared/types'
|
|
|
|
interface Props {
|
|
open: boolean
|
|
onClose: () => void
|
|
onCreate?: (t: Transmission) => void
|
|
onUpdate?: (t: Transmission) => void
|
|
transmission?: Transmission
|
|
onlyAddDestination?: boolean
|
|
onAddDestination?: (d: DestinationData) => void
|
|
}
|
|
|
|
type DestinationData = Destination & {
|
|
id: string
|
|
icon: React.ReactNode
|
|
badge?: React.ReactNode
|
|
}
|
|
|
|
const NewTransmissionModal: React.FC<Props> = ({ open, onClose, onCreate, onUpdate, transmission, onlyAddDestination, onAddDestination }) => {
|
|
const [view, setView] = useState<'main' | 'add-destination'>('main')
|
|
const [source, setSource] = useState('studio')
|
|
const isEditMode = !!transmission
|
|
|
|
const [destinations, setDestinations] = useState<DestinationData[]>([
|
|
{
|
|
id: 'yt_1',
|
|
platform: 'YouTube',
|
|
icon: <FaYoutube color="#FF0000" />,
|
|
badge: <span style={{ color: '#FF0000', fontSize: '12px' }}>▶</span>
|
|
}
|
|
])
|
|
const [selectedDestination, setSelectedDestination] = useState<string | null>(null)
|
|
|
|
const [blankTitle, setBlankTitle] = useState('')
|
|
const [title, setTitle] = useState('')
|
|
const [description, setDescription] = useState('')
|
|
const [privacy, setPrivacy] = useState('Pública')
|
|
const [category, setCategory] = useState('')
|
|
const [addReferral, setAddReferral] = useState(true)
|
|
const [scheduleForLater, setScheduleForLater] = useState(false)
|
|
|
|
const [scheduledDate, setScheduledDate] = useState('')
|
|
const [scheduledHour, setScheduledHour] = useState('01')
|
|
const [scheduledMinute, setScheduledMinute] = useState('10')
|
|
|
|
useEffect(() => {
|
|
if (transmission && open) {
|
|
setTitle(transmission.title)
|
|
if (transmission.platform === 'Genérico') {
|
|
setSelectedDestination('blank')
|
|
setBlankTitle(transmission.title)
|
|
} else {
|
|
setSelectedDestination(null)
|
|
}
|
|
} else if (!open) {
|
|
resetForm()
|
|
}
|
|
}, [transmission, open])
|
|
|
|
const resetForm = () => {
|
|
setView('main')
|
|
setSource('studio')
|
|
setSelectedDestination(null)
|
|
setTitle('')
|
|
setDescription('')
|
|
setPrivacy('Pública')
|
|
setCategory('')
|
|
setAddReferral(true)
|
|
setScheduleForLater(false)
|
|
setScheduledDate('')
|
|
setScheduledHour('01')
|
|
setScheduledMinute('10')
|
|
setBlankTitle('')
|
|
}
|
|
|
|
const generateId = () => `t_${Date.now()}_${Math.floor(Math.random()*1000)}`
|
|
|
|
const handleAddDestination = () => setView('add-destination')
|
|
const handleBackToMain = () => setView('main')
|
|
const handleSkipForNow = () => setSelectedDestination('blank')
|
|
|
|
const handlePlatformSelect = (platform: string) => {
|
|
const platformData: Record<string, { icon: React.ReactNode; color: string }> = {
|
|
'YouTube': { icon: <FaYoutube color="#FF0000" />, color: '#FF0000' },
|
|
'Facebook': { icon: <FaFacebook color="#1877F2" />, color: '#1877F2' },
|
|
'LinkedIn': { icon: <FaLinkedin color="#0A66C2" />, color: '#0A66C2' },
|
|
'X (Twitter)': { icon: <FaXTwitter color="#000000" />, color: '#000000' },
|
|
'Twitch': { icon: <FaTwitch color="#9146FF" />, color: '#9146FF' },
|
|
'Instagram Live': { icon: <FaInstagram color="#E4405F" />, color: '#E4405F' },
|
|
'Kick': { icon: <FaKickstarterK color="#53FC18" />, color: '#53FC18' },
|
|
'Brightcove': { icon: <div style={{ fontSize: '24px', fontWeight: 'bold' }}>B</div>, color: '#000000' }
|
|
}
|
|
|
|
const newDest: DestinationData = {
|
|
id: `dest_${Date.now()}`,
|
|
platform,
|
|
icon: platformData[platform]?.icon || <MdAdd />,
|
|
badge: platform === 'YouTube' ? <span style={{ color: '#FF0000', fontSize: '12px' }}>▶</span> : undefined
|
|
}
|
|
|
|
if (onlyAddDestination && onAddDestination) {
|
|
onAddDestination(newDest)
|
|
onClose()
|
|
return
|
|
}
|
|
|
|
setDestinations([...destinations, newDest])
|
|
setView('main')
|
|
}
|
|
|
|
const handleDestinationClick = (destId: string) => {
|
|
if (selectedDestination === destId) setSelectedDestination(null)
|
|
else setSelectedDestination(destId)
|
|
}
|
|
|
|
const handleCreate = () => {
|
|
if (!selectedDestination) {
|
|
alert('Por favor selecciona un destino de transmisión')
|
|
return
|
|
}
|
|
|
|
if (selectedDestination === 'blank') {
|
|
const blankTransmission: Transmission = {
|
|
id: isEditMode && transmission ? transmission.id : generateId(),
|
|
title: blankTitle || 'Transmisión en vivo',
|
|
platform: 'Genérico',
|
|
scheduled: 'Próximamente',
|
|
createdAt: isEditMode && transmission?.createdAt ? transmission.createdAt : new Date().toLocaleDateString('es-ES', { day: '2-digit', month: '2-digit', year: 'numeric' })
|
|
}
|
|
|
|
if (isEditMode && onUpdate) onUpdate(blankTransmission)
|
|
else if (onCreate) onCreate(blankTransmission)
|
|
|
|
resetForm()
|
|
onClose()
|
|
return
|
|
}
|
|
|
|
const t: Transmission = {
|
|
id: isEditMode && transmission ? transmission.id : generateId(),
|
|
title: title || 'Nueva transmisión',
|
|
platform: destinations.find(d => d.id === selectedDestination)?.platform || 'YouTube',
|
|
scheduled: '',
|
|
createdAt: isEditMode && transmission?.createdAt ? transmission.createdAt : new Date().toLocaleDateString('es-ES', { day: '2-digit', month: '2-digit', year: 'numeric' })
|
|
}
|
|
|
|
if (isEditMode && onUpdate) onUpdate(t)
|
|
else if (onCreate) onCreate(t)
|
|
|
|
resetForm()
|
|
onClose()
|
|
}
|
|
|
|
const modalTitle = view === 'add-destination' ? 'Agregar destino' : (isEditMode ? 'Editar transmisión' : 'Crear transmisión en vivo')
|
|
const showBackButton = view === 'add-destination'
|
|
|
|
return (
|
|
<div className={`${styles.modalWrapper} ${showBackButton ? styles.hasBackButton : ''}`}>
|
|
<Modal open={open} onClose={onClose} title={modalTitle} width="md">
|
|
{showBackButton && (
|
|
<button onClick={handleBackToMain} className={styles.backButton}>
|
|
<MdArrowBack size={20} />
|
|
</button>
|
|
)}
|
|
|
|
{view === 'main' && (
|
|
<>
|
|
<div className={styles.content}>
|
|
<ModalSection>
|
|
<ModalRadioGroup
|
|
label="Fuente"
|
|
helpIcon={<BsInfoCircle />}
|
|
name="source"
|
|
value={source}
|
|
onChange={setSource}
|
|
options={[
|
|
{ value: 'studio', label: 'Estudio', icon: <MdVideocam /> },
|
|
{ value: 'prerecorded', label: 'Video pregrabado', icon: <MdVideoLibrary /> }
|
|
]}
|
|
/>
|
|
</ModalSection>
|
|
|
|
<ModalSection label="Selecciona los destinos">
|
|
<div className={styles.destinations}>
|
|
{destinations.map((dest) => (
|
|
<div key={dest.id} className={selectedDestination === dest.id ? styles.selectedDestination : ''}>
|
|
<ModalDestinationButton
|
|
icon={dest.icon}
|
|
label=""
|
|
badge={dest.badge}
|
|
onClick={() => handleDestinationClick(dest.id)}
|
|
title={dest.platform}
|
|
/>
|
|
</div>
|
|
))}
|
|
|
|
<ModalDestinationButton
|
|
icon={<MdAdd />}
|
|
label=""
|
|
onClick={handleAddDestination}
|
|
title="Agregar destino"
|
|
/>
|
|
{!selectedDestination && (
|
|
<div className={styles.skipNowContainer}>
|
|
<ModalLink onClick={handleSkipForNow}>Omitir por ahora</ModalLink>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</ModalSection>
|
|
|
|
{selectedDestination && selectedDestination !== 'blank' && (
|
|
<>
|
|
<ModalSection>
|
|
<ModalInput label="Título" value={title} onChange={setTitle} maxLength={100} showCounter={true} />
|
|
</ModalSection>
|
|
|
|
<ModalSection>
|
|
<ModalTextarea label="Descripción" value={description} onChange={setDescription} placeholder="Cuéntanos un poco sobre esta transmisión en vivo" maxLength={5000} showCounter={true} rows={3} />
|
|
</ModalSection>
|
|
|
|
<ModalSection>
|
|
<ModalCheckbox checked={addReferral} onChange={setAddReferral} label="Agrega el mensaje de referencia a la descripción" helpIcon={<BsInfoCircle />} subtext="Gana $25 en crédito por cada referencia exitosa." />
|
|
</ModalSection>
|
|
|
|
<ModalSection>
|
|
<ModalSelect label="Privacidad" value={privacy} onChange={setPrivacy} options={[{ value: 'Pública', label: 'Pública' }, { value: 'No listada', label: 'No listada' }, { value: 'Privada', label: 'Privada' }]} />
|
|
</ModalSection>
|
|
|
|
<ModalSection>
|
|
<ModalSelect label="Categoría" value={category} onChange={setCategory} placeholder="Seleccionar categoría" options={[{ value: 'gaming', label: 'Juegos' }, { value: 'education', label: 'Educación' }, { value: 'entertainment', label: 'Entretenimiento' }, { value: 'music', label: 'Música' }, { value: 'sports', label: 'Deportes' }, { value: 'news', label: 'Noticias' }, { value: 'tech', label: 'Tecnología' }]} />
|
|
</ModalSection>
|
|
|
|
<ModalSection>
|
|
<ModalCheckbox checked={scheduleForLater} onChange={setScheduleForLater} label="Programar para más tarde" />
|
|
</ModalSection>
|
|
|
|
{scheduleForLater && (
|
|
<>
|
|
<ModalSection>
|
|
<ModalDateTimeGroup label="Hora de inicio programada" helpIcon={<BsInfoCircle />} dateValue={scheduledDate} hourValue={scheduledHour} minuteValue={scheduledMinute} onDateChange={setScheduledDate} onHourChange={setScheduledHour} onMinuteChange={setScheduledMinute} timezone="GMT-7" />
|
|
</ModalSection>
|
|
|
|
<ModalSection>
|
|
<ModalButtonGroup>
|
|
<ModalButton variant="secondary" icon={<MdImage />}>Subir imagen en miniatura</ModalButton>
|
|
<ModalButton variant="primary" icon={<MdAutoAwesome />}>Crear con IA</ModalButton>
|
|
</ModalButtonGroup>
|
|
</ModalSection>
|
|
</>
|
|
)}
|
|
</>
|
|
)}
|
|
|
|
{selectedDestination === 'blank' && (
|
|
<div className={styles.blankStreamForm}>
|
|
<p className={styles.blankStreamDescription}>Empezarás una transmisión en el estudio sin configurar ningún destino. Podrás agregar destinos más tarde desde el estudio.</p>
|
|
<ModalInput label="Título de la transmisión (opcional)" value={blankTitle} onChange={setBlankTitle} placeholder="Ej: Mi transmisión en vivo" maxLength={100} />
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div className={styles.footer}>
|
|
{selectedDestination && selectedDestination !== 'blank' && (
|
|
<p className={styles.footerNote}>Esta transmisión no se grabará en StreamYard. Para grabar, tendrás que <ModalLink href="/pricing">pasarte a un plan superior.</ModalLink></p>
|
|
)}
|
|
<button type="button" onClick={handleCreate} className={styles.createButton} disabled={!selectedDestination}>
|
|
{isEditMode ? (selectedDestination === 'blank' ? 'Guardar cambios' : 'Actualizar transmisión') : (selectedDestination === 'blank' ? 'Empezar ahora' : 'Crear transmisión en vivo')}
|
|
</button>
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
{view === 'add-destination' && (
|
|
<div className={styles.platformGrid}>
|
|
<ModalPlatformCard icon={<FaYoutube color="#FF0000" />} label="YouTube" onClick={() => handlePlatformSelect('YouTube')} />
|
|
<ModalPlatformCard icon={<FaFacebook color="#1877F2" />} label="Facebook" onClick={() => handlePlatformSelect('Facebook')} />
|
|
<ModalPlatformCard icon={<FaLinkedin color="#0A66C2" />} label="LinkedIn" onClick={() => handlePlatformSelect('LinkedIn')} />
|
|
<ModalPlatformCard icon={<FaXTwitter color="#000000" />} label="X (Twitter)" onClick={() => handlePlatformSelect('X (Twitter)')} />
|
|
<ModalPlatformCard icon={<FaTwitch color="#9146FF" />} label="Twitch" onClick={() => handlePlatformSelect('Twitch')} />
|
|
<ModalPlatformCard icon={<FaInstagram color="#E4405F" />} label="Instagram Live" onClick={() => handlePlatformSelect('Instagram Live')} />
|
|
<ModalPlatformCard icon={<FaKickstarterK color="#53FC18" />} label="Kick" onClick={() => handlePlatformSelect('Kick')} badge="pro" />
|
|
<ModalPlatformCard icon={<div style={{ fontSize: '24px', fontWeight: 'bold' }}>B</div>} label="Brightcove" onClick={() => handlePlatformSelect('Brightcove')} />
|
|
<ModalPlatformCard icon={<div style={{ fontSize: '20px', fontWeight: 'bold' }}>RTMP</div>} label="Otras plataformas" onClick={() => handlePlatformSelect('RTMP')} badge="pro" />
|
|
</div>
|
|
)}
|
|
</Modal>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export { NewTransmissionModal }
|
|
export default NewTransmissionModal
|