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 indexRouter = require("./routes/index");
var usersRouter = require("./routes/users"); var usersRouter = require("./routes/users");
var apiRouter = require("./routes/api");
const db = require("./models"); const db = require("./models");
var cors = require("cors"); var cors = require("cors");
@@ -24,7 +25,7 @@ app.use(express.static(path.join(__dirname, "public")));
app.use("/", indexRouter); app.use("/", indexRouter);
app.use("/users", usersRouter); app.use("/users", usersRouter);
app.use("/api", apiRouter);
// catch 404 and forward to error handler // catch 404 and forward to error handler
app.use(function (req, res, next) { app.use(function (req, res, next) {
next(createError(404)); 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;
};
+47 -33
View File
@@ -1,4 +1,4 @@
'use strict'; "use strict";
/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2020*/ /*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2020*/
/** /**
* Sequelize File * Sequelize File
@@ -8,49 +8,63 @@
* @author Ryan Wong * @author Ryan Wong
* *
*/ */
const fs = require('fs'); const fs = require("fs");
const path = require('path'); const path = require("path");
let Sequelize = require('sequelize'); let Sequelize = require("sequelize");
const basename = path.basename(__filename); const basename = path.basename(__filename);
const { DataTypes } = require('sequelize'); const { DataTypes } = require("sequelize");
const config = { const config = {
DB_DATABASE: 'mysql', DB_DATABASE: "mysql",
DB_USERNAME: 'root', DB_USERNAME: "root",
DB_PASSWORD: 'root', DB_PASSWORD: process.env.DB_PASSWORD || "root",
DB_ADAPTER: 'mysql', DB_ADAPTER: "mysql",
DB_NAME: 'day_1', DB_NAME: "day_17",
DB_HOSTNAME: 'localhost', DB_HOSTNAME: "localhost",
DB_PORT: 3306, DB_PORT: 3306,
}; };
let db = {}; let db = {};
let sequelize = new Sequelize(config.DB_DATABASE, config.DB_USERNAME, config.DB_PASSWORD, { let sequelize = new Sequelize(
dialect: config.DB_ADAPTER, config.DB_NAME,
username: config.DB_USERNAME, config.DB_USERNAME,
password: config.DB_PASSWORD, config.DB_PASSWORD,
database: config.DB_NAME, {
host: config.DB_HOSTNAME, dialect: config.DB_ADAPTER,
port: config.DB_PORT, username: config.DB_USERNAME,
logging: console.log, password: config.DB_PASSWORD,
timezone: '-04:00', database: config.DB_NAME,
pool: { host: config.DB_HOSTNAME,
maxConnections: 1, port: config.DB_PORT,
minConnections: 0, logging: console.log,
maxIdleTime: 100, timezone: "-04:00",
}, pool: {
define: { maxConnections: 1,
timestamps: false, minConnections: 0,
underscoredAll: true, maxIdleTime: 100,
underscored: true, },
}, 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) fs.readdirSync(__dirname)
.filter((file) => { .filter((file) => {
return file.indexOf('.') !== 0 && file !== basename && file.slice(-3) === '.js'; return (
file.indexOf(".") !== 0 && file !== basename && file.slice(-3) === ".js"
);
}) })
.forEach((file) => { .forEach((file) => {
var model = require(path.join(__dirname, file))(sequelize, DataTypes); var model = require(path.join(__dirname, file))(sequelize, DataTypes);
+1
View File
@@ -17,6 +17,7 @@
"moment-timezone": "^0.6.0", "moment-timezone": "^0.6.0",
"morgan": "~1.9.1", "morgan": "~1.9.1",
"mysql2": "^2.3.3", "mysql2": "^2.3.3",
"node-input-validator": "^4.5.1",
"sequelize": "^6.15.1" "sequelize": "^6.15.1"
} }
} }
+285 -4
View File
@@ -1,8 +1,289 @@
body { body {
padding: 50px; margin: 0;
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; padding: 0;
font-family: "Inter", "Lucida Grande", Helvetica, Arial, sans-serif;
background: #eaeaea;
color: #222;
} }
a { .calendar-container {
color: #00B7FF; 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 = {}; const groups = {};
for (const region in regions) { for (const region in regions) {
groups[region] = regions[region].map((tz) => ({ groups[region] = regions[region].map((tz) => ({
value: tz, name: tz,
label: moment().tz(tz).zoneAbbr() + " " + moment().tz(tz).format("h:mma"), label: tz.replace(/_/g, " ").replace("America/", ""),
time_am: moment().tz(tz).format("h:mma"),
time_24: moment().tz(tz).format("HH:mm"),
})); }));
} }
return groups; return groups;
@@ -44,9 +46,11 @@ function getAllTimezones() {
} }
// Helper: Generate week days and slots // Helper: Generate week days and slots
function getWeekDaysAndSlots(selectedTz) { function getWeekDaysAndSlots(selectedTz, weekOffset = 0) {
const weekDays = []; const weekDays = [];
const today = moment().tz(selectedTz); const today = moment()
.tz(selectedTz)
.add(weekOffset * 7, "days");
for (let i = 0; i < 7; i++) { for (let i = 0; i < 7; i++) {
const day = today.clone().add(i, "days"); const day = today.clone().add(i, "days");
weekDays.push({ weekDays.push({
@@ -89,20 +93,30 @@ router.get("/timezone", function (req, res) {
// Calendar slot selection screen // Calendar slot selection screen
router.get("/calendar", function (req, res) { router.get("/calendar", function (req, res) {
const selectedTimezone = req.query.tz || "America/New_York"; 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", { res.render("calendar", {
selectedTimezone, selectedTimezone,
weekDays, weekDays,
maxSlots, maxSlots,
showPrevWeek: false, // For demo, only next week link showPrevWeek: weekOffset > 0,
prevWeek: weekOffset - 1,
nextWeek: weekOffset + 1,
}); });
}); });
// Booking form screen // Booking form screen
router.get("/book", function (req, res) { router.get("/book", function (req, res) {
const selectedTimezone = req.query.tz || "America/New_York"; const selectedTimezone = req.query.tz || "America/New_York";
const selectedDate = req.query.date || "";
const selectedTime = req.query.time || "";
res.render("booking-form", { res.render("booking-form", {
selectedTimezone, selectedTimezone,
selectedDate,
selectedTime,
}); });
}); });
+94 -53
View File
@@ -1,56 +1,97 @@
<%- include('partials/header') %> <%- include('partials/header') %>
<div style="padding: 32px; max-width: 700px; margin: 0 auto"> <div class="calendar-container">
<h3>Pick a date and time</h3> <div class="calendar-header">Calendar</div>
<p><b>Duration:</b> 1 hour</p> <div class="calendar-content">
<p>Your timezone: <%= selectedTimezone %></p> <div class="calendar-labels">
<form action="/book" method="POST" style="margin-top: 32px"> <div class="calendar-label-main">Pick a date and time</div>
<h4>Additional Information</h4> <div class="calendar-label-duration">Duration: <span>1 hour</span></div>
<label>*Full Name</label> <div class="calendar-label-timezone">
<input Your timezone: <%= selectedTimezone %>
type="text" </div>
name="fullName" <% if (selectedDate && selectedTime) { %>
required <div class="calendar-label-selected">
style="width: 100%; margin-bottom: 12px" <strong>Selected:</strong> <%= selectedDate %> at <%= selectedTime %>
/> </div>
<label>*Email</label> <% } %>
<input </div>
type="email" <form class="booking-form" id="bookingForm">
name="email" <input type="hidden" name="date" value="<%= selectedDate %>" />
required <input type="hidden" name="time" value="<%= selectedTime %>" />
style="width: 100%; margin-bottom: 12px" <input type="hidden" name="tz" value="<%= selectedTimezone %>" />
/> <div class="form-group">
<label>*Company</label> <label for="fullName">Full Name</label>
<input <input type="text" id="fullName" name="name" required />
type="text" <div class="form-error" id="error-name"></div>
name="company" </div>
required <div class="form-group">
style="width: 100%; margin-bottom: 12px" <label for="email">Email</label>
/> <input type="email" id="email" name="email" required />
<label>*Phone</label> <div class="form-error" id="error-email"></div>
<input </div>
type="tel" <div class="form-group">
name="phone" <label for="company">Company</label>
required <input type="text" id="company" name="company" required />
style="width: 100%; margin-bottom: 12px" <div class="form-error" id="error-company"></div>
/> </div>
<label>*Your Notes</label> <div class="form-group">
<textarea <label for="phone">Phone</label>
name="notes" <input type="tel" id="phone" name="phone" required />
required <div class="form-error" id="error-phone"></div>
style="width: 100%; margin-bottom: 16px" </div>
></textarea> <div class="form-group">
<button <label for="notes">Your Notes</label>
type="submit" <textarea id="notes" name="notes" required></textarea>
style=" <div class="form-error" id="error-notes"></div>
background: #063970; </div>
color: #fff; <button type="submit" class="form-submit-btn">Done</button>
padding: 8px 24px; <div class="form-error" id="error-date"></div>
border: none; <div class="form-error" id="error-time"></div>
border-radius: 4px; <div class="form-error" id="error-timezone"></div>
" <div class="form-error" id="error-general"></div>
> </form>
Done </div>
</button>
</form>
</div> </div>
<%- include('partials/footer') %> <%- 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') %> <%- include('partials/header') %>
<div style="padding: 32px; max-width: 1100px; margin: 0 auto"> <div class="calendar-container">
<h3>Pick a date and time</h3> <div class="calendar-header">Calendar</div>
<p><b>Duration:</b> 1 hour</p> <div class="calendar-content">
<p>Your timezone: <%= selectedTimezone %> <a href="/timezone">(Change)</a></p> <div class="calendar-labels">
<div style="overflow-x: auto; margin-top: 32px"> <div class="calendar-label-main">Pick a date and time</div>
<table style="width: 100%; border-collapse: collapse"> <div class="calendar-label-duration">Duration: <span>1 hour</span></div>
<thead> <div class="calendar-label-timezone">
<tr> Your timezone: <%= selectedTimezone %> <a href="/timezone">(Change)</a>
<% weekDays.forEach(function(day) { %> </div>
<th style="padding: 8px 12px; border-bottom: 2px solid #eee"> </div>
<div style="font-weight: bold; font-size: 1.1em"> <div class="calendar-table-wrapper">
<%= day.label %> <table class="calendar-table">
</div> <thead>
<div style="font-size: 0.95em; color: #888"><%= day.date %></div> <tr>
</th> <% weekDays.forEach(function(day) { %>
<% }) %> <th>
</tr> <div class="calendar-day-label"><%= day.label %></div>
</thead> <div class="calendar-date-label"><%= day.date %></div>
<tbody> </th>
<% for (let i = 0; i < maxSlots; i++) { %> <% }) %>
<tr> </tr>
<% weekDays.forEach(function(day) { %> </thead>
<td style="padding: 8px 12px; text-align: center"> <tbody>
<% if (day.slots[i]) { %> <% for (let i = 0; i < maxSlots; i++) { %>
<form action="/book" method="GET" style="display: inline"> <tr>
<input type="hidden" name="date" value="<%= day.dateISO %>" /> <% weekDays.forEach(function(day) { %>
<input type="hidden" name="time" value="<%= day.slots[i] %>" /> <td>
<button <% if (day.slots[i]) { %>
type="submit" <form action="/book" method="get">
style=" <input type="hidden" name="date" value="<%= day.dateISO %>" />
background: #fff; <input type="hidden" name="time" value="<%= day.slots[i] %>" />
border: 1px solid #063970; <input
color: #063970; type="hidden"
border-radius: 4px; name="tz"
padding: 4px 12px; value="<%= selectedTimezone %>"
cursor: pointer; />
" <button class="calendar-slot-btn"><%= day.slots[i] %></button>
> </form>
<%= day.slots[i] %> <% } %>
</button> </td>
</form> <% }) %>
<% } %> </tr>
</td> <% } %>
<% }) %> </tbody>
</tr> </table>
<% } %> </div>
</tbody> <div class="calendar-week-nav">
</table> <% if (showPrevWeek) { %>
</div> <a href="/calendar?tz=<%= selectedTimezone %>&week=<%= prevWeek %>"
<div style="margin-top: 16px"> >Previous Week</a
<% if (showPrevWeek) { %> >
<a href="/calendar?week=prev">Previous Week</a> <% } %>
<% } %> <a href="/calendar?tz=<%= selectedTimezone %>&week=<%= nextWeek %>"
<a href="/calendar?week=next" style="margin-left: 24px">Next Week</a> >Next Week</a
>
</div>
</div> </div>
</div> </div>
<%- include('partials/footer') %> <%- 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 <!DOCTYPE html>
style=" <html>
background: #063970; <head>
color: white; <title>Calendar</title>
padding: 24px 24px 12px 24px; <link rel="stylesheet" href="/stylesheets/style.css" />
font-size: 2rem; <meta name="viewport" content="width=device-width, initial-scale=1.0" />
font-weight: bold; </head>
" <body></body>
> </html>
Calendar
</div>
+4 -3
View File
@@ -1,7 +1,8 @@
<%- include('partials/header') %> <%- include('partials/header') %>
<div style="padding: 32px; max-width: 700px; margin: 0 auto"> <div class="calendar-container">
<p style="margin-top: 32px; font-size: 1.2em"> <div class="calendar-header">Calendar</div>
<div class="calendar-content success-message">
Thanks for filling in the form. You will be emailed next steps. Thanks for filling in the form. You will be emailed next steps.
</p> </div>
</div> </div>
<%- include('partials/footer') %> <%- include('partials/footer') %>
+71 -41
View File
@@ -1,48 +1,78 @@
<%- include('partials/header') %> <%- include('partials/header') %>
<div style="padding: 32px; max-width: 900px; margin: 0 auto"> <div class="calendar-container">
<h3>Pick a date and time</h3> <div class="calendar-header">Calendar</div>
<p><b>Duration:</b> 1 hour</p> <div class="calendar-content">
<label>Your timezone:</label> <div class="calendar-labels">
<select name="timezone" id="timezone-select"> <div class="calendar-label-main">Pick a date and time</div>
<% timezones.forEach(function(tz) { %> <div class="calendar-label-duration">Duration: <span>1 hour</span></div>
<option value="<%= tz.value %>"><%= tz.label %></option> <div class="calendar-label-timezone">
<% }) %> Your timezone:
</select> <button id="select-timezone-btn" class="timezone-btn">
<hr /> Please Select
<div style="display: flex; justify-content: center; margin-top: 40px"> </button>
<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> </div>
<div style="margin-top: 16px"> </div>
<label <!-- Modal Overlay -->
><input type="radio" name="format" value="ampm" checked /> <div id="timezone-modal" class="modal-overlay" style="display: none">
am/pm</label <div class="modal-content">
> <div class="modal-title">TIME ZONE</div>
<label style="margin-left: 16px" <div class="modal-format-switch">
><input type="radio" name="format" value="24hr" /> 24hr</label <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>
</div> </div>
</div> </div>
<%- include('partials/footer') %> <%- 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>