diff options
-rwxr-xr-x | app.js | 24 | ||||
-rwxr-xr-x | models/Event.js | 3 | ||||
-rwxr-xr-x | public/css/style.css | 17 | ||||
-rwxr-xr-x | routes.js | 14 | ||||
-rwxr-xr-x | views/event.handlebars | 37 | ||||
-rw-r--r-- | views/partials/editeventmodal.handlebars | 32 | ||||
-rwxr-xr-x | views/partials/neweventform.handlebars | 43 |
7 files changed, 150 insertions, 20 deletions
@@ -16,8 +16,28 @@ const app = express(); // View engine // - -app.engine('handlebars', hbs({defaultLayout: 'main'})); +hbsInstance = hbs.create({ + defaultLayout: 'main', + partialsDir: ['views/partials/'], + helpers: { + plural: function(number, text) { + var singular = number === 1; + // If no text parameter was given, just return a conditional s. + if (typeof text !== 'string') return singular ? '' : 's'; + // Split with regex into group1/group2 or group1(group3) + var match = text.match(/^([^()\/]+)(?:\/(.+))?(?:\((\w+)\))?/); + // If no match, just append a conditional s. + if (!match) return text + (singular ? '' : 's'); + // We have a good match, so fire away + return singular && match[1] // Singular case + || + match[2] // Plural case: 'bagel/bagels' --> bagels + || + match[1] + (match[3] || 's'); // Plural case: 'bagel(s)' or 'bagel' --> bagels + } + } +}); +app.engine('handlebars', hbsInstance.engine); app.set('view engine', 'handlebars'); // Static files // diff --git a/models/Event.js b/models/Event.js index 90c8117..43af171 100755 --- a/models/Event.js +++ b/models/Event.js @@ -155,6 +155,9 @@ const EventSchema = new mongoose.Schema({ default: true }, attendees: [Attendees], + maxAttendees: { + type: Number + }, comments: [CommentSchema] }); diff --git a/public/css/style.css b/public/css/style.css index 43d9997..122c6ad 100755 --- a/public/css/style.css +++ b/public/css/style.css @@ -292,6 +292,23 @@ body, html { margin-top: 0.5rem; } +#maxAttendeesContainer { + display: none; +} +#maxAttendeesCheckboxContainer { + display: none; +} + +.edit-buttons { + text-align: right; +} + +@media (max-width: 1199.98px) { + .edit-buttons { + text-align: left; + } +} + @media (min-width: 577px) { #sidebar { border-right: 2px solid #e0e0e0; @@ -244,6 +244,13 @@ router.get('/:eventID', (req, res) => { } } let eventAttendees = event.attendees.sort((a,b) => (a.name > b.name) ? 1 : ((b.name > a.name) ? -1 : 0)); + let spotsRemaining, noMoreSpots; + if (event.maxAttendees) { + spotsRemaining = event.maxAttendees - eventAttendees.length; + if (spotsRemaining <= 0) { + noMoreSpots = true; + } + } let metadata = { title: event.name, description: marked(event.description, { renderer: render_plain()}).split(" ").splice(0,40).join(" ").trim(), @@ -256,6 +263,8 @@ router.get('/:eventID', (req, res) => { escapedName: escapedName, eventData: event, eventAttendees: eventAttendees, + spotsRemaining: spotsRemaining, + noMoreSpots: noMoreSpots, eventStartISO: eventStartISO, eventEndISO: eventEndISO, parsedLocation: parsedLocation, @@ -334,6 +343,7 @@ router.post('/newevent', (req, res) => { usersCanAttend: req.body.joinCheckbox ? true : false, showUsersList: req.body.guestlistCheckbox ? true : false, usersCanComment: req.body.interactionCheckbox ? true : false, + maxAttendees: req.body.maxAttendees, firstLoad: true }); event.save() @@ -470,7 +480,9 @@ router.post('/editevent/:eventID/:editToken', (req, res) => { image: eventImageFilename, usersCanAttend: req.body.joinCheckbox ? true : false, showUsersList: req.body.guestlistCheckbox ? true : false, - usersCanComment: req.body.interactionCheckbox ? true : false + usersCanComment: req.body.interactionCheckbox ? true : false, + maxAttendees: req.body.maxAttendeesCheckbox ? req.body.maxAttendees : null, + } Event.findOneAndUpdate({id: req.params.eventID}, updatedEvent, function(err, raw) { if (err) { diff --git a/views/event.handlebars b/views/event.handlebars index fdbc7cf..a4d714d 100755 --- a/views/event.handlebars +++ b/views/event.handlebars @@ -8,7 +8,7 @@ <h3 id="eventName">{{eventData.name}}</h3> </div> {{#if editingEnabled}} - <div class="col-lg-2 ml-2"> + <div class="col-lg-2 ml-2 edit-buttons"> <div class="btn-group" role="group" aria-label="Event controls"> <button type="button" id="editEvent" class="btn btn-success" data-toggle="modal" data-target="#editModal" {{#if eventHasConcluded}}disabled{{/if}}><i class="fas fa-edit"></i></button> <button type="button" id="deleteEvent" class="btn btn-danger" data-toggle="modal" data-target="#deleteModal"><i class="fas fa-trash"></i></button> @@ -102,11 +102,20 @@ <div class="card mb-4" id="eventAttendees"> <h5 class="card-header">Attendees {{#if eventAttendees}}({{eventAttendees.length}}){{/if}} <div class="btn-group" role="group" aria-label="Attendance controls"> - <button type="button" id="attendEvent" class="btn btn-success" data-toggle="modal" data-target="#attendModal"><i class="fas fa-user-plus"></i> Add me</button> + {{#unless noMoreSpots}} + <button type="button" id="attendEvent" class="btn btn-success" data-toggle="modal" data-target="#attendModal"><i class="fas fa-user-plus"></i> Add me</button> + {{/unless}} <button type="button" id="unattendEvent" class="btn btn-secondary" data-toggle="modal" data-target="#unattendModal"><i class="fas fa-user-times"></i> Remove me</button> </div> </h5> <div class="card-body"> + {{#if eventData.maxAttendees}} + {{#if noMoreSpots}} + <div class="alert alert-warning text-center">This event is at capacity.</div> + {{else}} + <div class="alert alert-warning text-center">{{spotsRemaining}} {{plural spotsRemaining "spot(s)"}} remaining - add yourself now!</div> + {{/if}} + {{/if}} {{#if eventAttendees}} <ul class="attendeesList"> {{#each eventAttendees}} @@ -384,6 +393,30 @@ setTimeout(function(){ $("#copyEventLink").html('<i class="fas fa-copy"></i> Copy');}, 5000); }) $(".daysToDeletion").html(moment("{{eventEndISO}}").add(7, 'days').fromNow()); + if ($("#joinCheckbox").is(':checked')){ + $("#maxAttendeesCheckboxContainer").css("display","flex"); + } + $("#maxAttendeesCheckbox").on("click", function() { + if ($(this).is(':checked')) { + $("#maxAttendeesContainer").slideDown('fast').css("display","flex"); + $("#maxAttendees").attr("data-validation-optional","false"); + } + else { + $("#maxAttendeesContainer").slideUp('fast'); + $("#maxAttendees").attr("data-validation-optional","true").val("").removeClass('is-valid is-invalid'); + } + }); + $("#joinCheckbox").on("click", function() { + if ($(this).is(':checked')) { + $("#maxAttendeesCheckboxContainer").slideDown('fast').css("display","flex"); + } + else { + $("#maxAttendeesCheckboxContainer").slideUp('fast'); + $("#maxAttendeesCheckbox").prop("checked",false); + $("#maxAttendeesContainer").slideUp('fast'); + $("#maxAttendees").attr("data-validation-optional","true").val("").removeClass('is-valid is-invalid'); + } + }); }); </script> diff --git a/views/partials/editeventmodal.handlebars b/views/partials/editeventmodal.handlebars index ceb172b..68c8f80 100644 --- a/views/partials/editeventmodal.handlebars +++ b/views/partials/editeventmodal.handlebars @@ -7,7 +7,7 @@ <span aria-hidden="true">×</span> </button> </div> - <form id="editEventForm" action="/editevent/{{eventData.id}}/{{eventData.editToken}}" method="post" enctype="multipart/form-data"> + <form id="editEventForm" action="/editevent/{{eventData.id}}/{{eventData.editToken}}" method="post" enctype="multipart/form-data" autocomplete="off"> <div class="modal-body"> <div class="form-group row"> <label for="eventName" class="col-sm-2 col-form-label">Event name</label> @@ -34,7 +34,7 @@ </div> </div> <div class="form-group row"> - <label for="timezone" class="col-sm-2 col-form-label">Timezone</label> + <label for="timezone" class="col-sm-2 col-form-label">Timezone</label> <div class="form-group col-sm-10"> <select class="select2" id="timezone" name="timezone"></select> </div> @@ -71,20 +71,32 @@ <div class="form-group row"> <div class="col-sm-2">Options</div> <div class="col-sm-10"> + <div class="form-check"> + <input class="form-check-input" type="checkbox" id="interactionCheckbox" name="interactionCheckbox" {{#if eventData.usersCanComment}}checked{{/if}}> + <label class="form-check-label" for="interactionCheckbox"> + Users can post comments on this event + </label> + </div> <div class="form-check"> - <input class="form-check-input" type="checkbox" id="joinCheckbox" name="joinCheckbox" {{#if eventData.usersCanAttend}}checked{{/if}}> + <input class="form-check-input {{#unless eventData.usersCanAttend}}unchecked{{/unless}}" type="checkbox" id="joinCheckbox" name="joinCheckbox" {{#if eventData.usersCanAttend}}checked{{/if}}> <label class="form-check-label" for="joinCheckbox"> Users can mark themselves as attending this event </label> </div> - <div class="form-check"> - <input class="form-check-input" type="checkbox" id="interactionCheckbox" name="interactionCheckbox" {{#if eventData.usersCanComment}}checked{{/if}}> - <label class="form-check-label" for="interactionCheckbox"> - Users can post comments on this event - </label> - </div> + <div class="form-check" id="maxAttendeesCheckboxContainer" {{#if eventData.maxAttendees}}style="display:flex"{{/if}}> + <input class="form-check-input" type="checkbox" id="maxAttendeesCheckbox" name="maxAttendeesCheckbox" {{#if eventData.maxAttendees}}checked{{/if}}> + <label class="form-check-label" for="maxAttendeesCheckbox"> + Set a limit on the maximum number of attendees + </label> + </div> </div> </div> + <div class="form-group row" id="maxAttendeesContainer" {{#if eventData.maxAttendees}}style="display:flex"{{/if}}> + <label for="maxAttendees" class="col-sm-2 col-form-label">Attendee limit</label> + <div class="form-group col-sm-10"> + <input type="number" class="form-control" id="maxAttendees" name="maxAttendees" placeholder="Enter a number." data-validation="number" data-validation-optional="true" value="{{eventData.maxAttendees}}"> + </div> + </div> </div> <div class="modal-footer"> <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> @@ -93,4 +105,4 @@ </form> </div> </div> -</div>
\ No newline at end of file +</div> diff --git a/views/partials/neweventform.handlebars b/views/partials/neweventform.handlebars index bf6d0ed..51d1695 100755 --- a/views/partials/neweventform.handlebars +++ b/views/partials/neweventform.handlebars @@ -25,7 +25,7 @@ </div> </div> <div class="form-group row"> - <label for="timezone" class="col-sm-2 col-form-label">Timezone</label> + <label for="timezone" class="col-sm-2 col-form-label">Timezone</label> <div class="form-group col-sm-10"> <select class="select2" id="timezone" name="timezone"></select> </div> @@ -81,19 +81,31 @@ <div class="col-sm-2">Options</div> <div class="col-sm-10"> <div class="form-check"> + <input class="form-check-input" type="checkbox" id="interactionCheckbox" name="interactionCheckbox"> + <label class="form-check-label" for="interactionCheckbox"> + Users can post comments on this event + </label> + </div> + <div class="form-check"> <input class="form-check-input" type="checkbox" id="joinCheckbox" name="joinCheckbox"> <label class="form-check-label" for="joinCheckbox"> Users can mark themselves as attending this event </label> </div> - <div class="form-check"> - <input class="form-check-input" type="checkbox" id="interactionCheckbox" name="interactionCheckbox"> - <label class="form-check-label" for="interactionCheckbox"> - Users can post comments on this event + <div class="form-check" id="maxAttendeesCheckboxContainer"> + <input class="form-check-input" type="checkbox" id="maxAttendeesCheckbox" name="maxAttendeesCheckbox"> + <label class="form-check-label" for="maxAttendeesCheckbox"> + Set a limit on the maximum number of attendees </label> </div> </div> </div> + <div class="form-group row" id="maxAttendeesContainer"> + <label for="maxAttendees" class="col-sm-2 col-form-label">Attendee limit</label> + <div class="form-group col-sm-10"> + <input type="number" class="form-control" id="maxAttendees" name="maxAttendees" placeholder="Enter a number." data-validation="number" data-validation-optional="true"> + </div> + </div> <div class="form-group row"> <div class="col-sm-12 pt-3 pb-3 text-center"> <button type="submit" class="btn btn-primary w-50">Create</button> @@ -114,5 +126,26 @@ no_label: false }); autosize($('textarea')); + $("#maxAttendeesCheckbox").on("click", function() { + if ($(this).is(':checked')) { + $("#maxAttendeesContainer").slideDown('fast').css("display","flex"); + $("#maxAttendees").attr("data-validation-optional","false"); + } + else { + $("#maxAttendeesContainer").slideUp('fast'); + $("#maxAttendees").attr("data-validation-optional","true").val("").removeClass('is-valid is-invalid'); + } + }); + $("#joinCheckbox").on("click", function() { + if ($(this).is(':checked')) { + $("#maxAttendeesCheckboxContainer").slideDown('fast').css("display","flex"); + } + else { + $("#maxAttendeesCheckboxContainer").slideUp('fast'); + $("#maxAttendeesCheckbox").prop("checked",false); + $("#maxAttendeesContainer").slideUp('fast'); + $("#maxAttendees").attr("data-validation-optional","true").val("").removeClass('is-valid is-invalid'); + } + }); }); </script> |