'use client'; import { useEffect, useRef, useState } from 'react'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { useI18n } from '@/lib/hooks/use-i18n'; import { displayNameWidth, FOLDER_NAME_MAX_WIDTH, FOLDER_COUNT_LIMIT, validateFolderName, } from '@/lib/utils/folder-name-validation'; import type { FolderRecord } from '@/lib/utils/database'; import { FolderNameError } from '@/lib/utils/stage-storage'; // ============ F1: New folder dialog ============ export function NewFolderDialog({ open, onOpenChange, folders, onCreate, }: { open: boolean; onOpenChange: (v: boolean) => void; folders: FolderRecord[]; onCreate: (name: string) => Promise; }) { const { t } = useI18n(); const [name, setName] = useState(''); const [error, setError] = useState(null); const [submitting, setSubmitting] = useState(false); const inputRef = useRef(null); useEffect(() => { if (open) { setName(''); setError(null); requestAnimationFrame(() => inputRef.current?.focus()); } }, [open]); const width = displayNameWidth(name.trim()); const submit = async () => { if (submitting) return; const widthCheck = validateFolderName(name); if (!widthCheck.ok) { setError( widthCheck.kind === 'empty' ? t('classroom.folderNameEmpty') : t('classroom.folderWidth', { width: widthCheck.width, max: FOLDER_NAME_MAX_WIDTH }), ); return; } const trimmed = name.trim(); // Case-insensitive duplicate check, matching the storage-boundary rule. if (folders.some((f) => f.name.toLowerCase() === trimmed.toLowerCase())) { setError(t('classroom.folderNameExists')); return; } if (folders.length >= FOLDER_COUNT_LIMIT) { setError(t('classroom.folderCountLimit')); return; } setSubmitting(true); try { await onCreate(trimmed); onOpenChange(false); } catch (err) { // Map the storage-boundary error to a specific message when possible. if (err instanceof FolderNameError) { setError( err.kind === 'duplicate' ? t('classroom.folderNameExists') : err.kind === 'tooLong' ? t('classroom.folderWidth', { width: displayNameWidth(trimmed), max: FOLDER_NAME_MAX_WIDTH, }) : err.kind === 'limit' ? t('classroom.folderCountLimit') : t('classroom.folderNameHint'), ); } else { setError(t('classroom.folderCreateFailed')); } } finally { setSubmitting(false); } }; return ( {t('classroom.newFolderTitle')} {t('classroom.newFolderDesc')}
{ setName(e.target.value); setError(null); }} onKeyDown={(e) => { if (e.key === 'Enter') submit(); }} placeholder={t('classroom.folderNamePlaceholder')} maxLength={80} className="mt-1.5 w-full rounded-lg border border-border bg-background px-3 py-2 text-[14px] outline-none focus:ring-2 focus:ring-violet-400/40 focus:border-violet-400" />
{error ?? t('classroom.folderNameHint')} FOLDER_NAME_MAX_WIDTH ? 'text-destructive' : 'text-muted-foreground/60' } > {t('classroom.folderWidth', { width: Math.max(0, width), max: FOLDER_NAME_MAX_WIDTH, })}
); }