Finished up with email verification system and profile page redux.
This commit is contained in:
parent
c32f3df3f3
commit
40c004795b
|
|
@ -62,27 +62,7 @@ module.exports.post = async function(req, res){
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
|
|
||||||
//Send the reset url via email
|
//Send the reset url via email
|
||||||
await mailUtils.mailem(
|
await mailUtils.sendAddressVerification(requestDB, userDB, email);
|
||||||
email,
|
|
||||||
`Email Change Request - ${userDB.user}`,
|
|
||||||
`<h1>Email Change Request</h1>
|
|
||||||
<p>A request to change the email associated with the ${config.instanceName} account '${userDB.user}' to this address has been requested.<br>
|
|
||||||
<a href="${requestDB.getChangeURL()}">Click here</a> to confirm this change.</p>
|
|
||||||
<sup>If you received this email without request, feel free to ignore and delete it! -Tokebot</sup>`,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
//If the user has a pre-existing email address
|
|
||||||
if(userDB.email != null && userDB.email != ""){
|
|
||||||
await mailUtils.mailem(
|
|
||||||
userDB.email,
|
|
||||||
`Email Change Request - ${userDB.user}`,
|
|
||||||
`<h1>Email Change Request Notification</h1>
|
|
||||||
<p>A request to change the email associated with the ${config.instanceName} account '${userDB.user}' to another address has been requested.<br>
|
|
||||||
<sup>If you received this email without request, you should <strong>immediately</strong> change your password and contact the server adminsitrator! -Tokebot</sup>`,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Clean our hands of the operation
|
//Clean our hands of the operation
|
||||||
return;
|
return;
|
||||||
|
|
@ -91,6 +71,7 @@ module.exports.post = async function(req, res){
|
||||||
return res.send({errors: validResult.array()});
|
return res.send({errors: validResult.array()});
|
||||||
}
|
}
|
||||||
}catch(err){
|
}catch(err){
|
||||||
|
console.log(err)
|
||||||
return exceptionHandler(res, err);
|
return exceptionHandler(res, err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -59,6 +59,7 @@ module.exports.post = async function(req, res){
|
||||||
return res.send({errors: validResult.array()});
|
return res.send({errors: validResult.array()});
|
||||||
}
|
}
|
||||||
}catch(err){
|
}catch(err){
|
||||||
|
console.log(err);
|
||||||
return exceptionHandler(res, err);
|
return exceptionHandler(res, err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -28,13 +28,17 @@ module.exports.get = async function(req, res){
|
||||||
try{
|
try{
|
||||||
var profileName = req.url.slice(1) == '' ? (req.session.user ? req.session.user.user : null) : req.url.slice(1);
|
var profileName = req.url.slice(1) == '' ? (req.session.user ? req.session.user.user : null) : req.url.slice(1);
|
||||||
|
|
||||||
const profile = await userModel.findProfile({user: profileName});
|
const profile = await userModel.findProfile({user: profileName}, true);
|
||||||
|
|
||||||
if(profile){
|
if(profile){
|
||||||
|
//If we have a user, check if the is looking at their own profile
|
||||||
|
const selfProfile = req.session.user ? profile.user == req.session.user.user : false;
|
||||||
|
|
||||||
res.render('profile', {
|
res.render('profile', {
|
||||||
instance: config.instanceName,
|
instance: config.instanceName,
|
||||||
user: req.session.user,
|
user: req.session.user,
|
||||||
profile,
|
profile,
|
||||||
|
selfProfile,
|
||||||
csrfToken: csrfUtils.generateToken(req)
|
csrfToken: csrfUtils.generateToken(req)
|
||||||
});
|
});
|
||||||
}else{
|
}else{
|
||||||
|
|
@ -42,6 +46,7 @@ module.exports.get = async function(req, res){
|
||||||
instance: config.instanceName,
|
instance: config.instanceName,
|
||||||
user: req.session.user,
|
user: req.session.user,
|
||||||
profile: null,
|
profile: null,
|
||||||
|
selfProfile: false,
|
||||||
csrfToken: csrfUtils.generateToken(req)
|
csrfToken: csrfUtils.generateToken(req)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,10 @@ const statModel = require('../statSchema');
|
||||||
const flairModel = require('../flairSchema');
|
const flairModel = require('../flairSchema');
|
||||||
const permissionModel = require('../permissionSchema');
|
const permissionModel = require('../permissionSchema');
|
||||||
const emoteModel = require('../emoteSchema');
|
const emoteModel = require('../emoteSchema');
|
||||||
|
const emailChangeModel = require('./emailChangeSchema');
|
||||||
//Utils
|
//Utils
|
||||||
const hashUtil = require('../../utils/hashUtils');
|
const hashUtil = require('../../utils/hashUtils');
|
||||||
|
const mailUtil = require('../../utils/mailUtils');
|
||||||
|
|
||||||
|
|
||||||
const userSchema = new mongoose.Schema({
|
const userSchema = new mongoose.Schema({
|
||||||
|
|
@ -216,12 +218,6 @@ userSchema.statics.register = async function(userObj, ip){
|
||||||
//Look for a user (case insensitive)
|
//Look for a user (case insensitive)
|
||||||
var userDB = await this.findOne({user: new RegExp(user, 'i')});
|
var userDB = await this.findOne({user: new RegExp(user, 'i')});
|
||||||
|
|
||||||
//If we didn't find a user and we submitted an email
|
|
||||||
if(userDB == null && email != null){
|
|
||||||
//Check to make sure no one's used this email address
|
|
||||||
userDB = await this.findOne({email});
|
|
||||||
}
|
|
||||||
|
|
||||||
//If the user is found or someones trying to impersonate tokeboi
|
//If the user is found or someones trying to impersonate tokeboi
|
||||||
if(userDB || user.toLowerCase() == "tokebot"){
|
if(userDB || user.toLowerCase() == "tokebot"){
|
||||||
throw new Error("User name/email already taken!");
|
throw new Error("User name/email already taken!");
|
||||||
|
|
@ -230,10 +226,17 @@ userSchema.statics.register = async function(userObj, ip){
|
||||||
const id = await statModel.incrementUserCount();
|
const id = await statModel.incrementUserCount();
|
||||||
|
|
||||||
//Create user document in the database
|
//Create user document in the database
|
||||||
const newUser = await this.create({id, user, pass, email});
|
const newUser = await this.create({id, user, pass});
|
||||||
|
|
||||||
//Tattoo the hashed IP used to register to the new user
|
//Tattoo the hashed IP used to register to the new user
|
||||||
await newUser.tattooIPRecord(ip);
|
await newUser.tattooIPRecord(ip);
|
||||||
|
|
||||||
|
//if we submitted an email
|
||||||
|
if(email != null){
|
||||||
|
const requestDB = await emailChangeModel.create({user: newUser._id, newEmail: email, ipHash: ip});
|
||||||
|
|
||||||
|
await mailUtil.sendAddressVerification(requestDB, newUser, email)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
throw new Error("Confirmation password doesn't match!");
|
throw new Error("Confirmation password doesn't match!");
|
||||||
|
|
@ -268,7 +271,7 @@ userSchema.statics.authenticate = async function(user, pass, failLine = "Bad Use
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
userSchema.statics.findProfile = async function(user){
|
userSchema.statics.findProfile = async function(user, includeEmail = false){
|
||||||
//Catch null users
|
//Catch null users
|
||||||
if(user == null || user.user == null){
|
if(user == null || user.user == null){
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -298,7 +301,7 @@ userSchema.statics.findProfile = async function(user){
|
||||||
}
|
}
|
||||||
|
|
||||||
//return the profile
|
//return the profile
|
||||||
return userDB.getProfile();
|
return userDB.getProfile(includeEmail);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -425,7 +428,7 @@ userSchema.methods.getAuthenticatedSessions = async function(){
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
userSchema.methods.getProfile = function(){
|
userSchema.methods.getProfile = function(includeEmail = false){
|
||||||
//Create profile hashtable
|
//Create profile hashtable
|
||||||
const profile = {
|
const profile = {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
|
|
@ -439,6 +442,11 @@ userSchema.methods.getProfile = function(){
|
||||||
bio: this.bio
|
bio: this.bio
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//Include the email if we need to
|
||||||
|
if(includeEmail){
|
||||||
|
profile.email = this.email;
|
||||||
|
}
|
||||||
|
|
||||||
//return profile hashtable
|
//return profile hashtable
|
||||||
return profile;
|
return profile;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,3 +55,28 @@ module.exports.mailem = async function(to, subject, body, htmlBody = false){
|
||||||
//return the mail info
|
//return the mail info
|
||||||
return sentMail;
|
return sentMail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module.exports.sendAddressVerification = async function(requestDB, userDB, newEmail){
|
||||||
|
//Send the reset url via email
|
||||||
|
await module.exports.mailem(
|
||||||
|
newEmail,
|
||||||
|
`Email Change Request - ${userDB.user}`,
|
||||||
|
`<h1>Email Change Request</h1>
|
||||||
|
<p>A request to change the email associated with the ${config.instanceName} account '${userDB.user}' to this address has been requested.<br>
|
||||||
|
<a href="${requestDB.getChangeURL()}">Click here</a> to confirm this change.</p>
|
||||||
|
<sup>If you received this email without request, feel free to ignore and delete it! -Tokebot</sup>`,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
//If the user has a pre-existing email address
|
||||||
|
if(userDB.email != null && userDB.email != ""){
|
||||||
|
await module.exports.mailem(
|
||||||
|
userDB.email,
|
||||||
|
`Email Change Request - ${userDB.user}`,
|
||||||
|
`<h1>Email Change Request Notification</h1>
|
||||||
|
<p>A request to change the email associated with the ${config.instanceName} account '${userDB.user}' to another address has been requested.<br>
|
||||||
|
<sup>If you received this email without request, you should <strong>immediately</strong> change your password and contact the server adminsitrator! -Tokebot</sup>`,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -34,7 +34,7 @@ module.exports = {
|
||||||
|
|
||||||
pronouns: (field = 'pronouns') => body(field).optional().escape().trim().isLength({min: 0, max: 15}),
|
pronouns: (field = 'pronouns') => body(field).optional().escape().trim().isLength({min: 0, max: 15}),
|
||||||
|
|
||||||
signature: (field = 'signature') => body(field).optional().escape().trim().isLength({min: 1, max: 150}),
|
signature: (field = 'signature') => body(field).optional().escape().trim().isLength({min: 1, max: 25}),
|
||||||
|
|
||||||
bio: (field = 'bio') => body(field).optional().escape().trim().isLength({min: 1, max: 1000}),
|
bio: (field = 'bio') => body(field).optional().escape().trim().isLength({min: 1, max: 1000}),
|
||||||
|
|
||||||
|
|
|
||||||
22
src/views/partial/popup/changeEmail.ejs
Normal file
22
src/views/partial/popup/changeEmail.ejs
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
<%# Canopy - The next generation of stoner streaming software
|
||||||
|
Copyright (C) 2024-2025 Rainbownapkin and the TTN Community
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
published by the Free Software Foundation, either version 3 of the
|
||||||
|
License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>. %>
|
||||||
|
<link rel="stylesheet" type="text/css" href="/css/popup/changeEmail.css">
|
||||||
|
<h3 id="email-change-popup-title" class="popup-title">Update Email</h3>
|
||||||
|
<div id="email-change-popup-content">
|
||||||
|
<p id="email-change-popup-caption">Enter new email and current password below:</p>
|
||||||
|
<input type="email" placeholder="email" id="email-change-popup-email">
|
||||||
|
<input type="password" placeholder="password" id="email-change-popup-password">
|
||||||
|
</div>
|
||||||
23
src/views/partial/popup/changePassword.ejs
Normal file
23
src/views/partial/popup/changePassword.ejs
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
<%# Canopy - The next generation of stoner streaming software
|
||||||
|
Copyright (C) 2024-2025 Rainbownapkin and the TTN Community
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
published by the Free Software Foundation, either version 3 of the
|
||||||
|
License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>. %>
|
||||||
|
<link rel="stylesheet" type="text/css" href="/css/popup/changePassword.css">
|
||||||
|
<h3 id="password-change-popup-title" class="popup-title">Update Password</h3>
|
||||||
|
<div id="password-change-popup-content">
|
||||||
|
<p id="password-change-popup-caption">Enter new email and current password below:</p>
|
||||||
|
<input type="password" placeholder="password" id="password-change-popup-old-password">
|
||||||
|
<input type="password" placeholder="new password" id="password-change-popup-new-password">
|
||||||
|
<input type="password" placeholder="confirm new password" id="password-change-popup-confirm-new-password">
|
||||||
|
</div>
|
||||||
|
|
@ -16,9 +16,10 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
|
||||||
<span class="profile-bio-span">
|
<span class="profile-bio-span">
|
||||||
<h4 id="profile-bio-label" class="profile-item-label">Bio:</h4>
|
<h4 id="profile-bio-label" class="profile-item-label">Bio:</h4>
|
||||||
<% if(selfProfile){ %>
|
<% if(selfProfile){ %>
|
||||||
<p class="profile-item interactive" id="profile-bio-content"><%- profile.bio %></p>
|
<%# Make sure to convert newlines to br so they display proepr %>
|
||||||
|
<p class="profile-item interactive" id="profile-bio-content"><%- profile.bio.replaceAll('\n','<br>') %></p>
|
||||||
<textarea class="profile-item-prompt" id="profile-bio-prompt"></textarea>
|
<textarea class="profile-item-prompt" id="profile-bio-prompt"></textarea>
|
||||||
<% }else{ %>
|
<% }else{ %>
|
||||||
<p class="profile-item" id="profile-bio-content"><%- profile.bio %></p>
|
<p class="profile-item" id="profile-bio-content"><%- profile.bio.replaceAll('\n','<br>') %></p>
|
||||||
<% } %>
|
<% } %>
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -15,6 +15,10 @@ You should have received a copy of the GNU Affero General Public License
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>. %>
|
along with this program. If not, see <https://www.gnu.org/licenses/>. %>
|
||||||
<div class="account-settings dynamic-container" id="account-settings-div">
|
<div class="account-settings dynamic-container" id="account-settings-div">
|
||||||
<h3 class="account-settings" id="account-settings-label">Account Settings</h3>
|
<h3 class="account-settings" id="account-settings-label">Account Settings</h3>
|
||||||
|
<% if(profile.email){ %>
|
||||||
|
<h4 class="account-settings" id="account-email-label">Email Address:</h3>
|
||||||
|
<h4 class="account-settings" id="account-email-address"><%= profile.email %></h4>
|
||||||
|
<% } %>
|
||||||
<span class="account-settings" id="account-settings-buttons">
|
<span class="account-settings" id="account-settings-buttons">
|
||||||
<button href="javascript:" class="account-settings positive-button" id="account-settings-update-email-button">Update Email</button>
|
<button href="javascript:" class="account-settings positive-button" id="account-settings-update-email-button">Update Email</button>
|
||||||
<button href="javascript:" class="account-settings positive-button" id="account-settings-change-password-button">Change Password</button>
|
<button href="javascript:" class="account-settings positive-button" id="account-settings-change-password-button">Change Password</button>
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. %>
|
||||||
<head>
|
<head>
|
||||||
<%- include('partial/styles', {instance, user}); %>
|
<%- include('partial/styles', {instance, user}); %>
|
||||||
<%- include('partial/csrfToken', {csrfToken}); %>
|
<%- include('partial/csrfToken', {csrfToken}); %>
|
||||||
<% var selfProfile = user ? profile ? profile.user == user.user : false : false %>
|
<% %>
|
||||||
<link rel="stylesheet" type="text/css" href="/css/profile.css">
|
<link rel="stylesheet" type="text/css" href="/css/profile.css">
|
||||||
<% if(profile){ %>
|
<% if(profile){ %>
|
||||||
<title><%= instance %> - Profile: <%= profile.user %></title>
|
<title><%= instance %> - Profile: <%= profile.user %></title>
|
||||||
|
|
|
||||||
32
www/css/popup/changeEmail.css
Normal file
32
www/css/popup/changeEmail.css
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*Canopy - The next generation of stoner streaming software
|
||||||
|
Copyright (C) 2024-2025 Rainbownapkin and the TTN Community
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
published by the Free Software Foundation, either version 3 of the
|
||||||
|
License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
||||||
|
|
||||||
|
#email-change-popup-content{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
#email-change-popup-title{
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#email-change-popup-caption{
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#email-change-popup-content input{
|
||||||
|
margin: 0.5em 0;
|
||||||
|
}
|
||||||
32
www/css/popup/changePassword.css
Normal file
32
www/css/popup/changePassword.css
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*Canopy - The next generation of stoner streaming software
|
||||||
|
Copyright (C) 2024-2025 Rainbownapkin and the TTN Community
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
published by the Free Software Foundation, either version 3 of the
|
||||||
|
License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
||||||
|
|
||||||
|
#password-change-popup-content{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
#password-change-popup-title{
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#password-change-popup-caption{
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#password-change-popup-content input{
|
||||||
|
margin: 0.5em 0;
|
||||||
|
}
|
||||||
|
|
@ -41,6 +41,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
||||||
#account-settings-div{
|
#account-settings-div{
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -57,16 +58,24 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.*/
|
||||||
|
|
||||||
#profile-img{
|
#profile-img{
|
||||||
position: relative;
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
height: 14em;
|
||||||
|
width: 14em;
|
||||||
|
margin: 1em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#profile-img-content{
|
#profile-img-content{
|
||||||
margin: 1em 0;
|
cursor: pointer;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
#profile-img-prompt{
|
#profile-img-prompt{
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: -2em;
|
left: -1.5em;
|
||||||
right: -2em;
|
right: -1.5em;
|
||||||
top: calc(50% - 1.3em);
|
top: calc(50% - 1.3em);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,7 +94,7 @@ p.profile-item{
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
span.profile-item-oneliner{
|
.profile-item-oneliner{
|
||||||
text-wrap: nowrap
|
text-wrap: nowrap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -99,7 +108,7 @@ span.profile-item-oneliner{
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
min-height: 1.5em;
|
min-height: 1.5em;
|
||||||
max-height: 5.8em;
|
max-height: 5.8em;
|
||||||
padding: 0.5em 0;
|
padding: 0.5em 0.2em;
|
||||||
border-bottom-right-radius: 0;
|
border-bottom-right-radius: 0;
|
||||||
text-wrap: nowrap;
|
text-wrap: nowrap;
|
||||||
visibility: collapse;
|
visibility: collapse;
|
||||||
|
|
@ -124,11 +133,14 @@ span.profile-item-oneliner{
|
||||||
|
|
||||||
#profile-bio-prompt{
|
#profile-bio-prompt{
|
||||||
width: 30VW;
|
width: 30VW;
|
||||||
height: 11em;
|
height: 19.4em;
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* temp */
|
#account-email-label{
|
||||||
input:not([type="checkbox"]):not(.navbar-item):not(.profile-item-prompt){
|
margin: 0.2em 0;
|
||||||
display: block;
|
}
|
||||||
|
|
||||||
|
#account-email-address{
|
||||||
|
margin-top: 0.2em
|
||||||
}
|
}
|
||||||
|
|
@ -37,7 +37,7 @@ class profileUpdatePrompt{
|
||||||
|
|
||||||
closePrompt(event){
|
closePrompt(event){
|
||||||
//Check if we're finished
|
//Check if we're finished
|
||||||
const fin = event.key == "Escape" || event.key == "Enter";
|
const fin = event.key == "Escape" || (event.key == "Enter" && !event.shiftKey);
|
||||||
|
|
||||||
//IF we are
|
//IF we are
|
||||||
if(fin){
|
if(fin){
|
||||||
|
|
@ -63,6 +63,12 @@ class profileUpdateTextPrompt extends profileUpdatePrompt{
|
||||||
//Call derived method
|
//Call derived method
|
||||||
super.popPrompt();
|
super.popPrompt();
|
||||||
|
|
||||||
|
//For each line break
|
||||||
|
this.contentNode.querySelectorAll('br').forEach((br)=>{
|
||||||
|
//Replace the linenreaks with newlines to get the text area to play nice
|
||||||
|
br.outerHTML = "\n"
|
||||||
|
});
|
||||||
|
|
||||||
//If we're filling the prompt
|
//If we're filling the prompt
|
||||||
if(this.fillPrompt){
|
if(this.fillPrompt){
|
||||||
//Fill the prompt content and placeholder
|
//Fill the prompt content and placeholder
|
||||||
|
|
@ -85,7 +91,7 @@ class profileUpdateTextPrompt extends profileUpdatePrompt{
|
||||||
this.contentNode.style.display = 'inline-block';
|
this.contentNode.style.display = 'inline-block';
|
||||||
|
|
||||||
//If we're not cancelling
|
//If we're not cancelling
|
||||||
if(event.key == "Enter"){
|
if(event.key == "Enter" && !event.shiftKey){
|
||||||
//Create empty update object
|
//Create empty update object
|
||||||
let updateObj = {};
|
let updateObj = {};
|
||||||
|
|
||||||
|
|
@ -95,8 +101,8 @@ class profileUpdateTextPrompt extends profileUpdatePrompt{
|
||||||
//Send er' off
|
//Send er' off
|
||||||
const update = await utils.ajax.updateProfile(updateObj);
|
const update = await utils.ajax.updateProfile(updateObj);
|
||||||
|
|
||||||
//Fill content from update
|
//Fill content from update, make sure to add line breaks for the bio
|
||||||
this.contentNode.innerHTML = update[this.field];
|
this.contentNode.innerHTML = update[this.field].replaceAll('\n','<br>');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -200,9 +206,10 @@ class tokeList{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class deleteAccountButton{
|
class accountSettingsButton{
|
||||||
constructor(){
|
constructor(field, popupClass){
|
||||||
this.deleteLink = document.querySelector('#account-settings-delete-button');
|
this.deleteLink = document.querySelector(`#account-settings-${field}-button`);
|
||||||
|
this.popupClass = popupClass;
|
||||||
|
|
||||||
if(this.deleteLink != null){
|
if(this.deleteLink != null){
|
||||||
this.setupInput();
|
this.setupInput();
|
||||||
|
|
@ -210,11 +217,74 @@ class deleteAccountButton{
|
||||||
}
|
}
|
||||||
|
|
||||||
setupInput(){
|
setupInput(){
|
||||||
this.deleteLink.addEventListener("click",this.deletePrompt.bind(this));
|
this.deleteLink.addEventListener("click",this.showPopup.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
async deletePrompt(event){
|
async showPopup(event){
|
||||||
this.popup = new deleteAccountPopup();
|
this.popup = new this.popupClass();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class changeEmailPopup{
|
||||||
|
constructor(){
|
||||||
|
this.popup = new canopyUXUtils.popup("changeEmail", true, this.asyncConstructor.bind(this), this.asyncConstructor);
|
||||||
|
}
|
||||||
|
|
||||||
|
asyncConstructor(){
|
||||||
|
this.emailPrompt = document.querySelector('#email-change-popup-email');
|
||||||
|
this.passPrompt = document.querySelector('#email-change-popup-password');
|
||||||
|
|
||||||
|
this.setupInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
setupInput(){
|
||||||
|
this.emailPrompt.addEventListener('keydown', this.emailRequest.bind(this));
|
||||||
|
this.passPrompt.addEventListener('keydown', this.emailRequest.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
async emailRequest(event){
|
||||||
|
if(event.key == "Enter"){
|
||||||
|
await utils.ajax.requestEmailChange(this.emailPrompt.value, this.passPrompt.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class changePasswordPopup{
|
||||||
|
constructor(){
|
||||||
|
this.popup = new canopyUXUtils.popup("changePassword", true, this.asyncConstructor.bind(this), this.asyncConstructor);
|
||||||
|
}
|
||||||
|
|
||||||
|
asyncConstructor(){
|
||||||
|
this.oldPassPrompt = document.querySelector('#password-change-popup-old-password');
|
||||||
|
this.newPassPrompt = document.querySelector('#password-change-popup-new-password');
|
||||||
|
this.confirmPassPrompt = document.querySelector('#password-change-popup-confirm-new-password');
|
||||||
|
|
||||||
|
this.setupInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
setupInput(){
|
||||||
|
this.oldPassPrompt.addEventListener('keydown', this.emailRequest.bind(this));
|
||||||
|
this.newPassPrompt.addEventListener('keydown', this.emailRequest.bind(this));
|
||||||
|
this.confirmPassPrompt.addEventListener('keydown', this.emailRequest.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
async emailRequest(event){
|
||||||
|
if(event.key == "Enter"){
|
||||||
|
const updateObj = {};
|
||||||
|
|
||||||
|
updateObj.passChange = {
|
||||||
|
oldPass: this.oldPassPrompt.value,
|
||||||
|
newPass: this.newPassPrompt.value,
|
||||||
|
confirmPass: this.confirmPassPrompt.value
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await utils.ajax.updateProfile(updateObj);
|
||||||
|
|
||||||
|
if(response != null){
|
||||||
|
//Return user homepage after good pass change, as we've probably been logged out by the server for security.
|
||||||
|
window.location.pathname = '/';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -246,5 +316,6 @@ const pronounsPrompt = new profileUpdateTextPrompt('pronouns');
|
||||||
const signaturePrompt = new profileUpdateTextPrompt('signature');
|
const signaturePrompt = new profileUpdateTextPrompt('signature');
|
||||||
const bioPrompt = new profileUpdateTextPrompt('bio', true);
|
const bioPrompt = new profileUpdateTextPrompt('bio', true);
|
||||||
const profileTokeList = new tokeList();
|
const profileTokeList = new tokeList();
|
||||||
const accountPassResetPrompt = new passwordResetPrompt();
|
const accountEmailChange = new accountSettingsButton('update-email', changeEmailPopup);
|
||||||
const accountDeleteButton = new deleteAccountButton();
|
const accountPasswordChange = new accountSettingsButton('change-password', changePasswordPopup);
|
||||||
|
const accountDeleteButton = new accountSettingsButton('delete', deleteAccountPopup);
|
||||||
Loading…
Reference in a new issue