-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
As a User, I'd like to be able to have a customized Profile. #26
Conversation
@AlexLLKong just changed the |
Co-authored-by: Alex Kong <[email protected]>
…o some file for footer and header Co-authored-by: Alex Kong <[email protected]>
a90f076
to
bd06ee8
Compare
main.js
Outdated
bcrypt.hashSync(req.body.password, 10, async(err, pwd) => { | ||
await db.collection('BBY-6_users') | ||
.insertOne({name, emailAddress, pwd, avatarURL, date, achievements, admin, kit}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can use await bcrypt.hash(…)
here instead of .hashSync(…)
.
// Redirect to profile page if logged in | ||
if (req.session.loggedIn) { | ||
res.redirect("/profile"); | ||
return; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Closes #21.
…/ShakeGuard into Jay-Alex-Profile-Page
…Profile Picture Upload.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review made on my phone, pardon some of the curt comments (and ask clarification if you need!)
Will come back with another review once I'm at a computer.
|
||
// based on what is present in the update query, | ||
const updateQuery = {}; | ||
if (req.body.email) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You probably don't need the ifs here: IIRC nothing happens if you set an object's property to undefined
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First comment was sort of wrong, but this can be made terser:
const queryValues = {
emailAddress: req.body.email,
name: req.body.name,
// admittedly, the next line is kind of silly, maybe keep the original password code:
pwd: req.body.pwd !== undefined ? (await bcrypt.hash(req.body.pwd, 10)) : undefined
};
const updateQuery = Object.fromEntries(
Object.entries(queryValues).filter(e => e[1] !== undefined)
);
|
||
// get user from db | ||
const filterQuery = {emailAddress: req.session.email}; | ||
const userResults = await db.collection('BBY-6_users').find(filterQuery).toArray(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wonder if it's better to skip this, and just do a single request to the database, catch this error case on the update failing. Reasoning being that two requests are slower than one, and that there's actually a weird race condition here, if someone deletes a user you want to update, just after you run the "user exists" query.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tracked in #37, resolving this for now.
} | ||
const results = await db.collection('BBY-6_users').updateOne({emailAddress:req.session.email}, { | ||
$set: { | ||
avatarURL: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably shouldn't call this “avatarURL
” any more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tracked in #36, resolving for now.
@@ -119,3 +125,15 @@ body { | |||
.fail { | |||
color: red; | |||
} | |||
|
|||
#Footer-Links { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should make sure all of our CSS follows the same naming convention sometime.
} | ||
|
||
async function executeUpdate(){ | ||
checkForInvalidInput(FullNameInput); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could shorten this to:
[fooInput, barInput, bazInput].map(checkForInvalidInput);
} | ||
} | ||
|
||
function checkForInvalidInput(input) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better name might be validate
.
x.value = ''; | ||
} | ||
|
||
function editFullName(){ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These editFoo
functions could be generated by a higher order function, don't repeat yourself.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
E.g:
function makeEditAction(inputEl, defaultValue){
return function () {
if (inputEl.disabled){
inputEl.disabled= false;
inputEl.focus();
inputEl.onfocus = clearInput(inputEl);
return;
}
inputEl.disabled = true;
inputEl.value = defaultValue;
}
}
const editEmail = makeEditAction(EmailInput, userEmail);
const editFullName = makeEditAction(FullNameInput, userName);
const editPassword = makeEditAction(PasswordInput, "12345");
|
||
const ResponseText = await response.text(); | ||
const feedback = document.getElementById("Update-Error"); | ||
switch(ResponseText){ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could replace the switch... case with an object mapping error IDs to strings/actions.
} | ||
|
||
// Load a HTML component | ||
// baseDOM is the return value of `new JSDOM(htmlFile)` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just use JSDoc comment style.
Also please try to make sure all of your functions have header comments describing what they do, ideally in JSDoc format |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More substantial review. General recommendations aside from the specific stuff:
- Look for reused or “magic” CSS values, put them in CSS custom properties.
- Add doc comments to any functions longer than a couple of lines. (Not a big priority, but nice going forward.)
- Avoid
readFileSync
, use the functions in thenode:fs/promises
package instead.
Other side stuff:
- Default profile picture could be an SVG instead of what we have now. Okay for now, since it's just a placeholder.
- Consider putting some of the clientside JS in separate ES6 modules, especially stuff we might re-use.
- I don't quite understand how the error flag system works, so can't really offer cogent criticism… It seems to make sense, but I'm not sure.
Otherwise, I haven't looked at the actual UX of the thing, just the code… something to do later. 😅
<meta charset="utf-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||
<link href="images/comp2800_logo_favicon.ico" rel="icon" type="image/x-icon" /> | ||
<link href="css/user-profile.css" rel="stylesheet"/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Kind of confusing that we have both a user-profile.html
+ user-profile.css
and a profile.html
+ profile.css
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alex says they're not used anymore – will remove.
main.js
Outdated
const loadHTMLComponent = (baseDOM, placeholderSelector, componentSelector, componentLocation) => { | ||
const document = baseDOM.window.document; | ||
const placeholder = document.querySelector(placeholderSelector); | ||
const html = fs.readFileSync(componentLocation, "utf8"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we ought to stop using fs.*Sync
methods - we can just await
the async versions from fs/promises
when we need to, it's just as easy to use.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// at top of file:
import {readFile} from 'node:fs/promises';
const html = fs.readFileSync(componentLocation, "utf8"); | |
const html = await readFile(componentLocation, "utf8"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As for why: readFileSync()
blocks the event loop, fs/promises
' readFile()
doesn't.
main.js
Outdated
let index = new JSDOM(doc); | ||
|
||
// Add the footer | ||
index = loadHTMLComponent(index, "footer", "footer", "./html/footer.html"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why reassign index
? Just use two different variables:
let index = new JSDOM(doc); | |
// Add the footer | |
index = loadHTMLComponent(index, "footer", "footer", "./html/footer.html"); | |
const dom = new JSDOM(doc); | |
// Add the footer | |
const index = loadHTMLComponent(dom, "footer", "footer", "./html/footer.html"); |
I can't really see a reason for the other way, maybe I'm missing something.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My preference is for that suggested version – then the types of the two variables are always the same as initialized.
}); | ||
app.get('/profile-details', async(req, res) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't seem to (explicitly) cover the case when the user is not logged in, in this endpoint.
|
||
// based on what is present in the update query, | ||
const updateQuery = {}; | ||
if (req.body.email) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First comment was sort of wrong, but this can be made terser:
const queryValues = {
emailAddress: req.body.email,
name: req.body.name,
// admittedly, the next line is kind of silly, maybe keep the original password code:
pwd: req.body.pwd !== undefined ? (await bcrypt.hash(req.body.pwd, 10)) : undefined
};
const updateQuery = Object.fromEntries(
Object.entries(queryValues).filter(e => e[1] !== undefined)
);
payload['pwd'] = PasswordInput.value; | ||
} | ||
if(AvatarInput.files[0]) { | ||
// Make the POST call seperatly |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Make the POST call seperatly | |
// Make the POST call separately |
public/js/user-profile.js
Outdated
body: formData | ||
}) | ||
const responseText = await response.text(); | ||
console.log(responseText); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
console.log(responseText); |
const ResponseText = await response.text(); | ||
const feedback = document.getElementById("Update-Error"); | ||
switch(ResponseText){ | ||
case "userUpdated": | ||
// Successfully updated | ||
feedback.innerText = "Successfully Updated Profile"; | ||
feedback.style.color = "green"; | ||
break; | ||
case "emailInUse": | ||
// Email already in use. | ||
feedback.innerText = "That email is already in use"; | ||
feedback.style.color = "red"; | ||
break; | ||
default: | ||
feedback.innerText = "There was an issue on the server"; | ||
feedback.style.color = "red"; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I meant:
const ResponseText = await response.text(); | |
const feedback = document.getElementById("Update-Error"); | |
switch(ResponseText){ | |
case "userUpdated": | |
// Successfully updated | |
feedback.innerText = "Successfully Updated Profile"; | |
feedback.style.color = "green"; | |
break; | |
case "emailInUse": | |
// Email already in use. | |
feedback.innerText = "That email is already in use"; | |
feedback.style.color = "red"; | |
break; | |
default: | |
feedback.innerText = "There was an issue on the server"; | |
feedback.style.color = "red"; | |
} | |
const ResponseText = await response.text(); | |
const feedbackEl = document.getElementById("Update-Error"); | |
const [newText, newStyle] = { | |
"userUpdated": ["Successfully updated profile.", { color: 'green' }], | |
"emailInUse": ["That email is already in use.", { color: 'red' }], | |
"_default": ["There was an issue on the server.", { color: 'red' }] | |
}[ResponseText ?? '_default']; | |
feedback.textContent = newText; | |
Object.assign(feedback.style, newStyle); |
And the even better thing to do would be to put that error message style (color: red
/color: green
) into a CSS class so that we can edit it from the stylesheet and not have too much markup in the JavaScript.
x.value = ''; | ||
} | ||
|
||
function editFullName(){ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
E.g:
function makeEditAction(inputEl, defaultValue){
return function () {
if (inputEl.disabled){
inputEl.disabled= false;
inputEl.focus();
inputEl.onfocus = clearInput(inputEl);
return;
}
inputEl.disabled = true;
inputEl.value = defaultValue;
}
}
const editEmail = makeEditAction(EmailInput, userEmail);
const editFullName = makeEditAction(FullNameInput, userName);
const editPassword = makeEditAction(PasswordInput, "12345");
Workflow runs are failing because templates are not masked from the HTML Validator. Sigh, will fix later. |
Post Code Review Changes
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow, even made the checks pass! Awesome work, guys.
wait why did the checks pass? I don't think anything was done to make them pass |
'Cause the workflow was only set up to check files in |
As a User, I'd like to be able to have a customized Profile.
This is the user's profile page. They can edit their name, email, and password.
Figma wireframe
HTML Components
Endpoints
PATCH /profile