feat: complete ShiftCraft — AI-powered shift scheduling SaaS
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>
This commit is contained in:
@@ -1,41 +1,272 @@
|
||||
<template>
|
||||
<div>
|
||||
<UPageHero
|
||||
:title="$t('hero.title')"
|
||||
:description="$t('hero.description')"
|
||||
:links="links"
|
||||
/>
|
||||
<div class="flex justify-center mt-8">
|
||||
<CounterWidget v-if="isAuthenticated" />
|
||||
<UEmpty
|
||||
v-else
|
||||
icon="i-lucide-user-x"
|
||||
:title="$t('counter.notAuthenticated')"
|
||||
:description="$t('counter.signInToUse')"
|
||||
/>
|
||||
</div>
|
||||
<div class="min-h-screen bg-white">
|
||||
<!-- Navigation -->
|
||||
<nav class="fixed top-0 w-full z-50 bg-white/80 backdrop-blur-xl border-b border-gray-100">
|
||||
<div class="max-w-7xl mx-auto px-6 h-16 flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-8 h-8 rounded-xl bg-gradient-to-br from-indigo-500 to-violet-600 flex items-center justify-center">
|
||||
<span class="text-white font-bold text-sm">S</span>
|
||||
</div>
|
||||
<span class="font-bold text-gray-900 text-lg">ShiftCraft</span>
|
||||
</div>
|
||||
<div class="hidden md:flex items-center gap-8">
|
||||
<a href="#features" class="text-gray-600 hover:text-gray-900 text-sm font-medium transition-colors">Features</a>
|
||||
<a href="#how-it-works" class="text-gray-600 hover:text-gray-900 text-sm font-medium transition-colors">So funktioniert's</a>
|
||||
<a href="#pricing" class="text-gray-600 hover:text-gray-900 text-sm font-medium transition-colors">Preise</a>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<NuxtLink to="/login" class="text-sm font-medium text-gray-700 hover:text-gray-900 transition-colors">Anmelden</NuxtLink>
|
||||
<NuxtLink to="/register">
|
||||
<UButton color="primary" size="sm">Kostenlos starten</UButton>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Hero -->
|
||||
<section class="pt-32 pb-20 px-6 relative overflow-hidden">
|
||||
<div class="absolute inset-0 bg-gradient-to-br from-indigo-50 via-violet-50 to-emerald-50 -z-10" />
|
||||
<div class="absolute top-20 right-0 w-96 h-96 bg-violet-200 rounded-full filter blur-3xl opacity-30 -z-10" />
|
||||
<div class="absolute bottom-0 left-20 w-64 h-64 bg-indigo-200 rounded-full filter blur-3xl opacity-30 -z-10" />
|
||||
|
||||
<div class="max-w-4xl mx-auto text-center">
|
||||
<div class="inline-flex items-center gap-2 px-4 py-1.5 bg-indigo-50 text-indigo-700 rounded-full text-sm font-medium mb-8 border border-indigo-100">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-indigo-500 animate-pulse" />
|
||||
Schichtplanung neu gedacht
|
||||
</div>
|
||||
|
||||
<h1 class="text-5xl md:text-7xl font-extrabold text-gray-900 leading-tight mb-6">
|
||||
Schichtplanung,<br>
|
||||
<span class="bg-gradient-to-r from-indigo-600 to-violet-600 bg-clip-text text-transparent">
|
||||
die Ihre Sprache<br>spricht
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
<p class="text-xl text-gray-600 max-w-2xl mx-auto mb-10 leading-relaxed">
|
||||
Einfach Ihre Wünsche und Regeln eingeben — wie in einer E-Mail.
|
||||
ShiftCraft erstellt automatisch den optimalen, fairen Schichtplan.
|
||||
</p>
|
||||
|
||||
<div class="flex flex-col sm:flex-row gap-4 justify-center mb-16">
|
||||
<NuxtLink to="/register">
|
||||
<UButton color="primary" size="xl" trailing-icon="i-lucide-arrow-right" class="shadow-lg shadow-indigo-200">
|
||||
Kostenlos starten
|
||||
</UButton>
|
||||
</NuxtLink>
|
||||
<a href="#how-it-works">
|
||||
<UButton color="neutral" variant="outline" size="xl">
|
||||
Demo ansehen
|
||||
</UButton>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Mock UI Preview -->
|
||||
<div class="relative max-w-3xl mx-auto">
|
||||
<div class="bg-white rounded-3xl shadow-2xl border border-gray-100 overflow-hidden">
|
||||
<div class="bg-gray-50 px-6 py-4 border-b border-gray-100 flex items-center gap-2">
|
||||
<div class="w-3 h-3 rounded-full bg-red-400" />
|
||||
<div class="w-3 h-3 rounded-full bg-yellow-400" />
|
||||
<div class="w-3 h-3 rounded-full bg-green-400" />
|
||||
<span class="ml-4 text-xs text-gray-400 font-medium">ShiftCraft — Neue Bedingung</span>
|
||||
</div>
|
||||
<div class="p-6 text-left">
|
||||
<div class="mb-4">
|
||||
<label class="text-xs font-medium text-gray-500 uppercase tracking-wide mb-2 block">Bedingung eingeben</label>
|
||||
<div class="w-full px-4 py-3 bg-gray-50 border border-gray-200 rounded-xl text-gray-700 text-sm min-h-[80px]">
|
||||
Sabine mag keine Nachtschichten und sollte maximal 4 Tage am Stück arbeiten. Tom darf nicht mehr als 3 Nachtschichten hintereinander machen.
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<div v-for="preview in constraintPreviews" :key="preview.text" class="bg-indigo-50 border border-indigo-100 rounded-xl p-3 flex items-start gap-3">
|
||||
<UIcon name="i-lucide-check-circle" class="text-indigo-600 mt-0.5 w-4 h-4 shrink-0" />
|
||||
<div>
|
||||
<p class="text-sm font-medium text-indigo-900">{{ preview.text }}</p>
|
||||
<p class="text-xs text-indigo-500">{{ preview.meta }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="absolute -bottom-4 -right-4 w-32 h-32 bg-violet-100 rounded-full -z-10" />
|
||||
<div class="absolute -top-4 -left-4 w-24 h-24 bg-emerald-100 rounded-full -z-10" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Features -->
|
||||
<section id="features" class="py-24 px-6">
|
||||
<div class="max-w-6xl mx-auto">
|
||||
<div class="text-center mb-16">
|
||||
<h2 class="text-4xl font-bold text-gray-900 mb-4">Alles was Sie brauchen</h2>
|
||||
<p class="text-xl text-gray-600 max-w-2xl mx-auto">Keine komplizierte Software mehr. Einfach eingeben, was Sie wollen.</p>
|
||||
</div>
|
||||
<div class="grid md:grid-cols-3 gap-8">
|
||||
<div v-for="feature in features" :key="feature.title" class="bg-white rounded-2xl border border-gray-100 shadow-sm p-8 hover:shadow-md transition-shadow">
|
||||
<div class="w-12 h-12 rounded-2xl flex items-center justify-center text-2xl mb-6" :class="feature.bgColor">
|
||||
{{ feature.icon }}
|
||||
</div>
|
||||
<h3 class="text-xl font-bold text-gray-900 mb-3">{{ feature.title }}</h3>
|
||||
<p class="text-gray-600 leading-relaxed">{{ feature.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- How it works -->
|
||||
<section id="how-it-works" class="py-24 px-6 bg-gradient-to-br from-gray-50 to-indigo-50/30">
|
||||
<div class="max-w-5xl mx-auto">
|
||||
<div class="text-center mb-16">
|
||||
<h2 class="text-4xl font-bold text-gray-900 mb-4">So einfach geht's</h2>
|
||||
<p class="text-xl text-gray-600">In drei Schritten zum perfekten Schichtplan</p>
|
||||
</div>
|
||||
<div class="grid md:grid-cols-3 gap-8">
|
||||
<div v-for="(step, i) in steps" :key="step.title" class="text-center relative">
|
||||
<div class="w-20 h-20 rounded-3xl bg-white shadow-lg border border-gray-100 flex items-center justify-center text-4xl mx-auto mb-6 relative">
|
||||
{{ step.icon }}
|
||||
<span class="absolute -top-2 -right-2 w-7 h-7 rounded-full bg-indigo-600 text-white text-xs font-bold flex items-center justify-center">{{ i + 1 }}</span>
|
||||
</div>
|
||||
<h3 class="text-xl font-bold text-gray-900 mb-3">{{ step.title }}</h3>
|
||||
<p class="text-gray-600 leading-relaxed">{{ step.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Legal highlight -->
|
||||
<section class="py-24 px-6">
|
||||
<div class="max-w-6xl mx-auto">
|
||||
<div class="bg-gradient-to-r from-indigo-600 to-violet-700 rounded-3xl p-12 text-white">
|
||||
<div class="grid md:grid-cols-2 gap-12 items-center">
|
||||
<div>
|
||||
<div class="inline-flex items-center gap-2 px-4 py-1.5 bg-white/20 rounded-full text-sm font-medium mb-6">
|
||||
⚖️ Rechtlich abgesichert
|
||||
</div>
|
||||
<h2 class="text-3xl font-bold mb-4">Gesetzliche Vorgaben inklusive</h2>
|
||||
<p class="text-indigo-100 text-lg leading-relaxed mb-6">
|
||||
Alle relevanten Vorschriften aus dem Arbeitszeitgesetz sind bereits als Vorlagen hinterlegt.
|
||||
Einfach aktivieren — fertig.
|
||||
</p>
|
||||
<ul class="space-y-3">
|
||||
<li v-for="rule in legalRules" :key="rule" class="flex items-center gap-3">
|
||||
<span class="text-emerald-300">✓</span>
|
||||
<span class="text-indigo-100">{{ rule }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="space-y-3">
|
||||
<div v-for="template in legalTemplates" :key="template.label" class="bg-white/10 backdrop-blur rounded-2xl p-4 border border-white/20">
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<span class="font-medium">{{ template.label }}</span>
|
||||
<span class="text-xs bg-red-400/30 text-red-200 px-2 py-0.5 rounded-full">Pflicht</span>
|
||||
</div>
|
||||
<p class="text-sm text-indigo-200">{{ template.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Pricing -->
|
||||
<section id="pricing" class="py-24 px-6 bg-gray-50">
|
||||
<div class="max-w-6xl mx-auto">
|
||||
<div class="text-center mb-16">
|
||||
<h2 class="text-4xl font-bold text-gray-900 mb-4">Einfache Preise</h2>
|
||||
<p class="text-xl text-gray-600">Kein verstecktes Kleingedrucktes. Starten Sie kostenlos.</p>
|
||||
</div>
|
||||
<div class="grid md:grid-cols-3 gap-8">
|
||||
<div
|
||||
v-for="(tier, key) in PLAN_LIMITS"
|
||||
:key="key"
|
||||
class="bg-white rounded-2xl border border-gray-100 shadow-sm p-8 relative"
|
||||
:class="key === 'pro' ? 'ring-2 ring-indigo-500 shadow-xl shadow-indigo-100' : ''"
|
||||
>
|
||||
<div v-if="key === 'pro'" class="absolute -top-4 left-1/2 -translate-x-1/2 px-4 py-1 bg-indigo-600 text-white text-sm font-medium rounded-full whitespace-nowrap">
|
||||
Beliebteste Wahl
|
||||
</div>
|
||||
<h3 class="text-2xl font-bold text-gray-900 mb-2">{{ tier.name }}</h3>
|
||||
<p class="text-gray-500 mb-6 text-sm">{{ tier.description }}</p>
|
||||
<div class="mb-8">
|
||||
<span class="text-5xl font-extrabold text-gray-900">€{{ tier.price_eur_month }}</span>
|
||||
<span class="text-gray-500 ml-2">/Monat</span>
|
||||
</div>
|
||||
<ul class="space-y-3 mb-8">
|
||||
<li v-for="f in tier.features" :key="f" class="flex items-center gap-3 text-gray-600 text-sm">
|
||||
<UIcon name="i-lucide-check" class="text-emerald-500 w-4 h-4 shrink-0" />
|
||||
{{ f }}
|
||||
</li>
|
||||
</ul>
|
||||
<NuxtLink :to="tier.price_eur_month === 0 ? '/register' : '/register'">
|
||||
<UButton
|
||||
:color="key === 'pro' ? 'primary' : 'neutral'"
|
||||
:variant="key === 'pro' ? 'solid' : 'outline'"
|
||||
class="w-full justify-center"
|
||||
size="lg"
|
||||
>
|
||||
{{ tier.price_eur_month === 0 ? 'Kostenlos starten' : 'Jetzt upgraden' }}
|
||||
</UButton>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="py-12 px-6 border-t border-gray-100">
|
||||
<div class="max-w-6xl mx-auto flex flex-col md:flex-row items-center justify-between gap-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="w-7 h-7 rounded-lg bg-gradient-to-br from-indigo-500 to-violet-600 flex items-center justify-center">
|
||||
<span class="text-white font-bold text-xs">S</span>
|
||||
</div>
|
||||
<span class="font-bold text-gray-900">ShiftCraft</span>
|
||||
</div>
|
||||
<p class="text-gray-500 text-sm">© 2025 ShiftCraft. Alle Rechte vorbehalten.</p>
|
||||
<div class="flex gap-6">
|
||||
<a href="#" class="text-gray-500 hover:text-gray-700 text-sm transition-colors">Datenschutz</a>
|
||||
<a href="#" class="text-gray-500 hover:text-gray-700 text-sm transition-colors">Impressum</a>
|
||||
<a href="#" class="text-gray-500 hover:text-gray-700 text-sm transition-colors">Kontakt</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { ButtonProps } from '@nuxt/ui'
|
||||
import { PLAN_LIMITS } from '~/app/utils/planLimits'
|
||||
|
||||
const { isAuthenticated } = storeToRefs(useUser())
|
||||
definePageMeta({ layout: false })
|
||||
|
||||
const { t } = useI18n()
|
||||
const constraintPreviews = [
|
||||
{ text: 'Sabine bevorzugt keine Nachtschichten', meta: 'Soft-Bedingung · Gewicht 65' },
|
||||
{ text: 'Max. 4 aufeinanderfolgende Schichten (Sabine)', meta: 'Harte Bedingung' },
|
||||
{ text: 'Max. 3 Nachtschichten am Stück (Tom)', meta: 'Harte Bedingung' },
|
||||
]
|
||||
|
||||
const links = computed(() => [
|
||||
{
|
||||
label: t('hero.signUp'),
|
||||
to: '/login',
|
||||
icon: 'i-lucide-log-in'
|
||||
},
|
||||
{
|
||||
label: t('hero.profile'),
|
||||
to: '/profile',
|
||||
color: 'neutral',
|
||||
variant: 'subtle',
|
||||
trailingIcon: 'i-lucide-arrow-right'
|
||||
}
|
||||
] as ButtonProps[])
|
||||
const features = [
|
||||
{ icon: '💬', title: 'Einfach eingeben', description: 'Keine komplizierten Formulare. Schreiben Sie einfach, was Sie wollen — wie in einer Nachricht.', bgColor: 'bg-indigo-50' },
|
||||
{ icon: '⚡', title: 'Automatisch optimiert', description: 'Unser intelligentes System erstellt den bestmöglichen, fairen Plan in Sekunden — nicht Stunden.', bgColor: 'bg-violet-50' },
|
||||
{ icon: '⚖️', title: 'Rechtlich sicher', description: 'Gesetzliche Vorlagen für Deutschland, Österreich und die Schweiz sind bereits eingebaut.', bgColor: 'bg-emerald-50' },
|
||||
{ icon: '🤝', title: 'Mitarbeiterwünsche', description: 'Individuelle Präferenzen werden berücksichtigt — für mehr Zufriedenheit im Team.', bgColor: 'bg-amber-50' },
|
||||
{ icon: '📊', title: 'Faire Verteilung', description: 'Automatisch faire Verteilung von Nacht-, Wochenend- und Feiertagsschichten.', bgColor: 'bg-pink-50' },
|
||||
{ icon: '📤', title: 'Export & Teilen', description: 'Den fertigen Plan als PDF oder Excel exportieren und direkt teilen.', bgColor: 'bg-cyan-50' },
|
||||
]
|
||||
|
||||
const steps = [
|
||||
{ icon: '🗓️', title: 'Rahmen festlegen', description: 'Definieren Sie Ihre Schichtzeiten und wie viele Mitarbeiter pro Schicht benötigt werden.' },
|
||||
{ icon: '✏️', title: 'Regeln eingeben', description: 'Schreiben Sie Ihre Wünsche und Regeln in normaler Sprache — so einfach wie eine E-Mail.' },
|
||||
{ icon: '✨', title: 'Plan erhalten', description: 'ShiftCraft berechnet automatisch den optimalen, fairen Schichtplan für Ihr Team.' },
|
||||
]
|
||||
|
||||
const legalRules = [
|
||||
'Max. 10 Stunden Arbeitszeit pro Tag',
|
||||
'Min. 11 Stunden Ruhezeit zwischen Schichten',
|
||||
'Max. 48 Stunden Arbeitszeit pro Woche',
|
||||
'Keine Frühschicht nach Nachtschicht',
|
||||
]
|
||||
|
||||
const legalTemplates = [
|
||||
{ label: 'Max. 10 Stunden/Tag', description: 'Arbeitszeitgesetz §3 — Pflicht für alle Unternehmen' },
|
||||
{ label: 'Min. 11h Ruhezeit', description: 'Arbeitszeitgesetz §5 — Zwischen zwei Schichten' },
|
||||
{ label: 'Max. 6 Tage am Stück', description: 'Arbeitszeitgesetz §9 — Sonntagsruhe' },
|
||||
]
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user