Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions routes/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
const { genPassword, validPassword } = require('../utils/functions/password');
const emailValidation = require('../utils/functions/emailValidation');

const expressLayouts = require('express-ejs-layouts');

const VIEWS = __dirname + '/../views/';

module.exports = (app, mongo) => {
Expand Down Expand Up @@ -286,10 +288,11 @@ module.exports = (app, mongo) => {
}
});

app.get('/settings', (req, res) => {
res.render(VIEWS + 'private/settings.ejs', {
app.get('/settings', expressLayouts, (req, res) => {
res.render(VIEWS + 'private/settingsV2.ejs', {
user: req.user,
pageName: 'Settings',
layout: 'layouts/base.ejs',
});
});
};
13 changes: 8 additions & 5 deletions routes/stats.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@ module.exports = (app, mongo) => {
}
});

app.get('/leaderboard', async (req, res) => {
app.get('/leaderboard', expressLayouts, async (req, res) => {
const leaderboard = await generateLeaderboard(mongo.User, 10);
res.render(VIEWS + 'private/leaderboard.ejs', {
res.render(VIEWS + 'private/leaderboardV2.ejs', {
rankings: leaderboard,
pageName: 'Leaderboard',
layout: 'layouts/base.ejs',
});
});

Expand Down Expand Up @@ -125,7 +126,7 @@ module.exports = (app, mongo) => {
}
});

app.get('/stats/:username', (req, res) => {
app.get('/stats/:username', expressLayouts, (req, res) => {
mongo.User.findOne({ ign: req.params.username }, async function (err, obj) {
if (obj) {
if (
Expand All @@ -146,12 +147,13 @@ module.exports = (app, mongo) => {
obj.stats.units ? obj.stats.units : {}
);

res.render(VIEWS + 'private/stats.ejs', {
res.render(VIEWS + 'private/statsV2.ejs', {
user: obj,
totalTags: tags,
userLevel,
analytics,
pageName: obj.ign + "'s Stats",
layout: 'layouts/base.ejs',
});
} else {
req.flash(
Expand All @@ -167,12 +169,13 @@ module.exports = (app, mongo) => {
);
let analytics = await analyze(obj.stats.units ? obj.stats.units : {});

res.render(VIEWS + 'private/stats.ejs', {
res.render(VIEWS + 'private/statsV2.ejs', {
user: obj,
totalTags: tags,
userLevel,
analytics,
pageName: obj.ign + "'s Stats",
layout: 'layouts/base.ejs',
});
}
} else {
Expand Down
138 changes: 138 additions & 0 deletions views/private/leaderboardV2.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
<%
const rankBg = (r) => {
if (r === 1) return 'bg-sky-500 text-white';
if (r === 2) return 'bg-yellow-400';
if (r === 3) return 'bg-red-500 text-white';
return 'bg-green-500 text-white';
};
const medalSrc = (r) => {
if (r === 1) return 'https://cdn.mutorials.org/images/icons/Leaderboard_First.svg';
if (r === 2) return 'https://cdn.mutorials.org/images/icons/Leaderboard_Second.svg';
if (r === 3) return 'https://cdn.mutorials.org/images/icons/Leaderboard_Third.svg';
return null;
};
const truncate = (s, n) => s.length <= n ? s : s.substring(0, n - 3) + '...';
%>

<%- contentFor('main') %>
<div class="flex flex-col items-center w-full max-w-6xl px-6 py-8 gap-10">

<h1 class="text-4xl sm:text-5xl text-center">Rating Leaderboard</h1>

<div class="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-6 w-full">
<% [
['Physics', rankings.physics, (u) => u.rating.physics],
['Chemistry', rankings.chem, (u) => u.rating.chemistry],
['Biology', rankings.bio, (u) => u.rating.biology],
['USABO', rankings.usabo, (u) => u.rating.usabo],
].forEach(([title, list, getValue]) => { %>
<div class="p-4 rounded-xl border-2">
<h3 class="text-center mb-3"><%= title %></h3>
<div class="overflow-x-auto">
<table class="w-full text-sm text-center">
<thead class="bg-neutral-100">
<tr>
<th>Rank</th>
<th>Username</th>
<th>Rating</th>
</tr>
</thead>
<tbody>
<% let rank = 1; %>
<% list.forEach((user) => { %>
<tr class="<%= rankBg(rank) %>">
<th scope="row">
<% const medal = medalSrc(rank); %>
<% if (medal) { %>
<img src="<%= medal %>" width="25" height="25" class="inline-block align-top" alt="">
<% } else { %>
<%= rank %>
<% } %>
</th>
<td><a class="no-underline hover:underline" href="/profile/<%= user.ign %>" target="_blank"><%= truncate(user.ign, 15) %></a></td>
<td><%= getValue(user) %></td>
</tr>
<% rank++; %>
<% }) %>
</tbody>
</table>
</div>
</div>
<% }) %>
</div>

<h1 class="text-4xl sm:text-5xl text-center mt-6">Problem Rush Leaderboard</h1>

<div class="w-full lg:max-w-3xl mx-auto p-4 rounded-xl border-2">
<h3 class="text-center mb-3">Highest Rush Scores</h3>
<div class="overflow-x-auto">
<table class="w-full text-sm text-center">
<thead class="bg-neutral-100">
<tr>
<th>Rank</th>
<th>Username</th>
<th>High Score</th>
<th>Attempts</th>
</tr>
</thead>
<tbody>
<% let rankRush = 1; %>
<% rankings.rush.forEach((user) => { %>
<tr class="<%= rankBg(rankRush) %>">
<th scope="row">
<% const medal = medalSrc(rankRush); %>
<% if (medal) { %>
<img src="<%= medal %>" width="25" height="25" class="inline-block align-top" alt="">
<% } else { %>
<%= rankRush %>
<% } %>
</th>
<td><a class="no-underline hover:underline" href="/profile/<%= user.ign %>" target="_blank"><%= truncate(user.ign, 33) %></a></td>
<td><%= user.stats.rush.highscore %></td>
<td><%= user.stats.rush.attempts %></td>
</tr>
<% rankRush++; %>
<% }) %>
</tbody>
</table>
</div>
</div>

<h1 class="text-4xl sm:text-5xl text-center mt-6">Experience Leaderboard</h1>

<div class="w-full lg:max-w-3xl mx-auto p-4 rounded-xl border-2">
<h3 class="text-center mb-3">Highest Level Mutorials Users</h3>
<div class="overflow-x-auto">
<table class="w-full text-sm text-center">
<thead class="bg-neutral-100">
<tr>
<th>Rank</th>
<th>Username</th>
<th>Level</th>
<th>Experience</th>
</tr>
</thead>
<tbody>
<% let rankExp = 1; %>
<% rankings.experience.forEach((user) => { %>
<tr class="<%= rankBg(rankExp) %>">
<th scope="row">
<% const medal = medalSrc(rankExp); %>
<% if (medal) { %>
<img src="<%= medal %>" width="25" height="25" class="inline-block align-top" alt="">
<% } else { %>
<%= rankExp %>
<% } %>
</th>
<td><a class="no-underline hover:underline" href="/profile/<%= user.ign %>" target="_blank"><%= truncate(user.ign, 33) %></a></td>
<td><%= user.level.level %></td>
<td><%= Math.round(user.experience) %></td>
</tr>
<% rankExp++; %>
<% }) %>
</tbody>
</table>
</div>
</div>

</div>
175 changes: 175 additions & 0 deletions views/private/settingsV2.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
<%- contentFor('main') %>
<div
class="flex flex-col items-stretch w-full max-w-3xl px-6 py-8 gap-6"
x-data="{ unsaved: false, showDeleteConfirm: false, showPwReq: false }"
>
<h1 class="text-4xl sm:text-5xl">Settings</h1>

<div
x-show="unsaved"
x-cloak
x-transition
class="px-4 py-2 rounded border-2 border-orange-300 bg-orange-50 text-orange-900"
>
Careful! You have unsaved changes!
</div>

<%# Profile %>
<form action="/changeProfile" method="POST" class="p-6 rounded-xl border-2 flex flex-col gap-4">
<h3 class="text-xl font-medium">Profile</h3>

<div class="grid grid-cols-1 sm:grid-cols-12 sm:items-center gap-2">
<label for="name" class="sm:col-span-3 font-medium">Name</label>
<input
type="text" id="name" name="name"
class="sm:col-span-9"
value="<%= user.profile.name %>" placeholder="Enter your name here"
@input="unsaved = true"
>
</div>
<div class="grid grid-cols-1 sm:grid-cols-12 sm:items-center gap-2">
<label for="bio" class="sm:col-span-3 font-medium">Bio</label>
<input
type="text" id="bio" name="bio"
class="sm:col-span-9"
value="<%= user.profile.bio %>" placeholder="Introduce yourself!"
@input="unsaved = true"
>
</div>
<div class="grid grid-cols-1 sm:grid-cols-12 sm:items-center gap-2">
<label for="location" class="sm:col-span-3 font-medium">Location</label>
<input
type="text" id="location" name="location"
class="sm:col-span-9"
value="<%= user.profile.location %>" placeholder="Where do you live?"
@input="unsaved = true"
>
</div>
<div class="grid grid-cols-1 sm:grid-cols-12 sm:items-center gap-2">
<label for="yob" class="sm:col-span-3 font-medium">Year of Birth</label>
<input
type="number" id="yob" name="yob"
class="sm:col-span-9"
value="<%= user.profile.yob %>" placeholder="Enter your year of birth here"
@input="unsaved = true"
>
</div>

<input class="btn btn-primary self-start" type="submit" value="Update Profile">
</form>

<%# Personalization %>
<form action="/changePreferences" method="POST" class="p-6 rounded-xl border-2 flex flex-col gap-4">
<h3 class="text-xl font-medium">Personalization</h3>

<label class="flex items-center gap-2">
<input type="checkbox" id="darkMode" name="darkMode" <% if (locals.darkMode) { %>checked<% } %>>
<span>Dark Mode</span>
</label>
<label class="flex items-center gap-2">
<input type="checkbox" id="hideProfile" name="hideProfile" <% if (user.preferences.hideProfile) { %>checked<% } %>>
<span>Hide Public Profile and Stats</span>
</label>

<input class="btn btn-primary self-start" type="submit" value="Update Preferences">
</form>

<%# Account %>
<form action="/changeSettings" method="POST" class="p-6 rounded-xl border-2 flex flex-col gap-4">
<h3 class="text-xl font-medium">Account</h3>

<div class="grid grid-cols-1 sm:grid-cols-12 sm:items-center gap-2">
<label for="ign" class="sm:col-span-3 font-medium">Username</label>
<input
type="text" id="ign" name="ign"
class="sm:col-span-9"
value="<%= user.ign %>" placeholder="Change your username"
@input="unsaved = true"
>
</div>
<div class="grid grid-cols-1 sm:grid-cols-12 sm:items-center gap-2">
<label for="username" class="sm:col-span-3 font-medium">Email</label>
<input
type="text" id="username" name="username"
class="sm:col-span-9"
value="<%= user.username %>" placeholder="Change your linked email"
@input="unsaved = true"
>
</div>

<div class="grid grid-cols-1 sm:grid-cols-12 sm:items-center gap-2">
<label for="plassword" class="sm:col-span-3 font-medium">Change Password</label>
<input
type="password" id="plassword" name="plassword"
class="sm:col-span-9"
placeholder="Current password"
>
<div class="hidden sm:block sm:col-span-3"></div>
<input
type="password" id="newpw" name="newpw"
class="sm:col-span-9"
placeholder="New password"
@focus="showPwReq = true"
@blur="setTimeout(() => showPwReq = false, 1000)"
>
<div class="hidden sm:block sm:col-span-3"></div>
<input
type="password" id="password" name="confirmnewpw"
class="sm:col-span-9"
placeholder="Confirm new password"
>
<p
x-show="showPwReq"
x-cloak
x-transition
class="sm:col-span-12 text-sm"
>
A valid password must have a minimum of <strong>7 characters</strong> which includes at least <strong>1 letter</strong> and at least <strong>1 number</strong>.
</p>
</div>

<input class="btn btn-primary self-start" type="submit" value="Update Account">
</form>

<%# Delete Account %>
<form id="delete-account-form" action="/deleteAccount" method="POST" class="p-6 rounded-xl border-2 flex flex-col gap-4">
<h3 class="text-xl font-medium">Delete Account</h3>

<div class="grid grid-cols-1 sm:grid-cols-12 sm:items-center gap-2">
<label for="delassword" class="sm:col-span-3 font-medium">Password</label>
<input
type="password" id="delassword" name="delassword"
class="sm:col-span-9"
placeholder="Current password"
>
</div>

<button
type="button"
class="btn bg-red-600 hover:bg-red-700 text-white self-start"
@click="showDeleteConfirm = true"
>Delete Account</button>
</form>

<%# Confirm-delete modal %>
<div
x-show="showDeleteConfirm"
x-cloak
class="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
@click.self="showDeleteConfirm = false"
@keydown.escape.window="showDeleteConfirm = false"
>
<div class="bg-white rounded-xl shadow-lg max-w-sm w-full mx-4 overflow-hidden">
<div class="px-6 py-4 border-b font-medium">Confirm Delete Account</div>
<div class="px-6 py-4">Are you sure you want to delete your account?</div>
<div class="px-6 py-4 border-t flex justify-end gap-2">
<button type="button" class="btn btn-outline" @click="showDeleteConfirm = false">Cancel</button>
<button
type="button"
class="btn bg-red-600 hover:bg-red-700 text-white"
@click="document.getElementById('delete-account-form').submit()"
>Delete</button>
</div>
</div>
</div>
</div>
Loading
Loading