16 |
26 |
27 |
28 | <% for (event of events) { %>
29 |
30 |
31 |
36 |
37 |
38 | <%= event.summary %>
39 |
40 |
41 |
42 |
43 |
44 | <%= event.address %>
45 |
46 |
47 |
48 |
View Details
49 |
50 |
51 |
52 |
53 | <% } %>
54 |
55 |
56 |
57 |
58 |
59 | <%- include partials/footer %>
60 |
61 |
63 |
64 | <%- include partials/html-scripts %>
65 |
66 |
67 |
--------------------------------------------------------------------------------
/src/views/manage.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Manage Events - Event Booking System
6 | <%- include partials/html-head %>
7 |
8 |
9 |
10 | <%- include partials/navbar %>
11 |
12 |
13 |
14 |
15 |
23 | <%- include partials/event-edit-cards %>
24 |
25 |
26 |
27 |
28 |
34 |
35 | Are you sure you want to delete this event?
36 |
37 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | <%- include partials/footer %>
50 |
51 |
53 |
54 | <%- include partials/html-scripts %>
55 |
56 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/src/test/user-book.js:
--------------------------------------------------------------------------------
1 | import mongoose from 'mongoose';
2 | import Event from '../models/event';
3 | import User from '../models/user';
4 | import assert from 'assert';
5 |
6 | describe('User booking test', () => {
7 | // sample event
8 | const eventName = 'Sample event 1';
9 | const summary = 'Sample summary 1';
10 | const address = 'UOW';
11 | const startDate = new Date(2018, 3, 14);
12 | const endDate = new Date(2018, 4, 15);
13 | const fullDesc = 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Totam placeat quasi atque, quae dignissimos aliquid harum porro consequuntur magnam voluptate hic laboriosam non cum. Modi consequuntur ipsam aspernatur nam temporibus maiores, sint voluptas? Doloribus, illo ex eum maxime assumenda molestias iste impedit illum, sit possimus sapiente saepe, ab fugiat aliquam.';
14 | const capacity = 40;
15 |
16 | // sample user
17 | const username = 'aaazureee';
18 | const email = 'aaazureee@gmail.com';
19 | const password = '123';
20 |
21 | beforeEach(done => {
22 | const promises = [
23 | mongoose.connection.db.dropCollection('users'), mongoose.connection.db.dropCollection('events')
24 | ];
25 | Promise.all(promises).then(() => done())
26 | .catch(() => done());
27 | });
28 |
29 | it('An user make an event booking', done => {
30 | // sample event
31 | const event = new Event({
32 | eventName, summary, address, startDate, endDate, fullDesc, capacity
33 | });
34 | const user = new User({
35 | username, email, password
36 | });
37 | let eventId;
38 | event.save().then(() => {
39 | user.save()
40 | .then(() => {
41 | assert(!user.isNew);
42 | // inc current bookings
43 | return Event.findByIdAndUpdate(
44 | event._id, { $inc: { currentBookings: 1 }, }, { new: true }
45 | );
46 | })
47 | .then(event => {
48 | eventId = event.eventId;
49 | assert(event.currentBookings === 1);
50 | // push eventId to user bookings
51 | return User.findByIdAndUpdate(
52 | user._id, { $push: { eventsBooked: event.eventId } }, { new: true }
53 | );
54 | })
55 | .then(user => {
56 | assert(user.eventsBooked.length === 1);
57 | assert(user.eventsBooked.indexOf(eventId) !== -1);
58 | done();
59 | });
60 | });
61 | });
62 |
63 | after(done => {
64 | const promises = [
65 | mongoose.connection.db.dropCollection('users'), mongoose.connection.db.dropCollection('events')
66 | ];
67 | Promise.all(promises).then(() => done())
68 | .catch(() => done());
69 | });
70 | });
71 |
--------------------------------------------------------------------------------
/src/views/register.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Register As
7 | <%= register_type %> - Event Booking System
8 |
9 | <%- include partials/html-head %>
10 |
11 |
12 |
13 | <%- include partials/navbar %>
14 |
15 |
16 |
17 |
18 |
19 |
20 |
25 |
26 |
27 |
28 | <%= register_type %> Registration
29 |
30 | <% register_type = register_type[0].toLowerCase() + register_type.slice(1) %>
31 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | <%- include partials/footer %>
61 |
62 |
64 |
65 | <%- include partials/html-scripts %>
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/src/routes/event/index.js:
--------------------------------------------------------------------------------
1 | import express from 'express';
2 | const router = express.Router();
3 | import Event from '../../models/event';
4 | import User from '../../models/user';
5 | import createRouter from './create';
6 | import bookRouter from './book';
7 | import updateRouter from './update';
8 | import deleteRouter from './delete';
9 | import { parseEvent } from '../common/eventParser';
10 | import { isSignedIn } from '../common/authCheck';
11 |
12 | router.use('/create', createRouter);
13 | router.use('/book', bookRouter);
14 | router.use('/update', updateRouter);
15 | router.use('/delete', deleteRouter);
16 |
17 | // view middleware
18 | router.get(['/id/:eventID', '/id/:eventID/checkout'], (req, res, next) => {
19 | Event.findOne({ eventId: req.params.eventID }).then(result => {
20 | if (!result) {
21 | return res.status(404).render('error_views/event-not-found', {
22 | error: 'There is no event with id: ' + req.params.eventID,
23 | link: '/'
24 | });
25 | }
26 | res.locals.options.event = parseEvent(result);
27 | return next();
28 | }).catch(() => {
29 | return res.status(404).render('error_views/event-not-found', {
30 | error: 'There is no event with id: ' + req.params.eventID,
31 | link: '/'
32 | });
33 | });
34 | });
35 |
36 | router.get('/id/:eventID', async (req, res, next) => {
37 | try {
38 | let event = res.locals.options.event;
39 |
40 | res.locals.options.eventFull = false;
41 | res.locals.options.booked = false;
42 |
43 | let user = await User.findOne({username: res.locals.options.username});
44 | //when user is logged in
45 | if (user) {
46 | if (user.eventsBooked.includes(event.eventId)) {
47 | res.locals.options.booked = true;
48 | }
49 | }
50 |
51 | if (event.currentBookings >= event.capacity) {
52 | res.locals.options.eventFull = true;
53 | }
54 |
55 | res.render('event-page', res.locals.options);
56 |
57 | } catch (err) {
58 | console.log(err);
59 | next(err);
60 | }
61 | });
62 |
63 | router.get('/id/:eventID/checkout', isSignedIn, (req, res) => {
64 | if (res.locals.options.event.price !== 'Free') {
65 | return res.render('checkout', res.locals.options);
66 | }
67 | return res.redirect('/event/id/' + req.params.eventID);
68 | });
69 |
70 | router.post('/id/:eventId/promo', isSignedIn, async (req, res, next) => {
71 | try {
72 | let event = await Event.findOne({ eventId: req.params.eventId });
73 | if (req.body.promo === event.promoCode) {
74 | res.json({valid: true, discount: event.discount});
75 | } else {
76 | res.json({valid: false});
77 | }
78 | } catch (err) {
79 | next(err);
80 | }
81 | });
82 | export default router;
--------------------------------------------------------------------------------
/src/app.js:
--------------------------------------------------------------------------------
1 | import 'babel-polyfill';
2 | require('dotenv').config();
3 | import express from 'express';
4 | import mongoose from 'mongoose';
5 | import bodyParser from 'body-parser';
6 | import session from 'express-session';
7 | import User from './models/user';
8 | const MongoStore = require('connect-mongo')(session);
9 | import compression from 'compression';
10 | import minify from 'express-minify';
11 | import uglifyEs from 'uglify-es';
12 | const redirectToHTTPS = require('express-http-to-https').redirectToHTTPS;
13 | import path from 'path';
14 |
15 | // router
16 | import homeRouter from './routes/home';
17 | import eventRouter from './routes/event';
18 | import userRouter from './routes/user';
19 |
20 | const app = express();
21 | app.use(compression());
22 | app.use(minify({
23 | uglifyJsModule: uglifyEs
24 |
25 | }));
26 | // connect to database
27 | mongoose.connect(process.env.DB_URI, {useNewUrlParser: true});
28 | const db = mongoose.connection;
29 | db.on('error', console.error.bind(console, 'MongoDB connection error:'));
30 | db.on('open', () => console.log('Connection to database established'));
31 |
32 | app.set('views', path.join(__dirname, 'views'));
33 | app.set('view engine', 'ejs');
34 | app.use('/assets', express.static(path.join(__dirname + '/public')));
35 | app.use(bodyParser.json());
36 | app.use(bodyParser.urlencoded({
37 | extended: false
38 | }));
39 | app.use(session({
40 | secret: process.env.SESSION_SECRET,
41 | resave: true,
42 | saveUninitialized: false,
43 | store: new MongoStore({
44 | mongooseConnection: db
45 | })
46 | }));
47 |
48 | // redirect to https except localhost
49 | app.use(redirectToHTTPS([/localhost:(\d{4})/]));
50 |
51 | // construct res.locals from session to pass to other middlewares
52 | // check authentication
53 | app.use((req, res, next) => {
54 | res.locals.options = {
55 | username: null,
56 | page: null,
57 | type: null
58 | };
59 | User.findOne({
60 | username: req.session.username
61 | })
62 | .then(user => {
63 | if (user) {
64 | res.locals.options.username = user.username;
65 | res.locals.options.email = user.email;
66 | res.locals.options.type = user.userType;
67 | res.locals.options.history = user.history;
68 | }
69 | return next();
70 | });
71 | });
72 |
73 | // app main routers
74 | app.use('/', homeRouter);
75 | app.use('/event', eventRouter);
76 | app.use('/user', userRouter);
77 |
78 | //page not found handler
79 | app.use((req, res) => {
80 | res.status(404).render('error_views/404-error');
81 | });
82 |
83 | //error handler
84 | app.use((err, req, res, next) => {
85 | console.log(err);
86 | res.status = err.status || 500;
87 | res.send(err.message);
88 | });
89 |
90 | const listener = app.listen(process.env.PORT || 3000, () => {
91 | console.log('Listening at port ' + listener.address().port);
92 | });
--------------------------------------------------------------------------------
/src/views/update-profile.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Update
6 | <%= username %>'s Profile - Event Booking System
7 | <%- include partials/html-head %>
8 |
9 |
10 |
11 |
12 | <%- include partials/navbar %>
13 |
14 |
15 |
16 |
17 |
18 |
Update
19 | <%= username %>'s Profile
20 |
40 |
41 |
42 |
43 |
44 |
62 |
63 |
64 | <%- include partials/footer %>
65 |
66 |
68 |
69 |
70 | <%- include partials/html-scripts %>
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/src/views/partials/navbar.ejs:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/public/style.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css?family=Nunito:400,400i');
2 | body {
3 | display: flex;
4 | flex-direction: column;
5 | height: 100vh;
6 | background: #FAFAFA;
7 | font-family: 'Nunito', sans-serif;
8 | }
9 |
10 | .header {
11 | min-height: 11.5vh;
12 | }
13 |
14 | main {
15 | flex: auto;
16 | }
17 |
18 | main.register,
19 | main.pre-register,
20 | main.sign-in {
21 | display: flex;
22 | }
23 |
24 | .footer {
25 | background: white;
26 | }
27 |
28 | .search {
29 | background: white;
30 | border: 1px solid #CED4DA;
31 | }
32 |
33 | .fa-search {
34 | color: rgba(0, 0, 0, 0.5);
35 | }
36 |
37 | .nav-link {
38 | font-size: 1.1em;
39 | }
40 |
41 | .nav-link:focus {
42 | color: rgba(0, 0, 0, .5) !important;
43 | }
44 |
45 | .active .nav-link {
46 | color: #039BE5 !important;
47 | }
48 |
49 | li:not(.active) .nav-link:hover {
50 | color: #039BE5 !important;
51 | }
52 |
53 | .dropdown-item:active {
54 | background: #039BE5;
55 | }
56 |
57 | .home-page .card {
58 | min-height: 25em;
59 | }
60 |
61 | .book-in {
62 | margin-top: 0.3em;
63 | width: 5.5em;
64 | }
65 |
66 | .fa-edit span {
67 | margin-left: -0.1em;
68 | }
69 |
70 | .fa-edit:hover {
71 | color: #039BE5 !important;
72 | }
73 |
74 | .info span {
75 | color: rgba(0, 0, 0, 0.8);
76 | font-weight: 400;
77 | line-height: 1.3em;
78 | font-family: 'Nunito', sans-serif;
79 | }
80 |
81 | .title {
82 | color: #0091EA;
83 | font-size: 2em;
84 | margin-bottom: -0.5em;
85 | }
86 |
87 | .fa-calendar-alt span {
88 | margin-left: 0.3em;
89 | }
90 |
91 | .fa-map-marker-alt span {
92 | margin-left: 0.4em;
93 | }
94 |
95 | .fa-dollar-sign {
96 | margin-left: 0.1em;
97 | }
98 |
99 | .fa-dollar-sign span {
100 | margin-left: 0.5em;
101 | }
102 |
103 | .home-page .card-text {
104 | font-size: 1.1em;
105 | }
106 |
107 | .card-text p {
108 | margin: 0 !important;
109 | }
110 |
111 | form[action="search"].input-group {
112 | width: 15em !important;
113 | }
114 |
115 | .dropdown-menu {
116 | width: 10em;
117 | }
118 |
119 | .big-font {
120 | font-size: 1.1em;
121 | }
122 |
123 | .big-font .btn {
124 | font-size: 1rem;
125 | }
126 |
127 | #date {
128 | background: white;
129 | }
130 |
131 | .picker__frame {
132 | position: static !important;
133 | margin-top: 7em !important;
134 | }
135 |
136 | .form-text {
137 | margin-left: 0.17em;
138 | }
139 |
140 | #full-desc {
141 | height: 15em;
142 | background: white;
143 | border: 1px solid #ced4da;
144 | width: 100%;
145 | transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out;
146 | }
147 |
148 | .picker__input.picker__input--active,
149 | .quill-active {
150 | color: #495057;
151 | background-color: #fff;
152 | border-color: #80bdff !important;
153 | outline: 0;
154 | box-shadow: 0 0 0 .2rem rgba(0, 123, 255, .25);
155 | }
156 |
157 | .ql-toolbar {
158 | display: none;
159 | }
160 |
161 | .ql-container {
162 | border-radius: 0.25rem;
163 | font-family: 'Nunito', sans-serif !important;
164 | font-size: 1rem !important;
165 | }
166 |
167 | .ql-editor.ql-blank::before {
168 | font-style: normal !important;
169 | }
170 |
171 | .fix {
172 | flex: 1;
173 | }
174 |
175 | .register input,
176 | button[type="submit"],
177 | .btn-block {
178 | font-size: 1em;
179 | }
180 |
181 | .home-page footer {
182 | margin-top: 2em;
183 | }
184 |
185 | .pre-register .border {
186 | height: 20em !important;
187 | }
188 |
189 | .close {
190 | -webkit-appearance: none !important;
191 | }
192 |
193 | a.close:hover {
194 | color: #039BE5 !important;
195 | }
196 |
197 | @media (min-width: 992px) {
198 | .home {
199 | margin-left: -0.45em;
200 | }
201 | }
202 |
203 | a:active,
204 | a:focus {
205 | outline: 0;
206 | }
207 |
208 | .event-page .card-body {
209 | margin: -0.5em 0;
210 | }
211 |
212 | .card-flex {
213 | display: flex;
214 | align-items: center;
215 | }
216 |
217 | .booked h5 {
218 | margin-bottom: 0;
219 | }
220 |
221 | .checkout-btn {
222 | font-size: 1.1em !important;
223 | }
224 |
225 | .checkout h6 {
226 | margin: 0 !important;
227 | line-height: unset !important;
228 | }
229 |
230 | .cart {
231 | margin-top: 2em;
232 | }
233 |
234 | .btn-success {
235 | margin-top: 0.25em;
236 | }
237 |
238 | .profile {
239 | font-size: 1.1em;
240 | }
241 |
242 | @media (max-height: 500px) {
243 | .register-mobile .footer {
244 | margin-top: 1.5em;
245 | }
246 | .register-mobile .header {
247 | margin-bottom: 2.5em;
248 | }
249 |
250 | @media screen and (-webkit-min-device-pixel-ratio:0)
251 | and (min-resolution:.001dpcm) {
252 | .register-mobile {
253 | height: unset;
254 | }
255 | }
256 | }
257 |
258 | @media (min-height: 501px) and (max-height: 640px) {
259 | .register-mobile .footer {
260 | margin-top: 1.5em;
261 | }
262 | .register-mobile .header {
263 | margin-bottom: 2em;
264 | }
265 |
266 | @media screen and (-webkit-min-device-pixel-ratio:0)
267 | and (min-resolution:.001dpcm) {
268 | .register-mobile {
269 | height: unset;
270 | }
271 | }
272 | }
--------------------------------------------------------------------------------
/src/public/create-form.js:
--------------------------------------------------------------------------------
1 | // Date and time picker setup
2 | const datePicker = $('#date').pickadate({
3 | editable: true,
4 | min: true,
5 | format: 'ddd d mmmm, yyyy'
6 | }).pickadate('picker');
7 |
8 | const timeOption = {
9 | editable: true,
10 | min: [7, 0], // min = 7 AM
11 | max: [22, 0] // max = 10PM
12 | };
13 |
14 | let startPicker = $('#start-time').pickatime(timeOption).pickatime('picker');
15 | let endPicker = $('#end-time').pickatime(timeOption).pickatime('picker');
16 | startPicker.on('open', () => {
17 | startPicker.close();
18 | });
19 | endPicker.on('open', () => {
20 | endPicker.close();
21 | });
22 | $(window).on('load', function () {
23 | // hack-fix timepicker pop up if clicked before page loaded
24 | setTimeout(() => {
25 | startPicker.off('open');
26 | endPicker.off('open');
27 | }, 500);
28 | });
29 | $('#date').click(() => datePicker.open());
30 | $('#start-time').click(() => startPicker.open());
31 | $('#end-time').click(() => endPicker.open());
32 |
33 | // Quilljs setup
34 | const quill = new Quill('#full-desc', {
35 | placeholder: 'Event full description',
36 | theme: 'snow'
37 | });
38 |
39 | $('#full-desc').click(function () {
40 | $(this).addClass('quill-active');
41 | });
42 |
43 | $(document).click(function (event) {
44 | if (!$(event.target).closest('#full-desc').length) {
45 | $('#full-desc').removeClass('quill-active');
46 | }
47 | });
48 |
49 | // Submit handler
50 | let form = document.getElementsByClassName('needs-validation')[0];
51 | form.onsubmit = event => {
52 | event.preventDefault();
53 | let valid = true;
54 |
55 | //reset
56 | $('#discount').prop('required', false);
57 | $('#promo-code').prop('required', false);
58 | // Promocode and discount percentage complementary handler
59 | if ($('#promo-code').val().trim()) {
60 | $('#discount').prop('required', true);
61 | valid = false;
62 | }
63 | if ($('#discount').val().trim()) {
64 | $('#promo-code').prop('required', true);
65 | valid = false;
66 | }
67 |
68 | if (($('#promo-code').val().trim()) && ($('#discount').val().trim())) valid = true;
69 |
70 | form.classList.add('was-validated');
71 | // Textarea handler
72 | if (quill.getLength() === 1) {
73 | $('#quill-feedback').show();
74 | $('.ql-container').css('border-color', '#dc3545');
75 | valid = false;
76 | } else {
77 | $('#quill-feedback').hide();
78 | $('.ql-container').css('border-color', '#28a745');
79 | }
80 |
81 | // Time handler
82 | let filledStatus;
83 | filledStatus = (datePicker.get('select') === null || startPicker.get('select') === null ||
84 | endPicker.get('select') === null) ? false : true;
85 | if (filledStatus) {
86 | const validTime = checkTime(datePicker, startPicker, endPicker);
87 | if (!validTime) {
88 | $('#start-feedback').text('Start time must be before end time.');
89 | $('#start-time').addClass('is-invalid');
90 | $('#start-time').css('border-color', '#dc3545'); // red
91 | $('#end-time').css('border-color', '#dc3545');
92 | valid = false;
93 | } else {
94 | $('#start-time').removeClass('is-invalid');
95 | $('#start-time').css('border-color', '#28a745'); // green
96 | $('#end-time').css('border-color', '#28a745');
97 | }
98 | }
99 |
100 | //submit request
101 | if (!form.checkValidity()) valid = false; // html5 input check
102 | if (valid === false) return false;
103 | // construct start Date and end Date object
104 | const { hour: startHour, mins: startMin } = startPicker.get('select');
105 | const { hour: endHour, mins: endMin } = endPicker.get('select');
106 | const date = datePicker.get('select').obj.getTime();
107 | let startDate = new Date(date);
108 | startDate.setHours(startHour);
109 | startDate.setMinutes(startMin);
110 | let endDate = new Date(date);
111 | endDate.setHours(endHour);
112 | endDate.setMinutes(endMin);
113 |
114 | let newEvent = {
115 | eventName: $('#event-name').val().trim(),
116 | summary: $('#overview').val().trim(),
117 | address: $('#address').val().trim(),
118 | startDate: startDate.toJSON(),
119 | endDate: endDate.toJSON(),
120 | fullDesc: quill.root.innerHTML,
121 | capacity: $('#capacity').val(),
122 | promoCode: $('#promo-code').val().trim(),
123 | discount: $('#discount').val(),
124 | price: $('#price').val(),
125 | };
126 |
127 | //disable submit button
128 | $('.submit-btn').attr('disabled', true);
129 |
130 | $.ajax({
131 | type: 'POST',
132 | url: '/event/create',
133 | data: JSON.stringify(newEvent),
134 | contentType: 'application/json',
135 | success: eventCreated => {
136 | //redirect user to event page
137 | window.location.href = `/event/id/${eventCreated.id}`;
138 | },
139 | error: err => {
140 | console.log(err);
141 | },
142 |
143 | });
144 |
145 | };
146 |
147 | // check if start time < end time
148 | function checkTime(datePicker, startPicker, endPicker) {
149 | const { hour: startHour, mins: startMin } = startPicker.get('select');
150 | const { hour: endHour, mins: endMin } = endPicker.get('select');
151 |
152 | const date = datePicker.get('select').obj;
153 | const startTime = new Date(date.getTime());
154 | startTime.setHours(startHour);
155 | startTime.setMinutes(startMin);
156 |
157 | const endTime = new Date(date.getTime());
158 | endTime.setHours(endHour);
159 | endTime.setMinutes(endMin);
160 |
161 | return startTime.getTime() < endTime.getTime();
162 | }
--------------------------------------------------------------------------------
/src/views/checkout.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Checkout
6 | <%= event.eventName %> - Event Booking System
7 | <%- include partials/html-head %>
8 |
9 |
10 |
11 |
12 | <%- include partials/navbar %>
13 |
14 |
15 |
16 |
17 |
110 |
111 |
112 |
113 |
131 |
132 | <%- include partials/footer %>
133 |
134 |
136 |
137 |
138 | <%- include partials/html-scripts %>
139 |
140 |
141 |
142 |
--------------------------------------------------------------------------------
/src/views/create.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Create New Event - Event Booking System
6 | <%- include partials/html-head %>
7 |
8 |
10 |
12 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | <%- include partials/navbar %>
21 |
22 |
23 |
24 |
135 |
136 |
137 | <%- include partials/footer %>
138 |
139 |
141 |
142 |
143 | <%- include partials/html-scripts %>
144 |
146 |
148 |
150 |
151 |
152 |
153 |
154 |
155 |
--------------------------------------------------------------------------------
/src/public/update-form.js:
--------------------------------------------------------------------------------
1 | // Date and time picker setup
2 | const datePicker = $('#date').pickadate({
3 | editable: true,
4 | format: 'ddd d mmmm, yyyy'
5 | }).pickadate('picker');
6 |
7 | const timeOption = {
8 | editable: true,
9 | min: [7, 0], // min = 7 AM
10 | max: [22, 0] // max = 10PM
11 | };
12 |
13 | let startPicker = $('#start-time').pickatime(timeOption).pickatime('picker');
14 | let endPicker = $('#end-time').pickatime(timeOption).pickatime('picker');
15 |
16 | // Quilljs setup
17 | const quill = new Quill('#full-desc', {
18 | placeholder: 'Event full description',
19 | theme: 'snow'
20 | });
21 |
22 | // reset button
23 | $('.reset').click(() => {
24 | startPicker.on('open', () => {
25 | startPicker.close();
26 | });
27 | endPicker.on('open', () => {
28 | endPicker.close();
29 | });
30 | $('form').get(1).reset();
31 | datePicker.set('select', new Date($('#date').data('date')));
32 | startPicker.set('select', new Date($('#start-time').data('start-time')));
33 | endPicker.set('select', new Date($('#end-time').data('end-time')));
34 | quill.root.innerHTML = $('#full-desc').data('full-desc');
35 | setTimeout(() => {
36 | startPicker.off('open');
37 | endPicker.off('open');
38 | }, 200);
39 | });
40 |
41 | datePicker.set('select', new Date($('#date').data('date')));
42 | startPicker.set('select', new Date($('#start-time').data('start-time')));
43 | endPicker.set('select', new Date($('#end-time').data('end-time')));
44 | quill.root.innerHTML = $('#full-desc').data('full-desc');
45 |
46 | startPicker.on('open', () => {
47 | startPicker.close();
48 | });
49 | endPicker.on('open', () => {
50 | endPicker.close();
51 | });
52 | $(window).on('load', function () {
53 | // hack-fix timepicker pop up if clicked before page loaded
54 | setTimeout(() => {
55 | startPicker.off('open');
56 | endPicker.off('open');
57 | }, 500);
58 | });
59 | $('#date').click(() => datePicker.open());
60 | $('#start-time').click(() => startPicker.open());
61 | $('#end-time').click(() => endPicker.open());
62 |
63 | $('#full-desc').click(function () {
64 | $(this).addClass('quill-active');
65 | });
66 |
67 | $(document).click(function (event) {
68 | if (!$(event.target).closest('#full-desc').length) {
69 | $('#full-desc').removeClass('quill-active');
70 | }
71 | });
72 |
73 | // Submit handler
74 | let form = document.getElementsByClassName('needs-validation')[0];
75 | form.onsubmit = event => {
76 | event.preventDefault();
77 | let valid = true;
78 |
79 | //reset
80 | $('#discount').prop('required', false);
81 | $('#promo-code').prop('required', false);
82 | // Promocode and discount percentage complementary handler
83 | if ($('#promo-code').val().trim()) {
84 | $('#discount').prop('required', true);
85 | valid = false;
86 | }
87 | if ($('#discount').val().trim()) {
88 | $('#promo-code').prop('required', true);
89 | valid = false;
90 | }
91 |
92 | if (($('#promo-code').val().trim()) && ($('#discount').val().trim())) valid = true;
93 |
94 | form.classList.add('was-validated');
95 | // Textarea handler
96 | if (quill.getLength() === 1) {
97 | $('#quill-feedback').show();
98 | $('.ql-container').css('border-color', '#dc3545');
99 | valid = false;
100 | } else {
101 | $('#quill-feedback').hide();
102 | $('.ql-container').css('border-color', '#28a745');
103 | }
104 |
105 | // Time handler
106 | let filledStatus;
107 | filledStatus = (datePicker.get('select') === null || startPicker.get('select') === null ||
108 | endPicker.get('select') === null) ? false : true;
109 | if (filledStatus) {
110 | const validTime = checkTime(datePicker, startPicker, endPicker);
111 | if (!validTime) {
112 | $('#start-feedback').text('Start time must be before end time.');
113 | $('#start-time').addClass('is-invalid');
114 | $('#start-time').css('border-color', '#dc3545'); // red
115 | $('#end-time').css('border-color', '#dc3545');
116 | valid = false;
117 | } else {
118 | $('#start-time').removeClass('is-invalid');
119 | $('#start-time').css('border-color', '#28a745'); // green
120 | $('#end-time').css('border-color', '#28a745');
121 | }
122 | }
123 |
124 | //submit request
125 | if (!form.checkValidity()) valid = false; // html5 input check
126 | if (valid === false) return false;
127 | // construct start Date and end Date object
128 | const { hour: startHour, mins: startMin } = startPicker.get('select');
129 | const { hour: endHour, mins: endMin } = endPicker.get('select');
130 | const date = datePicker.get('select').obj.getTime();
131 | let startDate = new Date(date);
132 | startDate.setHours(startHour);
133 | startDate.setMinutes(startMin);
134 | let endDate = new Date(date);
135 | endDate.setHours(endHour);
136 | endDate.setMinutes(endMin);
137 |
138 | let updatedEvent = {
139 | eventName: $('#event-name').val().trim(),
140 | summary: $('#overview').val().trim(),
141 | address: $('#address').val().trim(),
142 | startDate: startDate.toJSON(),
143 | endDate: endDate.toJSON(),
144 | fullDesc: quill.root.innerHTML,
145 | capacity: $('#capacity').val(),
146 | promoCode: $('#promo-code').val().trim(),
147 | discount: $('#discount').val(),
148 | price: $('#price').val(),
149 | };
150 |
151 | //disable submit button
152 | $('.submit-btn').attr('disabled', true);
153 |
154 | $.ajax({
155 | type: 'PUT',
156 | url: window.location.href,
157 | data: JSON.stringify(updatedEvent),
158 | contentType: 'application/json',
159 | success: result => {
160 | if (result.error) {
161 | $('#update-modal').on('hide.bs.modal', () => {
162 | $('.submit-btn').attr('disabled', false);
163 | });
164 | $('#update-modal .modal-body p').text(result.error);
165 | $('#update-modal').modal('show');
166 | } else {
167 | //redirect user to event page
168 | window.location.href = `/event/id/${result.id}`;
169 | }
170 | },
171 | error: err => {
172 | console.log(err);
173 | },
174 |
175 | });
176 |
177 | };
178 |
179 | // check if start time < end time
180 | function checkTime(datePicker, startPicker, endPicker) {
181 | const { hour: startHour, mins: startMin } = startPicker.get('select');
182 | const { hour: endHour, mins: endMin } = endPicker.get('select');
183 |
184 | const date = datePicker.get('select').obj;
185 | const startTime = new Date(date.getTime());
186 | startTime.setHours(startHour);
187 | startTime.setMinutes(startMin);
188 |
189 | const endTime = new Date(date.getTime());
190 | endTime.setHours(endHour);
191 | endTime.setMinutes(endMin);
192 |
193 | return startTime.getTime() < endTime.getTime();
194 | }
--------------------------------------------------------------------------------
/src/views/event-page.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | <%= event.eventName %> - Event Booking System
7 | <%- include partials/html-head %>
8 |
9 |
10 |
11 | <%- include partials/navbar %>
12 |
13 |
14 |
15 |
16 |
17 | <%= event.eventName %>
18 |
19 | <% if (type === 'staff') { %>
20 |
Edit
21 |
22 | <% } %>
23 |
24 |
25 |
26 |
27 |
33 |
34 | Are you sure you want to delete this event?
35 |
36 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | <%= event.durationString %>
51 |
52 |
53 |
54 |
55 |
56 |
57 | <%= event.address%>
58 |
59 |
60 |
61 |
62 |
63 |
64 | <%= event.price %>
65 |
66 |
67 |
68 |
69 |
70 |
71 | <% if (event.endDate > new Date() && booked) { %>
72 |
You are currently booked into this event.
73 | <% } %>
74 |
75 | <% if (event.endDate < new Date()) { %>
76 |
This event has already ended.
77 | <% } %>
78 |
79 |
80 |
81 |
82 | <%- event.fullDesc %>
83 |
84 |
85 |
86 | <% if (eventFull && !booked && event.endDate > new Date()) { %>
87 |
This event is currently fully booked.
88 | <% } %>
89 |
90 | <% if (event.endDate > new Date() && username != null ) { %>
91 |
94 | <% } %>
95 |
96 |
97 | <% if (type === 'staff') { %>
98 |
99 |
100 |
101 |
102 |
103 | Maximum capacity:
104 |
105 |
106 | <%= event.capacity %>
107 |
108 |
109 | Number of bookings:
110 |
111 |
112 | <%= event.currentBookings %>
113 |
114 |
115 | Promotional Code:
116 |
117 |
118 | <% if (!event.promoCode) { %>
119 | N/A
120 | <% } else { %>
121 | <%= event.promoCode %>
122 | <% } %>
123 |
124 |
125 | Discount Percentage:
126 |
127 |
128 | <% if (!event.discount) { %>
129 | N/A
130 | <% } else { %>
131 | <%= event.discount %>%
132 | <% } %>
133 |
134 |
135 |
136 |
137 | <% } %>
138 |
139 |
140 |
141 |
142 |
143 |
144 |
162 |
163 | <%- include partials/footer %>
164 |
165 |
167 |
168 | <%- include partials/html-scripts %>
169 |
170 |
171 |
172 |
173 |
--------------------------------------------------------------------------------
/src/views/update-event.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Update
6 | <%= event.eventName %> - Event Booking System
7 | <%- include partials/html-head %>
8 |
9 |
11 |
13 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | <%- include partials/navbar %>
22 |
23 |
24 |
25 |
26 |
27 |
Update
28 | <%= event.eventName %>
29 |
30 |
139 |
140 |
141 |
142 |
143 |
161 |
162 |
163 | <%- include partials/footer %>
164 |
165 |
167 |
168 |
169 | <%- include partials/html-scripts %>
170 |
172 |
174 |
176 |
177 |
178 |
179 |
180 |
181 |
--------------------------------------------------------------------------------