2026-01-07 09:31:02 +00:00
|
|
|
import React, { useCallback, useState } from 'react';
|
|
|
|
|
import { UploadCloud } from 'lucide-react';
|
2026-01-26 13:12:17 +00:00
|
|
|
import { useToast } from '../../components/feedback/ToastProvider';
|
2026-01-07 09:31:02 +00:00
|
|
|
|
|
|
|
|
interface FileUploadZoneProps {
|
|
|
|
|
onFilesSelected: (files: File[]) => void;
|
|
|
|
|
acceptedFormats?: string[];
|
|
|
|
|
maxSizeInMB?: number;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
export const FileUploadZone: React.FC<FileUploadZoneProps> = ({
|
|
|
|
|
onFilesSelected,
|
|
|
|
|
acceptedFormats = ['.wav', '.mp3', '.aiff', '.flac', '.zip'],
|
|
|
|
|
maxSizeInMB = 500,
|
2026-01-07 09:31:02 +00:00
|
|
|
}) => {
|
|
|
|
|
const { addToast } = useToast();
|
|
|
|
|
const [isDragging, setIsDragging] = useState(false);
|
|
|
|
|
|
|
|
|
|
const validateFile = (file: File): boolean => {
|
|
|
|
|
// Check size
|
|
|
|
|
if (file.size > maxSizeInMB * 1024 * 1024) {
|
|
|
|
|
addToast(`File ${file.name} exceeds ${maxSizeInMB}MB limit`, 'error');
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// Check extension (simple check)
|
2026-01-13 18:47:57 +00:00
|
|
|
const ext = `.${file.name.split('.').pop()?.toLowerCase()}`;
|
2026-01-07 09:31:02 +00:00
|
|
|
if (!acceptedFormats.includes(ext)) {
|
|
|
|
|
addToast(`Format ${ext} not supported for ${file.name}`, 'error');
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
|
2026-01-13 18:47:57 +00:00
|
|
|
const handleDrop = useCallback(
|
|
|
|
|
(e: React.DragEvent) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
setIsDragging(false);
|
|
|
|
|
|
|
|
|
|
const droppedFiles = Array.from(e.dataTransfer.files);
|
|
|
|
|
const validFiles = droppedFiles.filter(validateFile);
|
|
|
|
|
|
|
|
|
|
if (validFiles.length > 0) {
|
|
|
|
|
onFilesSelected(validFiles);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[onFilesSelected],
|
|
|
|
|
);
|
2026-01-07 09:31:02 +00:00
|
|
|
|
|
|
|
|
const handleFileInput = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
|
|
|
if (e.target.files) {
|
|
|
|
|
const selectedFiles = Array.from(e.target.files);
|
|
|
|
|
const validFiles = selectedFiles.filter(validateFile);
|
|
|
|
|
if (validFiles.length > 0) {
|
|
|
|
|
onFilesSelected(validFiles);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
2026-01-13 18:47:57 +00:00
|
|
|
<div
|
|
|
|
|
onDragOver={(e) => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
setIsDragging(true);
|
|
|
|
|
}}
|
2026-01-07 09:31:02 +00:00
|
|
|
onDragLeave={() => setIsDragging(false)}
|
|
|
|
|
onDrop={handleDrop}
|
|
|
|
|
className={`
|
feat(web): UI premium Discord/Spotify-like — tokens, shadows, focus, layout
Plan UI premium 6–8 semaines (design system, shell, Storybook, a11y):
- Design system: DESIGN_TOKENS.md, APP_SHELL.md, FULL_LAYOUT_PAGE.md. Single source
for layout/shell (index.css), shadows (design-system.css), durations/easing.
- Tokens: shadow-cover-depth, shadow-gold-glow, shadow-fab-glow; layout max-height
(max-h-layout-drawer, max-h-layout-panel, max-h-layout-list). All duration-200/300/500
replaced by --duration-fast/normal/slow. Arbitrary shadows replaced by token classes.
- Shell & player: Sidebar, Header, GlobalPlayer, MiniPlayer, PlayerQueue, PlayerControls,
AudioPlayer use tokens; focus-visible on Sidebar, PlayerQueue, DropdownMenuTrigger/Item,
TabsTrigger. Typography: text-[10px]/[9px] → text-xs where applicable.
- ESLint: no-restricted-syntax (warn) for w-/h-/rounded-/shadow-/text-/spacing arbitrary.
- Scripts: report-arbitrary-values.mjs, capture/compare/generate visual; visual-complete.spec.ts.
- Stories full layout: Dashboard, Playlists, Library, Settings, Profile in DashboardLayout.stories.
- .cursorrules + README: DESIGN_TOKENS, APP_SHELL, visual commands, no arbitrary without justification.
- apps/web/.gitignore: e2e test artifacts (test-results-visual, playwright-report-visual).
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 16:15:58 +00:00
|
|
|
border-2 border-dashed rounded-xl p-12 flex flex-col items-center justify-center text-center transition-all duration-[var(--duration-normal)] group cursor-pointer
|
2026-01-13 18:47:57 +00:00
|
|
|
${
|
|
|
|
|
isDragging
|
2026-02-07 15:07:09 +00:00
|
|
|
? 'border-primary bg-primary/10 scale-[1.02]'
|
|
|
|
|
: 'border-border/50 bg-muted/30 hover:bg-muted/50 hover:border-border/50'
|
2026-01-13 18:47:57 +00:00
|
|
|
}
|
2026-01-07 09:31:02 +00:00
|
|
|
`}
|
|
|
|
|
>
|
2026-01-13 18:47:57 +00:00
|
|
|
<input
|
|
|
|
|
type="file"
|
|
|
|
|
multiple
|
|
|
|
|
className="hidden"
|
2026-01-07 09:31:02 +00:00
|
|
|
id="file-upload-input"
|
|
|
|
|
onChange={handleFileInput}
|
|
|
|
|
accept={acceptedFormats.join(',')}
|
|
|
|
|
/>
|
2026-01-13 18:47:57 +00:00
|
|
|
<label
|
|
|
|
|
htmlFor="file-upload-input"
|
|
|
|
|
className="cursor-pointer w-full flex flex-col items-center"
|
|
|
|
|
>
|
|
|
|
|
<div
|
|
|
|
|
className={`
|
feat(web): UI premium Discord/Spotify-like — tokens, shadows, focus, layout
Plan UI premium 6–8 semaines (design system, shell, Storybook, a11y):
- Design system: DESIGN_TOKENS.md, APP_SHELL.md, FULL_LAYOUT_PAGE.md. Single source
for layout/shell (index.css), shadows (design-system.css), durations/easing.
- Tokens: shadow-cover-depth, shadow-gold-glow, shadow-fab-glow; layout max-height
(max-h-layout-drawer, max-h-layout-panel, max-h-layout-list). All duration-200/300/500
replaced by --duration-fast/normal/slow. Arbitrary shadows replaced by token classes.
- Shell & player: Sidebar, Header, GlobalPlayer, MiniPlayer, PlayerQueue, PlayerControls,
AudioPlayer use tokens; focus-visible on Sidebar, PlayerQueue, DropdownMenuTrigger/Item,
TabsTrigger. Typography: text-[10px]/[9px] → text-xs where applicable.
- ESLint: no-restricted-syntax (warn) for w-/h-/rounded-/shadow-/text-/spacing arbitrary.
- Scripts: report-arbitrary-values.mjs, capture/compare/generate visual; visual-complete.spec.ts.
- Stories full layout: Dashboard, Playlists, Library, Settings, Profile in DashboardLayout.stories.
- .cursorrules + README: DESIGN_TOKENS, APP_SHELL, visual commands, no arbitrary without justification.
- apps/web/.gitignore: e2e test artifacts (test-results-visual, playwright-report-visual).
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 16:15:58 +00:00
|
|
|
w-20 h-20 rounded-full flex items-center justify-center mb-6 transition-all duration-[var(--duration-normal)] shadow-lg
|
2026-02-07 15:07:09 +00:00
|
|
|
${isDragging ? 'bg-kodo-cyan text-black' : 'bg-muted text-primary group-hover:bg-border'}
|
2026-01-13 18:47:57 +00:00
|
|
|
`}
|
|
|
|
|
>
|
2026-01-07 09:31:02 +00:00
|
|
|
<UploadCloud className="w-10 h-10" />
|
|
|
|
|
</div>
|
2026-01-13 18:47:57 +00:00
|
|
|
|
2026-01-15 22:54:05 +00:00
|
|
|
<h3 className="text-xl font-display font-bold text-white mb-2">
|
2026-01-07 09:31:02 +00:00
|
|
|
{isDragging ? 'Drop Files Here' : 'Drag & Drop or Click'}
|
|
|
|
|
</h3>
|
2026-02-07 15:07:09 +00:00
|
|
|
<p className="text-muted-foreground mb-6 max-w-sm">
|
2026-01-07 09:31:02 +00:00
|
|
|
Upload audio stems, project archives, or sample packs.
|
|
|
|
|
</p>
|
2026-01-13 18:47:57 +00:00
|
|
|
|
2026-01-07 09:31:02 +00:00
|
|
|
<div className="flex flex-wrap justify-center gap-2 max-w-md">
|
2026-01-13 18:47:57 +00:00
|
|
|
{acceptedFormats.map((fmt) => (
|
|
|
|
|
<span
|
|
|
|
|
key={fmt}
|
feat(web): UI premium Discord/Spotify-like — tokens, shadows, focus, layout
Plan UI premium 6–8 semaines (design system, shell, Storybook, a11y):
- Design system: DESIGN_TOKENS.md, APP_SHELL.md, FULL_LAYOUT_PAGE.md. Single source
for layout/shell (index.css), shadows (design-system.css), durations/easing.
- Tokens: shadow-cover-depth, shadow-gold-glow, shadow-fab-glow; layout max-height
(max-h-layout-drawer, max-h-layout-panel, max-h-layout-list). All duration-200/300/500
replaced by --duration-fast/normal/slow. Arbitrary shadows replaced by token classes.
- Shell & player: Sidebar, Header, GlobalPlayer, MiniPlayer, PlayerQueue, PlayerControls,
AudioPlayer use tokens; focus-visible on Sidebar, PlayerQueue, DropdownMenuTrigger/Item,
TabsTrigger. Typography: text-[10px]/[9px] → text-xs where applicable.
- ESLint: no-restricted-syntax (warn) for w-/h-/rounded-/shadow-/text-/spacing arbitrary.
- Scripts: report-arbitrary-values.mjs, capture/compare/generate visual; visual-complete.spec.ts.
- Stories full layout: Dashboard, Playlists, Library, Settings, Profile in DashboardLayout.stories.
- .cursorrules + README: DESIGN_TOKENS, APP_SHELL, visual commands, no arbitrary without justification.
- apps/web/.gitignore: e2e test artifacts (test-results-visual, playwright-report-visual).
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 16:15:58 +00:00
|
|
|
className="px-2 py-1 bg-black/30 rounded text-xs font-mono text-muted-foreground border border-white/5 uppercase"
|
2026-01-13 18:47:57 +00:00
|
|
|
>
|
2026-01-07 09:31:02 +00:00
|
|
|
{fmt.replace('.', '')}
|
|
|
|
|
</span>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
feat(web): UI premium Discord/Spotify-like — tokens, shadows, focus, layout
Plan UI premium 6–8 semaines (design system, shell, Storybook, a11y):
- Design system: DESIGN_TOKENS.md, APP_SHELL.md, FULL_LAYOUT_PAGE.md. Single source
for layout/shell (index.css), shadows (design-system.css), durations/easing.
- Tokens: shadow-cover-depth, shadow-gold-glow, shadow-fab-glow; layout max-height
(max-h-layout-drawer, max-h-layout-panel, max-h-layout-list). All duration-200/300/500
replaced by --duration-fast/normal/slow. Arbitrary shadows replaced by token classes.
- Shell & player: Sidebar, Header, GlobalPlayer, MiniPlayer, PlayerQueue, PlayerControls,
AudioPlayer use tokens; focus-visible on Sidebar, PlayerQueue, DropdownMenuTrigger/Item,
TabsTrigger. Typography: text-[10px]/[9px] → text-xs where applicable.
- ESLint: no-restricted-syntax (warn) for w-/h-/rounded-/shadow-/text-/spacing arbitrary.
- Scripts: report-arbitrary-values.mjs, capture/compare/generate visual; visual-complete.spec.ts.
- Stories full layout: Dashboard, Playlists, Library, Settings, Profile in DashboardLayout.stories.
- .cursorrules + README: DESIGN_TOKENS, APP_SHELL, visual commands, no arbitrary without justification.
- apps/web/.gitignore: e2e test artifacts (test-results-visual, playwright-report-visual).
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 16:15:58 +00:00
|
|
|
<p className="mt-4 text-xs text-muted-foreground">
|
2026-01-13 18:47:57 +00:00
|
|
|
Max file size: {maxSizeInMB}MB
|
|
|
|
|
</p>
|
2026-01-07 09:31:02 +00:00
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
2026-01-13 18:47:57 +00:00
|
|
|
};
|