feat: complete day 17

This commit is contained in:
Ayobami
2025-07-22 19:21:32 +01:00
parent 29e6eb82c7
commit 743187b216
19 changed files with 721 additions and 226 deletions
+2 -1
View File
@@ -6,6 +6,7 @@ var logger = require("morgan");
var indexRouter = require("./routes/index");
var usersRouter = require("./routes/users");
var apiRouter = require("./routes/api");
const db = require("./models");
var cors = require("cors");
@@ -24,7 +25,7 @@ app.use(express.static(path.join(__dirname, "public")));
app.use("/", indexRouter);
app.use("/users", usersRouter);
app.use("/api", apiRouter);
// catch 404 and forward to error handler
app.use(function (req, res, next) {
next(createError(404));
+32
View File
@@ -0,0 +1,32 @@
module.exports = (sequelize, DataTypes) => {
const booking = sequelize.define(
"booking",
{
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true,
},
name: DataTypes.STRING,
email: DataTypes.STRING,
company: DataTypes.STRING,
phone: DataTypes.STRING,
notes: DataTypes.TEXT,
date: DataTypes.STRING,
time: DataTypes.STRING,
timezone: DataTypes.STRING,
created_at: {
type: DataTypes.DATE,
defaultValue: DataTypes.NOW,
},
},
{
timestamps: true,
freezeTableName: true,
tableName: "booking",
createdAt: "created_at",
updatedAt: false,
}
);
return booking;
};
+48 -34
View File
@@ -1,4 +1,4 @@
'use strict';
"use strict";
/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2020*/
/**
* Sequelize File
@@ -8,49 +8,63 @@
* @author Ryan Wong
*
*/
const fs = require('fs');
const path = require('path');
let Sequelize = require('sequelize');
const fs = require("fs");
const path = require("path");
let Sequelize = require("sequelize");
const basename = path.basename(__filename);
const { DataTypes } = require('sequelize');
const { DataTypes } = require("sequelize");
const config = {
DB_DATABASE: 'mysql',
DB_USERNAME: 'root',
DB_PASSWORD: 'root',
DB_ADAPTER: 'mysql',
DB_NAME: 'day_1',
DB_HOSTNAME: 'localhost',
DB_DATABASE: "mysql",
DB_USERNAME: "root",
DB_PASSWORD: process.env.DB_PASSWORD || "root",
DB_ADAPTER: "mysql",
DB_NAME: "day_17",
DB_HOSTNAME: "localhost",
DB_PORT: 3306,
};
let db = {};
let sequelize = new Sequelize(config.DB_DATABASE, config.DB_USERNAME, config.DB_PASSWORD, {
dialect: config.DB_ADAPTER,
username: config.DB_USERNAME,
password: config.DB_PASSWORD,
database: config.DB_NAME,
host: config.DB_HOSTNAME,
port: config.DB_PORT,
logging: console.log,
timezone: '-04:00',
pool: {
maxConnections: 1,
minConnections: 0,
maxIdleTime: 100,
},
define: {
timestamps: false,
underscoredAll: true,
underscored: true,
},
});
let sequelize = new Sequelize(
config.DB_NAME,
config.DB_USERNAME,
config.DB_PASSWORD,
{
dialect: config.DB_ADAPTER,
username: config.DB_USERNAME,
password: config.DB_PASSWORD,
database: config.DB_NAME,
host: config.DB_HOSTNAME,
port: config.DB_PORT,
logging: console.log,
timezone: "-04:00",
pool: {
maxConnections: 1,
minConnections: 0,
maxIdleTime: 100,
},
define: {
timestamps: false,
underscoredAll: true,
underscored: true,
},
}
);
// sequelize.sync({ force: true });
sequelize
.sync()
.then(() => {
console.log("Database & tables created!");
})
.catch((err) => {
console.log(err);
});
fs.readdirSync(__dirname)
.filter((file) => {
return file.indexOf('.') !== 0 && file !== basename && file.slice(-3) === '.js';
return (
file.indexOf(".") !== 0 && file !== basename && file.slice(-3) === ".js"
);
})
.forEach((file) => {
var model = require(path.join(__dirname, file))(sequelize, DataTypes);
@@ -66,4 +80,4 @@ Object.keys(db).forEach((modelName) => {
db.sequelize = sequelize;
db.Sequelize = Sequelize;
module.exports = db;
module.exports = db;
+1
View File
@@ -17,6 +17,7 @@
"moment-timezone": "^0.6.0",
"morgan": "~1.9.1",
"mysql2": "^2.3.3",
"node-input-validator": "^4.5.1",
"sequelize": "^6.15.1"
}
}
+285 -4
View File
@@ -1,8 +1,289 @@
body {
padding: 50px;
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
margin: 0;
padding: 0;
font-family: "Inter", "Lucida Grande", Helvetica, Arial, sans-serif;
background: #eaeaea;
color: #222;
}
a {
color: #00B7FF;
.calendar-container {
max-width: 1100px;
margin: 40px auto;
background: #fff;
border-radius: 6px;
box-shadow: 0 2px 16px rgba(0, 0, 0, 0.07);
padding-bottom: 40px;
}
.calendar-header {
background: #04316a;
color: #fff;
font-size: 2rem;
font-weight: 700;
padding: 28px 32px;
border-radius: 6px 6px 0 0;
letter-spacing: 0.01em;
}
.calendar-content {
padding: 32px 32px 0 32px;
}
.calendar-labels {
margin-bottom: 32px;
}
.calendar-label-main {
font-size: 1.15rem;
font-weight: 600;
margin-bottom: 8px;
}
.calendar-label-duration {
font-size: 1rem;
margin-bottom: 8px;
}
.calendar-label-duration span {
font-weight: 400;
}
.calendar-label-timezone {
font-size: 1rem;
margin-bottom: 8px;
}
.timezone-btn {
background: none;
border: none;
color: #888;
font-weight: 500;
cursor: pointer;
text-decoration: underline;
font-size: 1rem;
margin-left: 8px;
}
/* Modal Overlay */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-content {
background: #fff;
border-radius: 8px;
padding: 32px 40px;
min-width: 340px;
max-width: 90vw;
box-shadow: 0 4px 32px rgba(0, 0, 0, 0.15);
display: flex;
flex-direction: column;
align-items: center;
}
.modal-title {
font-size: 1.2rem;
font-weight: 700;
margin-bottom: 18px;
letter-spacing: 0.04em;
}
.modal-format-switch {
margin-bottom: 18px;
font-size: 0.98rem;
display: flex;
gap: 16px;
}
.timezone-groups {
display: flex;
flex-wrap: wrap;
gap: 32px 48px;
justify-content: center;
}
.timezone-group {
min-width: 180px;
}
.timezone-group-title {
font-size: 0.98rem;
font-weight: 600;
margin-bottom: 8px;
color: #04316a;
}
.timezone-option {
display: block;
margin-bottom: 8px;
font-size: 0.97rem;
cursor: pointer;
}
/* Calendar Table */
.calendar-table-wrapper {
overflow-x: auto;
margin-bottom: 24px;
}
.calendar-table {
width: 100%;
border-collapse: separate;
border-spacing: 0;
background: #fff;
}
.calendar-table th,
.calendar-table td {
text-align: center;
padding: 8px 0;
min-width: 120px;
}
.calendar-table th {
font-size: 1rem;
font-weight: 600;
color: #04316a;
border-bottom: 2px solid #eaeaea;
padding-bottom: 12px;
}
.calendar-day-label {
font-size: 1.05rem;
font-weight: 600;
}
.calendar-date-label {
font-size: 0.98rem;
color: #888;
font-weight: 400;
}
.calendar-slot-btn {
background: #f5f8fa;
border: 1px solid #dbe6f3;
border-radius: 5px;
color: #04316a;
font-size: 1rem;
padding: 7px 0;
width: 90%;
margin: 0 auto;
cursor: pointer;
transition: background 0.15s, border 0.15s;
font-weight: 500;
}
.calendar-slot-btn:hover {
background: #e6f0ff;
border-color: #04316a;
}
.calendar-table td {
border-bottom: 1px solid #f0f0f0;
height: 44px;
}
.calendar-week-nav {
display: flex;
justify-content: flex-end;
gap: 18px;
margin-top: 12px;
font-size: 1rem;
}
.calendar-week-nav a {
color: #04316a;
text-decoration: underline;
font-weight: 500;
cursor: pointer;
}
/* Booking Form */
.booking-form {
max-width: 420px;
margin: 0 auto;
display: flex;
flex-direction: column;
gap: 18px;
}
.form-error {
color: red;
font-size: 0.9rem;
font-weight: 500;
margin-top: 4px;
}
.form-group {
display: flex;
flex-direction: column;
gap: 6px;
}
.form-group label {
font-weight: 600;
font-size: 1rem;
color: #222;
}
.form-group input,
.form-group textarea {
border: 1px solid #dbe6f3;
border-radius: 5px;
padding: 8px 10px;
font-size: 1rem;
font-family: inherit;
background: #f5f8fa;
resize: none;
}
.form-group textarea {
min-height: 70px;
}
.form-submit-btn {
background: #04316a;
color: #fff;
border: none;
border-radius: 5px;
font-size: 1.08rem;
font-weight: 600;
padding: 10px 0;
margin-top: 10px;
cursor: pointer;
transition: background 0.15s;
}
.form-submit-btn:hover {
background: #0050b3;
}
/* Success Message */
.success-message {
text-align: center;
font-size: 1.15rem;
font-weight: 500;
margin-top: 60px;
}
/* Responsive Styles */
@media (max-width: 900px) {
.calendar-container {
max-width: 98vw;
margin: 16px auto;
}
.calendar-content {
padding: 18px 6vw 0 6vw;
}
.modal-content {
padding: 18px 8vw;
}
.calendar-table th,
.calendar-table td {
min-width: 80px;
font-size: 0.98rem;
}
}
@media (max-width: 600px) {
.calendar-header {
font-size: 1.2rem;
padding: 18px 10px;
}
.calendar-content {
padding: 10px 2vw 0 2vw;
}
.modal-content {
min-width: 90vw;
padding: 10px 2vw;
}
.calendar-table th,
.calendar-table td {
min-width: 60px;
font-size: 0.93rem;
padding: 4px 0;
}
.booking-form {
max-width: 98vw;
}
}
+69
View File
@@ -0,0 +1,69 @@
const express = require("express");
const router = express.Router();
const db = require("../models");
const {
validateInput,
handleValidationErrorForAPI,
} = require("../services/ValidationService");
// Validation rules for booking
const bookingValidation = {
name: "required|string",
email: "required|email",
company: "required|string",
phone: "required|string",
notes: "required|string",
date: "required|string",
time: "required|string",
timezone: "required|string",
};
// POST /api/bookings - Create a new booking
router.post(
"/bookings",
validateInput(bookingValidation, {
"name.required": "Name is required",
"email.required": "Email is required",
"email.email": "Invalid email address",
"company.required": "Company is required",
"phone.required": "Phone is required",
"notes.required": "Notes are required",
"date.required": "Date is required",
"time.required": "Time is required",
"timezone.required": "Timezone is required",
}),
handleValidationErrorForAPI,
async (req, res) => {
try {
const { name, email, company, phone, notes, date, time, timezone } =
req.body;
const booking = await db.booking.create({
name,
email,
company,
phone,
notes,
date,
time,
timezone,
});
res.status(201).json({ success: true, booking });
} catch (err) {
res.status(500).json({ error: err.message });
}
}
);
// GET /api/bookings - List all bookings
router.get("/bookings", async (req, res) => {
try {
const bookings = await db.booking.findAll({
order: [["created_at", "DESC"]],
});
res.json({ bookings });
} catch (err) {
res.status(500).json({ error: err.message });
}
});
module.exports = router;
+20 -6
View File
@@ -28,8 +28,10 @@ function getTimezoneGroups() {
const groups = {};
for (const region in regions) {
groups[region] = regions[region].map((tz) => ({
value: tz,
label: moment().tz(tz).zoneAbbr() + " " + moment().tz(tz).format("h:mma"),
name: tz,
label: tz.replace(/_/g, " ").replace("America/", ""),
time_am: moment().tz(tz).format("h:mma"),
time_24: moment().tz(tz).format("HH:mm"),
}));
}
return groups;
@@ -44,9 +46,11 @@ function getAllTimezones() {
}
// Helper: Generate week days and slots
function getWeekDaysAndSlots(selectedTz) {
function getWeekDaysAndSlots(selectedTz, weekOffset = 0) {
const weekDays = [];
const today = moment().tz(selectedTz);
const today = moment()
.tz(selectedTz)
.add(weekOffset * 7, "days");
for (let i = 0; i < 7; i++) {
const day = today.clone().add(i, "days");
weekDays.push({
@@ -89,20 +93,30 @@ router.get("/timezone", function (req, res) {
// Calendar slot selection screen
router.get("/calendar", function (req, res) {
const selectedTimezone = req.query.tz || "America/New_York";
const { weekDays, maxSlots } = getWeekDaysAndSlots(selectedTimezone);
const weekOffset = parseInt(req.query.week) || 0;
const { weekDays, maxSlots } = getWeekDaysAndSlots(
selectedTimezone,
weekOffset
);
res.render("calendar", {
selectedTimezone,
weekDays,
maxSlots,
showPrevWeek: false, // For demo, only next week link
showPrevWeek: weekOffset > 0,
prevWeek: weekOffset - 1,
nextWeek: weekOffset + 1,
});
});
// Booking form screen
router.get("/book", function (req, res) {
const selectedTimezone = req.query.tz || "America/New_York";
const selectedDate = req.query.date || "";
const selectedTime = req.query.time || "";
res.render("booking-form", {
selectedTimezone,
selectedDate,
selectedTime,
});
});
+94 -53
View File
@@ -1,56 +1,97 @@
<%- include('partials/header') %>
<div style="padding: 32px; max-width: 700px; margin: 0 auto">
<h3>Pick a date and time</h3>
<p><b>Duration:</b> 1 hour</p>
<p>Your timezone: <%= selectedTimezone %></p>
<form action="/book" method="POST" style="margin-top: 32px">
<h4>Additional Information</h4>
<label>*Full Name</label>
<input
type="text"
name="fullName"
required
style="width: 100%; margin-bottom: 12px"
/>
<label>*Email</label>
<input
type="email"
name="email"
required
style="width: 100%; margin-bottom: 12px"
/>
<label>*Company</label>
<input
type="text"
name="company"
required
style="width: 100%; margin-bottom: 12px"
/>
<label>*Phone</label>
<input
type="tel"
name="phone"
required
style="width: 100%; margin-bottom: 12px"
/>
<label>*Your Notes</label>
<textarea
name="notes"
required
style="width: 100%; margin-bottom: 16px"
></textarea>
<button
type="submit"
style="
background: #063970;
color: #fff;
padding: 8px 24px;
border: none;
border-radius: 4px;
"
>
Done
</button>
</form>
<div class="calendar-container">
<div class="calendar-header">Calendar</div>
<div class="calendar-content">
<div class="calendar-labels">
<div class="calendar-label-main">Pick a date and time</div>
<div class="calendar-label-duration">Duration: <span>1 hour</span></div>
<div class="calendar-label-timezone">
Your timezone: <%= selectedTimezone %>
</div>
<% if (selectedDate && selectedTime) { %>
<div class="calendar-label-selected">
<strong>Selected:</strong> <%= selectedDate %> at <%= selectedTime %>
</div>
<% } %>
</div>
<form class="booking-form" id="bookingForm">
<input type="hidden" name="date" value="<%= selectedDate %>" />
<input type="hidden" name="time" value="<%= selectedTime %>" />
<input type="hidden" name="tz" value="<%= selectedTimezone %>" />
<div class="form-group">
<label for="fullName">Full Name</label>
<input type="text" id="fullName" name="name" required />
<div class="form-error" id="error-name"></div>
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="email" id="email" name="email" required />
<div class="form-error" id="error-email"></div>
</div>
<div class="form-group">
<label for="company">Company</label>
<input type="text" id="company" name="company" required />
<div class="form-error" id="error-company"></div>
</div>
<div class="form-group">
<label for="phone">Phone</label>
<input type="tel" id="phone" name="phone" required />
<div class="form-error" id="error-phone"></div>
</div>
<div class="form-group">
<label for="notes">Your Notes</label>
<textarea id="notes" name="notes" required></textarea>
<div class="form-error" id="error-notes"></div>
</div>
<button type="submit" class="form-submit-btn">Done</button>
<div class="form-error" id="error-date"></div>
<div class="form-error" id="error-time"></div>
<div class="form-error" id="error-timezone"></div>
<div class="form-error" id="error-general"></div>
</form>
</div>
</div>
<%- include('partials/footer') %>
<script>
document.getElementById("bookingForm").onsubmit = async function (e) {
e.preventDefault();
// Clear previous errors
document
.querySelectorAll(".form-error")
.forEach((el) => (el.textContent = ""));
const form = e.target;
const data = {
name: form.fullName.value,
email: form.email.value,
company: form.company.value,
phone: form.phone.value,
notes: form.notes.value,
date: form.date.value,
time: form.time.value,
timezone: form.tz.value,
};
try {
const res = await fetch("/api/bookings", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
const result = await res.json();
if (result.success) {
window.location.href = "/success";
} else if (result.error) {
if (typeof result.error === "object") {
for (const key in result.error) {
const el = document.getElementById("error-" + key);
if (el) el.textContent = result.error[key];
}
} else {
document.getElementById("error-general").textContent = result.error;
}
}
} catch (err) {
document.getElementById("error-general").textContent =
"An error occurred. Please try again.";
}
};
</script>
+56 -54
View File
@@ -1,58 +1,60 @@
<%- include('partials/header') %>
<div style="padding: 32px; max-width: 1100px; margin: 0 auto">
<h3>Pick a date and time</h3>
<p><b>Duration:</b> 1 hour</p>
<p>Your timezone: <%= selectedTimezone %> <a href="/timezone">(Change)</a></p>
<div style="overflow-x: auto; margin-top: 32px">
<table style="width: 100%; border-collapse: collapse">
<thead>
<tr>
<% weekDays.forEach(function(day) { %>
<th style="padding: 8px 12px; border-bottom: 2px solid #eee">
<div style="font-weight: bold; font-size: 1.1em">
<%= day.label %>
</div>
<div style="font-size: 0.95em; color: #888"><%= day.date %></div>
</th>
<% }) %>
</tr>
</thead>
<tbody>
<% for (let i = 0; i < maxSlots; i++) { %>
<tr>
<% weekDays.forEach(function(day) { %>
<td style="padding: 8px 12px; text-align: center">
<% if (day.slots[i]) { %>
<form action="/book" method="GET" style="display: inline">
<input type="hidden" name="date" value="<%= day.dateISO %>" />
<input type="hidden" name="time" value="<%= day.slots[i] %>" />
<button
type="submit"
style="
background: #fff;
border: 1px solid #063970;
color: #063970;
border-radius: 4px;
padding: 4px 12px;
cursor: pointer;
"
>
<%= day.slots[i] %>
</button>
</form>
<% } %>
</td>
<% }) %>
</tr>
<% } %>
</tbody>
</table>
</div>
<div style="margin-top: 16px">
<% if (showPrevWeek) { %>
<a href="/calendar?week=prev">Previous Week</a>
<% } %>
<a href="/calendar?week=next" style="margin-left: 24px">Next Week</a>
<div class="calendar-container">
<div class="calendar-header">Calendar</div>
<div class="calendar-content">
<div class="calendar-labels">
<div class="calendar-label-main">Pick a date and time</div>
<div class="calendar-label-duration">Duration: <span>1 hour</span></div>
<div class="calendar-label-timezone">
Your timezone: <%= selectedTimezone %> <a href="/timezone">(Change)</a>
</div>
</div>
<div class="calendar-table-wrapper">
<table class="calendar-table">
<thead>
<tr>
<% weekDays.forEach(function(day) { %>
<th>
<div class="calendar-day-label"><%= day.label %></div>
<div class="calendar-date-label"><%= day.date %></div>
</th>
<% }) %>
</tr>
</thead>
<tbody>
<% for (let i = 0; i < maxSlots; i++) { %>
<tr>
<% weekDays.forEach(function(day) { %>
<td>
<% if (day.slots[i]) { %>
<form action="/book" method="get">
<input type="hidden" name="date" value="<%= day.dateISO %>" />
<input type="hidden" name="time" value="<%= day.slots[i] %>" />
<input
type="hidden"
name="tz"
value="<%= selectedTimezone %>"
/>
<button class="calendar-slot-btn"><%= day.slots[i] %></button>
</form>
<% } %>
</td>
<% }) %>
</tr>
<% } %>
</tbody>
</table>
</div>
<div class="calendar-week-nav">
<% if (showPrevWeek) { %>
<a href="/calendar?tz=<%= selectedTimezone %>&week=<%= prevWeek %>"
>Previous Week</a
>
<% } %>
<a href="/calendar?tz=<%= selectedTimezone %>&week=<%= nextWeek %>"
>Next Week</a
>
</div>
</div>
</div>
<%- include('partials/footer') %>
+10
View File
@@ -0,0 +1,10 @@
<%- include('partials/header') %>
<div class="calendar-container">
<div class="calendar-header">Error</div>
<div class="calendar-content">
<h1><%= message %></h1>
<h2><%= error.status %></h2>
<pre><%= error.stack %></pre>
</div>
</div>
<%- include('partials/footer') %>
-6
View File
@@ -1,6 +0,0 @@
extends layout
block content
h1= message
h2= error.status
pre #{error.stack}
+8
View File
@@ -0,0 +1,8 @@
<%- include('partials/header') %>
<div class="calendar-container">
<div class="calendar-header"><%= title %></div>
<div class="calendar-content">
<p>Welcome to <%= title %></p>
</div>
</div>
<%- include('partials/footer') %>
-5
View File
@@ -1,5 +0,0 @@
extends layout
block content
h1= title
p Welcome to #{title}
+10
View File
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel="stylesheet" href="/stylesheets/style.css" />
</head>
<body>
<%- body %>
</body>
</html>
-7
View File
@@ -1,7 +0,0 @@
doctype html
html
head
title= title
link(rel='stylesheet', href='/stylesheets/style.css')
body
block content
+2 -1
View File
@@ -1 +1,2 @@
<div style="height: 40px"></div>
</body>
</html>
+9 -11
View File
@@ -1,11 +1,9 @@
<div
style="
background: #063970;
color: white;
padding: 24px 24px 12px 24px;
font-size: 2rem;
font-weight: bold;
"
>
Calendar
</div>
<!DOCTYPE html>
<html>
<head>
<title>Calendar</title>
<link rel="stylesheet" href="/stylesheets/style.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body></body>
</html>
+4 -3
View File
@@ -1,7 +1,8 @@
<%- include('partials/header') %>
<div style="padding: 32px; max-width: 700px; margin: 0 auto">
<p style="margin-top: 32px; font-size: 1.2em">
<div class="calendar-container">
<div class="calendar-header">Calendar</div>
<div class="calendar-content success-message">
Thanks for filling in the form. You will be emailed next steps.
</p>
</div>
</div>
<%- include('partials/footer') %>
+71 -41
View File
@@ -1,48 +1,78 @@
<%- include('partials/header') %>
<div style="padding: 32px; max-width: 900px; margin: 0 auto">
<h3>Pick a date and time</h3>
<p><b>Duration:</b> 1 hour</p>
<label>Your timezone:</label>
<select name="timezone" id="timezone-select">
<% timezones.forEach(function(tz) { %>
<option value="<%= tz.value %>"><%= tz.label %></option>
<% }) %>
</select>
<hr />
<div style="display: flex; justify-content: center; margin-top: 40px">
<div
style="
background: #fff;
padding: 32px;
border-radius: 8px;
box-shadow: 0 2px 8px #0001;
min-width: 400px;
"
>
<h4>TIME ZONE</h4>
<div style="display: flex; gap: 32px">
<% Object.keys(timezoneGroups).forEach(function(region) { %>
<div>
<b><%= region %></b>
<% timezoneGroups[region].forEach(function(tz) { %>
<div>
<input type="radio" name="timezoneRadio" value="<%= tz.value %>" />
<%= tz.label %>
</div>
<% }) %>
</div>
<% }) %>
<div class="calendar-container">
<div class="calendar-header">Calendar</div>
<div class="calendar-content">
<div class="calendar-labels">
<div class="calendar-label-main">Pick a date and time</div>
<div class="calendar-label-duration">Duration: <span>1 hour</span></div>
<div class="calendar-label-timezone">
Your timezone:
<button id="select-timezone-btn" class="timezone-btn">
Please Select
</button>
</div>
<div style="margin-top: 16px">
<label
><input type="radio" name="format" value="ampm" checked />
am/pm</label
>
<label style="margin-left: 16px"
><input type="radio" name="format" value="24hr" /> 24hr</label
>
</div>
<!-- Modal Overlay -->
<div id="timezone-modal" class="modal-overlay" style="display: none">
<div class="modal-content">
<div class="modal-title">TIME ZONE</div>
<div class="modal-format-switch">
<label
><input type="radio" name="format" value="ampm" checked />
am/pm</label
>
<label><input type="radio" name="format" value="24hr" /> 24hr</label>
</div>
<div class="timezone-groups">
<% for (const group in timezoneGroups) { %>
<div class="timezone-group">
<div class="timezone-group-title"><%= group %></div>
<% timezoneGroups[group].forEach(function(tz) { %>
<label class="timezone-option">
<input type="radio" name="timezone" value="<%= tz.name %>" />
<span
class="tz-time"
data-am="<%= tz.time_am %>"
data-24="<%= tz.time_24 %>"
>
<%= tz.label %>
<span class="tz-time-value"><%= tz.time_am %></span>
</span>
</label>
<% }) %>
</div>
<% } %>
</div>
</div>
</div>
</div>
</div>
<%- include('partials/footer') %>
<script>
// Modal logic
const btn = document.getElementById("select-timezone-btn");
const modal = document.getElementById("timezone-modal");
btn.onclick = () => {
modal.style.display = "flex";
};
modal.onclick = (e) => {
if (e.target === modal) modal.style.display = "none";
};
document.querySelectorAll('input[name="timezone"]').forEach((el) => {
el.onclick = () => {
window.location.href = "/calendar?tz=" + encodeURIComponent(el.value);
};
});
// Time format toggle logic
document.querySelectorAll('input[name="format"]').forEach((el) => {
el.onchange = function () {
const is24 = this.value === "24hr";
document.querySelectorAll(".tz-time").forEach((span) => {
const am = span.getAttribute("data-am");
const t24 = span.getAttribute("data-24");
span.querySelector(".tz-time-value").textContent = is24 ? t24 : am;
});
};
});
</script>