Skip to content
Open
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
7da31bd
feat: add prototype for Nutrient Tracker Dictionary lab prototype
Sebastian-Wlo Dec 15, 2025
c2ca3e7
feat: add user-stories.md to the nutrient tracker prototype
Sebastian-Wlo Dec 16, 2025
fba7edb
chore: renamed "lab-nutrient-tracker-dictionary" directory
Sebastian-Wlo Dec 19, 2025
492458a
docs: change "nutrient tracker" references in lab's "user-stories.md"
Sebastian-Wlo Dec 19, 2025
d790e41
refactor: rewrite of "nutrient tracker" functions
Sebastian-Wlo Dec 20, 2025
d510dcb
docs: changed the lab's summary section to be more descriptive
Sebastian-Wlo Dec 30, 2025
b59a232
refactor: change "listTopMembers" function to "listMembers"
Sebastian-Wlo Feb 9, 2026
789a19f
docs: remove the "listTopMembers" mentions from "user-stories.md", ad…
Sebastian-Wlo Feb 9, 2026
615c8a5
refactor: removed users stories for Guild Loot Tracker
Sebastian-Wlo Mar 19, 2026
c1777d2
refactor: restructured Guild Loot Tracker prototype
Sebastian-Wlo Mar 19, 2026
686091e
chore: remove the "lab-" prefix from the prototype's directory name
Sebastian-Wlo Apr 23, 2026
3b7695f
feat: add listMemberNames() function that introduces Object.keys() me…
Sebastian-Wlo Apr 23, 2026
cb705ae
refactor: rewrite getMemberTotal() to return an object or `false`
Sebastian-Wlo Apr 23, 2026
77284da
refactor: rewrite 'listMembers', 'getMemberTotals', and 'addLootEntry…
Sebastian-Wlo Apr 27, 2026
16cace1
refactor: change the guild members' names
Sebastian-Wlo May 1, 2026
529235e
refactor: changes the loop in listMemberNames function to 'for...in' …
Sebastian-Wlo May 2, 2026
fd83374
refactor: change loops in addLootEntry() to 'for...in', fix the order…
Sebastian-Wlo May 3, 2026
2a4c46e
feat: use 'getMemberTotals()' function to introduce 'Object.entries()…
Sebastian-Wlo May 12, 2026
e15cac6
refactor: use 'listMembers()' to introduce only the 'Object.values()'…
Sebastian-Wlo May 12, 2026
ad044a5
style: format the script for consistency and readability
Sebastian-Wlo May 12, 2026
4f63860
chore: update comments to reflect steps of the workshop
Sebastian-Wlo May 24, 2026
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
174 changes: 174 additions & 0 deletions fullstack-cert/js-projects/guild-loot-tracker/script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// This will be a workshop for the Loops section, probably after the "Build a Profile Lookup" lab. Probably around 32 steps.

// The "guild" object could be already provided, or created over 3 or so steps (creating "guild" object itself and adding the first "guild member" object as the first property - rest of the "guild members" can be filled out somewhere between the next steps to avoid too much repetition).
let guild = {
Comment thread
jdwilkin4 marked this conversation as resolved.
ethan: {
gold: 31,
silver: 48,
reputation: 9,
experience: 198,
},
elara: {
gold: 78,
silver: 64,
reputation: 12,
experience: 111,
},
brandon: {
gold: 41,
silver: 7,
reputation: 7,
experience: 70,
},
dylan: {
gold: 81,
silver: 2,
reputation: 20,
experience: 220,
},
lucas: {
gold: 34,
silver: 28,
reputation: 10,
experience: 179,
},
natalie: {
gold: 36,
silver: 81,
reputation: 12,
experience: 82,
},
};

/* NOTE:
This method seemed like a good addition before I refactored `listMembers()` - it still might be a good(?), isolated example of `Object.keys()` use, but otherwise seems slightly redundant.
*/
// 'listMemberNames()` introduces `Object.keys()` static method, and provides a list of current members of the guild. Could be 3 steps:
// 1. create a function,
// 2. assign the array returned by the `Object.keys()` to a variable + explanation of the method,
// 3. for loop and `console.log()` methods for the name,
function listMemberNames(guildObject) {
console.log("Current Guild Members:");
const guildMembers = Object.keys(guildObject);
for (const member in guildMembers) {
console.log(`${parseInt(member) + 1}. ${guildMembers[member]}`);
}
}

listMemberNames(guild);

// `listMembers()` introduces `Object.values()`. Probably good for about 4 steps:
// 1. Create a new function and add a `console.log()` with the function's description,
// 2. Create a `for()` loop, and log the `Object.values()`,
// 3. Replace the `console.log()` for the `Object.values()` with destructuring,
// 4. add the 3 `console.log()` for the member's name, value names and the destructured values
function listMembers(guildObject) {
console.log("Guild Member's Resources:");
for (const member in guildObject) {
const [gold, silver, reputation, experience] = Object.values(
guildObject[member],
);

console.log(`${member}:`);
console.log("gold\tsilver\treputation\texperience");
console.log(`${gold}\t${silver}\t${reputation}\t\t${experience}`);
}
}

listMembers(guild);

// 'getMemberTotals()` introduces `Object.entries() method.
// 1. Create a function and assign `Object.entries()` to a variable,
// 2. Use `console.log()` to see what has been assigned to the variable
// 3. Remove the `console.log()` and create a `for()` loop, add `return false` statement,
// 4. Destructure `guildMember` inside the `if()` conditional, add `return entry`
function getMemberTotals(guildObject, member) {
const guildMembers = Object.entries(guildObject);

for (const guildMember of guildMembers) {
const [name, entry] = guildMember;
if (member === name) {
return entry;
}
}
return false;
}

// Checks for validating `getMemberTotals()` is working correctly - can be one of the steps and removed later
console.log(getMemberTotals(guild, "natalie"));
console.log(getMemberTotals(guild, "francis"));

// "cloneGuildData()" can be broken into multiple (4?) steps, showing that using the spread syntax creates a shallow copy of the object (and explaining how it prevents accidental mutations) for example:
// 1. Write the function and assign the input parameter to one variable directly, and via the spread operator to a second variable, then call the function,
// 2. Modify the content of the first variable, then use `console.log()` to compare the original input parameter, and both variables, showing that modifying the direct copy modified the original as well,
// 3. Modify the shallow copy to show it didn't affect the original
// 4. Remove the variables and the function call, write the final version of the function.
function cloneGuildData(guildObject) {
return { ...guildObject };
}

function addLootEntry(guildObject, entry) {
// 1. Use cloneGuildData() to get a copy of the first input parameter,
const clonedGuild = cloneGuildData(guildObject);
// 2. Use `Object.keys() to get a list of guild member's names, and use the spread operator to separate data from the second parameter
const guildMemberNames = Object.keys(guildObject);
const { member: memberName, ...newMemberTotals } = entry;
// 3, 4. Write conditional checking to validate the member's name exists, and is a string
if (!memberName) {
console.log("New entry must include a name of the guild member!");
return;
} else if (typeof memberName !== "string") {
console.log("Member name must be a string!");
return;
}
// 5. Add a `for()` loop iterating over the guild member's names, add the `console.log()` and a return statement after it (I just realized that `return` is kind of pointless...), Check if the guildObject includes a record of the provided member,
for (const guildMember in guildMemberNames) {
// 6. Write a `if()` statement for finding the name provided in the second input parameter, then get this guild member's data with `getMemberTotals()` and assign it to a variable for readability (we're not making a copy of the object! it could be worth pointing out the difference in the steps),
if (guildMemberNames[guildMember] === memberName) {
const currentMemberTotals = getMemberTotals(clonedGuild, memberName);
// 7. Add a `for()` loop to iterate over the member's resources,
for (const resource in currentMemberTotals) {
// 8. Add a `if()` statement to check if the new data is missing one of the values - if it does, end the function execution if it does,
if (!Object.hasOwn(newMemberTotals, resource)) {
console.log(`Updated data is missing an entry for "${resource}"!`);
return;
} else if (
// 9. Add the else if statement to check if the new value is of the same type as the current one, return early if it isn't
typeof newMemberTotals[resource] !==
typeof currentMemberTotals[resource]
) {
console.log(
`"${resource}" must be a ${typeof currentMemberTotals[resource]}!`,
);
return;
}
// 10., 11. Then, check if the new entry has ONLY the required resources (could be just one step, but it might be a bit too confusing),
for (const resource in newMemberTotals) {
if (!Object.hasOwn(currentMemberTotals, resource)) {
console.log(`Updated data has an unknown entry "${resource}!`);
return;
}
}
// 12. Since at this point the new data passed all of the guard clauses, we can overwrite the old data
currentMemberTotals[resource] = newMemberTotals[resource];
}
// 13. Finally, now we're sure our input was correct, we can overwrite the old data, inform the user the operation was successful, and exit the function.
guild = clonedGuild;
console.log("Guild roster updated.");
return;
}
}
console.log(`${memberName} is not a member of this guild!`);
return;
}

// Last step: use `AddLootEntry() to change the guild member's resources, and use `listMembers()` to see if it worked.
addLootEntry(guild, {
member: "natalie",
gold: 1,
silver: 2,
reputation: 3,
experience: 4,
});

listMembers(guild);