public
imalexnord
read
Slot
Calendar-first scheduling powered by Cloudflare Workers and Google Calendar.
Languages
Repository composition by tracked source files.
TypeScript
79%
CSS
19%
SQL
2%
HTML
0%
Create file
Wiki Documentation
Clone
https://nobgit.com/user/imalexnord/slot.git
ssh://[email protected]:2222/user/imalexnord/slot.git
Commit
Keep booking flow in one panel
83e0c31
src/App.tsx | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++
src/styles.css | 29 ++++++++++++++---------------
2 files changed, 65 insertions(+), 15 deletions(-)
Diff
diff --git a/src/App.tsx b/src/App.tsx
index c829779..d86bf48 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -414,6 +414,57 @@ function PublicBooking() {
</div>
</div>
)}
+
+ {selectedSlot && duration && (
+ <div className="inline-details">
+ <div className="drawer-summary">
+ <p className="step-label">04 · YOUR DETAILS</p>
+ <h2>{selectedSlot.time} · {duration} minutes</h2>
+ <p>{prettyDate(selectedDate, config.timezone)}</p>
+ </div>
+ <form className="details-form" onSubmit={submitBooking}>
+ <div className="form-row">
+ <label>Name<input name="name" required autoComplete="name" placeholder="Your name" /></label>
+ <label>Email<input name="email" type="email" required autoComplete="email" placeholder="[email protected]" /></label>
+ </div>
+
+ {config.allowMeetingModes.length > 1 && (
+ <div className="mode-picker">
+ <span className="field-label">Meeting</span>
+ <div className="mode-options">
+ {config.allowMeetingModes.map((mode) => {
+ const Icon = modeMeta[mode].icon;
+ return (
+ <button
+ key={mode}
+ type="button"
+ className={`mode-option ${meetingMode === mode ? 'active' : ''}`}
+ onClick={() => setMeetingMode(mode)}
+ >
+ <Icon size={18} />
+ <span><strong>{modeMeta[mode].label}</strong><small>{modeMeta[mode].help}</small></span>
+ </button>
+ );
+ })}
+ </div>
+ </div>
+ )}
+
+ {meetingMode === 'phone' && (
+ <label>Phone number<input name="phone" required autoComplete="tel" placeholder="+46 ..." /></label>
+ )}
+ <label>Anything I should know? <span className="optional">Optional</span>
+ <textarea name="message" rows={3} placeholder="Context, topic, links..." />
+ </label>
+ <label className="honeypot" aria-hidden="true">Website<input name="website" tabIndex={-1} autoComplete="off" /></label>
+
+ {error && <div className="error-banner">{error}</div>}
+ <button className="primary-button" type="submit" disabled={booking}>
+ {booking ? <><Loader2 className="spin" size={18} /> Booking...</> : <>Book this time <ArrowRight size={18} /></>}
+ </button>
+ </form>
+ </div>
+ )}
</>
)}
</aside>
diff --git a/src/styles.css b/src/styles.css
index eb20172..d6b5fb2 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -385,7 +385,12 @@ a { color: inherit; text-decoration: none; }
.day:disabled { cursor: default; opacity: .25; }
.day.outside:disabled { opacity: .12; }
-.selection-panel { padding: 38px 34px; background: color-mix(in srgb, var(--surface-soft) 52%, var(--surface)); }
+.selection-panel {
+ padding: 38px 34px;
+ background: color-mix(in srgb, var(--surface-soft) 52%, var(--surface));
+ overflow-y: auto;
+ min-height: 0;
+}
.selection-heading { margin-bottom: 25px; }
.selection-heading h2 { font-size: 19px; }
.empty-state {
@@ -434,18 +439,11 @@ a { color: inherit; text-decoration: none; }
.duration-button.active { background: var(--text); color: var(--bg); border-color: var(--text); }
.duration-button.active span { color: color-mix(in srgb, var(--bg) 65%, transparent); }
-.details-drawer {
- width: 100%;
- max-width: 1320px;
- margin: 0;
- padding: 26px 28px;
- border: 1px solid var(--border);
- border-radius: 22px;
- background: var(--surface);
- box-shadow: var(--shadow);
- display: grid;
- grid-template-columns: .55fr 1.45fr;
- gap: 44px;
+.details-drawer { display: none; }
+.inline-details {
+ border-top: 1px solid var(--border);
+ margin-top: 28px;
+ padding-top: 24px;
animation: drawer-in 180ms ease-out;
}
@keyframes drawer-in { from { opacity: 0; transform: translateY(10px); } }
@@ -523,10 +521,10 @@ input:focus, textarea:focus, select:focus { border-color: color-mix(in srgb, var
.success-banner { color: var(--success); background: color-mix(in srgb, var(--success) 8%, transparent); border: 1px solid color-mix(in srgb, var(--success) 22%, transparent); margin-bottom: 14px; }
.floating-error { width: 100%; max-width: 1320px; margin: 0; }
.booking-page footer {
- position: absolute;
+ position: fixed;
left: 26px;
right: 26px;
- bottom: 28px;
+ bottom: 10px;
max-width: 1320px;
width: calc(100% - 52px);
margin: 0;
@@ -536,6 +534,7 @@ input:focus, textarea:focus, select:focus { border-color: color-mix(in srgb, var
font-size: 10px;
color: var(--faint);
pointer-events: none;
+ z-index: 20;
}
.booking-page footer a { pointer-events: auto; }
.powered-by {