Gotta Latte Do is a "smart to-do app" that organizes user tasks, and allows for interactivity on tasks between users. It is inspired by Remember the Milk.
Try making your own to-do lists at our live site: Gotta Latte Do
| MVP Feature List | Database Schema | API Documentation | Frontend Routes | User Stories |
-
Clone this repository
git clone git@github.com:strewm/Gotta-Latte-Do.git
-
Install dependencies
npm install
-
Create a .env file based on the .env.example given
-
Setup your username and database based on what you setup in your .env
-
Migrate and Seed models
npx dotenv sequelize db:migrate
&&npx dotenv sequelize db:seed:all
-
Start the app using:
npm start
-
You can use the Demo user or create an account
Gotta-Latte-Do is a completely dynamic website that allows logged-in users to add/edit/delete/get features from the home page without ever redirecting from the root.
Logged in users can:
- Add/Edit/Delete Lists
- Add/Edit/Delete Tasks
- Add/Edit/Delete Comments on Tasks
- Add/Delete Contacts
- Give Tasks to Contacts
- View Tasks by Category: All, Due Today, Due Tomorrow, Given to User by Contact, Given to Contact by User, Incomplete, Completed, and User-Created List Categories
- Search for Tasks
- User notifications when:
- A task is coming due
- Another user adds user as a contact
- Another user assigns user a task
- User profiles
- Keyboard shortcuts
- One of our first challenges was associating User IDs to themselves so that Users can have Contacts:
User.associate = function(models) {
// associations can be defined here
User.belongsToMany(models.User, {foreignKey: 'userId', through: 'Contact', otherKey: 'contactId', as: 'contacts'})
User.belongsToMany(models.User, {foreignKey: 'contactId', through: 'Contact', otherKey: 'userId', as: 'contactees'})
- In a similar vein, adding created lists to a join table for Lists with the proper Task and List IDs:
const task = await Task.create({
userId,
description,
dueDate,
isCompleted
})
const listInfo = await List.findOne({
where: [{ title, userId }]
})
const taskId = task.id
const listId = listInfo.id
const taskList = await TaskList.create({
taskId,
listId
})
res.status(201).json({task, taskList});
- The dreaded Date Object:
router.get('/tomorrow', asyncHandler(async (req, res) => {
const userId = res.locals.userId;
const today = new Date();
const tomorrow = new Date(today);
tomorrow.setDate(tomorrow.getDate() + 1)
const start = tomorrow.setHours(0, 0, 0, 0);
const end = tomorrow.setHours(23, 59, 59, 999);
const tasks = await Task.findAll({
where: {
userId,
dueDate: {
[Op.between]: [start, end]
}
},
order: [['dueDate']]
})
res.json({ tasks })
}))
- Search bar functionality:
router.get('/:id', asyncHandler(async (req, res, next) => {
const searchQuery = req.params.id;
const firstThree = searchQuery.slice(0, 3);
const firstThreeUp = firstThree.charAt(0).toUpperCase() + firstThree.slice(1);
const lastThree = searchQuery.slice(-3);
const userId = res.locals.userId;
try{
const results = await Task.findAll({
where: {
userId,
[Op.or]: [
{description: { [Op.substring]: `${searchQuery}`}},
{description: { [Op.substring]: `${firstThree}`}},
{description: { [Op.substring]: `${lastThree}`}},
{description: { [Op.substring]: `${firstThreeUp}`}},
{description: { [Op.iLike]: `${firstThree}`}},
{description: { [Op.iLike]: `${lastThree}`}},
]
},
include: {
model: List,
}
})
res.status(201).json({results})
} catch (e) {
next(e)
}
}))