-
Notifications
You must be signed in to change notification settings - Fork 320
feat: build Fahrschule Bz Ucar production website #81
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
d1c7c13
23de128
8f83ef3
9246434
657b5f7
5956251
99a2214
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import { NextResponse } from "next/server"; | ||
|
|
||
| const requiredFields = ["name", "email", "phone", "license", "date"] as const; | ||
|
|
||
| export async function POST(request: Request) { | ||
| const payload = (await request.json().catch(() => null)) as Record<string, unknown> | null; | ||
| if (!payload || requiredFields.some((field) => typeof payload[field] !== "string" || !payload[field])) { | ||
| return NextResponse.json({ message: "Bitte fülle alle Pflichtfelder aus." }, { status: 400 }); | ||
| } | ||
|
|
||
| // TODO: WordPress REST endpoint, SMTP and calendar credentials belong in server-only env vars. | ||
| // Example boundary: POST `${WORDPRESS_URL}/wp-json/bz-ucar/v1/appointments`. | ||
| if (!process.env.WORDPRESS_URL || !process.env.APPOINTMENT_API_TOKEN) { | ||
| return NextResponse.json( | ||
| { message: "Die Terminannahme ist noch nicht konfiguriert. Bitte ruf uns direkt an: 030 123 45 67." }, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Replace the placeholder telephone number before release. When appointment integration is unavailable, the form displays 🤖 Prompt for AI Agents |
||
| { status: 503 }, | ||
| ); | ||
| } | ||
|
|
||
| // TODO: Send the validated payload to WordPress, notify via SMTP and persist admin tracking ID. | ||
| return NextResponse.json({ message: "Anfrage erfolgreich übermittelt." }, { status: 202 }); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift Do not return success before the appointment is delivered. This branch returns HTTP 202 without sending or persisting the payload. The booking form then clears the entered data and confirms submission, although the appointment is lost. Complete the integration or keep the route unavailable until a durable handoff succeeds. 🤖 Prompt for AI Agents |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| export default function Datenschutz() { | ||
| return <main className="mx-auto max-w-3xl px-6 py-20"><h1 className="text-4xl font-black">Datenschutzerklärung</h1><div className="prose mt-8"><p>Hier informieren wir dich darüber, welche personenbezogenen Daten bei einer Terminanfrage verarbeitet werden.</p><p><strong>Platzhalter für die finale Datenschutzerklärung:</strong> Bitte vor dem Livegang durch eine rechtlich geprüfte Fassung und die tatsächlichen WordPress-, SMTP- und Kalenderanbieter ersetzen.</p></div></main>; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -176,3 +176,159 @@ | |
| @apply bg-background text-foreground; | ||
| } | ||
| } | ||
|
|
||
| html { | ||
| scroll-behavior: smooth; | ||
| } | ||
|
|
||
| .form-field { | ||
| width: 100%; | ||
| border: 1px solid #d8e2dd; | ||
| border-radius: 0.85rem; | ||
| background: #fbfcfa; | ||
| padding: 0.8rem 0.95rem; | ||
| color: #09090b; | ||
| outline: none; | ||
| transition: border-color 150ms ease, box-shadow 150ms ease; | ||
| } | ||
|
|
||
| .form-field:focus { | ||
| border-color: #09090b; | ||
| box-shadow: 0 0 0 3px rgb(29 95 87 / 12%); | ||
| } | ||
|
|
||
| .modern-site { | ||
| font-family: var(--font-sans), "Inter", ui-sans-serif, system-ui, sans-serif; | ||
| } | ||
|
|
||
| .modern-site h1, | ||
| .modern-site h2, | ||
| .modern-site h3 { | ||
| text-wrap: balance; | ||
| } | ||
|
|
||
| .course-card { | ||
| min-height: 26rem; | ||
| transition: transform 180ms ease, box-shadow 180ms ease; | ||
| } | ||
|
|
||
| .course-card:hover { | ||
| transform: translateY(-0.35rem); | ||
| box-shadow: 0 1.5rem 3rem rgb(16 35 43 / 12%); | ||
| } | ||
|
|
||
| .course-card-lime { | ||
| background: #ef233c; | ||
| color: #09090b; | ||
| } | ||
|
|
||
| .course-card-coral { | ||
| background: #d90429; | ||
| color: #fff; | ||
| } | ||
|
|
||
| .course-card-blue { | ||
| background: #f4f4f5; | ||
| color: #09090b; | ||
| } | ||
|
|
||
| .scene-city { | ||
| background: | ||
| linear-gradient(90deg, transparent 0 3%, #27272a 3% 10%, transparent 10% 12%, #27272a 12% 20%, transparent 20% 23%, #27272a 23% 31%, transparent 31% 34%, #27272a 34% 44%, transparent 44% 47%, #27272a 47% 57%, transparent 57% 60%, #27272a 60% 69%, transparent 69% 73%, #27272a 73% 84%, transparent 84% 87%, #27272a 87% 96%, transparent 96%); | ||
| clip-path: polygon(0 40%, 3% 40%, 3% 8%, 10% 8%, 10% 34%, 12% 34%, 12% 14%, 20% 14%, 20% 44%, 23% 44%, 23% 23%, 31% 23%, 31% 38%, 34% 38%, 34% 5%, 44% 5%, 44% 35%, 47% 35%, 47% 17%, 57% 17%, 57% 45%, 60% 45%, 60% 10%, 69% 10%, 69% 38%, 73% 38%, 73% 19%, 84% 19%, 84% 43%, 87% 43%, 87% 7%, 96% 7%, 96% 38%, 100% 38%, 100% 100%, 0 100%); | ||
| } | ||
|
|
||
| .scene-cloud { | ||
| position: absolute; | ||
| height: 3rem; | ||
| width: 9rem; | ||
| border-radius: 999px; | ||
| background: rgb(255 255 255 / 10%); | ||
| filter: blur(1px); | ||
| } | ||
|
|
||
| .scene-cloud::before, | ||
| .scene-cloud::after { | ||
| position: absolute; | ||
| bottom: 0; | ||
| content: ""; | ||
| border-radius: 999px; | ||
| background: inherit; | ||
| } | ||
|
|
||
| .scene-cloud::before { | ||
| left: 1.5rem; | ||
| height: 4rem; | ||
| width: 4rem; | ||
| } | ||
|
|
||
| .scene-cloud::after { | ||
| right: 1.3rem; | ||
| height: 3.5rem; | ||
| width: 3.5rem; | ||
| } | ||
|
|
||
| .scene-cloud-one { | ||
| left: 18%; | ||
| top: 22%; | ||
| animation: cloud-drift 18s ease-in-out infinite alternate; | ||
| } | ||
|
|
||
| .scene-cloud-two { | ||
| right: 12%; | ||
| top: 36%; | ||
| transform: scale(.65); | ||
| animation: cloud-drift 23s ease-in-out -5s infinite alternate-reverse; | ||
| } | ||
|
|
||
| .scene-bus { | ||
| animation: bus-arrival 12s ease-in-out infinite; | ||
| } | ||
|
|
||
| .scene-person { | ||
| animation: passenger-exit 12s ease-in-out infinite; | ||
| } | ||
|
|
||
| .scene-car { | ||
| animation: training-car-exit 12s ease-in-out infinite; | ||
| } | ||
|
|
||
| .hero-reference-video { | ||
| filter: saturate(.9) contrast(1.05); | ||
| } | ||
|
|
||
| .hero-video-overlay { | ||
| background: linear-gradient(135deg, rgb(13 39 48 / 30%), rgb(8 26 32 / 62%)); | ||
| } | ||
|
|
||
| @keyframes bus-arrival { | ||
| 0%, 8% { transform: translateX(0); } | ||
| 25%, 58% { transform: translateX(190%); } | ||
| 70%, 100% { transform: translateX(480%); } | ||
| } | ||
|
|
||
| @keyframes passenger-exit { | ||
| 0%, 24% { opacity: 0; transform: translateY(1rem); } | ||
| 34%, 55% { opacity: 1; transform: translateY(0); } | ||
| 65%, 100% { opacity: 0; transform: translate(4rem, 0); } | ||
| } | ||
|
|
||
| @keyframes training-car-exit { | ||
| 0%, 48% { transform: translateX(0); } | ||
| 62%, 100% { transform: translateX(300%); } | ||
| } | ||
|
|
||
| @keyframes cloud-drift { | ||
| from { transform: translateX(-1rem); } | ||
| to { transform: translateX(2rem); } | ||
| } | ||
|
|
||
| @media (prefers-reduced-motion: reduce) { | ||
| html { scroll-behavior: auto; } | ||
| .scene-bus, .scene-person, .scene-car, .scene-cloud-one, .scene-cloud-two { | ||
| animation: none; | ||
| } | ||
|
Comment on lines
+328
to
+330
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Disable the hero video when reduced motion is requested. These rules stop only the procedural fallback animations. The successful video path still autoplays and loops. Hide or pause Based on learnings, animation code must honor the reduced-motion preference. 🤖 Prompt for AI AgentsSource: Learnings |
||
| .scene-bus { transform: translateX(190%); } | ||
| .scene-person { opacity: 1; } | ||
| .scene-car { transform: translateX(80%); } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| export default function Impressum() { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win Use the craft layout contract on both legal pages. Both pages use raw layout wrappers instead of the required local primitives.
As per coding guidelines, 🤖 Prompt for AI AgentsSource: Coding guidelines 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift Do not ship placeholder legal pages. Both public legal routes contain placeholders instead of final, legally reviewed content.
🤖 Prompt for AI Agents |
||
| return <main className="mx-auto max-w-3xl px-6 py-20"><h1 className="text-4xl font-black">Impressum</h1><div className="prose mt-8"><p><strong>Fahrschule Bz Ucar</strong><br />Musterstraße 12<br />12345 Berlin</p><p>Vertreten durch: [Name der verantwortlichen Person]</p><p>Telefon: [Telefonnummer]<br />E-Mail: [E-Mail-Adresse]</p><p><strong>Hinweis:</strong> Diese Platzhalter müssen vor dem produktiven Start mit den vollständigen Anbieterangaben ergänzt werden.</p></div></main>; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Validate the complete appointment payload on the server.
This check accepts whitespace names, malformed emails and phone numbers, unsupported license classes, invalid dates, past dates, and unbounded messages. Direct API calls bypass the form constraints. Use a server-side schema for every field before processing the appointment.
Based on learnings, client-side validation is not a security or integrity control.
Proposed schema validation
🤖 Prompt for AI Agents
Source: Learnings