Complete implementation including:
- Landing page with hero, features, how-it-works, pricing
- Employee management (CRUD with soft delete)
- AI constraint parser (Anthropic Claude API)
- German labor law templates (ArbZG §3, §5, §9)
- HiGHS ILP solver for optimal fair schedules
- Schedule calendar result view (employee × date grid)
- Shift framework configuration (periods + shifts)
- Subscription tiers: Free / Pro / Business
- PocketBase setup script with collection creation + seed data
- .env.example with all required variables documented
Pages: employees, constraints (list/new/templates), schedules (list/new/[id]),
settings (organization/shifts/billing), dashboard
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
66 lines
2.1 KiB
Vue
66 lines
2.1 KiB
Vue
<template>
|
|
<div class="bg-white rounded-2xl border border-gray-100 shadow-sm p-8">
|
|
<h2 class="text-2xl font-bold text-gray-900 mb-2">Willkommen zurück</h2>
|
|
<p class="text-gray-500 mb-6 text-sm">Melden Sie sich in Ihrem ShiftCraft-Konto an</p>
|
|
|
|
<UForm :schema="schema" :state="formState" @submit="onSubmit" class="space-y-4">
|
|
<UFormField label="E-Mail" name="email">
|
|
<UInput v-model="formState.email" type="email" placeholder="name@firma.de" class="w-full" />
|
|
</UFormField>
|
|
<UFormField label="Passwort" name="password">
|
|
<UInput v-model="formState.password" type="password" placeholder="••••••••" class="w-full" />
|
|
</UFormField>
|
|
|
|
<UButton type="submit" color="primary" class="w-full justify-center" :loading="loading" size="lg">
|
|
Anmelden
|
|
</UButton>
|
|
</UForm>
|
|
|
|
<div class="mt-6 text-center">
|
|
<p class="text-sm text-gray-500">
|
|
Noch kein Konto?
|
|
<NuxtLink to="/register" class="text-indigo-600 hover:text-indigo-700 font-medium">Kostenlos registrieren</NuxtLink>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import * as z from 'zod'
|
|
import type { FormSubmitEvent } from '@nuxt/ui'
|
|
|
|
definePageMeta({ layout: 'auth', middleware: 'guest' })
|
|
|
|
const toast = useToast()
|
|
const loading = ref(false)
|
|
|
|
const formState = reactive({
|
|
email: '',
|
|
password: '',
|
|
})
|
|
|
|
const schema = z.object({
|
|
email: z.email('Ungültige E-Mail-Adresse'),
|
|
password: z.string().min(1, 'Passwort ist erforderlich'),
|
|
})
|
|
|
|
type Schema = z.output<typeof schema>
|
|
|
|
async function onSubmit(payload: FormSubmitEvent<Schema>) {
|
|
loading.value = true
|
|
try {
|
|
const { pb } = usePocketBase()
|
|
const authData = await pb.collection('users').authWithPassword(payload.data.email, payload.data.password)
|
|
// Load org
|
|
const orgStore = useOrg()
|
|
const orgId = (authData.record as { org_id?: string }).org_id
|
|
if (orgId) await orgStore.fetchOrg(orgId)
|
|
await navigateTo('/dashboard')
|
|
} catch {
|
|
toast.add({ color: 'error', title: 'Anmeldung fehlgeschlagen', description: 'E-Mail oder Passwort ist falsch.' })
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
</script>
|