'use client'; import { useState, useEffect, useCallback } from 'react'; import Dropdown from '@/components/Dropdown'; import { Team } from '@/types'; import { useToast } from '@/lib/useToast'; import { ToastContainer } from '@/components/Toast'; interface Stream { id: number; name: string; obs_source_name: string; url: string; team_id: number; } export default function AddStream() { const [formData, setFormData] = useState({ name: '', obs_source_name: '', twitch_username: '', team_id: null, }); const [teams, setTeams] = useState<{id: number; name: string}[]>([]); const [streams, setStreams] = useState([]); const [isLoading, setIsLoading] = useState(true); const [isSubmitting, setIsSubmitting] = useState(false); const [validationErrors, setValidationErrors] = useState<{[key: string]: string}>({}); const [deleteConfirm, setDeleteConfirm] = useState<{id: number; name: string} | null>(null); const [isDeleting, setIsDeleting] = useState(false); const { toasts, removeToast, showSuccess, showError } = useToast(); const fetchData = useCallback(async () => { setIsLoading(true); try { const [teamsResponse, streamsResponse] = await Promise.all([ fetch('/api/teams'), fetch('/api/streams') ]); const teamsData = await teamsResponse.json(); const streamsData = await streamsResponse.json(); // Handle both old and new API response formats const teams = teamsData.success ? teamsData.data : teamsData; const streams = streamsData.success ? streamsData.data : streamsData; // Map the API data to the format required by the Dropdown setTeams( teams.map((team: Team) => ({ id: team.team_id, name: team.team_name, })) ); setStreams(streams); } catch (error) { console.error('Failed to fetch data:', error); showError('Failed to Load Data', 'Could not fetch teams and streams. Please refresh the page.'); } finally { setIsLoading(false); } }, [showError]); // Fetch teams and streams on component mount useEffect(() => { fetchData(); }, [fetchData]); const handleInputChange = (e: React.ChangeEvent) => { const { name, value } = e.target; setFormData((prev) => ({ ...prev, [name]: value })); // Clear validation error when user starts typing if (validationErrors[name]) { setValidationErrors(prev => ({ ...prev, [name]: '' })); } }; const handleTeamSelect = (teamId: number) => { // @ts-expect-error - team_id can be null or number in formData, but TypeScript expects only number setFormData((prev) => ({ ...prev, team_id: teamId })); // Clear validation error when user selects team if (validationErrors.team_id) { setValidationErrors(prev => ({ ...prev, team_id: '' })); } }; const handleDelete = async () => { if (!deleteConfirm) return; setIsDeleting(true); try { const response = await fetch(`/api/streams/${deleteConfirm.id}`, { method: 'DELETE', }); const data = await response.json(); if (response.ok) { showSuccess('Stream Deleted', `"${deleteConfirm.name}" has been deleted successfully`); setDeleteConfirm(null); // Refetch the streams list await fetchData(); } else { showError('Failed to Delete Stream', data.error || 'Unknown error occurred'); } } catch (error) { console.error('Error deleting stream:', error); showError('Failed to Delete Stream', 'Network error or server unavailable'); } finally { setIsDeleting(false); } }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); // Client-side validation const errors: {[key: string]: string} = {}; if (!formData.name.trim()) { errors.name = 'Stream name is required'; } else if (formData.name.trim().length < 2) { errors.name = 'Stream name must be at least 2 characters'; } if (!formData.obs_source_name.trim()) { errors.obs_source_name = 'OBS source name is required'; } if (!formData.twitch_username.trim()) { errors.twitch_username = 'Twitch username is required'; } else if (!/^[a-zA-Z0-9_]{4,25}$/.test(formData.twitch_username.trim())) { errors.twitch_username = 'Twitch username must be 4-25 characters and contain only letters, numbers, and underscores'; } if (!formData.team_id) { errors.team_id = 'Please select a team'; } setValidationErrors(errors); if (Object.keys(errors).length > 0) { showError('Validation Error', 'Please fix the form errors'); return; } setIsSubmitting(true); try { const submissionData = { ...formData, url: `https://www.twitch.tv/${formData.twitch_username.trim()}` }; const response = await fetch('/api/addStream', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(submissionData), }); const data = await response.json(); if (response.ok) { showSuccess('Stream Added', `"${formData.name}" has been added successfully`); setFormData({ name: '', obs_source_name: '', twitch_username: '', team_id: null }); setValidationErrors({}); fetchData(); } else { showError('Failed to Add Stream', data.error || 'Unknown error occurred'); } } catch (error) { console.error('Error adding stream:', error); showError('Failed to Add Stream', 'Network error or server unavailable'); } finally { setIsSubmitting(false); } }; return ( <>
{/* Title */}

Streams

Organize your content by creating and managing stream sources

{/* Add New Stream */}

Add Stream

{/* Stream Name */}
{validationErrors.name && (
{validationErrors.name}
)}
{/* OBS Source Name */}
{validationErrors.obs_source_name && (
{validationErrors.obs_source_name}
)}
{/* Twitch Username */}
{validationErrors.twitch_username && (
{validationErrors.twitch_username}
)}
{/* Team Selection and Submit Button */}
{validationErrors.team_id && (
{validationErrors.team_id}
)}
{/* Streams List */}

Existing Streams

{isLoading ? (
Loading streams...
) : streams.length === 0 ? (
No streams found
Create your first stream above!
) : (
{streams.map((stream) => { const team = teams.find(t => t.id === stream.team_id); return (
{stream.name.charAt(0).toUpperCase()}
{stream.name}
OBS: {stream.obs_source_name}
Team: {team?.name || 'Unknown'}
ID: {stream.id}
View Stream
); })}
)}
{/* Toast Notifications */}
{/* Delete Confirmation Modal */} {deleteConfirm && (

Confirm Deletion

Are you sure you want to delete the stream “{deleteConfirm.name}”? This will remove it from both the database and OBS.

)} ); }