feat(journey): replace new-journey placeholder with JourneyCreate form
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 2m40s
CI / OCR Service Tests (pull_request) Successful in 22s
CI / fail2ban Regex (pull_request) Has been cancelled
CI / Semgrep Security Scan (pull_request) Has been cancelled
CI / Compose Bucket Idempotency (pull_request) Has been cancelled
CI / Backend Unit Tests (pull_request) Has been cancelled

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-06-09 19:32:23 +02:00
parent abe5d62102
commit 3e54b6e90a
6 changed files with 93 additions and 13 deletions

View File

@@ -1170,7 +1170,7 @@
"journey_selector_journey_desc": "Eine kuratierte Auswahl von Briefen mit Notizen.",
"journey_selector_next_btn": "Weiter",
"journey_placeholder_back": "andere Auswahl",
"journey_placeholder_heading": "Lesereise-Editor folgt in #753",
"journey_create_submit": "Lesereise erstellen",
"journey_item_open_aria": "Brief vom {date} öffnen",
"journey_item_open_aria_undated": "Brief öffnen",
"journey_empty_state": "Diese Lesereise ist noch leer.",

View File

@@ -1170,7 +1170,7 @@
"journey_selector_journey_desc": "A curated selection of letters with notes.",
"journey_selector_next_btn": "Continue",
"journey_placeholder_back": "different selection",
"journey_placeholder_heading": "Reading Journey editor coming in #753",
"journey_create_submit": "Create reading journey",
"journey_item_open_aria": "Open letter from {date}",
"journey_item_open_aria_undated": "Open letter",
"journey_empty_state": "This reading journey is still empty.",

View File

@@ -1170,7 +1170,7 @@
"journey_selector_journey_desc": "Una selección curada de cartas con notas.",
"journey_selector_next_btn": "Continuar",
"journey_placeholder_back": "otra selección",
"journey_placeholder_heading": "Editor de viaje de lectura próximamente en #753",
"journey_create_submit": "Crear viaje de lectura",
"journey_item_open_aria": "Abrir carta del {date}",
"journey_item_open_aria_undated": "Abrir carta",
"journey_empty_state": "Este viaje de lectura está vacío.",

View File

@@ -4,6 +4,7 @@ import { m } from '$lib/paraglide/messages.js';
import BackButton from '$lib/shared/primitives/BackButton.svelte';
import TypeSelector from './TypeSelector.svelte';
import StoryCreate from './StoryCreate.svelte';
import JourneyCreate from './JourneyCreate.svelte';
import type { PageData } from './$types';
let { data }: { data: PageData } = $props();
@@ -19,12 +20,7 @@ let { data }: { data: PageData } = $props();
{#if data.selectedType === 'STORY'}
<StoryCreate initialPersons={data.initialPersons} />
{:else if data.selectedType === 'JOURNEY'}
<div data-testid="journey-placeholder">
<p class="mb-4 font-sans text-base text-ink-2">{m.journey_placeholder_heading()}</p>
<a href="/geschichten/new" class="font-sans text-sm text-ink-3 underline hover:text-ink">
{m.journey_placeholder_back()}
</a>
</div>
<JourneyCreate />
{:else}
<TypeSelector onweiter={(type) => goto(`/geschichten/new?type=${type}`)} />
{/if}

View File

@@ -0,0 +1,85 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { m } from '$lib/paraglide/messages.js';
import { getErrorMessage } from '$lib/shared/errors';
import { csrfFetch } from '$lib/shared/cookies';
let title = $state('');
let titleTouched = $state(false);
let submitting = $state(false);
let errorMessage: string | null = $state(null);
const titleEmpty = $derived(title.trim().length === 0);
const showTitleError = $derived(titleEmpty && titleTouched);
async function handleSubmit(e: SubmitEvent) {
e.preventDefault();
titleTouched = true;
if (titleEmpty) return;
submitting = true;
errorMessage = null;
try {
const res = await csrfFetch('/api/geschichten', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: title.trim(),
type: 'JOURNEY',
status: 'DRAFT',
body: '',
personIds: []
})
});
if (!res.ok) {
const code = (await res.json().catch(() => ({})))?.code;
errorMessage = getErrorMessage(code);
return;
}
const created = await res.json();
goto(`/geschichten/${created.id}/edit`);
} finally {
submitting = false;
}
}
</script>
<form onsubmit={handleSubmit} class="max-w-lg">
{#if errorMessage}
<div
class="mb-4 rounded border border-danger bg-danger/10 p-3 text-sm text-danger"
role="alert"
>
{errorMessage}
</div>
{/if}
<div class="mb-4">
<input
type="text"
bind:value={title}
onblur={() => (titleTouched = true)}
placeholder={m.geschichte_editor_title_placeholder()}
aria-invalid={showTitleError}
class="block w-full rounded border px-3 py-2 font-serif text-lg text-ink placeholder:text-ink-3 focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring {showTitleError
? 'border-danger'
: 'border-line'}"
/>
{#if showTitleError}
<p class="mt-1 font-sans text-xs text-danger">{m.geschichte_editor_title_required()}</p>
{/if}
</div>
<div class="flex items-center gap-4">
<button
type="submit"
disabled={submitting}
class="rounded bg-brand-navy px-4 py-2 font-sans text-sm font-medium text-white hover:opacity-90 focus:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring disabled:opacity-50"
>
{m.journey_create_submit()}
</button>
<a href="/geschichten/new" class="font-sans text-sm text-ink-3 underline hover:text-ink">
{m.journey_placeholder_back()}
</a>
</div>
</form>

View File

@@ -73,14 +73,13 @@ describe('geschichten/new page', () => {
await expect.element(page.getByRole('radiogroup')).toBeVisible();
});
it('shows JOURNEY placeholder when selectedType is JOURNEY', async () => {
it('shows JourneyCreate form when selectedType is JOURNEY', async () => {
render(GeschichtenNewPage, { props: { data: baseData({ selectedType: 'JOURNEY' }) } });
const placeholder = document.querySelector('[data-testid="journey-placeholder"]');
expect(placeholder).not.toBeNull();
await expect.element(page.getByRole('button', { name: /Lesereise erstellen/i })).toBeVisible();
});
it('JOURNEY placeholder offers a return-to-selection link', async () => {
it('JOURNEY create form offers a return-to-selection link', async () => {
render(GeschichtenNewPage, { props: { data: baseData({ selectedType: 'JOURNEY' }) } });
const backLink = page.getByRole('link', { name: /andere Auswahl/i });