Add calendar-based booking system: accounts, availability, admin calendar

- Customer accounts (separate auth collection) with /konto area
- Availability + AvailabilityOverrides collections driving real slot calculation
- BookingRequests with race-safe confirmation (Postgres advisory lock + transaction)
- Booking emails (request received, admin notify, confirmed, rejected, alternative proposed/accepted, cancelled)
- Confirmed online bookings auto-create a private linked video-call Event
- Custom Payload admin calendar view (month grid + day schedule)
- Wired booking widget into offer pages and /termin-buchen
This commit is contained in:
2026-08-25 16:52:34 +02:00
parent 45261a0461
commit 5d83c0dc1e
11 changed files with 611 additions and 30 deletions
@@ -0,0 +1,198 @@
.page {
padding: 24px;
max-width: 1100px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
color: #1f1f1f;
}
.title {
font-size: 22px;
font-weight: 600;
margin: 0 0 4px;
}
.subtitle {
color: #666;
font-size: 14px;
margin: 0 0 24px;
}
.layout {
display: grid;
grid-template-columns: minmax(0, 460px) 1fr;
gap: 32px;
}
@media (max-width: 900px) {
.layout {
grid-template-columns: 1fr;
}
}
.monthNav {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.monthNav a {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border-radius: 8px;
color: #333;
text-decoration: none;
border: 1px solid #e2e2e2;
}
.monthNav a:hover {
background: #f5f5f5;
}
.monthLabel {
font-weight: 600;
font-size: 16px;
text-transform: capitalize;
}
.grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 4px;
}
.weekdayLabel {
text-align: center;
font-size: 11px;
font-weight: 600;
color: #888;
text-transform: uppercase;
padding-bottom: 4px;
}
.dayCell {
aspect-ratio: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border-radius: 10px;
text-decoration: none;
color: #1f1f1f;
font-size: 13px;
border: 1px solid transparent;
gap: 3px;
}
.dayCell:hover {
background: #f5f5f5;
}
.dayCellSelected {
background: #8b616d;
color: #fff;
}
.dayCellEmpty {
visibility: hidden;
}
.dayCellToday {
font-weight: 700;
}
.dots {
display: flex;
gap: 2px;
}
.dot {
width: 5px;
height: 5px;
border-radius: 50%;
}
.dotPending { background: #d4a04c; }
.dotConfirmed { background: #5c7a4f; }
.dotRejected { background: #c14b4b; }
.dotCancelled { background: #999; }
.dotPublic { background: #b3717a; }
.schedule {
border: 1px solid #e6e6e6;
border-radius: 12px;
overflow: hidden;
}
.scheduleHeader {
padding: 14px 18px;
border-bottom: 1px solid #e6e6e6;
background: #fafafa;
font-weight: 600;
}
.hourRow {
display: grid;
grid-template-columns: 64px 1fr;
border-bottom: 1px solid #f0f0f0;
min-height: 44px;
}
.hourLabel {
padding: 8px 12px;
font-size: 12px;
color: #999;
border-right: 1px solid #f0f0f0;
}
.hourContent {
padding: 6px 12px;
display: flex;
flex-direction: column;
gap: 4px;
}
.entry {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 10px;
border-radius: 8px;
background: #f7f4f1;
text-decoration: none;
color: #1f1f1f;
font-size: 13px;
}
.entry:hover {
background: #efe7e1;
}
.badge {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
padding: 2px 8px;
border-radius: 999px;
white-space: nowrap;
}
.badgePending { background: #f7e6c4; color: #6b4f14; }
.badgeConfirmed { background: #dbe7d5; color: #3a4f30; }
.badgeRejected { background: #f6d4d4; color: #7a2020; }
.badgeCancelled { background: #e8e8e8; color: #555; }
.badgePublic { background: #f0dde0; color: #6d3540; }
.empty {
padding: 24px 18px;
color: #888;
font-size: 14px;
}
.legend {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-top: 16px;
font-size: 12px;
color: #555;
}
.legendItem {
display: flex;
align-items: center;
gap: 6px;
}