Skip to content

Commit d9566a3

Browse files
committed
feat(setup): modularize wizard, demo-init for events/notes, add E2E test
1 parent f2ea6dc commit d9566a3

6 files changed

Lines changed: 133 additions & 21 deletions

File tree

e2e/setup-demo.spec.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { test, expect } from '@playwright/test'
2+
3+
test('SetupWizard demo mode activates demo data', async ({ page }) => {
4+
await page.goto('/setup')
5+
await page.fill('#orgName', 'Musikverein Alsdorf')
6+
await page.selectOption('#language', 'de')
7+
await page.check('input[type="checkbox"]')
8+
await page.click('button[type="submit"]')
9+
10+
await expect(page.locator('h1')).toContainText('Musikverein Alsdorf')
11+
await expect(page.locator('[role="listitem"]')).toContainText('Dirigent')
12+
})

src/components/SetupWizard.vue

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { ref, watch, onMounted, nextTick } from 'vue'
3333
import { useRouter } from 'vue-router'
3434
import { useI18n } from 'vue-i18n'
3535
import { useSetupStore } from '../store/setup'
36+
import StartStep from './wizard/StartStep.vue'
3637
import LanguageStep from './wizard/LanguageStep.vue'
3738
import AdminStep from './wizard/AdminStep.vue'
3839
import BackupStep from './wizard/BackupStep.vue'
@@ -45,6 +46,7 @@ const router = useRouter()
4546
const { t, locale } = useI18n()
4647
4748
const steps = [
49+
{ key: 'start', component: StartStep },
4850
{ key: 'language', component: LanguageStep },
4951
{ key: 'admin', component: AdminStep },
5052
{ key: 'backup', component: BackupStep },
@@ -55,11 +57,18 @@ const steps = [
5557
5658
const currentStep = ref(1)
5759
const submitting = ref(false)
58-
const form = ref({})
60+
const form = ref({ orgName: '', language: 'de', demoMode: false })
5961
6062
// react to language changes in the store
63+
// keep i18n in sync with the store
6164
watch(() => store.language, (v) => { if (v) locale.value = v })
6265
66+
// When the user changes language in the wizard form, apply it immediately
67+
watch(() => form.value.language, (newLang) => {
68+
if (!newLang) return
69+
try { store.setLanguage(newLang); locale.value = newLang } catch (e) {}
70+
})
71+
6372
function prevStep() {
6473
if (currentStep.value > 1) currentStep.value--
6574
}
@@ -71,9 +80,27 @@ async function nextStep() {
7180
}
7281
// finish: persist and redirect
7382
submitting.value = true
74-
store.setSetupCompleted(true)
75-
// store persists to localStorage in the action; also ensure i18n locale set
76-
if (store.language) locale.value = store.language
83+
try {
84+
// persist orgName, language and demoMode
85+
const payload = {
86+
orgName: (form.value && (form.value as any).orgName) || store.orgName || '',
87+
language: (form.value && (form.value as any).language) || store.language || 'de',
88+
demoMode: !!((form.value && (form.value as any).demoMode) || store.demoMode)
89+
}
90+
// complete() persists the whole setup to localStorage
91+
if (typeof store.complete === 'function') {
92+
// call the complete action
93+
// @ts-ignore
94+
store.complete(payload)
95+
} else {
96+
// fallback to existing setters
97+
try { store.setLanguage(payload.language) } catch (e) {}
98+
}
99+
// ensure i18n locale reflects the persisted language
100+
try { locale.value = payload.language } catch (e) {}
101+
} catch (e) {
102+
// swallow persistence errors
103+
}
77104
submitting.value = false
78105
// navigate to dashboard
79106
await router.push({ name: 'Dashboard' })
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<template>
2+
<form>
3+
<label for="orgName">{{ t('setup.orgName') }}</label>
4+
<input id="orgName" v-model="form.orgName" />
5+
6+
<label for="language">{{ t('setup.language') }}</label>
7+
<select id="language" v-model="form.language">
8+
<option value="de">Deutsch</option>
9+
<option value="en">English</option>
10+
</select>
11+
12+
<label>
13+
<input type="checkbox" v-model="form.demoMode" />
14+
{{ t('setup.enableDemo') }}
15+
</label>
16+
</form>
17+
</template>
18+
19+
<script setup lang="ts">
20+
import { useI18n } from 'vue-i18n'
21+
const { t } = useI18n()
22+
defineProps<{ form: { orgName: string; language: string; demoMode: boolean } }>()
23+
</script>

src/main.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ const app = createApp(App)
1414
const pinia = createPinia()
1515

1616
const savedLang = localStorage.getItem('lang') || 'de'
17-
const i18n = createI18n({ locale: savedLang, fallbackLocale: 'en', messages })
17+
// Use the Composition API mode for vue-i18n (legacy: false) and enable global injection
18+
// so templates can still use $t. This allows `useI18n()` in setup() and keeps
19+
// legacy-style template usage available via globalInjection.
20+
const i18n = createI18n({ legacy: false, globalInjection: true, locale: savedLang, fallbackLocale: 'en', messages })
1821

1922
app.use(pinia)
2023
app.use(router)
@@ -28,6 +31,42 @@ app.directive('can-calendar', vCanCalendar)
2831

2932
app.mount('#app')
3033

34+
// Load persisted setup and apply language + demo data initialization
35+
try {
36+
const setupStore = (pinia as any)._s || undefined
37+
} catch (e) {}
38+
try {
39+
// import stores dynamically to avoid tree-shaking issues
40+
const { useSetupStore } = await import('./store/setup')
41+
const setup = useSetupStore(pinia)
42+
setup.load()
43+
// apply saved language to i18n global
44+
try { (i18n as any).global.locale.value = setup.language || savedLang } catch (e) {}
45+
// initialize demo stores that respect demoMode
46+
try {
47+
const { useMembersStore } = await import('./stores/membersStore')
48+
const members = useMembersStore(pinia)
49+
if (typeof members.init === 'function') members.init()
50+
} catch (e) {}
51+
try {
52+
const { useSheetsStore } = await import('./stores/sheetsStore')
53+
const sheets = useSheetsStore(pinia)
54+
if (typeof sheets.init === 'function') sheets.init()
55+
} catch (e) {}
56+
try {
57+
const { useEventsStore } = await import('./store/eventsStore')
58+
const events = useEventsStore(pinia)
59+
if (typeof events.init === 'function') events.init()
60+
} catch (e) {}
61+
try {
62+
const { useNoteStore } = await import('./stores/noteStore')
63+
const notes = useNoteStore(pinia)
64+
if (typeof notes.init === 'function') notes.init()
65+
} catch (e) {}
66+
} catch (e) {
67+
// best-effort: don't block app if dynamic imports fail
68+
}
69+
3170
// default readiness flag for component-level readiness used by Playwright tests
3271
try { (window as any).APP_READY_FOR_TESTS = false } catch (e) { /* noop in non-browser env */ }
3372

src/store/eventsStore.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { defineStore } from 'pinia'
2+
import { useSetupStore } from './setup'
3+
4+
export const useEventsStore = defineStore('events', {
5+
state: () => ({ events: [] as Array<{ id: number; title: string; date: string; location: string }> }),
6+
actions: {
7+
init() {
8+
const setup = useSetupStore()
9+
if ((import.meta.env.VITE_DEMO === 'true') || !!setup.demoMode) {
10+
this.events = [
11+
{ id: 1, title: 'Probe', date: '2025-11-05T19:00', location: 'Musikheim' },
12+
{ id: 2, title: 'Konzert', date: '2025-11-12T20:00', location: 'Stadthalle' },
13+
]
14+
}
15+
}
16+
}
17+
})

src/stores/noteStore.ts

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,10 @@
11
import { defineStore } from 'pinia'
22
import type { Note } from '../types/note'
3+
import { useSetupStore } from '../store/setup'
34

45
export const useNoteStore = defineStore('note', {
56
state: () => ({
6-
notes: [
7-
{
8-
id: 1,
9-
sheetId: 1,
10-
author: 'Dirigent',
11-
content: 'Takt 42: Einsatz verzögern',
12-
createdAt: new Date().toISOString(),
13-
},
14-
{
15-
id: 2,
16-
sheetId: 1,
17-
author: 'Mitglied',
18-
content: 'Bitte Tempo beachten',
19-
createdAt: new Date().toISOString(),
20-
},
21-
] as Note[],
7+
notes: [] as Note[],
228
}),
239
actions: {
2410
addNote(sheetId: number, content: string, author = 'Du') {
@@ -30,5 +16,13 @@ export const useNoteStore = defineStore('note', {
3016
getNotesForSheet(sheetId: number) {
3117
return this.notes.filter(n => n.sheetId === sheetId)
3218
},
19+
init() {
20+
const setup = useSetupStore()
21+
if ((import.meta.env.VITE_DEMO === 'true') || !!setup.demoMode) {
22+
this.notes = [
23+
{ id: 1, sheetId: 1, author: 'Dirigent', content: 'Takt 42: Einsatz verzögern', createdAt: new Date().toISOString() },
24+
]
25+
}
26+
}
3327
},
3428
})

0 commit comments

Comments
 (0)