theRyanJoleneProject is a wedding guest registration web application for my friends, Ryan and Jolene. It is my second project assignment at General Assembly's Web Development Immersive (WDI) Course and is also the first full-stack web application that I have developed.
As the hosts of the wedding event, my friends would like to track their guests' RSVPs so that they can make informed arrangements for their wedding ceremony.
Most couples use a survey form which sometimes result in them forgetting which guests replied, thus complicating seating arrangements. There is also the hassle to print guests lists to check guests off at the ceremony. Worse is that there are multiple list printed out that is difficult to compile.
To tackle the problem, the application was targeted to include the following features:
-
Indication of which guests have replied, are coming and have checked in.
-
Indication on which tables can still be filled.
-
Information of guests' latest preferences.
-
Ability to overwrite guests' preferences if necessary.
-
As this is a private event. Signup should be limited to those on the guest list.
Try it out (Note: Log in required)
-
"async": "^2.3.0"
-
"bcrypt": "^1.0.2"
-
"body-parser": "^1.17.1"
-
"connect-flash": "^0.1.1"
-
"connect-mongo": "^1.3.2"
-
"dotenv": "^4.0.0"
-
"ejs": "^2.5.6"
-
"express": "^4.15.2"
-
"express-ejs-layouts": "^2.3.0"
-
"express-session": "^1.15.2"
-
"flash": "^1.1.0"
-
"method-override": "^2.3.8"
-
"mongoose": "^4.9.4"
-
"nodemon": "^1.11.0"
-
"passport": "^0.3.2"
-
"passport-local": "^1.0.0"
-
"path": "^0.12.7"
-
"sheetsu-node": "0.0.7"
app.use('/', require('./routes/mainRouter'))
router.route('/')
.get(isLoggedIn, haveInit, mainController.getMain)
router.route('/login')
.get(isLoggedOut, mainController.getLogin)
.post(mainController.postLogin)
router.route('/changepassword')
.get(isLoggedIn, mainController.getChangePass)
.put(isLoggedIn, mainController.putChangePass)
router.route('/preference')
.get(isLoggedIn, haveInit, mainController.getPreference)
.put(isLoggedIn, haveInit, mainController.putPreference)
router.route('/logout')
.get(isLoggedIn, mainController.getLogout)
app.use('/admin', isLoggedIn, isAdmin, require('./routes/adminRouter'))
router.route('/')
.get(adminController.getAdminManage)
router.route('/guest/add')
.get(adminController.getAdminAddGuest)
.post(adminController.postAdminAddGuest)
router.route('/guest/:id')
.get(adminController.getAdminEditGuest)
.put(adminController.editGuest)
.delete(adminController.deleteGuest)
router.route('/table/add')
.get(adminController.getAdminAddTable)
.post(adminController.postAdminAddTable)
router.route('/table/:id')
.get(adminController.getAdminEditTable)
.put(adminController.editTable)
.delete(adminController.deleteTable)
router.route('/checkin')
.get(adminController.getAdminCheckIn)
router.route('/checkin/:id')
.get(adminController.getAdminCheckInGuest)
.put(adminController.putAdminCheckInGuest)
Table was referenced in each User
var UserSchema = new mongoose.Schema({
name: {
type: String,
required: true,
minlength: [1, 'Name must be between 1 and 99 characters'],
maxlength: [99, 'Name must be between 1 and 99 characters'],
},
email: {
type: String,
required: true,
unique: true,
lowercase: true,
match: emailRegex
},
password: {
type: String,
required: true ,
minlength: [6, 'Password must be between 6 and 99 characters'],
maxlength: [99, 'Password must be between 8 and 99 characters'],
},
admin: {
type: Boolean,
default: false
},
attending: {
type: Boolean,
default: false
},
table: {
type: mongoose.Schema.ObjectId,
ref: 'Table'
},
foodPref: {
type: String,
default: 'any'
},
headCountAllowed: {
type: Number,
default: 2,
min: [1, 'Head Count Allowed must be at least 1']
},
headCountSelected: {
type: Number,
default: 1,
min: [0, 'Head Count Selected must be at least 0'],
max: [this.headCountAllowed, 'Head Count Selected cannot be more than Head Count Allowed']
},
checkedin: {
type: Number,
default: 0,
min: [0, 'Number Checked in must be at least 0'],
max: [this.headCountSelected, 'Head Count Selected cannot be more than Head Count Selected']
},
haveInit: {
type: Boolean,
default: false
},
permanent: {
type: Boolean,
default: false
}
})
Note:
headCountAllowed: Quantity of guest(s) allocated to this user (includes additional guests)
headCountSelected: Quantity of guest(s) this user has selected
checkedin: Quantity of guest(s) this user has checked in
haveInit: true if user has set preferences for the first time
permanent: true if this is the default user / owner
var TableSchema = new mongoose.Schema({
name: {
type: String,
unique: true
},
capacity: {
type: Number,
default: 10,
min: [1, 'Table capacity must be at least 1']
},
plannedFor: {
type: Number,
default: 0
},
reservedFor: {
type: Number,
default: 0
},
checkedIn: {
type: Number,
default: 0
},
permanent: {
type: Boolean,
default: false
}
})
Note:
plannedFor: Quantity of guest(s) allocated to this table (includes additional guests)
reservedFor: Quantity of guest(s) who replied that belong to this table
checkedIn: Quantity of guest(s) that belong to this table who have checked in
permanent: true if this is the default table
Below are the user actions that involved both Schemas.
Async Series was used to call functions in order.
Steps
-
Conduct Checks on form for errors
-
Save New User
-
Find selected table ID and increase 'plannedFor', 'reservedFor' & 'checkedIn' values from new guest
Steps
-
Conduct Checks on form for errors
-
Find user's previous table ID, 'headCountAllowed', 'headCountSelected' & 'checkedin'
-
Find previous table with ID, decrease 'plannedFor', 'reservedFor' and 'checkedIn' values accordingly
-
Update user's new details
-
Find user's new table ID and increase 'plannedFor', 'reservedFor' and 'checkedIn' values accordingly
Steps
-
Find user's previous table ID, 'headCountAllowed', 'headCountSelected' & 'checkedin'
-
Find previous table with ID, decrease 'plannedFor', 'reservedFor' and 'checkedIn' values accordingly
-
Remove User
Steps
-
Find default table ID
-
Find array of affected users
-
Find total 'headCountAllowed', 'headCountSelected' & 'checkedin' from array of users
-
Find user's default table ID and increase 'plannedFor', 'reservedFor' and 'checkedIn' values accordingly
-
Change all affected users' table ID to default table's
-
Remove selected table
jQuery.expr[':'].Contains = function(a,i,m){
return (a.textContent || a.innerText || "").toUpperCase().indexOf(m[3].toUpperCase())>=0
}
function listFilter (searchBar, list) {
$(searchBar).change( function () {
var filter = $(this).val()
if (filter) {
$(list).find("a:not(:Contains(" + filter + "))").parent().slideUp()
$(list).find("a:Contains(" + filter + ")").parent().slideDown()
}
else {
$(list).find("li").slideDown()
}
}).keyup( function () {
$(this).change()
})
}
listFilter ($('#nameSearch'), $('#guestList'))
Steps
-
Start when there is a change on the search bar (key up)
-
Slide up the names that do not contain what was typed in the search bar
-
Slide down if nothing was typed or if the name contains what was typed into the search bar
-
Compare both in CAPS because .contains is case sensitive. Have to create a new expression .Contains that compares all in CAPS
theRyanJoleneProject is not a complete solution yet. Feel free to add comments and suggestions to improve it!
Currently, users have to manually key in their guests one at a time. With Sheetsu, they can upload / sync their guest list from google spreadsheets. This is a work in progress. Here is the spreadsheet example.
UPDATE: Sheetsu's free trial has expired. Will be looking into Google Sheets API instead.
Email / SMS Notifications
- Automate Invite
- Allow users to reset password
Automate fill tables according to guests' groups
- Auto assign seating arrangement for host
Search Sort Function
- Need a search / sort function for the manage tables. Difficult for host to search when there are more guests.
Accountability
- Log date & time of changes on guest's preferences and the user who changed it.
Delete Confirmation
- Verify with user if they want to delete a guest.
Event Schema
Allow hosts to change location, time, date, picture
Facebook Login
Simplify the log in process
Entertainment features for the event
-
Instragram API to view wedding couple's photos
-
Socket IO for live well wishes
-
See Photodrop
-
Users can currently type an unlimited length of text for their food preferences - will have to limit that.
-
Validation of table values - Currently, they can be left blank
-
https://www.beourguest.co/ <-- Idea & Design inspiration. These guys are awesome. Would love to collaborate with them one day.
-
https://kilianvalkhof.com/2010/javascript/how-to-build-a-fast-simple-list-filter-with-jquery/
-
http://stackoverflow.com/questions/22246626/show-hide-children-on-parent-click
Placeholder Picture
Favicon
- wedding by Gan Khoon Lay from the Noun Project