veza/apps/web/src/components/library/playlists/AddToPlaylistModal.tsx
senke 3fb12b2ce2 aesthetic-improvements: automated replacement of decorative cyan with steel (80/20 rule, Action 11.3.1.3)
- Created automated script (scripts/replace-decorative-cyan.py) to systematically replace decorative/informational kodo-cyan instances with kodo-steel variants
- Script intelligently preserves active/functional states, design system variants, semantic indicators, and interactive states
- Modified 85 files, replaced 145 decorative instances, preserved 47 functional instances
- No linter errors, type safety maintained
- Action 11.3.1.3 significantly advanced (total: ~302 instances replaced across ~229 files including previous batches)
2026-01-16 11:40:13 +01:00

154 lines
5.1 KiB
TypeScript

import React, { useState } from 'react';
import { Button } from '../../ui/button';
import { X, Search, Plus, Check } from 'lucide-react';
import { useToast } from '../../../context/ToastContext';
interface AddToPlaylistModalProps {
onClose: () => void;
onAdd: (playlistIds: string[]) => void;
}
// Mock user playlists
const MOCK_USER_PLAYLISTS: any[] = [
{
id: 'p1',
title: 'Cyberpunk Essentials',
creator: 'Cyber_Producer',
userId: 'u1',
is_public: true,
cover_url: 'https://picsum.photos/100',
track_count: 45,
likes: 120,
tags: [],
},
{
id: 'p2',
title: 'Late Night Coding',
creator: 'Cyber_Producer',
userId: 'u1',
is_public: false,
cover_url: 'https://picsum.photos/101',
track_count: 22,
likes: 15,
tags: [],
},
{
id: 'p3',
title: 'Gym Phonk',
creator: 'Cyber_Producer',
userId: 'u1',
is_public: true,
cover_url: 'https://picsum.photos/102',
track_count: 105,
likes: 300,
tags: [],
},
];
export const AddToPlaylistModal: React.FC<AddToPlaylistModalProps> = ({
onClose,
onAdd,
}) => {
const { addToast } = useToast();
const [search, setSearch] = useState('');
const [selectedIds, setSelectedIds] = useState<string[]>([]);
const filteredPlaylists = MOCK_USER_PLAYLISTS.filter((p) =>
p.title.toLowerCase().includes(search.toLowerCase()),
);
const toggleSelection = (id: string) => {
setSelectedIds((prev) =>
prev.includes(id) ? prev.filter((pid) => pid !== id) : [...prev, id],
);
};
const handleConfirm = () => {
if (selectedIds.length === 0) return;
onAdd(selectedIds);
onClose();
};
return (
<div className="fixed inset-0 z-[100] flex items-center justify-center p-4">
<div
className="absolute inset-0 bg-kodo-void/90 backdrop-blur-sm"
onClick={onClose}
></div>
<div className="relative w-full max-w-md bg-kodo-graphite border border-kodo-steel rounded-xl shadow-2xl animate-scaleIn overflow-hidden flex flex-col max-h-[70vh]">
<div className="p-4 border-b border-kodo-steel bg-kodo-ink flex justify-between items-center">
<h3 className="font-bold text-white">Add to Playlist</h3>
<button onClick={onClose}>
<X className="w-5 h-5 text-kodo-content-dim hover:text-white" />
</button>
</div>
<div className="p-4">
<div className="relative mb-4">
<input
className="w-full bg-kodo-void border border-kodo-steel rounded-lg py-2 pl-9 pr-4 text-white text-sm focus:border-kodo-steel outline-none"
placeholder="Find playlist"
value={search}
onChange={(e) => setSearch(e.target.value)}
autoFocus
/>
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-kodo-content-dim" />
</div>
<Button
variant="ghost"
className="w-full justify-start border border-dashed border-kodo-steel mb-4 hover:border-kodo-steel/50 hover:text-white group"
onClick={() => addToast('New Playlist Flow')}
>
<div className="w-8 h-8 bg-kodo-steel rounded flex items-center justify-center mr-3 group-hover:bg-white/5">
<Plus className="w-4 h-4" />
</div>
<span className="text-sm font-bold">New Playlist</span>
</Button>
<div className="space-y-1 overflow-y-auto max-h-64 custom-scrollbar">
{filteredPlaylists.map((playlist) => (
<div
key={playlist.id}
onClick={() => toggleSelection(playlist.id)}
className={`flex items-center p-2 rounded cursor-pointer group transition-colors ${selectedIds.includes(playlist.id) ? 'bg-kodo-cyan/10' : 'hover:bg-white/5'}`}
>
<img
src={playlist.cover_url}
className="w-10 h-10 rounded object-cover mr-3"
/>
<div className="flex-1 min-w-0">
<div
className={`text-sm font-bold truncate ${selectedIds.includes(playlist.id) ? 'text-kodo-cyan' : 'text-white'}`}
>
{playlist.title}
</div>
<div className="text-xs text-kodo-content-dim">
{playlist.track_count} tracks
</div>
</div>
<div
className={`w-5 h-5 rounded-full border flex items-center justify-center ${selectedIds.includes(playlist.id) ? 'bg-kodo-cyan border-kodo-cyan' : 'border-kodo-steel'}`}
>
{selectedIds.includes(playlist.id) && (
<Check className="w-3 h-3 text-black" />
)}
</div>
</div>
))}
</div>
</div>
<div className="p-4 border-t border-kodo-steel bg-kodo-ink flex justify-end">
<Button
variant="primary"
onClick={handleConfirm}
disabled={selectedIds.length === 0}
>
Done
</Button>
</div>
</div>
</div>
);
};