QuestTrackr is a clone of the popular web app Remember the Milk with an RPG twist.
To run this application locally, you'll need to:
git clone
this repocd
into the local reponpm install
to install the dependencies- Create a
.env
file based on the.env.example
file included in the repo with your own values - Create a user on your local machine with the username and password specified in your
.env
file in PostgreSQL - Run
npx dotenv sequelize db:create
to create the database- If the
sequelize
module is not found, try runningnpx dotenv sequelize-cli db:create
and replacesequelize
withsequelize-cli
for the rest of these commands
- If the
- Run
npx dotenv sequelize db:migrate
to run the migrations - Run
npx dotenv sequelize db:seed:all
to seed the database - Finally, start the development server with
npm start
. The scripts in thepackage.json
should do the work. You'll see the local address you can use show up in the terminal.
- PostgreSQL
- Express.js
- Pug.js
- JavaScript
- CSS
- Bcryptjs
- Express-session
- Express-validator
- Node.js
Here's a link to our live app!
Here's a link to our Wiki!
Users can:
- View open quests and choosing to take on said quests
- Create their own quests
- Delete their quests
- Mark a quest they've taken as completed
- See all the quests they've signed up for
- See their current XP status
- See their current quest completion summary
Some of our challenges included:
- Getting the vanilla JavaScript to render elements from an AJAX call to an API route
AJAX to API route to render the Quests on the Main Quests page at /quests
.
async function createQuestDivs(category = 'all') {
const questsContainer = document.querySelector('.quests-container');
const res = await fetch(`/api/quests/${category}`);
const { quests } = await res.json();
questsContainer.innerHTML = '';
if (!quests.length) {
questsContainer.innerHTML = `
<div class="errorDiv">
<h3>No quests found in this category of ${category}</h3>
</div>
`;
return;
}
for (let quest of quests) {
const outerDiv = document.createElement('div');
const nameDiv = document.createElement('div');
const deadlineDiv = document.createElement('div');
const xpValueDiv = document.createElement('div');
const soloDiv = document.createElement('div');
const categoryDiv = document.createElement('div');
const buttonDiv = document.createElement('div');
outerDiv.classList.add('quests-container__quest-container');
nameDiv.classList.add('quests-container__quest-container--name', 'quest-container');
deadlineDiv.classList.add('quests-container__quest-container--deadline', 'quest-container');
xpValueDiv.classList.add('quests-container__quest-container--xpValue', 'quest-container');
categoryDiv.classList.add('quests-container__quest-container--category', 'quest-container');
soloDiv.classList.add('quests-container__quest-container--solo', 'quest-container');
buttonDiv.classList.add('quests-container__quest-container--button', 'quest-container');
nameDiv.innerHTML = `
<h4> Quest Name </h4>
<a href="/quests/${quest.id}"><p>${quest.name}</p></a>
`;
outerDiv.appendChild(nameDiv);
deadlineDiv.innerHTML = `
<h4> Deadline </h4>
<p>${new Date(quest.deadline)}</p>
`;
outerDiv.appendChild(deadlineDiv);
xpValueDiv.innerHTML = `
<h4> XP Value </h4>
<p>${quest.xpValue}</p>
`;
outerDiv.appendChild(xpValueDiv);
soloDiv.innerHTML = `
<h4> Solo? </h4>
<p>${quest.solo}</p>
`;
outerDiv.appendChild(soloDiv);
if (quest.Category) {
categoryDiv.innerHTML = `
<h4> Category </h4>
<p>${quest.Category.name}</p>
`;
outerDiv.appendChild(categoryDiv);
} else {
categoryDiv.innerHTML = `
<h4> Category </h4>
<p> None </p>
`;
outerDiv.appendChild(categoryDiv);
}
buttonDiv.innerHTML = `
<form method="post" action="/quests/join/${quest.id}">
<button class="join-quest-btn" id="quest-${quest.id}"> Join Quest </button>
</form>
`;
outerDiv.appendChild(buttonDiv);
questsContainer.appendChild(outerDiv);
}
};
document.addEventListener('DOMContentLoaded', async () => {
await createQuestDivs();
const categorySelect = document.querySelector('#category-select');
categorySelect.addEventListener('change', async () => {
const newValue = categorySelect.value;
await createQuestDivs(newValue);
});
});
API Route the above code is hitting
router.get('/quests/:category([\-\\w]+)', asyncHandler(async (req, res) => {
const category = req.params.category;
let quests;
if (category === 'all') {
quests = await Quest.findAll({ include: Category, where: { completedDate: null } });
} else {
quests = await Quest.findAll({
include: {
model: Category,
where: {
tag: category,
},
},
where: {
completedDate: null,
},
});
}
res.json({ quests });
}));