import { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { useProjectStore } from '../../store/useProjectStore'; import { createProject, updateProject } from '../../services/projectService'; import { trackCreateProject, trackSaveProject } from '../../utils/analytics'; import { buildSavePayload } from '../../utils/projectPayload'; interface SaveProjectModalProps { onClose: () => void; } export const SaveProjectModal: React.FC = ({ onClose }) => { const { t } = useTranslation(); const navigate = useNavigate(); const currentProject = useProjectStore((s) => s.currentProject); const setCurrentProject = useProjectStore((s) => s.setCurrentProject); const [name, setName] = useState(''); const [description, setDescription] = useState(''); const [isPublic, setIsPublic] = useState(true); const [saving, setSaving] = useState(false); const [error, setError] = useState(''); const isUpdate = !!currentProject; useEffect(() => { if (isUpdate) { setName(currentProject.slug); // will be overridden if we load proper name setIsPublic(currentProject.isPublic); } }, [isUpdate]); const handleSave = async (e: React.FormEvent) => { e.preventDefault(); if (!name.trim()) { setError(t('editor.saveProject.errors.nameRequired')); return; } setSaving(true); setError(''); const payload = buildSavePayload({ name: name.trim(), description: description.trim() || undefined, isPublic, }); const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; const isValidUpdate = isUpdate && currentProject && UUID_RE.test(currentProject.id); try { let saved; if (isValidUpdate) { saved = await updateProject(currentProject!.id, payload); trackSaveProject(); } else { saved = await createProject(payload); trackCreateProject(); } setCurrentProject({ id: saved.id, slug: saved.slug, ownerUsername: saved.owner_username, isPublic: saved.is_public, }); navigate(`/project/${saved.id}`, { replace: true }); onClose(); } catch (err: any) { if (!err?.response) { setError(t('editor.saveProject.errors.unreachable')); } else if (err.response.status === 401) { setError(t('editor.saveProject.errors.notAuth')); } else { setError( err.response?.data?.detail || t('editor.saveProject.errors.failedStatus', { status: err.response.status }) ); } } finally { setSaving(false); } }; return (
e.stopPropagation()}>

{isUpdate ? t('editor.saveProject.titleUpdate') : t('editor.saveProject.titleSave')}

{error &&
{error}
}
setName(e.target.value)} required style={styles.input} autoFocus placeholder={t('editor.saveProject.namePlaceholder')} /> setDescription(e.target.value)} style={styles.input} placeholder={t('editor.saveProject.descriptionPlaceholder')} />
setIsPublic(!isPublic)} role="button" tabIndex={0} >
{isPublic ? ( ) : ( )}
{isPublic ? t('editor.saveProject.visibility.public') : t('editor.saveProject.visibility.private')}
{isPublic ? t('editor.saveProject.visibility.publicHint') : t('editor.saveProject.visibility.privateHint')}
); }; const styles: Record = { overlay: { position: 'fixed', inset: 0, background: 'rgba(0,0,0,.6)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1000, }, modal: { background: '#252526', border: '1px solid #3c3c3c', borderRadius: 8, padding: '1.75rem', width: 380, display: 'flex', flexDirection: 'column', gap: 14, }, title: { color: '#ccc', margin: 0, fontSize: 18, fontWeight: 600 }, form: { display: 'flex', flexDirection: 'column', gap: 10 }, label: { color: '#9d9d9d', fontSize: 13 }, input: { background: '#3c3c3c', border: '1px solid #555', borderRadius: 4, padding: '8px 10px', color: '#ccc', fontSize: 14, outline: 'none', }, checkboxRow: { display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer' }, visibilityToggle: { display: 'flex', alignItems: 'center', padding: '8px 10px', background: '#1e1e1e', border: '1px solid #444', borderRadius: 6, cursor: 'pointer', transition: 'border-color 0.15s', }, actions: { display: 'flex', gap: 8, marginTop: 4 }, saveBtn: { flex: 1, background: '#0e639c', border: 'none', borderRadius: 4, color: '#fff', padding: '9px', fontSize: 14, cursor: 'pointer', fontWeight: 500, }, cancelBtn: { background: 'transparent', border: '1px solid #555', borderRadius: 4, color: '#ccc', padding: '9px 16px', fontSize: 14, cursor: 'pointer', }, error: { background: '#5a1d1d', border: '1px solid #f44747', borderRadius: 4, color: '#f44747', padding: '8px 12px', fontSize: 13, }, };