Add reusable project templates

This commit is contained in:
2026-09-08 10:25:52 +00:00
committed by Twyco
parent 5abfae2736
commit 40f9b473fc
24 changed files with 1154 additions and 88 deletions
+63 -5
View File
@@ -31,15 +31,19 @@ const breadcrumbs: BreadcrumbItem[] = [
},
];
const defaultTemplateId = props.selectedSourceTemplateId ?? props.sourceTemplates[0]?.id ?? null;
const form = useForm({
copy_areas: [] as string[],
creation_mode: 'scratch',
creation_mode: defaultTemplateId !== null ? 'template' : 'scratch',
key: '',
name: '',
source_project_id: props.sourceProjects[0]?.id ? String(props.sourceProjects[0].id) : '',
source_template_id: defaultTemplateId !== null ? String(defaultTemplateId) : '',
});
const hasSourceProjects = computed(() => props.sourceProjects.length > 0);
const hasSourceTemplates = computed(() => props.sourceTemplates.length > 0);
const copyAreasError = computed(() => {
return (
@@ -55,6 +59,10 @@ const submit = (): void => {
form.copy_areas = [];
}
if (form.creation_mode !== 'template') {
form.source_template_id = '';
}
form.post(ProjectController.store(), {
preserveScroll: true,
});
@@ -68,7 +76,7 @@ const submit = (): void => {
<div class="mx-auto flex w-full max-w-3xl flex-1 flex-col gap-6 p-4">
<Heading
title="Create project"
description="Create a fresh project or start from an existing project's settings without copying any tickets."
description="Create a fresh project, duplicate selected settings from an existing project, or start from a saved template."
/>
<Card>
@@ -84,7 +92,7 @@ const submit = (): void => {
<form class="space-y-6" @submit.prevent="submit">
<div class="grid gap-3">
<Label>How do you want to start?</Label>
<div class="grid gap-3 sm:grid-cols-2">
<div class="grid gap-3 sm:grid-cols-3">
<Label class="flex cursor-pointer items-start gap-3 rounded-xl border p-4">
<input
v-model="form.creation_mode"
@@ -118,6 +126,25 @@ const submit = (): void => {
</p>
</div>
</Label>
<Label
class="flex items-start gap-3 rounded-xl border p-4"
:class="hasSourceTemplates ? 'cursor-pointer' : 'cursor-not-allowed opacity-60'"
>
<input
v-model="form.creation_mode"
type="radio"
value="template"
class="mt-1"
:disabled="!hasSourceTemplates"
/>
<div class="space-y-1">
<div class="font-medium">Create from template</div>
<p class="text-muted-foreground text-sm">
Reuse a saved configuration template.
</p>
</div>
</Label>
</div>
</div>
@@ -213,8 +240,39 @@ const submit = (): void => {
</div>
</div>
<div v-else-if="!hasSourceProjects" class="text-muted-foreground text-sm">
No accessible source projects are available for duplication yet.
<div
v-else-if="form.creation_mode === 'template'"
class="space-y-4 rounded-xl border border-dashed p-4"
>
<div class="grid gap-2">
<Label for="source_template_id">Template</Label>
<select
id="source_template_id"
v-model="form.source_template_id"
class="border-input bg-background ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 w-full rounded-md border px-3 py-2 text-sm focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50"
:disabled="!hasSourceTemplates"
>
<option value="">Select a template</option>
<option
v-for="sourceTemplate in sourceTemplates"
:key="sourceTemplate.id"
:value="String(sourceTemplate.id)"
>
{{ sourceTemplate.name }} ({{ sourceTemplate.slug }})
</option>
</select>
<p class="text-muted-foreground text-sm">
Templates apply their saved configuration groups to the new project.
</p>
<InputError :message="form.errors.source_template_id" />
</div>
</div>
<div
v-else-if="!hasSourceProjects && !hasSourceTemplates"
class="text-muted-foreground text-sm"
>
No accessible source projects or saved templates are available yet.
</div>
<div class="flex items-center gap-3">
+10
View File
@@ -94,6 +94,16 @@ const projectAreas = [
</Link>
</Button>
<Button
v-if="$page.props.auth.permissions.projectCreate"
as-child
variant="outline"
>
<Link :href="`/projects/${project.id}/templates/create`">
Save as template
</Link>
</Button>
<Button as-child variant="outline">
<Link :href="index()">Back to projects</Link>
</Button>