# Problem and Product
**Problem**:
* A consolidated site for people to find great artisan coffee shops around NYC with information including name, image, location, and rating.
* Interactive experience that potential users could not only filter by clicking on the dropdown for the cafe's location or the rating but also engage with click-and-expand features of each coffee shop card to see more information/recommendations that are tailored to their interests.
* In an ideal world that we have all the technical knowledge, this site could even leverage AI to create personalized recommendations.
**Link to CodePen**: https://codepen.io/wt2364/pen/RwvmeKZ
# Changes
### 1. Sign In Logic (Tina)
**a. Storing Existing User's Data**
Utilizing objects and arrays and if else function to prompt alerts when clicking the signin button.
**b. Login Prompt**
Stored existing user data using our group's information
```javascript!
const users = [
{ username: "Tina", password: "000" },
{ username: "Marsha", password: "111" },
{ username: "Wandy", password: "222" }
]
```
**c. Conditions**
With ChatGPT's help, we created function that clicking on the sign-in button would result in the prompting of username and password input box.
```javascript!
function signIn() {
const username = prompt("Enter your username:");
const password = prompt("Enter your password:");
const foundUser = users.find(
(user) => user.username === username && user.password === password
);
}
```
Use if else function to give alerts according to whether the username and password matches
```javascript!
if (foundUser) {
alert("Logged in successfully!");
} else {
alert("Invalid username or password. Please try again.");
}
````
### 2. Layouting
**a. Array to shorten HTML (Marsha)**
We recognize the ability of loop function to automate design process. Now we're able to use as minimum space as possible in HTML.
```htmlembedded!
<div class="row row-cols-1 row-cols-md-3 g-4 py-5" id="coffeeShopList">
<!-- Location of Template Literal -->
</div>
```
```javascript!
// Coffee shops list
const coffeeShops = [
{
name: "Korbrick Coffee Co.",
location: "Chelsea/West Village",
address: "24 9th Ave, New York, NY 10014",
image: "https://dw82ptradz9jo.cloudfront.net/daylog/6203524894d75733cd3bcbb2/103f254a-3d21-4245-a4ce-3949405fa679",
website: "https://www.kobricks.com/",
rating: 4.4
},
...
];
```
Storing the list as an array made it possible for us to implement DRY principle. Not only did it preserve the styling given the possibility of growing number of the data, but also to utilize some of the elements for other developments.
**b. Styling Automation (Wandee)**
We use template literal for a modal displaying detailed information about a coffee shop. The modal includes the coffee shop’s name, image, location, address, website, and rating. The modal ID is dynamically set based on the provided index.
```javascript!
function generateCoffeeShopHTML(coffeeShop, index) {
return `
<div class="col">
<div class="card h-100" data-bs-toggle="modal" data-bs-target="#coffeeShopModal${index}"
data-coffee-shop="${JSON.stringify(coffeeShop)}">
<img src="${coffeeShop.image}" class="card-img-top" alt="${coffeeShop.name}">
<div class="card-body">
<h6 class="card-title">${coffeeShop.name}</h6>
<p class="card-text">${coffeeShop.location}</p>
</div>
</div>
</div>
```
We iterate over each item in the coffeeShops array. For each coffee shop,
generate its HTML representation and append it to the coffeeShopListContainer.
```javascript!
coffeeShops.forEach((coffeeShop, index) => {
coffeeShopListContainer.innerHTML += generateCoffeeShopHTML(
coffeeShop,
index
);
});
```
We create a function to generate HTML content for a set of carousel boxes that display random coffee shops as recommendations, excluding the coffee shop currently being viewed.
```javascript!
function generateCarouselBoxes(excludedIndex) {
let carouselBoxes = "";
const numberOfBoxes = 6; // Define the number of carousel boxes to display.
// Set to store indices of coffee shops to ensure unique selection.
let selectedIndices = new Set();
// Randomly select unique indices for coffee shops until the set has the
// required number of indices.
while (selectedIndices.size < numberOfBoxes) {
let randomIndex = Math.floor(Math.random() * coffeeShops.length);
if (randomIndex !== excludedIndex) {
selectedIndices.add(randomIndex);
}
}
```
For each selected index, generate the HTML for a carousel box and append it to the carouselBoxes string.
```javascript!
selectedIndices.forEach((index) => {
carouselBoxes += `
<div class="box">
<img src="${coffeeShops[index].image}">
<div>
<p class="container1">${coffeeShops[index].name}</p>
</div>
</div>
`;
});
```
### 3. Dropdown for filter
We have two dropdown filters:
**📍 location** **⭐️ rating**
Both have similar logical sequence to develop:
• Generate options
• Insert options under the HTML dropdown code
• Event listener
• Change button text after an option is clicked
**a. Location**
```htmlembedded!
<div class="dropdown">
<button class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown" aria-expanded="false" id="locationDropdown">
Location
</button>
<ul class="dropdown-menu" aria-labelledby="locationDropdown">
<!-- Options generated from Javascript -->
</ul>
</div>
```
* **Generate Options (Marsha)**
In this process, we tried to make the most of the array's location element in order to not write the same lines of code more than once. However, we realized it's necessary to provide option for both location and ratings dropdowns to go back to see all options. To achieve that, we put an additional string "See All" above getting all the location values. After populating the options, we asked the function to insert them to the dropdown button in HTML under the correct ID with the template literal.
```javascript!
function generateLocationOptions() {
const uniqueLocations = [
"See All",
...new Set(coffeeShops.map((shop) => shop.location))
];
const locationDropdown = document.getElementById("locationDropdown");
const locationDropdownMenu = locationDropdown.nextElementSibling;
const locationOptionsHTML = uniqueLocations
.map(
(location) =>
`<li><a class="dropdown-item" href="#" data-location="${location}">${location}</a></li>`
)
.join("");
locationDropdownMenu.innerHTML = locationOptionsHTML;
```
* **Event listener (Tina)**
```javascript!
locationDropdownMenu.addEventListener("click", function (event) {
if (event.target.classList.contains("dropdown-item")) {
selectedLocation = event.target.innerText;
displayCoffeeShops(selectedLocation, selectedRating);
updateLocationButtonText(selectedLocation);
}
});
}
```
* **Change button text after an option is clicked (Tina)**
```javascript!
function updateLocationButtonText(location) {
const locationButton = document.getElementById("locationDropdown");
locationButton.textContent = location;
}
```
**b. Rating (Tina)**
```htmlembedded!
<div class="dropdown">
<button class="btn btn-primary dropdown-toggle" type="button" data-toggle="dropdown" aria-expanded="false" id="ratingDropdown">
Rating
</button>
<ul class="dropdown-menu" aria-labelledby="ratingDropdown">
<!-- JavaScript will dynamically populate this part -->
</ul>
</div>
```
* **Generate Options**
Make ratings a range. Used if else functions to make ratings a range so that this filter is meaningful for users:
```javascript!
function generateRatingOptions() {
const ratingRanges = ["See All", "3.5-3.9", "4.0-4.4", "4.5-5.0"];
const ratingDropdown = document.getElementById("ratingDropdown");
const ratingDropdownMenu = ratingDropdown.nextElementSibling;
const ratingOptionsHTML = ratingRanges
.map(
(range) =>
`<li><a class="dropdown-item" href="#" data-rating-range="${range}">${range}</a></li>`
)
.join("");
ratingDropdownMenu.innerHTML = ratingOptionsHTML;
```
* **Event listener**
```javascript!
ratingDropdownMenu.addEventListener("click", function (event) {
if (event.target.classList.contains("dropdown-item")) {
const selectedRatingRange = event.target.dataset.ratingRange;
selectedRating = selectedRatingRange;
displayCoffeeShops(selectedLocation, selectedRating);
updateRatingButtonText(selectedRatingRange);
}
});
}
```
* **Change button text after an option is clicked**
```javascript!
function updateRatingButtonText(rating) {
const ratingButton = document.getElementById("ratingDropdown");
ratingButton.textContent = rating;
}
```
**c. Filter Function (Marsha)**
* **Display coffee shops based on the selected location and rating**
```javascript!
function displayCoffeeShops(location, ratingRange) {
let filteredCoffeeShops = coffeeShops;
```
* **Filtering based on location**
Only if the choosen option is NOT see all then the display will filter the locations.
```javascript!
if (location && location !== "See All") {
filteredCoffeeShops = filteredCoffeeShops.filter(
(shop) => shop.location === location
);
}
```
* **Filtering based on rating range**
And only if the choosen option is NOT see all then the display will filter the rating range.
```javascript!
if (ratingRange && ratingRange !== "See All") {
filteredCoffeeShops = filteredCoffeeShops.filter((shop) => {
const rating = shop.rating;
if (ratingRange === "3.5-3.9") {
return rating >= 3.5 && rating <= 3.9;
} else if (ratingRange === "4.0-4.4") {
return rating >= 4.0 && rating <= 4.4;
} else if (ratingRange === "4.5-5.0") {
return rating >= 4.5 && rating <= 5.0;
}
return true; // Return true for any other case or if 'See All' is selected
});
}
const coffeeShopList = document.getElementById("coffeeShopList");
```
* **Clear existing content**
This is to enable user put options interchangeably.
```javascript!
coffeeShopList.innerHTML = "";
```
* **Display filtered coffee shops**
In this process, we noticed that even asking ChatGPT would require our own ability to scrutinize and select snippets of codes to again keep the DRY principle remain applicable. Now that it looks simple and seems like no necessary issue should happened with this loop, the lesson learnt was that try to always call the existing variable instead of writing new lines of codes if we are expecting a same thing to happen for different functions.
```javascript!
filteredCoffeeShops.forEach((shop, index) => {
coffeeShopList.innerHTML += generateCoffeeShopHTML(shop, index);
});
}
```
* **Setting "See All" as neutral position**
```javascript!
displayCoffeeShops("See All", "See All");
```
* **Call the functions to generate location and rating options**
```javascript!
generateLocationOptions();
generateRatingOptions();
```
### 4. Newsletter (Tina)
**a. Store user information into a new object:**
Saving information from input field to a new object.
```javascript!
function collectAndStoreData() {
const fullName = document.querySelector('input[placeholder="Full Name"]')
.value;
const email = document.querySelector('input[placeholder="Email here"]').value;
}
```
**b. Subscribe button**
Give alerts using if else function.
```javascript!
if (fullName && email) {
const subscription = { fullName, email };
//If the fields have contents, alert success; give alert if field is empty
alert("You have successfully subscribed!");
} else {
alert("Please fill in both Full Name and Email fields.");
}
```
---