2026-03-06 20:14:50 +07:00
|
|
|
import { useState, useEffect } from 'react';
|
|
|
|
|
import { useNavigate } from 'react-router-dom';
|
feat(editor): translate Save / Share / LoginPrompt modals (Editor block 3)
The three modal dialogs that fire during the editor's routine save +
share + auth-required flows are now fully localised across all 9
locales.
LoginPromptModal
- Title, body, and the three buttons (Sign in / Create account /
Cancel). Sign in / Sign up Links use localize() so a Spanish
reader prompted to log in lands at /es/login rather than dropping
back to English.
SaveProjectModal
- Title (toggles between Save / Update), name + description fields
with placeholders, save button (toggles between Save / Update /
Saving…), Cancel button.
- Visibility toggle: Public / Private label + hint copy under the
icon.
- All four error paths now go through t() with a {{status}}
interpolation for the generic HTTP failure message.
ShareModal
- Title, public/private label + hint pair, "Make private" /
"Make public" toggle, Copy button, the warning shown when the
project is private, and the Close button.
Hand-curated translations for all 8 non-English locales. Status
codes (403) and shortcut markers preserved.
2026-05-09 13:12:52 +07:00
|
|
|
import { useTranslation } from 'react-i18next';
|
2026-03-06 20:14:50 +07:00
|
|
|
import { useProjectStore } from '../../store/useProjectStore';
|
|
|
|
|
import { createProject, updateProject } from '../../services/projectService';
|
2026-03-25 13:06:38 +07:00
|
|
|
import { trackCreateProject, trackSaveProject } from '../../utils/analytics';
|
feat: persist multi-board projects + add auto-save
The project save/load pipeline only persisted a single `board_type`, so
multi-board workspaces silently lost every board except the active one
on save, and wires referencing the dropped boards' IDs orphaned to the
canvas corner on reload. An audit of the production backup found 74/306
projects (24%) with at least one orphaned wire and 174/301 non-trivial
projects whose code was still the default Blink template — strong signal
that users save once and never re-save.
Backend
- Add `boards_json` column on `projects` with idempotent ALTER TABLE in
the lifespan migration list.
- New `FileGroup` schema + `file_groups` array on
ProjectCreate/Update/Response. Legacy `files`/`code` kept for back-compat.
- `project_files.py` now uses `{pid}/{groupId}/{filename}` subdirs via
`read_groups`/`write_groups`. Legacy flat layouts are auto-promoted on
read; legacy single-list `files` only updates the active group, leaving
other boards' files intact.
- `_persist_files_from_body` honors file_groups → files → code priority.
Frontend
- `useSimulatorStore.addBoard` accepts an optional `explicitId` so
saved board IDs can be restored verbatim (wires reference IDs literally).
- New `loadProjectState({boards, fileGroups, components, wires,
activeBoardId})` action: tears down current boards, recreates from the
payload, restores file groups atomically, recalculates wire positions
on the next frame, and refreshes the Interconnect.
- `useEditorStore.replaceFileGroups` for atomic multi-group restore.
- `SaveProjectModal` and `ProjectByIdPage`/`ProjectPage` now go through
`buildSavePayload` / `buildLoadPayload` (handles pre-backfill projects
by synthesising a default board from `board_type`).
Auto-save (#useAutoSaveProject hook)
- 2.5s debounced silent PUT triggered ONLY when an authenticated user
has a `currentProject` with a UUID. State hash detects real changes
vs. UI-only churn; baseline is reset on project load so the just-loaded
state isn't immediately re-saved.
- `beforeunload` flush via `fetch keepalive: true` (supports PUT +
credentials, survives unload).
- Compact status indicator in `AppHeader` (idle/dirty/saving/saved/error).
Backfill script (one-off, idempotent)
- `backend/scripts/backfill_boards_2026_05.py` populates `boards_json`
for legacy projects. Heuristic per project, based on which board IDs
the wires reference:
Case A — wires only ref 'arduino-uno' but board_type ≠ uno:
rename id→board_type and rewrite wire endpoints.
Case B — single-board normal: keep verbatim.
Case C — multi-board: recreate one board per distinct ref, infer
kind by stripping trailing -N suffix.
Also moves any flat files into the active board's group subdir.
Stdlib-only, runs from host or `docker exec`.
Docker
- `Dockerfile.standalone` now copies `backend/scripts/` into the image
so the backfill is callable via `docker exec velxio-app python
/app/scripts/backfill_boards_2026_05.py --apply`.
Verified locally on the restored production backup (363 projects):
33 Case A, 316 Case B, 14 Case C, 135 wire endpoints renamed, 0 orphans.
Re-running the script after apply skips all 363 (idempotent).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 23:43:33 +07:00
|
|
|
import { buildSavePayload } from '../../utils/projectPayload';
|
2026-03-06 20:14:50 +07:00
|
|
|
|
|
|
|
|
interface SaveProjectModalProps {
|
|
|
|
|
onClose: () => void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const SaveProjectModal: React.FC<SaveProjectModalProps> = ({ onClose }) => {
|
feat(editor): translate Save / Share / LoginPrompt modals (Editor block 3)
The three modal dialogs that fire during the editor's routine save +
share + auth-required flows are now fully localised across all 9
locales.
LoginPromptModal
- Title, body, and the three buttons (Sign in / Create account /
Cancel). Sign in / Sign up Links use localize() so a Spanish
reader prompted to log in lands at /es/login rather than dropping
back to English.
SaveProjectModal
- Title (toggles between Save / Update), name + description fields
with placeholders, save button (toggles between Save / Update /
Saving…), Cancel button.
- Visibility toggle: Public / Private label + hint copy under the
icon.
- All four error paths now go through t() with a {{status}}
interpolation for the generic HTTP failure message.
ShareModal
- Title, public/private label + hint pair, "Make private" /
"Make public" toggle, Copy button, the warning shown when the
project is private, and the Close button.
Hand-curated translations for all 8 non-English locales. Status
codes (403) and shortcut markers preserved.
2026-05-09 13:12:52 +07:00
|
|
|
const { t } = useTranslation();
|
2026-03-06 20:14:50 +07:00
|
|
|
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();
|
2026-04-22 02:45:45 +07:00
|
|
|
if (!name.trim()) {
|
feat(editor): translate Save / Share / LoginPrompt modals (Editor block 3)
The three modal dialogs that fire during the editor's routine save +
share + auth-required flows are now fully localised across all 9
locales.
LoginPromptModal
- Title, body, and the three buttons (Sign in / Create account /
Cancel). Sign in / Sign up Links use localize() so a Spanish
reader prompted to log in lands at /es/login rather than dropping
back to English.
SaveProjectModal
- Title (toggles between Save / Update), name + description fields
with placeholders, save button (toggles between Save / Update /
Saving…), Cancel button.
- Visibility toggle: Public / Private label + hint copy under the
icon.
- All four error paths now go through t() with a {{status}}
interpolation for the generic HTTP failure message.
ShareModal
- Title, public/private label + hint pair, "Make private" /
"Make public" toggle, Copy button, the warning shown when the
project is private, and the Close button.
Hand-curated translations for all 8 non-English locales. Status
codes (403) and shortcut markers preserved.
2026-05-09 13:12:52 +07:00
|
|
|
setError(t('editor.saveProject.errors.nameRequired'));
|
2026-04-22 02:45:45 +07:00
|
|
|
return;
|
|
|
|
|
}
|
2026-03-06 20:14:50 +07:00
|
|
|
setSaving(true);
|
|
|
|
|
setError('');
|
|
|
|
|
|
feat: persist multi-board projects + add auto-save
The project save/load pipeline only persisted a single `board_type`, so
multi-board workspaces silently lost every board except the active one
on save, and wires referencing the dropped boards' IDs orphaned to the
canvas corner on reload. An audit of the production backup found 74/306
projects (24%) with at least one orphaned wire and 174/301 non-trivial
projects whose code was still the default Blink template — strong signal
that users save once and never re-save.
Backend
- Add `boards_json` column on `projects` with idempotent ALTER TABLE in
the lifespan migration list.
- New `FileGroup` schema + `file_groups` array on
ProjectCreate/Update/Response. Legacy `files`/`code` kept for back-compat.
- `project_files.py` now uses `{pid}/{groupId}/{filename}` subdirs via
`read_groups`/`write_groups`. Legacy flat layouts are auto-promoted on
read; legacy single-list `files` only updates the active group, leaving
other boards' files intact.
- `_persist_files_from_body` honors file_groups → files → code priority.
Frontend
- `useSimulatorStore.addBoard` accepts an optional `explicitId` so
saved board IDs can be restored verbatim (wires reference IDs literally).
- New `loadProjectState({boards, fileGroups, components, wires,
activeBoardId})` action: tears down current boards, recreates from the
payload, restores file groups atomically, recalculates wire positions
on the next frame, and refreshes the Interconnect.
- `useEditorStore.replaceFileGroups` for atomic multi-group restore.
- `SaveProjectModal` and `ProjectByIdPage`/`ProjectPage` now go through
`buildSavePayload` / `buildLoadPayload` (handles pre-backfill projects
by synthesising a default board from `board_type`).
Auto-save (#useAutoSaveProject hook)
- 2.5s debounced silent PUT triggered ONLY when an authenticated user
has a `currentProject` with a UUID. State hash detects real changes
vs. UI-only churn; baseline is reset on project load so the just-loaded
state isn't immediately re-saved.
- `beforeunload` flush via `fetch keepalive: true` (supports PUT +
credentials, survives unload).
- Compact status indicator in `AppHeader` (idle/dirty/saving/saved/error).
Backfill script (one-off, idempotent)
- `backend/scripts/backfill_boards_2026_05.py` populates `boards_json`
for legacy projects. Heuristic per project, based on which board IDs
the wires reference:
Case A — wires only ref 'arduino-uno' but board_type ≠ uno:
rename id→board_type and rewrite wire endpoints.
Case B — single-board normal: keep verbatim.
Case C — multi-board: recreate one board per distinct ref, infer
kind by stripping trailing -N suffix.
Also moves any flat files into the active board's group subdir.
Stdlib-only, runs from host or `docker exec`.
Docker
- `Dockerfile.standalone` now copies `backend/scripts/` into the image
so the backfill is callable via `docker exec velxio-app python
/app/scripts/backfill_boards_2026_05.py --apply`.
Verified locally on the restored production backup (363 projects):
33 Case A, 316 Case B, 14 Case C, 135 wire endpoints renamed, 0 orphans.
Re-running the script after apply skips all 363 (idempotent).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 23:43:33 +07:00
|
|
|
const payload = buildSavePayload({
|
2026-03-06 20:14:50 +07:00
|
|
|
name: name.trim(),
|
|
|
|
|
description: description.trim() || undefined,
|
feat: persist multi-board projects + add auto-save
The project save/load pipeline only persisted a single `board_type`, so
multi-board workspaces silently lost every board except the active one
on save, and wires referencing the dropped boards' IDs orphaned to the
canvas corner on reload. An audit of the production backup found 74/306
projects (24%) with at least one orphaned wire and 174/301 non-trivial
projects whose code was still the default Blink template — strong signal
that users save once and never re-save.
Backend
- Add `boards_json` column on `projects` with idempotent ALTER TABLE in
the lifespan migration list.
- New `FileGroup` schema + `file_groups` array on
ProjectCreate/Update/Response. Legacy `files`/`code` kept for back-compat.
- `project_files.py` now uses `{pid}/{groupId}/{filename}` subdirs via
`read_groups`/`write_groups`. Legacy flat layouts are auto-promoted on
read; legacy single-list `files` only updates the active group, leaving
other boards' files intact.
- `_persist_files_from_body` honors file_groups → files → code priority.
Frontend
- `useSimulatorStore.addBoard` accepts an optional `explicitId` so
saved board IDs can be restored verbatim (wires reference IDs literally).
- New `loadProjectState({boards, fileGroups, components, wires,
activeBoardId})` action: tears down current boards, recreates from the
payload, restores file groups atomically, recalculates wire positions
on the next frame, and refreshes the Interconnect.
- `useEditorStore.replaceFileGroups` for atomic multi-group restore.
- `SaveProjectModal` and `ProjectByIdPage`/`ProjectPage` now go through
`buildSavePayload` / `buildLoadPayload` (handles pre-backfill projects
by synthesising a default board from `board_type`).
Auto-save (#useAutoSaveProject hook)
- 2.5s debounced silent PUT triggered ONLY when an authenticated user
has a `currentProject` with a UUID. State hash detects real changes
vs. UI-only churn; baseline is reset on project load so the just-loaded
state isn't immediately re-saved.
- `beforeunload` flush via `fetch keepalive: true` (supports PUT +
credentials, survives unload).
- Compact status indicator in `AppHeader` (idle/dirty/saving/saved/error).
Backfill script (one-off, idempotent)
- `backend/scripts/backfill_boards_2026_05.py` populates `boards_json`
for legacy projects. Heuristic per project, based on which board IDs
the wires reference:
Case A — wires only ref 'arduino-uno' but board_type ≠ uno:
rename id→board_type and rewrite wire endpoints.
Case B — single-board normal: keep verbatim.
Case C — multi-board: recreate one board per distinct ref, infer
kind by stripping trailing -N suffix.
Also moves any flat files into the active board's group subdir.
Stdlib-only, runs from host or `docker exec`.
Docker
- `Dockerfile.standalone` now copies `backend/scripts/` into the image
so the backfill is callable via `docker exec velxio-app python
/app/scripts/backfill_boards_2026_05.py --apply`.
Verified locally on the restored production backup (363 projects):
33 Case A, 316 Case B, 14 Case C, 135 wire endpoints renamed, 0 orphans.
Re-running the script after apply skips all 363 (idempotent).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 23:43:33 +07:00
|
|
|
isPublic,
|
|
|
|
|
});
|
2026-03-06 20:14:50 +07:00
|
|
|
|
2026-04-09 07:34:43 +07:00
|
|
|
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);
|
|
|
|
|
|
2026-03-06 20:14:50 +07:00
|
|
|
try {
|
|
|
|
|
let saved;
|
2026-04-09 07:34:43 +07:00
|
|
|
if (isValidUpdate) {
|
|
|
|
|
saved = await updateProject(currentProject!.id, payload);
|
2026-03-25 13:06:38 +07:00
|
|
|
trackSaveProject();
|
2026-03-06 20:14:50 +07:00
|
|
|
} else {
|
|
|
|
|
saved = await createProject(payload);
|
2026-03-13 05:02:24 +07:00
|
|
|
trackCreateProject();
|
2026-03-06 20:14:50 +07:00
|
|
|
}
|
2026-04-09 07:34:43 +07:00
|
|
|
|
2026-03-06 20:14:50 +07:00
|
|
|
setCurrentProject({
|
|
|
|
|
id: saved.id,
|
|
|
|
|
slug: saved.slug,
|
|
|
|
|
ownerUsername: saved.owner_username,
|
|
|
|
|
isPublic: saved.is_public,
|
|
|
|
|
});
|
2026-03-07 06:38:06 +07:00
|
|
|
navigate(`/project/${saved.id}`, { replace: true });
|
2026-03-06 20:14:50 +07:00
|
|
|
onClose();
|
|
|
|
|
} catch (err: any) {
|
2026-04-06 03:27:17 +07:00
|
|
|
if (!err?.response) {
|
feat(editor): translate Save / Share / LoginPrompt modals (Editor block 3)
The three modal dialogs that fire during the editor's routine save +
share + auth-required flows are now fully localised across all 9
locales.
LoginPromptModal
- Title, body, and the three buttons (Sign in / Create account /
Cancel). Sign in / Sign up Links use localize() so a Spanish
reader prompted to log in lands at /es/login rather than dropping
back to English.
SaveProjectModal
- Title (toggles between Save / Update), name + description fields
with placeholders, save button (toggles between Save / Update /
Saving…), Cancel button.
- Visibility toggle: Public / Private label + hint copy under the
icon.
- All four error paths now go through t() with a {{status}}
interpolation for the generic HTTP failure message.
ShareModal
- Title, public/private label + hint pair, "Make private" /
"Make public" toggle, Copy button, the warning shown when the
project is private, and the Close button.
Hand-curated translations for all 8 non-English locales. Status
codes (403) and shortcut markers preserved.
2026-05-09 13:12:52 +07:00
|
|
|
setError(t('editor.saveProject.errors.unreachable'));
|
2026-04-06 03:27:17 +07:00
|
|
|
} else if (err.response.status === 401) {
|
feat(editor): translate Save / Share / LoginPrompt modals (Editor block 3)
The three modal dialogs that fire during the editor's routine save +
share + auth-required flows are now fully localised across all 9
locales.
LoginPromptModal
- Title, body, and the three buttons (Sign in / Create account /
Cancel). Sign in / Sign up Links use localize() so a Spanish
reader prompted to log in lands at /es/login rather than dropping
back to English.
SaveProjectModal
- Title (toggles between Save / Update), name + description fields
with placeholders, save button (toggles between Save / Update /
Saving…), Cancel button.
- Visibility toggle: Public / Private label + hint copy under the
icon.
- All four error paths now go through t() with a {{status}}
interpolation for the generic HTTP failure message.
ShareModal
- Title, public/private label + hint pair, "Make private" /
"Make public" toggle, Copy button, the warning shown when the
project is private, and the Close button.
Hand-curated translations for all 8 non-English locales. Status
codes (403) and shortcut markers preserved.
2026-05-09 13:12:52 +07:00
|
|
|
setError(t('editor.saveProject.errors.notAuth'));
|
2026-04-06 03:27:17 +07:00
|
|
|
} else {
|
feat(editor): translate Save / Share / LoginPrompt modals (Editor block 3)
The three modal dialogs that fire during the editor's routine save +
share + auth-required flows are now fully localised across all 9
locales.
LoginPromptModal
- Title, body, and the three buttons (Sign in / Create account /
Cancel). Sign in / Sign up Links use localize() so a Spanish
reader prompted to log in lands at /es/login rather than dropping
back to English.
SaveProjectModal
- Title (toggles between Save / Update), name + description fields
with placeholders, save button (toggles between Save / Update /
Saving…), Cancel button.
- Visibility toggle: Public / Private label + hint copy under the
icon.
- All four error paths now go through t() with a {{status}}
interpolation for the generic HTTP failure message.
ShareModal
- Title, public/private label + hint pair, "Make private" /
"Make public" toggle, Copy button, the warning shown when the
project is private, and the Close button.
Hand-curated translations for all 8 non-English locales. Status
codes (403) and shortcut markers preserved.
2026-05-09 13:12:52 +07:00
|
|
|
setError(
|
|
|
|
|
err.response?.data?.detail ||
|
|
|
|
|
t('editor.saveProject.errors.failedStatus', { status: err.response.status })
|
|
|
|
|
);
|
2026-04-06 03:27:17 +07:00
|
|
|
}
|
2026-03-06 20:14:50 +07:00
|
|
|
} finally {
|
|
|
|
|
setSaving(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div style={styles.overlay} onClick={onClose}>
|
|
|
|
|
<div style={styles.modal} onClick={(e) => e.stopPropagation()}>
|
feat(editor): translate Save / Share / LoginPrompt modals (Editor block 3)
The three modal dialogs that fire during the editor's routine save +
share + auth-required flows are now fully localised across all 9
locales.
LoginPromptModal
- Title, body, and the three buttons (Sign in / Create account /
Cancel). Sign in / Sign up Links use localize() so a Spanish
reader prompted to log in lands at /es/login rather than dropping
back to English.
SaveProjectModal
- Title (toggles between Save / Update), name + description fields
with placeholders, save button (toggles between Save / Update /
Saving…), Cancel button.
- Visibility toggle: Public / Private label + hint copy under the
icon.
- All four error paths now go through t() with a {{status}}
interpolation for the generic HTTP failure message.
ShareModal
- Title, public/private label + hint pair, "Make private" /
"Make public" toggle, Copy button, the warning shown when the
project is private, and the Close button.
Hand-curated translations for all 8 non-English locales. Status
codes (403) and shortcut markers preserved.
2026-05-09 13:12:52 +07:00
|
|
|
<h2 style={styles.title}>
|
|
|
|
|
{isUpdate ? t('editor.saveProject.titleUpdate') : t('editor.saveProject.titleSave')}
|
|
|
|
|
</h2>
|
2026-03-06 20:14:50 +07:00
|
|
|
|
|
|
|
|
{error && <div style={styles.error}>{error}</div>}
|
|
|
|
|
|
|
|
|
|
<form onSubmit={handleSave} style={styles.form}>
|
feat(editor): translate Save / Share / LoginPrompt modals (Editor block 3)
The three modal dialogs that fire during the editor's routine save +
share + auth-required flows are now fully localised across all 9
locales.
LoginPromptModal
- Title, body, and the three buttons (Sign in / Create account /
Cancel). Sign in / Sign up Links use localize() so a Spanish
reader prompted to log in lands at /es/login rather than dropping
back to English.
SaveProjectModal
- Title (toggles between Save / Update), name + description fields
with placeholders, save button (toggles between Save / Update /
Saving…), Cancel button.
- Visibility toggle: Public / Private label + hint copy under the
icon.
- All four error paths now go through t() with a {{status}}
interpolation for the generic HTTP failure message.
ShareModal
- Title, public/private label + hint pair, "Make private" /
"Make public" toggle, Copy button, the warning shown when the
project is private, and the Close button.
Hand-curated translations for all 8 non-English locales. Status
codes (403) and shortcut markers preserved.
2026-05-09 13:12:52 +07:00
|
|
|
<label style={styles.label}>{t('editor.saveProject.nameLabel')}</label>
|
2026-03-06 20:14:50 +07:00
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
value={name}
|
|
|
|
|
onChange={(e) => setName(e.target.value)}
|
|
|
|
|
required
|
|
|
|
|
style={styles.input}
|
|
|
|
|
autoFocus
|
feat(editor): translate Save / Share / LoginPrompt modals (Editor block 3)
The three modal dialogs that fire during the editor's routine save +
share + auth-required flows are now fully localised across all 9
locales.
LoginPromptModal
- Title, body, and the three buttons (Sign in / Create account /
Cancel). Sign in / Sign up Links use localize() so a Spanish
reader prompted to log in lands at /es/login rather than dropping
back to English.
SaveProjectModal
- Title (toggles between Save / Update), name + description fields
with placeholders, save button (toggles between Save / Update /
Saving…), Cancel button.
- Visibility toggle: Public / Private label + hint copy under the
icon.
- All four error paths now go through t() with a {{status}}
interpolation for the generic HTTP failure message.
ShareModal
- Title, public/private label + hint pair, "Make private" /
"Make public" toggle, Copy button, the warning shown when the
project is private, and the Close button.
Hand-curated translations for all 8 non-English locales. Status
codes (403) and shortcut markers preserved.
2026-05-09 13:12:52 +07:00
|
|
|
placeholder={t('editor.saveProject.namePlaceholder')}
|
2026-03-06 20:14:50 +07:00
|
|
|
/>
|
|
|
|
|
|
feat(editor): translate Save / Share / LoginPrompt modals (Editor block 3)
The three modal dialogs that fire during the editor's routine save +
share + auth-required flows are now fully localised across all 9
locales.
LoginPromptModal
- Title, body, and the three buttons (Sign in / Create account /
Cancel). Sign in / Sign up Links use localize() so a Spanish
reader prompted to log in lands at /es/login rather than dropping
back to English.
SaveProjectModal
- Title (toggles between Save / Update), name + description fields
with placeholders, save button (toggles between Save / Update /
Saving…), Cancel button.
- Visibility toggle: Public / Private label + hint copy under the
icon.
- All four error paths now go through t() with a {{status}}
interpolation for the generic HTTP failure message.
ShareModal
- Title, public/private label + hint pair, "Make private" /
"Make public" toggle, Copy button, the warning shown when the
project is private, and the Close button.
Hand-curated translations for all 8 non-English locales. Status
codes (403) and shortcut markers preserved.
2026-05-09 13:12:52 +07:00
|
|
|
<label style={styles.label}>{t('editor.saveProject.descriptionLabel')}</label>
|
2026-03-06 20:14:50 +07:00
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
value={description}
|
|
|
|
|
onChange={(e) => setDescription(e.target.value)}
|
|
|
|
|
style={styles.input}
|
feat(editor): translate Save / Share / LoginPrompt modals (Editor block 3)
The three modal dialogs that fire during the editor's routine save +
share + auth-required flows are now fully localised across all 9
locales.
LoginPromptModal
- Title, body, and the three buttons (Sign in / Create account /
Cancel). Sign in / Sign up Links use localize() so a Spanish
reader prompted to log in lands at /es/login rather than dropping
back to English.
SaveProjectModal
- Title (toggles between Save / Update), name + description fields
with placeholders, save button (toggles between Save / Update /
Saving…), Cancel button.
- Visibility toggle: Public / Private label + hint copy under the
icon.
- All four error paths now go through t() with a {{status}}
interpolation for the generic HTTP failure message.
ShareModal
- Title, public/private label + hint pair, "Make private" /
"Make public" toggle, Copy button, the warning shown when the
project is private, and the Close button.
Hand-curated translations for all 8 non-English locales. Status
codes (403) and shortcut markers preserved.
2026-05-09 13:12:52 +07:00
|
|
|
placeholder={t('editor.saveProject.descriptionPlaceholder')}
|
2026-03-06 20:14:50 +07:00
|
|
|
/>
|
|
|
|
|
|
2026-03-31 05:00:33 +07:00
|
|
|
<div
|
|
|
|
|
style={styles.visibilityToggle}
|
|
|
|
|
onClick={() => setIsPublic(!isPublic)}
|
|
|
|
|
role="button"
|
|
|
|
|
tabIndex={0}
|
|
|
|
|
>
|
|
|
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
|
|
|
|
{isPublic ? (
|
2026-04-22 02:45:45 +07:00
|
|
|
<svg
|
|
|
|
|
width="16"
|
|
|
|
|
height="16"
|
|
|
|
|
viewBox="0 0 24 24"
|
|
|
|
|
fill="none"
|
|
|
|
|
stroke="#4ade80"
|
|
|
|
|
strokeWidth="2"
|
|
|
|
|
strokeLinecap="round"
|
|
|
|
|
strokeLinejoin="round"
|
|
|
|
|
>
|
2026-03-31 05:00:33 +07:00
|
|
|
<circle cx="12" cy="12" r="10" />
|
|
|
|
|
<line x1="2" y1="12" x2="22" y2="12" />
|
|
|
|
|
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
|
|
|
|
|
</svg>
|
|
|
|
|
) : (
|
2026-04-22 02:45:45 +07:00
|
|
|
<svg
|
|
|
|
|
width="16"
|
|
|
|
|
height="16"
|
|
|
|
|
viewBox="0 0 24 24"
|
|
|
|
|
fill="none"
|
|
|
|
|
stroke="#f59e0b"
|
|
|
|
|
strokeWidth="2"
|
|
|
|
|
strokeLinecap="round"
|
|
|
|
|
strokeLinejoin="round"
|
|
|
|
|
>
|
2026-03-31 05:00:33 +07:00
|
|
|
<rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
|
|
|
|
|
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
|
|
|
|
|
</svg>
|
|
|
|
|
)}
|
|
|
|
|
<div>
|
2026-04-22 02:45:45 +07:00
|
|
|
<div
|
|
|
|
|
style={{ color: isPublic ? '#4ade80' : '#f59e0b', fontSize: 13, fontWeight: 600 }}
|
|
|
|
|
>
|
feat(editor): translate Save / Share / LoginPrompt modals (Editor block 3)
The three modal dialogs that fire during the editor's routine save +
share + auth-required flows are now fully localised across all 9
locales.
LoginPromptModal
- Title, body, and the three buttons (Sign in / Create account /
Cancel). Sign in / Sign up Links use localize() so a Spanish
reader prompted to log in lands at /es/login rather than dropping
back to English.
SaveProjectModal
- Title (toggles between Save / Update), name + description fields
with placeholders, save button (toggles between Save / Update /
Saving…), Cancel button.
- Visibility toggle: Public / Private label + hint copy under the
icon.
- All four error paths now go through t() with a {{status}}
interpolation for the generic HTTP failure message.
ShareModal
- Title, public/private label + hint pair, "Make private" /
"Make public" toggle, Copy button, the warning shown when the
project is private, and the Close button.
Hand-curated translations for all 8 non-English locales. Status
codes (403) and shortcut markers preserved.
2026-05-09 13:12:52 +07:00
|
|
|
{isPublic
|
|
|
|
|
? t('editor.saveProject.visibility.public')
|
|
|
|
|
: t('editor.saveProject.visibility.private')}
|
2026-03-31 05:00:33 +07:00
|
|
|
</div>
|
|
|
|
|
<div style={{ color: '#888', fontSize: 11 }}>
|
feat(editor): translate Save / Share / LoginPrompt modals (Editor block 3)
The three modal dialogs that fire during the editor's routine save +
share + auth-required flows are now fully localised across all 9
locales.
LoginPromptModal
- Title, body, and the three buttons (Sign in / Create account /
Cancel). Sign in / Sign up Links use localize() so a Spanish
reader prompted to log in lands at /es/login rather than dropping
back to English.
SaveProjectModal
- Title (toggles between Save / Update), name + description fields
with placeholders, save button (toggles between Save / Update /
Saving…), Cancel button.
- Visibility toggle: Public / Private label + hint copy under the
icon.
- All four error paths now go through t() with a {{status}}
interpolation for the generic HTTP failure message.
ShareModal
- Title, public/private label + hint pair, "Make private" /
"Make public" toggle, Copy button, the warning shown when the
project is private, and the Close button.
Hand-curated translations for all 8 non-English locales. Status
codes (403) and shortcut markers preserved.
2026-05-09 13:12:52 +07:00
|
|
|
{isPublic
|
|
|
|
|
? t('editor.saveProject.visibility.publicHint')
|
|
|
|
|
: t('editor.saveProject.visibility.privateHint')}
|
2026-03-31 05:00:33 +07:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-03-06 20:14:50 +07:00
|
|
|
|
|
|
|
|
<div style={styles.actions}>
|
|
|
|
|
<button type="submit" disabled={saving} style={styles.saveBtn}>
|
feat(editor): translate Save / Share / LoginPrompt modals (Editor block 3)
The three modal dialogs that fire during the editor's routine save +
share + auth-required flows are now fully localised across all 9
locales.
LoginPromptModal
- Title, body, and the three buttons (Sign in / Create account /
Cancel). Sign in / Sign up Links use localize() so a Spanish
reader prompted to log in lands at /es/login rather than dropping
back to English.
SaveProjectModal
- Title (toggles between Save / Update), name + description fields
with placeholders, save button (toggles between Save / Update /
Saving…), Cancel button.
- Visibility toggle: Public / Private label + hint copy under the
icon.
- All four error paths now go through t() with a {{status}}
interpolation for the generic HTTP failure message.
ShareModal
- Title, public/private label + hint pair, "Make private" /
"Make public" toggle, Copy button, the warning shown when the
project is private, and the Close button.
Hand-curated translations for all 8 non-English locales. Status
codes (403) and shortcut markers preserved.
2026-05-09 13:12:52 +07:00
|
|
|
{saving
|
|
|
|
|
? t('editor.saveProject.saving')
|
|
|
|
|
: isUpdate
|
|
|
|
|
? t('editor.saveProject.update')
|
|
|
|
|
: t('editor.saveProject.save')}
|
2026-03-06 20:14:50 +07:00
|
|
|
</button>
|
2026-04-22 02:45:45 +07:00
|
|
|
<button type="button" onClick={onClose} style={styles.cancelBtn}>
|
feat(editor): translate Save / Share / LoginPrompt modals (Editor block 3)
The three modal dialogs that fire during the editor's routine save +
share + auth-required flows are now fully localised across all 9
locales.
LoginPromptModal
- Title, body, and the three buttons (Sign in / Create account /
Cancel). Sign in / Sign up Links use localize() so a Spanish
reader prompted to log in lands at /es/login rather than dropping
back to English.
SaveProjectModal
- Title (toggles between Save / Update), name + description fields
with placeholders, save button (toggles between Save / Update /
Saving…), Cancel button.
- Visibility toggle: Public / Private label + hint copy under the
icon.
- All four error paths now go through t() with a {{status}}
interpolation for the generic HTTP failure message.
ShareModal
- Title, public/private label + hint pair, "Make private" /
"Make public" toggle, Copy button, the warning shown when the
project is private, and the Close button.
Hand-curated translations for all 8 non-English locales. Status
codes (403) and shortcut markers preserved.
2026-05-09 13:12:52 +07:00
|
|
|
{t('editor.saveProject.cancel')}
|
2026-04-22 02:45:45 +07:00
|
|
|
</button>
|
2026-03-06 20:14:50 +07:00
|
|
|
</div>
|
|
|
|
|
</form>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const styles: Record<string, React.CSSProperties> = {
|
2026-04-22 02:45:45 +07:00
|
|
|
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,
|
|
|
|
|
},
|
2026-03-06 20:14:50 +07:00
|
|
|
title: { color: '#ccc', margin: 0, fontSize: 18, fontWeight: 600 },
|
|
|
|
|
form: { display: 'flex', flexDirection: 'column', gap: 10 },
|
|
|
|
|
label: { color: '#9d9d9d', fontSize: 13 },
|
2026-04-22 02:45:45 +07:00
|
|
|
input: {
|
|
|
|
|
background: '#3c3c3c',
|
|
|
|
|
border: '1px solid #555',
|
|
|
|
|
borderRadius: 4,
|
|
|
|
|
padding: '8px 10px',
|
|
|
|
|
color: '#ccc',
|
|
|
|
|
fontSize: 14,
|
|
|
|
|
outline: 'none',
|
|
|
|
|
},
|
2026-03-06 20:14:50 +07:00
|
|
|
checkboxRow: { display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer' },
|
2026-04-22 02:45:45 +07:00
|
|
|
visibilityToggle: {
|
|
|
|
|
display: 'flex',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
padding: '8px 10px',
|
|
|
|
|
background: '#1e1e1e',
|
|
|
|
|
border: '1px solid #444',
|
|
|
|
|
borderRadius: 6,
|
|
|
|
|
cursor: 'pointer',
|
|
|
|
|
transition: 'border-color 0.15s',
|
|
|
|
|
},
|
2026-03-06 20:14:50 +07:00
|
|
|
actions: { display: 'flex', gap: 8, marginTop: 4 },
|
2026-04-22 02:45:45 +07:00
|
|
|
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,
|
|
|
|
|
},
|
2026-03-06 20:14:50 +07:00
|
|
|
};
|