AvanzaCast/packages/broadcast-panel/src/components/NewTransmissionModal.tsx
Cesar Mendivil e43686e36d feat(modal-parts): add modular components for modal UI
- Introduced ModalDestinationButton for destination selection with customizable icons and labels.
- Added ModalInput for text input with optional character counter.
- Implemented ModalLink for reusable links styled as underlined text.
- Created ModalPlatformCard for platform selection with badges.
- Developed ModalRadioGroup for radio button groups with custom styling.
- Added ModalSection for grouping modal content with optional labels.
- Implemented ModalSelect for dropdown selections with custom styling.
- Created ModalShareButtons for sharing options via Gmail, Email, and Messenger.
- Developed ModalTextarea for multi-line text input with character counter.
- Introduced ModalToggle for toggle switches with optional help text and links.
- Updated README.md with component descriptions, usage examples, and design guidelines.
- Added index.ts for centralized exports of modal components.
2025-11-06 00:32:08 -07:00

418 lines
14 KiB
TypeScript

import React, { useState } from 'react'
import {
Modal,
ModalRadioGroup,
ModalSection,
ModalDestinationButton,
ModalInput,
ModalTextarea,
ModalSelect,
ModalCheckbox,
ModalLink,
ModalDateTimeGroup,
ModalButton,
ModalButtonGroup,
ModalPlatformCard
} from '@shared/components'
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 } from '../types'
interface Props {
open: boolean
onClose: () => void
onCreate: (t: Transmission) => void
}
interface DestinationData {
id: string
platform: string
icon: React.ReactNode
badge?: React.ReactNode
}
const NewTransmissionModal: React.FC<Props> = ({ open, onClose, onCreate }) => {
const [view, setView] = useState<'main' | 'add-destination'>('main')
const [source, setSource] = useState('studio')
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)
// Blank stream
const [blankTitle, setBlankTitle] = useState('')
// Form fields
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)
// Scheduled date/time
const [scheduledDate, setScheduledDate] = useState('')
const [scheduledHour, setScheduledHour] = useState('01')
const [scheduledMinute, setScheduledMinute] = useState('10')
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
}
setDestinations([...destinations, newDest])
setView('main')
}
const handleDestinationClick = (destId: string) => {
// Si el destino ya está seleccionado, deseleccionarlo
if (selectedDestination === destId) {
setSelectedDestination(null)
} else {
setSelectedDestination(destId)
}
}
const handleCreate = () => {
if (!selectedDestination) {
alert('Por favor selecciona un destino de transmisión')
return
}
// Si es transmisión en blanco, usar el título del formulario blanco
if (selectedDestination === 'blank') {
// Por el momento solo cierra el modal
// TODO: navegar a studio con título blankTitle
onClose()
return
}
const t: Transmission = {
id: generateId(),
title: title || 'Nueva transmisión',
platform: destinations.find(d => d.id === selectedDestination)?.platform || 'YouTube',
scheduled: ''
}
onCreate(t)
// Reset form
setView('main')
setSource('studio')
setSelectedDestination(null)
setTitle('')
setDescription('')
setPrivacy('Pública')
setCategory('')
setAddReferral(true)
setScheduleForLater(false)
setScheduledDate('')
setScheduledHour('01')
setScheduledMinute('10')
setBlankTitle('')
}
const modalTitle = view === 'add-destination' ? 'Agregar destino' : 'Crear transmisión en vivo'
const showBackButton = view === 'add-destination'
return (
<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}
>
{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>
)
}
export default NewTransmissionModal