Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions apps/web/src/components/EntryShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ interface Props {
designSystems: DesignSystemSummary[];
projects: Project[];
templates: ProjectTemplate[];
onDeleteTemplate?: (id: string) => Promise<boolean>;
promptTemplates: PromptTemplateSummary[];
defaultDesignSystemId: string | null;
connectors: ConnectorDetail[];
Expand Down Expand Up @@ -215,6 +216,7 @@ export function EntryShell({
designSystems,
projects,
templates,
onDeleteTemplate,
promptTemplates,
defaultDesignSystemId,
connectors,
Expand Down Expand Up @@ -603,6 +605,7 @@ export function EntryShell({
designSystems={designSystems}
defaultDesignSystemId={defaultDesignSystemId}
templates={templates}
{...(onDeleteTemplate ? { onDeleteTemplate } : {})}
promptTemplates={promptTemplates}
connectors={connectors}
connectorsLoading={connectorsLoading}
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/components/EntryView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ export function EntryView({
designSystems={designSystems}
projects={projects}
templates={templates}
onDeleteTemplate={onDeleteTemplate}
promptTemplates={promptTemplates}
defaultDesignSystemId={defaultDesignSystemId}
connectors={connectors}
Expand Down
3 changes: 3 additions & 0 deletions apps/web/src/components/NewProjectModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ interface Props {
designSystems: DesignSystemSummary[];
defaultDesignSystemId: string | null;
templates: ProjectTemplate[];
onDeleteTemplate?: (id: string) => Promise<boolean>;
promptTemplates: PromptTemplateSummary[];
mediaProviders?: Record<string, MediaProviderCredentials>;
connectors?: ConnectorDetail[];
Expand All @@ -45,6 +46,7 @@ export function NewProjectModal({
designSystems,
defaultDesignSystemId,
templates,
onDeleteTemplate,
promptTemplates,
mediaProviders,
connectors,
Expand Down Expand Up @@ -115,6 +117,7 @@ export function NewProjectModal({
designSystems={designSystems}
defaultDesignSystemId={defaultDesignSystemId}
templates={templates}
{...(onDeleteTemplate ? { onDeleteTemplate } : {})}
promptTemplates={promptTemplates}
{...(mediaProviders ? { mediaProviders } : {})}
{...(connectors ? { connectors } : {})}
Expand Down
109 changes: 103 additions & 6 deletions apps/web/src/components/NewProjectPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1300,9 +1300,57 @@ function TemplatePicker({
onDelete?: (id: string) => Promise<boolean>;
}) {
const t = useT();
const [confirmTarget, setConfirmTarget] = useState<ProjectTemplate | null>(null);
const [deleteError, setDeleteError] = useState<string | null>(null);
const [deleting, setDeleting] = useState(false);

function dismissConfirm() {
if (deleting) return;
setConfirmTarget(null);
setDeleteError(null);
}

useEffect(() => {
if (!confirmTarget) return;
const onKey = (e: KeyboardEvent) => {
if (e.key !== 'Escape') return;
e.preventDefault();
e.stopPropagation();
if (deleting) return;
setConfirmTarget(null);
setDeleteError(null);
};
document.addEventListener('keydown', onKey, true);
return () => document.removeEventListener('keydown', onKey, true);
}, [confirmTarget, deleting]);

async function handleConfirmDelete() {
if (!confirmTarget || !onDelete) return;
setDeleting(true);
setDeleteError(null);
try {
const ok = await onDelete(confirmTarget.id);
if (!ok) {
setDeleteError(t('newproj.deleteTemplateError'));
return;
}
if (value === confirmTarget.id) onChange(null);
setConfirmTarget(null);
} catch {
setDeleteError(t('newproj.deleteTemplateError'));
} finally {
setDeleting(false);
}
}

return (
<div className="newproj-section">
<label className="newproj-label">{t('newproj.templateLabel')}</label>
{deleteError ? (
<p className="prompt-template-error" role="alert" data-testid="template-delete-error">
<span className="prompt-template-error-msg">{deleteError}</span>
</p>
) : null}
{templates.length === 0 ? (
<div className="template-howto">
<span className="template-howto-title">
Expand All @@ -1325,17 +1373,65 @@ function TemplatePicker({
key={tpl.id}
active={value === tpl.id}
onClick={() => onChange(tpl.id)}
onDelete={onDelete ? async () => {
const ok = await onDelete(tpl.id);
if (ok && value === tpl.id) onChange(null);
} : () => {}}
onDelete={
onDelete
? () => {
setDeleteError(null);
setConfirmTarget(tpl);
}
: () => {}
}
name={tpl.name}
description={tpl.description ?? fallbackDesc}
/>
);
})}
</div>
)}
{confirmTarget ? (
<div
className="modal-backdrop template-delete-confirm-backdrop"
onClick={dismissConfirm}
>
<div
className="modal modal-confirm"
onClick={(e) => e.stopPropagation()}
onKeyDown={(e) => {
if (e.key !== 'Escape') return;
e.stopPropagation();
dismissConfirm();
}}
role="alertdialog"
aria-modal="true"
data-testid="template-delete-confirm-dialog"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opening this nested confirmation inside NewProjectModal introduces an Escape-key regression: while this dialog is open, pressing Escape still hits the parent modal's document-level handler and closes the entire New Project flow instead of just dismissing the delete confirmation. That drops any in-progress form state the user has already entered, so the new safety prompt becomes destructive for keyboard users. Please intercept Escape while confirmTarget is set (or temporarily suppress the parent handler) so Escape cancels only this dialog.

🔁 Powered by Looper · runner=reviewer · agent=opencode · An autonomous AI dev team for your GitHub repos.

>
<h2>{t('newproj.deleteTemplateTitle')}</h2>
<p className="modal-confirm-message">
{t('newproj.deleteTemplateBody', { name: confirmTarget.name })}
</p>
<div className="row">
<button
type="button"
disabled={deleting}
data-testid="template-delete-cancel"
onClick={dismissConfirm}
>
{t('common.cancel')}
</button>
<button
type="button"
className="primary danger"
autoFocus
disabled={deleting}
data-testid="template-delete-confirm"
onClick={() => void handleConfirmDelete()}
>
{t('newproj.deleteTemplateConfirm')}
</button>
</div>
</div>
</div>
) : null}
</div>
);
}
Expand Down Expand Up @@ -1649,6 +1745,7 @@ function TemplateOption({
name: string;
description: string;
}) {
const t = useT();
return (
<div className={`template-option${active ? ' active' : ''}`}>
<button
Expand All @@ -1667,8 +1764,8 @@ function TemplateOption({
type="button"
className="template-option-delete"
onClick={(e) => { e.stopPropagation(); onDelete(); }}
title="Delete template"
aria-label={`Delete template ${name}`}
title={t('newproj.deleteTemplateAria', { name })}
aria-label={t('newproj.deleteTemplateAria', { name })}
>
</button>
Expand Down
6 changes: 6 additions & 0 deletions apps/web/src/i18n/locales/ar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,12 @@ export const ar: Dict = {
'newproj.savedTemplate': 'قالب محفوظ',
'newproj.fileSingular': 'ملف',
'newproj.filePlural': 'ملفات',
'newproj.deleteTemplateTitle': 'Delete template?',
'newproj.deleteTemplateBody':
'This will permanently remove "{name}" from your saved templates.',
'newproj.deleteTemplateConfirm': 'Delete',
'newproj.deleteTemplateError': 'Could not delete template. Try again.',
'newproj.deleteTemplateAria': 'Delete template {name}',
'newproj.create': 'إنشاء',
'newproj.createFromTemplate': 'إنشاء من قالب',
'newproj.createDisabledTitle':
Expand Down
6 changes: 6 additions & 0 deletions apps/web/src/i18n/locales/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,12 @@ export const de: Dict = {
'newproj.savedTemplate': 'Gespeichertes Template',
'newproj.fileSingular': 'Datei',
'newproj.filePlural': 'Dateien',
'newproj.deleteTemplateTitle': 'Delete template?',
'newproj.deleteTemplateBody':
'This will permanently remove "{name}" from your saved templates.',
'newproj.deleteTemplateConfirm': 'Delete',
'newproj.deleteTemplateError': 'Could not delete template. Try again.',
'newproj.deleteTemplateAria': 'Delete template {name}',
'newproj.create': 'Erstellen',
'newproj.createFromTemplate': 'Aus Template erstellen',
'newproj.createDisabledTitle':
Expand Down
6 changes: 6 additions & 0 deletions apps/web/src/i18n/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -924,6 +924,12 @@ export const en: Dict = {
'newproj.savedTemplate': 'Saved template',
'newproj.fileSingular': 'file',
'newproj.filePlural': 'files',
'newproj.deleteTemplateTitle': 'Delete template?',
'newproj.deleteTemplateBody':
'This will permanently remove "{name}" from your saved templates.',
'newproj.deleteTemplateConfirm': 'Delete',
'newproj.deleteTemplateError': 'Could not delete template. Try again.',
'newproj.deleteTemplateAria': 'Delete template {name}',
'newproj.create': 'Create',
'newproj.createLiveArtifact': 'Create live artifact',
'newproj.createFromTemplate': 'Create from template',
Expand Down
6 changes: 6 additions & 0 deletions apps/web/src/i18n/locales/es-ES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,12 @@ export const esES: Dict = {
'newproj.savedTemplate': 'Plantilla guardada',
'newproj.fileSingular': 'archivo',
'newproj.filePlural': 'archivos',
'newproj.deleteTemplateTitle': 'Delete template?',
'newproj.deleteTemplateBody':
'This will permanently remove "{name}" from your saved templates.',
'newproj.deleteTemplateConfirm': 'Delete',
'newproj.deleteTemplateError': 'Could not delete template. Try again.',
'newproj.deleteTemplateAria': 'Delete template {name}',
'newproj.create': 'Crear',
'newproj.createFromTemplate': 'Crear desde plantilla',
'newproj.createDisabledTitle':
Expand Down
6 changes: 6 additions & 0 deletions apps/web/src/i18n/locales/fa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,12 @@ export const fa: Dict = {
'newproj.savedTemplate': 'قالب ذخیره شده',
'newproj.fileSingular': 'فایل',
'newproj.filePlural': 'فایل',
'newproj.deleteTemplateTitle': 'Delete template?',
'newproj.deleteTemplateBody':
'This will permanently remove "{name}" from your saved templates.',
'newproj.deleteTemplateConfirm': 'Delete',
'newproj.deleteTemplateError': 'Could not delete template. Try again.',
'newproj.deleteTemplateAria': 'Delete template {name}',
'newproj.create': 'ایجاد',
'newproj.createLiveArtifact': 'ایجاد مصنوع زنده',
'newproj.createFromTemplate': 'ایجاد از قالب',
Expand Down
6 changes: 6 additions & 0 deletions apps/web/src/i18n/locales/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,12 @@ export const fr: Dict = {
'newproj.savedTemplate': 'Modèle enregistré',
'newproj.fileSingular': 'fichier',
'newproj.filePlural': 'fichiers',
'newproj.deleteTemplateTitle': 'Delete template?',
'newproj.deleteTemplateBody':
'This will permanently remove "{name}" from your saved templates.',
'newproj.deleteTemplateConfirm': 'Delete',
'newproj.deleteTemplateError': 'Could not delete template. Try again.',
'newproj.deleteTemplateAria': 'Delete template {name}',
'newproj.create': 'Créer',
'newproj.createFromTemplate': 'Créer depuis le modèle',
'newproj.createDisabledTitle':
Expand Down
6 changes: 6 additions & 0 deletions apps/web/src/i18n/locales/hu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,12 @@ export const hu: Dict = {
'newproj.savedTemplate': 'Mentett sablon',
'newproj.fileSingular': 'fájl',
'newproj.filePlural': 'fájl',
'newproj.deleteTemplateTitle': 'Delete template?',
'newproj.deleteTemplateBody':
'This will permanently remove "{name}" from your saved templates.',
'newproj.deleteTemplateConfirm': 'Delete',
'newproj.deleteTemplateError': 'Could not delete template. Try again.',
'newproj.deleteTemplateAria': 'Delete template {name}',
'newproj.create': 'Létrehozás',
'newproj.createFromTemplate': 'Létrehozás sablonból',
'newproj.createDisabledTitle':
Expand Down
6 changes: 6 additions & 0 deletions apps/web/src/i18n/locales/id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,12 @@ export const id: Dict = {
'newproj.savedTemplate': 'Templat tersimpan',
'newproj.fileSingular': 'berkas',
'newproj.filePlural': 'berkas',
'newproj.deleteTemplateTitle': 'Delete template?',
'newproj.deleteTemplateBody':
'This will permanently remove "{name}" from your saved templates.',
'newproj.deleteTemplateConfirm': 'Delete',
'newproj.deleteTemplateError': 'Could not delete template. Try again.',
'newproj.deleteTemplateAria': 'Delete template {name}',
'newproj.create': 'Buat',
'newproj.createLiveArtifact': 'Buat live artifact',
'newproj.createFromTemplate': 'Buat dari templat',
Expand Down
6 changes: 6 additions & 0 deletions apps/web/src/i18n/locales/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,12 @@ export const it: Dict = {
'newproj.savedTemplate': 'Modello salvato',
'newproj.fileSingular': 'file',
'newproj.filePlural': 'file',
'newproj.deleteTemplateTitle': 'Delete template?',
'newproj.deleteTemplateBody':
'This will permanently remove "{name}" from your saved templates.',
'newproj.deleteTemplateConfirm': 'Delete',
'newproj.deleteTemplateError': 'Could not delete template. Try again.',
'newproj.deleteTemplateAria': 'Delete template {name}',
'newproj.create': 'Crea',
'newproj.createFromTemplate': 'Crea dal modello',
'newproj.createDisabledTitle':
Expand Down
6 changes: 6 additions & 0 deletions apps/web/src/i18n/locales/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,12 @@ export const ja: Dict = {
'newproj.savedTemplate': '保存済みテンプレート',
'newproj.fileSingular': 'ファイル',
'newproj.filePlural': 'ファイル',
'newproj.deleteTemplateTitle': 'Delete template?',
'newproj.deleteTemplateBody':
'This will permanently remove "{name}" from your saved templates.',
'newproj.deleteTemplateConfirm': 'Delete',
'newproj.deleteTemplateError': 'Could not delete template. Try again.',
'newproj.deleteTemplateAria': 'Delete template {name}',
'newproj.create': '作成',
'newproj.createFromTemplate': 'テンプレートから作成',
'newproj.createDisabledTitle':
Expand Down
6 changes: 6 additions & 0 deletions apps/web/src/i18n/locales/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,12 @@ export const ko: Dict = {
'newproj.savedTemplate': '저장된 템플릿',
'newproj.fileSingular': '파일',
'newproj.filePlural': '파일들',
'newproj.deleteTemplateTitle': 'Delete template?',
'newproj.deleteTemplateBody':
'This will permanently remove "{name}" from your saved templates.',
'newproj.deleteTemplateConfirm': 'Delete',
'newproj.deleteTemplateError': 'Could not delete template. Try again.',
'newproj.deleteTemplateAria': 'Delete template {name}',
'newproj.create': '생성',
'newproj.createFromTemplate': '템플릿으로 생성',
'newproj.createDisabledTitle':
Expand Down
6 changes: 6 additions & 0 deletions apps/web/src/i18n/locales/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,12 @@ export const pl: Dict = {
'newproj.savedTemplate': 'Zapisany szablon',
'newproj.fileSingular': 'plik',
'newproj.filePlural': 'pliki',
'newproj.deleteTemplateTitle': 'Delete template?',
'newproj.deleteTemplateBody':
'This will permanently remove "{name}" from your saved templates.',
'newproj.deleteTemplateConfirm': 'Delete',
'newproj.deleteTemplateError': 'Could not delete template. Try again.',
'newproj.deleteTemplateAria': 'Delete template {name}',
'newproj.create': 'Utwórz',
'newproj.createFromTemplate': 'Utwórz z szablonu',
'newproj.createDisabledTitle':
Expand Down
6 changes: 6 additions & 0 deletions apps/web/src/i18n/locales/pt-BR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,12 @@ export const ptBR: Dict = {
'newproj.savedTemplate': 'Template salvo',
'newproj.fileSingular': 'arquivo',
'newproj.filePlural': 'arquivos',
'newproj.deleteTemplateTitle': 'Delete template?',
'newproj.deleteTemplateBody':
'This will permanently remove "{name}" from your saved templates.',
'newproj.deleteTemplateConfirm': 'Delete',
'newproj.deleteTemplateError': 'Could not delete template. Try again.',
'newproj.deleteTemplateAria': 'Delete template {name}',
'newproj.create': 'Criar',
'newproj.createLiveArtifact': 'Criar artefato live',
'newproj.createFromTemplate': 'Criar a partir do template',
Expand Down
6 changes: 6 additions & 0 deletions apps/web/src/i18n/locales/ru.ts
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,12 @@ export const ru: Dict = {
'newproj.savedTemplate': 'Сохраненный шаблон',
'newproj.fileSingular': 'файл',
'newproj.filePlural': 'файлов',
'newproj.deleteTemplateTitle': 'Delete template?',
'newproj.deleteTemplateBody':
'This will permanently remove "{name}" from your saved templates.',
'newproj.deleteTemplateConfirm': 'Delete',
'newproj.deleteTemplateError': 'Could not delete template. Try again.',
'newproj.deleteTemplateAria': 'Delete template {name}',
'newproj.create': 'Создать',
'newproj.createLiveArtifact': 'Создать live-артефакт',
'newproj.createFromTemplate': 'Создать из шаблона',
Expand Down
6 changes: 6 additions & 0 deletions apps/web/src/i18n/locales/th.ts
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,12 @@ export const th: Dict = {
'newproj.savedTemplate': 'เทมเพลตที่บันทึกแล้ว',
'newproj.fileSingular': 'ไฟล์',
'newproj.filePlural': 'ไฟล์',
'newproj.deleteTemplateTitle': 'Delete template?',
'newproj.deleteTemplateBody':
'This will permanently remove "{name}" from your saved templates.',
'newproj.deleteTemplateConfirm': 'Delete',
'newproj.deleteTemplateError': 'Could not delete template. Try again.',
'newproj.deleteTemplateAria': 'Delete template {name}',
'newproj.create': 'สร้าง',
'newproj.createLiveArtifact': 'สร้าง live artifact',
'newproj.createFromTemplate': 'สร้างจากเทมเพลต',
Expand Down
Loading