Adds waitlist feature for booking system
Introduces new waitlist functionality to allow customers to register for unavailable time slots Includes: - Waitlist trigger and section in booking interface - Ability to select preferred time periods - Date and optional message input - Success overlay for waitlist submission Updates salon address in multiple files
This commit is contained in:
parent
dacdf5d153
commit
f73133b51c
4 changed files with 627 additions and 6 deletions
|
|
@ -504,6 +504,192 @@
|
|||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
/* ==========================================
|
||||
WAITLIST
|
||||
========================================== */
|
||||
.waitlist-trigger {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 20px;
|
||||
padding: 12px 16px;
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
border: 1px dashed var(--color-border);
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
font-family: var(--font-family);
|
||||
color: var(--color-text-secondary);
|
||||
cursor: pointer;
|
||||
transition: all 150ms ease;
|
||||
}
|
||||
|
||||
.waitlist-trigger:hover {
|
||||
border-color: var(--color-teal);
|
||||
color: var(--color-teal);
|
||||
background: var(--color-teal-light);
|
||||
}
|
||||
|
||||
.waitlist-trigger i {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.waitlist-section {
|
||||
overflow: hidden;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.waitlist-content {
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.waitlist-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.waitlist-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.waitlist-close {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
color: var(--color-text-secondary);
|
||||
cursor: pointer;
|
||||
transition: all 150ms ease;
|
||||
}
|
||||
|
||||
.waitlist-close:hover {
|
||||
background: var(--color-background);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.waitlist-close i {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.waitlist-desc {
|
||||
font-size: 13px;
|
||||
color: var(--color-text-secondary);
|
||||
margin-bottom: 20px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.waitlist-field {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.waitlist-field:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.waitlist-label {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--color-text);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.waitlist-periods {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.period-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 14px;
|
||||
background: var(--color-surface);
|
||||
border: 2px solid transparent;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 150ms ease;
|
||||
}
|
||||
|
||||
.period-option:hover {
|
||||
border-color: var(--color-border);
|
||||
}
|
||||
|
||||
.period-option.selected {
|
||||
border-color: var(--color-teal);
|
||||
background: var(--color-teal-light);
|
||||
}
|
||||
|
||||
.period-check {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 4px;
|
||||
border: 2px solid var(--color-border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 12px;
|
||||
color: white;
|
||||
transition: all 150ms ease;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.period-option.selected .period-check {
|
||||
background: var(--color-teal);
|
||||
border-color: var(--color-teal);
|
||||
}
|
||||
|
||||
.period-check i {
|
||||
opacity: 0;
|
||||
transition: opacity 150ms ease;
|
||||
}
|
||||
|
||||
.period-option.selected .period-check i {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.period-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.period-name {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.period-time {
|
||||
font-size: 11px;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.waitlist-submit {
|
||||
margin-top: 20px;
|
||||
width: 100%;
|
||||
padding: 12px 20px;
|
||||
background: var(--color-teal);
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
font-family: var(--font-family);
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: background 150ms ease;
|
||||
}
|
||||
|
||||
.waitlist-submit:hover {
|
||||
background: #00695c;
|
||||
}
|
||||
|
||||
/* ==========================================
|
||||
FORM
|
||||
========================================== */
|
||||
|
|
@ -847,6 +1033,39 @@
|
|||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.success-icon.waitlist {
|
||||
background: rgba(0, 137, 123, 0.12);
|
||||
}
|
||||
|
||||
.success-icon.waitlist i {
|
||||
color: var(--color-teal);
|
||||
}
|
||||
|
||||
.success-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-top: 20px;
|
||||
padding: 12px 24px;
|
||||
background: var(--color-teal);
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
font-family: var(--font-family);
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: background 150ms ease;
|
||||
}
|
||||
|
||||
.success-btn:hover {
|
||||
background: #00695c;
|
||||
}
|
||||
|
||||
.success-btn i {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* ==========================================
|
||||
LANDING VIEW
|
||||
========================================== */
|
||||
|
|
@ -1318,7 +1537,7 @@
|
|||
<div class="landing-card">
|
||||
<div class="salon-logo">KK</div>
|
||||
<div class="salon-name">KARINA KNUDSEN®</div>
|
||||
<div class="salon-address">Hovedgaden 123, København</div>
|
||||
<div class="salon-address">Amager Strandvej 22f, 2300 Kbh S</div>
|
||||
|
||||
<!-- Step 1: Phone -->
|
||||
<div class="landing-step" id="landingStep1">
|
||||
|
|
@ -1465,7 +1684,7 @@
|
|||
<div class="salon-logo">KK</div>
|
||||
<div>
|
||||
<div class="salon-name">KARINA KNUDSEN®</div>
|
||||
<div class="salon-address">Hovedgaden 123, København</div>
|
||||
<div class="salon-address">Amager Strandvej 22f, 2300 Kbh S</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -1627,6 +1846,20 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Waitlist success overlay -->
|
||||
<div class="success-overlay" id="waitlistSuccessOverlay">
|
||||
<div class="success-modal">
|
||||
<div class="success-icon waitlist">
|
||||
<i class="ph ph-clock-countdown"></i>
|
||||
</div>
|
||||
<h2>Du er på ventelisten!</h2>
|
||||
<p>Tak for din tilmelding. Vi kontakter dig hurtigst muligt, hvis der bliver en ledig tid.</p>
|
||||
<button class="success-btn" id="waitlistSuccessClose">
|
||||
Luk <i class="ph ph-x"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// ==========================================
|
||||
// CONFIG
|
||||
|
|
@ -1997,6 +2230,75 @@
|
|||
return `<div class="${cls}" data-time="${t}">${t}</div>`;
|
||||
}).join('')}
|
||||
</div>
|
||||
|
||||
<!-- Waitlist -->
|
||||
<button class="waitlist-trigger" id="waitlistTrigger">
|
||||
<i class="ph ph-clock-countdown"></i>
|
||||
Ingen tid der passer? Skriv dig på venteliste
|
||||
</button>
|
||||
|
||||
<div class="waitlist-section" id="waitlistSection">
|
||||
<div class="waitlist-content">
|
||||
<div class="waitlist-header">
|
||||
<div class="waitlist-title">Skriv dig på venteliste</div>
|
||||
<button class="waitlist-close" id="waitlistClose">
|
||||
<i class="ph ph-x"></i>
|
||||
</button>
|
||||
</div>
|
||||
<p class="waitlist-desc">
|
||||
Hvis der bliver en ledig tid, kontakter vi dig hurtigst muligt.
|
||||
Angiv hvornår du senest ønsker en tid, og hvilke tidspunkter der passer dig.
|
||||
</p>
|
||||
|
||||
<div class="waitlist-field">
|
||||
<label class="waitlist-label">Senest dato for aftale</label>
|
||||
<input type="date" class="form-input" id="waitlistDate">
|
||||
</div>
|
||||
|
||||
<div class="waitlist-field">
|
||||
<label class="waitlist-label">Hvilke tidspunkter passer dig?</label>
|
||||
<div class="waitlist-periods">
|
||||
<div class="period-option" data-period="morning">
|
||||
<div class="period-check"><i class="ph ph-check"></i></div>
|
||||
<div class="period-info">
|
||||
<div class="period-name">Morgen</div>
|
||||
<div class="period-time">Åbning - 10:00</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="period-option" data-period="lateMorning">
|
||||
<div class="period-check"><i class="ph ph-check"></i></div>
|
||||
<div class="period-info">
|
||||
<div class="period-name">Formiddag</div>
|
||||
<div class="period-time">10:00 - 12:00</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="period-option" data-period="afternoon">
|
||||
<div class="period-check"><i class="ph ph-check"></i></div>
|
||||
<div class="period-info">
|
||||
<div class="period-name">Eftermiddag</div>
|
||||
<div class="period-time">12:00 - 17:00</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="period-option" data-period="evening">
|
||||
<div class="period-check"><i class="ph ph-check"></i></div>
|
||||
<div class="period-info">
|
||||
<div class="period-name">Aften</div>
|
||||
<div class="period-time">17:00 - Luk</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="waitlist-field">
|
||||
<label class="waitlist-label">Besked til salonen <span class="optional">(valgfrit)</span></label>
|
||||
<textarea class="form-input" id="waitlistMessage" placeholder="Evt. særlige ønsker eller kommentarer..." rows="3"></textarea>
|
||||
</div>
|
||||
|
||||
<button class="waitlist-submit" id="waitlistSubmit">
|
||||
<i class="ph ph-check"></i> Skriv mig på venteliste
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
|
@ -2032,6 +2334,113 @@
|
|||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Waitlist functionality
|
||||
setupWaitlist();
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// WAITLIST
|
||||
// ==========================================
|
||||
let waitlistOpen = false;
|
||||
const waitlistState = {
|
||||
date: null,
|
||||
periods: [],
|
||||
message: ''
|
||||
};
|
||||
|
||||
function setupWaitlist() {
|
||||
const trigger = document.getElementById('waitlistTrigger');
|
||||
const section = document.getElementById('waitlistSection');
|
||||
const closeBtn = document.getElementById('waitlistClose');
|
||||
const submitBtn = document.getElementById('waitlistSubmit');
|
||||
|
||||
if (!trigger || !section) return;
|
||||
|
||||
trigger.addEventListener('click', () => toggleWaitlist(true));
|
||||
closeBtn.addEventListener('click', () => toggleWaitlist(false));
|
||||
|
||||
// Period selection
|
||||
document.querySelectorAll('.period-option').forEach(option => {
|
||||
option.addEventListener('click', () => {
|
||||
option.classList.toggle('selected');
|
||||
const period = option.dataset.period;
|
||||
const idx = waitlistState.periods.indexOf(period);
|
||||
if (idx > -1) {
|
||||
waitlistState.periods.splice(idx, 1);
|
||||
} else {
|
||||
waitlistState.periods.push(period);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Submit
|
||||
submitBtn.addEventListener('click', handleWaitlistSubmit);
|
||||
}
|
||||
|
||||
function toggleWaitlist(open) {
|
||||
const trigger = document.getElementById('waitlistTrigger');
|
||||
const section = document.getElementById('waitlistSection');
|
||||
|
||||
if (open) {
|
||||
trigger.style.display = 'none';
|
||||
section.style.height = 'auto';
|
||||
const targetHeight = section.scrollHeight;
|
||||
section.style.height = '0px';
|
||||
section.offsetHeight;
|
||||
|
||||
const anim = section.animate(
|
||||
[{ height: '0px' }, { height: targetHeight + 'px' }],
|
||||
{ duration: ANIM_DURATION, easing: 'ease-out' }
|
||||
);
|
||||
anim.onfinish = () => { section.style.height = 'auto'; };
|
||||
waitlistOpen = true;
|
||||
} else {
|
||||
const currentHeight = section.scrollHeight;
|
||||
const anim = section.animate(
|
||||
[{ height: currentHeight + 'px' }, { height: '0px' }],
|
||||
{ duration: ANIM_DURATION, easing: 'ease-out' }
|
||||
);
|
||||
anim.onfinish = () => {
|
||||
section.style.height = '0px';
|
||||
trigger.style.display = 'flex';
|
||||
};
|
||||
waitlistOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleWaitlistSubmit() {
|
||||
const date = document.getElementById('waitlistDate').value;
|
||||
const message = document.getElementById('waitlistMessage').value;
|
||||
|
||||
if (!date) {
|
||||
alert('Vælg venligst en seneste dato');
|
||||
return;
|
||||
}
|
||||
|
||||
if (waitlistState.periods.length === 0) {
|
||||
alert('Vælg mindst ét tidspunkt der passer dig');
|
||||
return;
|
||||
}
|
||||
|
||||
waitlistState.date = date;
|
||||
waitlistState.message = message;
|
||||
|
||||
// Show success overlay
|
||||
document.getElementById('waitlistSuccessOverlay').classList.add('visible');
|
||||
}
|
||||
|
||||
function closeWaitlistSuccess() {
|
||||
document.getElementById('waitlistSuccessOverlay').classList.remove('visible');
|
||||
toggleWaitlist(false);
|
||||
|
||||
// Reset waitlist form
|
||||
document.getElementById('waitlistDate').value = '';
|
||||
document.getElementById('waitlistMessage').value = '';
|
||||
document.querySelectorAll('.period-option').forEach(opt => opt.classList.remove('selected'));
|
||||
waitlistState.periods = [];
|
||||
waitlistState.date = null;
|
||||
waitlistState.message = '';
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
|
|
@ -2352,6 +2761,12 @@
|
|||
if (e.target.id === 'successOverlay') location.reload();
|
||||
});
|
||||
|
||||
// Waitlist success overlay close
|
||||
document.getElementById('waitlistSuccessClose').addEventListener('click', closeWaitlistSuccess);
|
||||
document.getElementById('waitlistSuccessOverlay').addEventListener('click', (e) => {
|
||||
if (e.target.id === 'waitlistSuccessOverlay') closeWaitlistSuccess();
|
||||
});
|
||||
|
||||
// View switching - Landing
|
||||
document.getElementById('phoneNextBtn').addEventListener('click', handlePhoneNext);
|
||||
document.getElementById('backToPhoneBtn').addEventListener('click', handleBackToPhone);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue