Skip to content
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

Merged
merged 22 commits into from
May 13, 2022

Conversation

AlexLLKong
Copy link
Collaborator

@AlexLLKong AlexLLKong commented May 9, 2022

This is the user's profile page. They can edit their name, email, and password.

image
Figma wireframe

  • HTML Components

    • Profile
    • Footer
  • Endpoints

    • PATCH /profile

@nok-ko nok-ko changed the base branch from main to dev May 10, 2022 00:38
@nok-ko
Copy link
Collaborator

nok-ko commented May 10, 2022

@AlexLLKong just changed the base branch on the PR to dev. 😅 It's easy to forget.

@AlexLLKong AlexLLKong force-pushed the Jay-Alex-Profile-Page branch from a90f076 to bd06ee8 Compare May 10, 2022 16:09
main.js Outdated
Comment on lines 410 to 413
bcrypt.hashSync(req.body.password, 10, async(err, pwd) => {
await db.collection('BBY-6_users')
.insertOne({name, emailAddress, pwd, avatarURL, date, achievements, admin, kit});
});
Copy link
Collaborator

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(…).

Comment on lines +330 to +334
// Redirect to profile page if logged in
if (req.session.loggedIn) {
res.redirect("/profile");
return;
}
Copy link
Collaborator

@nok-ko nok-ko May 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Closes #21.

@AlexLLKong AlexLLKong requested a review from nok-ko May 12, 2022 21:37
Copy link
Collaborator

@nok-ko nok-ko left a 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.

main.js Show resolved Hide resolved

// based on what is present in the update query,
const updateQuery = {};
if (req.body.email) {
Copy link
Collaborator

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.

Copy link
Collaborator

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();
Copy link
Collaborator

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.

Copy link
Collaborator

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: {
Copy link
Collaborator

@nok-ko nok-ko May 12, 2022

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.

Copy link
Collaborator

@nok-ko nok-ko May 13, 2022

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 {
Copy link
Collaborator

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);
Copy link
Collaborator

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) {
Copy link
Collaborator

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(){
Copy link
Collaborator

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.

Copy link
Collaborator

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){
Copy link
Collaborator

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)`
Copy link
Collaborator

@nok-ko nok-ko May 12, 2022

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.

@nok-ko
Copy link
Collaborator

nok-ko commented May 12, 2022

Also please try to make sure all of your functions have header comments describing what they do, ideally in JSDoc format

Copy link
Collaborator

@nok-ko nok-ko left a 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 the node: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"/>
Copy link
Collaborator

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.

Copy link
Collaborator

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");
Copy link
Collaborator

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.

Copy link
Collaborator

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';
Suggested change
const html = fs.readFileSync(componentLocation, "utf8");
const html = await readFile(componentLocation, "utf8");

Copy link
Collaborator

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
Comment on lines 198 to 201
let index = new JSDOM(doc);

// Add the footer
index = loadHTMLComponent(index, "footer", "footer", "./html/footer.html");
Copy link
Collaborator

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:

Suggested change
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.

Copy link
Collaborator

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) => {
Copy link
Collaborator

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) {
Copy link
Collaborator

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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Make the POST call seperatly
// Make the POST call separately

body: formData
})
const responseText = await response.text();
console.log(responseText);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
console.log(responseText);

Comment on lines +88 to +104
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";
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I meant:

Suggested change
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(){
Copy link
Collaborator

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");

public/js/user-profile.js Show resolved Hide resolved
@nok-ko
Copy link
Collaborator

nok-ko commented May 13, 2022

Workflow runs are failing because templates are not masked from the HTML Validator. Sigh, will fix later.

@AlexLLKong
Copy link
Collaborator Author

Post Code Review Changes

  • Move html components into separate folder and naming convention.
  • Credentials in the README.md for reg user and admin.
  • Strip out ready function.
  • Strip out profile.html and profile.css.
  • Use await for fs.
  • Add session check for /profile-details.
    • Also refactor frequently-used redirect/error check code into functions: checkLoginError and redirectToLogin.
    • Should really be Express middlewares.
  • Strip out /signup.
  • Check for console.logs.
  • Fix stretching on profile pictures

@nok-ko
Copy link
Collaborator

nok-ko commented May 13, 2022

Code review with Alex

@AlexLLKong AlexLLKong marked this pull request as ready for review May 13, 2022 18:13
@AlexLLKong AlexLLKong requested a review from nok-ko May 13, 2022 18:18
Copy link
Collaborator

@nok-ko nok-ko left a 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.

@AlexLLKong AlexLLKong merged commit 0c1f649 into dev May 13, 2022
@AlexLLKong
Copy link
Collaborator Author

AlexLLKong commented May 13, 2022

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

@nok-ko
Copy link
Collaborator

nok-ko commented May 13, 2022

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 html/ :)

@nok-ko
Copy link
Collaborator

nok-ko commented May 13, 2022

nok-ko pushed a commit that referenced this pull request May 20, 2022
As a User, I'd like to be able to have a customized Profile.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants