obs-ss-plugin-webui/app/teams/page.tsx
Decobus 9a79decda3
Some checks failed
Lint and Build / build (22) (pull_request) Failing after 44s
Lint and Build / build (20) (pull_request) Failing after 53s
Comprehensive UI improvements with proper button spacing and modern design
- Fix broken Tailwind gap classes by implementing custom CSS spacing system
- Add .form-row and .button-group classes for consistent 16px and 12px spacing
- Replace broken SVG icons with reliable emoji icons throughout application
- Implement comprehensive button system with proper sizing and color coding:
  * btn-success (green) for add/save actions
  * btn-danger (red) for delete actions
  * btn-secondary (glass) for edit/cancel actions
  * btn-sm for smaller buttons with proper proportions

Updated all pages with consistent spacing:
- Header: Fixed navigation button spacing with button-group class
- Teams: Fixed add team form and action button spacing
- Streams: Moved Add Stream button to same line as team dropdown
- Home: Updated edit button styling and spacing
- Edit: Fixed all form buttons with proper spacing and modern icons

Enhanced user experience:
- Professional button hover effects with subtle animations
- Consistent visual hierarchy with proper spacing
- Responsive design maintained across all screen sizes
- Modern glass morphism design with improved accessibility

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-19 05:12:10 -04:00

236 lines
No EOL
7.6 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
import { useState, useEffect } from 'react';
import { Team } from '@/types';
export default function Teams() {
const [teams, setTeams] = useState<Team[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [newTeamName, setNewTeamName] = useState('');
const [editingTeam, setEditingTeam] = useState<Team | null>(null);
const [editingName, setEditingName] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);
useEffect(() => {
fetchTeams();
}, []);
const fetchTeams = async () => {
setIsLoading(true);
try {
const res = await fetch('/api/teams');
const data = await res.json();
setTeams(data);
} catch (error) {
console.error('Error fetching teams:', error);
} finally {
setIsLoading(false);
}
};
const handleAddTeam = async (e: React.FormEvent) => {
e.preventDefault();
if (!newTeamName.trim()) return;
setIsSubmitting(true);
try {
const res = await fetch('/api/teams', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ team_name: newTeamName }),
});
if (res.ok) {
setNewTeamName('');
fetchTeams();
} else {
const error = await res.json();
alert(`Error adding team: ${error.error}`);
}
} catch (error) {
console.error('Error adding team:', error);
alert('Failed to add team');
} finally {
setIsSubmitting(false);
}
};
const handleUpdateTeam = async (teamId: number) => {
if (!editingName.trim()) return;
try {
const res = await fetch(`/api/teams/${teamId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ team_name: editingName }),
});
if (res.ok) {
setEditingTeam(null);
setEditingName('');
fetchTeams();
} else {
const error = await res.json();
alert(`Error updating team: ${error.error}`);
}
} catch (error) {
console.error('Error updating team:', error);
alert('Failed to update team');
}
};
const handleDeleteTeam = async (teamId: number) => {
if (!confirm('Are you sure you want to delete this team? This will also delete all associated streams.')) {
return;
}
try {
const res = await fetch(`/api/teams/${teamId}`, {
method: 'DELETE',
});
if (res.ok) {
fetchTeams();
} else {
const error = await res.json();
alert(`Error deleting team: ${error.error}`);
}
} catch (error) {
console.error('Error deleting team:', error);
alert('Failed to delete team');
}
};
const startEditing = (team: Team) => {
setEditingTeam(team);
setEditingName(team.team_name);
};
const cancelEditing = () => {
setEditingTeam(null);
setEditingName('');
};
return (
<div className="container section">
{/* Title */}
<div className="text-center mb-8">
<h1 className="title">Team Management</h1>
<p className="subtitle">
Organize your streams by creating and managing teams
</p>
</div>
{/* Add New Team */}
<div className="glass p-6 mb-6">
<h2 className="card-title">Add New Team</h2>
<form onSubmit={handleAddTeam} className="max-w-md mx-auto">
<div className="form-row">
<input
type="text"
value={newTeamName}
onChange={(e) => setNewTeamName(e.target.value)}
placeholder="Enter team name"
className="input"
style={{ flex: 1 }}
required
/>
<button
type="submit"
disabled={isSubmitting}
className="btn btn-success"
>
<span className="icon"></span>
{isSubmitting ? 'Adding...' : 'Add Team'}
</button>
</div>
</form>
</div>
{/* Teams List */}
<div className="glass p-6">
<h2 className="card-title">Existing Teams</h2>
{isLoading ? (
<div className="text-center p-8">
<div className="w-8 h-8 border-2 border-white/30 border-t-white rounded-full animate-spin mx-auto mb-4"></div>
<div className="text-white/60">Loading teams...</div>
</div>
) : teams.length === 0 ? (
<div className="text-center p-8">
<svg className="icon-lg mx-auto mb-4 text-white/40" fill="currentColor" viewBox="0 0 20 20">
<path d="M13 6a3 3 0 11-6 0 3 3 0 016 0zM18 8a2 2 0 11-4 0 2 2 0 014 0zM14 15a4 4 0 00-8 0v3h8v-3z" />
</svg>
<div className="text-white/60">No teams found</div>
<div className="text-white/40 text-sm">Create your first team above!</div>
</div>
) : (
<div className="space-y-4">
{teams.map((team) => (
<div key={team.team_id} className="glass p-4">
{editingTeam?.team_id === team.team_id ? (
<div className="form-row">
<input
type="text"
value={editingName}
onChange={(e) => setEditingName(e.target.value)}
className="input"
style={{ flex: 1 }}
autoFocus
/>
<button
onClick={() => handleUpdateTeam(team.team_id)}
className="btn btn-success btn-sm"
title="Save changes"
>
<span className="icon"></span>
Save
</button>
<button
onClick={cancelEditing}
className="btn-secondary btn-sm"
title="Cancel editing"
>
<span className="icon"></span>
Cancel
</button>
</div>
) : (
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-gradient-to-br from-blue-500 to-purple-600 rounded-lg flex items-center justify-center text-white font-bold text-sm">
{team.team_name.charAt(0).toUpperCase()}
</div>
<div>
<div className="font-semibold text-white">{team.team_name}</div>
<div className="text-sm text-white/60">ID: {team.team_id}</div>
</div>
</div>
<div className="button-group">
<button
onClick={() => startEditing(team)}
className="btn-secondary btn-sm"
title="Edit team"
>
<span className="icon"></span>
Edit
</button>
<button
onClick={() => handleDeleteTeam(team.team_id)}
className="btn-danger btn-sm"
title="Delete team"
>
<span className="icon">🗑</span>
Delete
</button>
</div>
</div>
)}
</div>
))}
</div>
)}
</div>
</div>
);
}