feat: add broadcast-panel package and migrate broadcast components
This commit is contained in:
parent
f7ede05001
commit
408e3b24b9
6
packages/broadcast-panel/package.json
Normal file
6
packages/broadcast-panel/package.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "broadcast-panel",
|
||||
"version": "0.1.0",
|
||||
"main": "src/index.ts",
|
||||
"type": "module"
|
||||
}
|
||||
29
packages/broadcast-panel/src/components/Header.tsx
Normal file
29
packages/broadcast-panel/src/components/Header.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
import React from 'react'
|
||||
|
||||
const Header: React.FC = () => {
|
||||
const handleLogout = () => {
|
||||
localStorage.removeItem('mock_user')
|
||||
window.location.href = '/auth/login'
|
||||
}
|
||||
|
||||
return (
|
||||
<header className="h-16 bg-white border-b flex items-center justify-between px-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<img src="/assets/logo-dark.png" alt="logo" className="w-28 h-auto object-contain" />
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<button className="px-3 py-2 border rounded flex items-center gap-2"><i className="mdi mdi-help-circle-outline"></i> Ayuda</button>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-8 h-8 rounded-full bg-slate-100 flex items-center justify-center overflow-hidden">
|
||||
<img src="/assets/logo-icon.png" alt="avatar" className="w-full h-full object-cover" />
|
||||
</div>
|
||||
<div className="text-sm">Demo User</div>
|
||||
</div>
|
||||
<button onClick={handleLogout} className="px-3 py-2 bg-red-50 text-red-600 border rounded">Cerrar sesión</button>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
export default Header
|
||||
28
packages/broadcast-panel/src/components/PageContainer.tsx
Normal file
28
packages/broadcast-panel/src/components/PageContainer.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import React from 'react'
|
||||
import Sidebar from './Sidebar'
|
||||
import Header from './Header'
|
||||
import TransmissionsTable from './TransmissionsTable'
|
||||
|
||||
const PageContainer: React.FC = () => {
|
||||
return (
|
||||
<div className="min-h-screen flex bg-[#f7f8fa]">
|
||||
<Sidebar />
|
||||
<div className="flex-1 flex flex-col">
|
||||
<Header />
|
||||
<main className="p-8">
|
||||
<div className="mb-6 flex items-center justify-between">
|
||||
<h1 className="text-2xl font-semibold">Transmisiones</h1>
|
||||
<div className="flex items-center gap-3">
|
||||
<button className="px-4 py-2 bg-indigo-600 text-white rounded">Nueva transmisión</button>
|
||||
<button className="px-3 py-2 border rounded">Importar</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<TransmissionsTable />
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PageContainer
|
||||
52
packages/broadcast-panel/src/components/Sidebar.tsx
Normal file
52
packages/broadcast-panel/src/components/Sidebar.tsx
Normal file
@ -0,0 +1,52 @@
|
||||
import React from 'react'
|
||||
|
||||
const Sidebar: React.FC = () => {
|
||||
const navItems = [
|
||||
{ id: 'dashboard', label: 'Inicio' },
|
||||
{ id: 'create', label: 'Crear' },
|
||||
{ id: 'transmissions', label: 'Transmisiones' },
|
||||
{ id: 'recordings', label: 'Grabaciones' },
|
||||
{ id: 'settings', label: 'Ajustes' },
|
||||
]
|
||||
|
||||
return (
|
||||
<aside className="w-64 bg-white border-r shadow-sm flex flex-col">
|
||||
<div className="p-4 border-b flex items-center gap-3">
|
||||
<img src="/assets/logo-light.png" alt="logo" className="w-10 h-10 object-contain" />
|
||||
<div>
|
||||
<div className="text-lg font-semibold">AvanzaCast</div>
|
||||
<div className="text-sm text-slate-500">Cuenta Demo</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav className="p-4 flex-1 overflow-y-auto">
|
||||
<ul className="space-y-1">
|
||||
{navItems.map(item => (
|
||||
<li key={item.id}>
|
||||
<a href="#" className="flex items-center gap-3 px-3 py-2 rounded hover:bg-indigo-50 text-sm">
|
||||
<i className="mdi mdi-view-dashboard text-lg text-slate-400"></i>
|
||||
<span>{item.label}</span>
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div className="mt-auto p-4 border-t">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<div className="w-10 h-10 rounded-full bg-slate-100 flex items-center justify-center overflow-hidden">
|
||||
<img src="/assets/logo-icon.png" alt="avatar" className="w-full h-full object-cover" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm font-medium">Demo User</div>
|
||||
<div className="text-xs text-slate-500">demo@avanzacast.test</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-sm text-slate-600 mb-3">Almacenamiento: <strong>0</strong> de 5 GB</div>
|
||||
<button className="mt-3 w-full py-2 bg-indigo-600 text-white rounded">Comprar más</button>
|
||||
</div>
|
||||
</aside>
|
||||
)
|
||||
}
|
||||
|
||||
export default Sidebar
|
||||
@ -0,0 +1,51 @@
|
||||
import React, { useState } from 'react'
|
||||
|
||||
interface Transmission { id: string, title: string, platform: string, scheduled: string }
|
||||
|
||||
const mockTransmissions: Transmission[] = [
|
||||
{ id: 't1', title: 'Demo Stream - Producto A', platform: 'YouTube', scheduled: '2025-11-10 18:00' },
|
||||
{ id: 't2', title: 'Webinar - Marketing', platform: 'Facebook', scheduled: '2025-11-12 16:00' },
|
||||
]
|
||||
|
||||
const TransmissionsTable: React.FC = () => {
|
||||
const [activeTab, setActiveTab] = useState<'upcoming' | 'past'>('upcoming')
|
||||
|
||||
const filtered = mockTransmissions
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-4 flex items-center gap-3">
|
||||
<button onClick={() => setActiveTab('upcoming')} className={`px-3 py-1 rounded ${activeTab==='upcoming' ? 'bg-indigo-600 text-white' : 'bg-white border'}`}>Próximamente</button>
|
||||
<button onClick={() => setActiveTab('past')} className={`px-3 py-1 rounded ${activeTab==='past' ? 'bg-indigo-600 text-white' : 'bg-white border'}`}>Anteriores</button>
|
||||
</div>
|
||||
|
||||
<div className="bg-white border rounded">
|
||||
<table className="w-full">
|
||||
<thead className="bg-gray-50">
|
||||
<tr>
|
||||
<th className="text-left p-3">Título</th>
|
||||
<th className="text-left p-3">Plataforma</th>
|
||||
<th className="text-left p-3">Fecha</th>
|
||||
<th className="text-right p-3">Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filtered.map(t => (
|
||||
<tr key={t.id} className="border-t">
|
||||
<td className="p-3">{t.title}</td>
|
||||
<td className="p-3">{t.platform}</td>
|
||||
<td className="p-3">{t.scheduled}</td>
|
||||
<td className="p-3 text-right">
|
||||
<button className="px-3 py-1 bg-indigo-600 text-white rounded mr-2">Entrar</button>
|
||||
<button className="px-3 py-1 border rounded">Opciones</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TransmissionsTable
|
||||
4
packages/broadcast-panel/src/index.ts
Normal file
4
packages/broadcast-panel/src/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export { default as PageContainer } from './components/PageContainer'
|
||||
export { default as Sidebar } from './components/Sidebar'
|
||||
export { default as Header } from './components/Header'
|
||||
export { default as TransmissionsTable } from './components/TransmissionsTable'
|
||||
BIN
packages/landing-page/public/assets/logo-dark.png
Normal file
BIN
packages/landing-page/public/assets/logo-dark.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
BIN
packages/landing-page/public/assets/logo-icon.png
Normal file
BIN
packages/landing-page/public/assets/logo-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
BIN
packages/landing-page/public/assets/logo-light.png
Normal file
BIN
packages/landing-page/public/assets/logo-light.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
Binary file not shown.
29
packages/landing-page/src/components/broadcast/Header.tsx
Normal file
29
packages/landing-page/src/components/broadcast/Header.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
import React from 'react'
|
||||
|
||||
const Header: React.FC = () => {
|
||||
const handleLogout = () => {
|
||||
localStorage.removeItem('mock_user')
|
||||
window.location.href = '/auth/login'
|
||||
}
|
||||
|
||||
return (
|
||||
<header className="h-16 bg-white border-b flex items-center justify-between px-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<img src="/assets/logo-dark.png" alt="logo" className="w-28 h-auto object-contain" />
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<button className="px-3 py-2 border rounded flex items-center gap-2"><i className="mdi mdi-help-circle-outline"></i> Ayuda</button>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-8 h-8 rounded-full bg-slate-100 flex items-center justify-center overflow-hidden">
|
||||
<img src="/assets/logo-icon.png" alt="avatar" className="w-full h-full object-cover" />
|
||||
</div>
|
||||
<div className="text-sm">Demo User</div>
|
||||
</div>
|
||||
<button onClick={handleLogout} className="px-3 py-2 bg-red-50 text-red-600 border rounded">Cerrar sesión</button>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
export default Header
|
||||
@ -0,0 +1,28 @@
|
||||
import React from 'react'
|
||||
import Sidebar from './Sidebar'
|
||||
import Header from './Header'
|
||||
import TransmissionsTable from './TransmissionsTable'
|
||||
|
||||
const PageContainer: React.FC = () => {
|
||||
return (
|
||||
<div className="min-h-screen flex bg-[#f7f8fa]">
|
||||
<Sidebar />
|
||||
<div className="flex-1 flex flex-col">
|
||||
<Header />
|
||||
<main className="p-8">
|
||||
<div className="mb-6 flex items-center justify-between">
|
||||
<h1 className="text-2xl font-semibold">Transmisiones</h1>
|
||||
<div className="flex items-center gap-3">
|
||||
<button className="px-4 py-2 bg-indigo-600 text-white rounded">Nueva transmisión</button>
|
||||
<button className="px-3 py-2 border rounded">Importar</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<TransmissionsTable />
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PageContainer
|
||||
12
packages/landing-page/src/components/broadcast/README.md
Normal file
12
packages/landing-page/src/components/broadcast/README.md
Normal file
@ -0,0 +1,12 @@
|
||||
Panel Broadcast - Instrucciones rápidas
|
||||
|
||||
Cómo probar el panel broadcast (mock):
|
||||
|
||||
1. Abre la app (npm run dev en el workspace `packages/landing-page`).
|
||||
2. Ve a `/auth/login` y usa cualquier correo/contraseña para "loguearte". Esto guardará `mock_user` en localStorage.
|
||||
3. Serás redirigido a `/broadcast`, que carga el panel con los assets del template.
|
||||
4. Para cerrar sesión, usa el botón "Cerrar sesión" en la cabecera.
|
||||
|
||||
Notas:
|
||||
- Este es un mock inicial para la UX; la autenticación real y llamadas al backend deben implementarse posteriormente.
|
||||
- Los assets (logos, icon font) se copiaron desde el template Techwind adjunto y están en `public/assets` y `public/fonts`.
|
||||
52
packages/landing-page/src/components/broadcast/Sidebar.tsx
Normal file
52
packages/landing-page/src/components/broadcast/Sidebar.tsx
Normal file
@ -0,0 +1,52 @@
|
||||
import React from 'react'
|
||||
|
||||
const Sidebar: React.FC = () => {
|
||||
const navItems = [
|
||||
{ id: 'dashboard', label: 'Inicio' },
|
||||
{ id: 'create', label: 'Crear' },
|
||||
{ id: 'transmissions', label: 'Transmisiones' },
|
||||
{ id: 'recordings', label: 'Grabaciones' },
|
||||
{ id: 'settings', label: 'Ajustes' },
|
||||
]
|
||||
|
||||
return (
|
||||
<aside className="w-64 bg-white border-r shadow-sm flex flex-col">
|
||||
<div className="p-4 border-b flex items-center gap-3">
|
||||
<img src="/assets/logo-light.png" alt="logo" className="w-10 h-10 object-contain" />
|
||||
<div>
|
||||
<div className="text-lg font-semibold">AvanzaCast</div>
|
||||
<div className="text-sm text-slate-500">Cuenta Demo</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav className="p-4 flex-1 overflow-y-auto">
|
||||
<ul className="space-y-1">
|
||||
{navItems.map(item => (
|
||||
<li key={item.id}>
|
||||
<a href="#" className="flex items-center gap-3 px-3 py-2 rounded hover:bg-indigo-50 text-sm">
|
||||
<i className="mdi mdi-view-dashboard text-lg text-slate-400"></i>
|
||||
<span>{item.label}</span>
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<div className="mt-auto p-4 border-t">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<div className="w-10 h-10 rounded-full bg-slate-100 flex items-center justify-center overflow-hidden">
|
||||
<img src="/assets/logo-icon.png" alt="avatar" className="w-full h-full object-cover" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm font-medium">Demo User</div>
|
||||
<div className="text-xs text-slate-500">demo@avanzacast.test</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-sm text-slate-600 mb-3">Almacenamiento: <strong>0</strong> de 5 GB</div>
|
||||
<button className="mt-3 w-full py-2 bg-indigo-600 text-white rounded">Comprar más</button>
|
||||
</div>
|
||||
</aside>
|
||||
)
|
||||
}
|
||||
|
||||
export default Sidebar
|
||||
@ -0,0 +1,51 @@
|
||||
import React, { useState } from 'react'
|
||||
|
||||
interface Transmission { id: string, title: string, platform: string, scheduled: string }
|
||||
|
||||
const mockTransmissions: Transmission[] = [
|
||||
{ id: 't1', title: 'Demo Stream - Producto A', platform: 'YouTube', scheduled: '2025-11-10 18:00' },
|
||||
{ id: 't2', title: 'Webinar - Marketing', platform: 'Facebook', scheduled: '2025-11-12 16:00' },
|
||||
]
|
||||
|
||||
const TransmissionsTable: React.FC = () => {
|
||||
const [activeTab, setActiveTab] = useState<'upcoming' | 'past'>('upcoming')
|
||||
|
||||
const filtered = mockTransmissions
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-4 flex items-center gap-3">
|
||||
<button onClick={() => setActiveTab('upcoming')} className={`px-3 py-1 rounded ${activeTab==='upcoming' ? 'bg-indigo-600 text-white' : 'bg-white border'}`}>Próximamente</button>
|
||||
<button onClick={() => setActiveTab('past')} className={`px-3 py-1 rounded ${activeTab==='past' ? 'bg-indigo-600 text-white' : 'bg-white border'}`}>Anteriores</button>
|
||||
</div>
|
||||
|
||||
<div className="bg-white border rounded">
|
||||
<table className="w-full">
|
||||
<thead className="bg-gray-50">
|
||||
<tr>
|
||||
<th className="text-left p-3">Título</th>
|
||||
<th className="text-left p-3">Plataforma</th>
|
||||
<th className="text-left p-3">Fecha</th>
|
||||
<th className="text-right p-3">Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filtered.map(t => (
|
||||
<tr key={t.id} className="border-t">
|
||||
<td className="p-3">{t.title}</td>
|
||||
<td className="p-3">{t.platform}</td>
|
||||
<td className="p-3">{t.scheduled}</td>
|
||||
<td className="p-3 text-right">
|
||||
<button className="px-3 py-1 bg-indigo-600 text-white rounded mr-2">Entrar</button>
|
||||
<button className="px-3 py-1 border rounded">Opciones</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TransmissionsTable
|
||||
2
packages/landing-page/src/components/broadcast/index.ts
Normal file
2
packages/landing-page/src/components/broadcast/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
// Re-exports to migrate broadcast components to the new `broadcast-panel` package
|
||||
export { PageContainer, Sidebar, Header, TransmissionsTable } from 'broadcast-panel'
|
||||
32
packages/landing-page/src/pages/auth/login.tsx
Normal file
32
packages/landing-page/src/pages/auth/login.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import React, { useState } from 'react'
|
||||
|
||||
const Login: React.FC = () => {
|
||||
const [email, setEmail] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
// Mock authentication: accept any email/password
|
||||
const mockUser = { id: 'user-1', name: 'Demo User', email }
|
||||
localStorage.setItem('mock_user', JSON.stringify(mockUser))
|
||||
// Redirect to broadcast panel
|
||||
window.location.href = '/broadcast'
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-gray-50">
|
||||
<div className="max-w-md w-full bg-white p-8 rounded-lg shadow">
|
||||
<h2 className="text-2xl font-semibold mb-6">Iniciar sesión (Mock)</h2>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<label className="block mb-2 text-sm font-medium">Correo</label>
|
||||
<input className="w-full mb-4 p-2 border rounded" value={email} onChange={e => setEmail(e.target.value)} />
|
||||
<label className="block mb-2 text-sm font-medium">Contraseña</label>
|
||||
<input type="password" className="w-full mb-4 p-2 border rounded" value={password} onChange={e => setPassword(e.target.value)} />
|
||||
<button className="w-full py-2 bg-indigo-600 text-white rounded">Entrar</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Login
|
||||
20
packages/landing-page/src/pages/broadcast.tsx
Normal file
20
packages/landing-page/src/pages/broadcast.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import React, { useEffect } from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import PageContainer from '../components/broadcast/PageContainer'
|
||||
|
||||
const BroadcastPage: React.FC = () => {
|
||||
const router = useRouter()
|
||||
|
||||
useEffect(() => {
|
||||
const user = localStorage.getItem('mock_user')
|
||||
if (!user) {
|
||||
router.push('/auth/login')
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<PageContainer />
|
||||
)
|
||||
}
|
||||
|
||||
export default BroadcastPage
|
||||
Loading…
x
Reference in New Issue
Block a user