Skip to content

Commit

Permalink
Adds nextjs-end (#241)
Browse files Browse the repository at this point in the history
* Adds nextjs-end

* Fix grammar

* Fix hardcoded URL and use better images
  • Loading branch information
umaar authored Jul 27, 2023
1 parent 77cbeef commit c8f3a26
Show file tree
Hide file tree
Showing 44 changed files with 12,005 additions and 0 deletions.
2 changes: 2 additions & 0 deletions nextjs-end/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
lib/firebase/config.js
.next/
165 changes: 165 additions & 0 deletions nextjs-end/components/Filters.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
// The filters shown on the restaurant listings page

import Tag from "@/components/Tag.jsx";

function FilterSelect({ label, options, value, onChange, name, icon }) {
return (
<div>
<img src={icon} alt={label} />
<label>
{label}
<select value={value} onChange={onChange} name={name}>
{options.map((option, index) => (
<option value={option} key={index}>
{option === "" ? "All" : option}
</option>
))}
</select>
</label>
</div>
);
}

export default function Filters({ filters, setFilters }) {
const handleSelectionChange = (event, name) => {
setFilters(prevFilters => ({
...prevFilters,
[name]: event.target.value,
}));
};

const updateField = (type, value) => {
setFilters({ ...filters, [type]: value });
};

return (
<section className="filter">
<details className="filter-menu">
<summary>
<img src="/filter.svg" alt="filter" />
<div>
<p>Restaurants</p>
<p>Sorted by {filters.sort || "Rating"}</p>
</div>
</summary>

<form
method="GET"
onSubmit={event => {
event.preventDefault();
event.target.parentNode.removeAttribute("open");
}}
>
<FilterSelect
label="Category"
options={[
"",
"Italian",
"Chinese",
"Japanese",
"Mexican",
"Indian",
"Mediterranean",
"Caribbean",
"Cajun",
"German",
"Russian",
"Cuban",
"Organic",
"Tapas",
]}
value={filters.category}
onChange={event =>
handleSelectionChange(event, "category")
}
name="category"
icon="/food.svg"
/>

<FilterSelect
label="City"
options={[
"",
"New York",
"Los Angeles",
"London",
"Paris",
"Tokyo",
"Mumbai",
"Dubai",
"Amsterdam",
"Seoul",
"Singapore",
"Istanbul",
]}
value={filters.city}
onChange={event => handleSelectionChange(event, "city")}
name="city"
icon="/location.svg"
/>

<FilterSelect
label="Price"
options={["", "$", "$$", "$$$", "$$$$"]}
value={filters.price}
onChange={event =>
handleSelectionChange(event, "price")
}
name="price"
icon="/price.svg"
/>

<FilterSelect
label="Sort"
options={["Rating", "Review"]}
value={filters.sort}
onChange={event => handleSelectionChange(event, "sort")}
name="sort"
icon="/sortBy.svg"
/>

<footer>
<menu>
<button
className="button--cancel"
type="reset"
onClick={() => {
setFilters({
city: "",
category: "",
price: "",
sort: "",
});
}}
>
Reset
</button>
<button type="submit" className="button--confirm">
Submit
</button>
</menu>
</footer>
</form>
</details>

<div className="tags">
{Object.entries(filters).map(([type, value]) => {
// The main filter bar already specifies what
// sorting is being used. So skip showing the
// sorting as a 'tag'
if (type == "sort" || value == "") {
return null;
}
return (
<Tag
key={value}
type={type}
value={value}
updateField={updateField}
/>
);
})}
</div>
</section>
);
}
108 changes: 108 additions & 0 deletions nextjs-end/components/Header.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
"use client";

import React, { useState, useEffect } from "react";
import Link from "next/link";
import {
signInWithGoogle,
signOut,
onAuthStateChanged,
} from "@/lib/firebase/auth.js";
import { addFakeRestaurantsAndReviews } from "@/lib/firebase/firestore.js";

// If the user logs in on the client, we send the user data to the server
// Sending a user payload to this endpoint will create a persistent cookie
async function handleUserSession(user = {}) {
return fetch("/api", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(user),
});
}

function useUserSession(initialUser) {
// The initialUser comes from the server via a server component
const [user, setUser] = useState(initialUser);

useEffect(() => {
const unsubscribe = onAuthStateChanged(async user => {
if (user) {
setUser(user);

// A more resillient approach would be to send the Firebase auth token to the server, and have the server verify the token with firebase-admin, however this approach of sending just the user data we need is simpler and works for this demo
await handleUserSession({
email: user.email,
id: user.uid,
displayName: user.displayName,
});
} else {
setUser(null);
await handleUserSession();
}
});
return () => {
unsubscribe();
};
}, []);
return user;
}

export default function Header({ initialUser }) {
const user = useUserSession(initialUser);

const handleSignOut = event => {
event.preventDefault();
signOut();
};

const handleSignIn = event => {
event.preventDefault();
signInWithGoogle();
};

return (
<header>
<Link href="/" className="logo">
<img src="/friendly-eats.svg" alt="FriendlyEats" />
Friendly Eats
</Link>
{user ? (
<>
<div className="profile">
<p>
<img src="/profile.svg" alt={user.email} />
{user.displayName}
</p>

<div className="menu">
...
<ul>
<li>{user.displayName}</li>

<li>
<a
href="#"
onClick={addFakeRestaurantsAndReviews}
>
Add sample restaurants
</a>
</li>

<li>
<a href="#" onClick={handleSignOut}>
Sign Out
</a>
</li>
</ul>
</div>
</div>
</>
) : (
<a href="#" onClick={handleSignIn}>
Sign In with Google
</a>
)}
</header>
);
}
66 changes: 66 additions & 0 deletions nextjs-end/components/RatingPicker.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React from "react";

// A HTML and CSS only rating picker thanks to: https://codepen.io/chris22smith/pen/MJzLJN

const RatingPicker = () => {
return (
<p className="rating-picker">
<input
className="radio-input"
type="radio"
id="star5"
name="rating"
value="5"
/>
<label className="radio-label" htmlFor="star5" title="5 stars">
5 stars
</label>

<input
className="radio-input"
type="radio"
id="star4"
name="rating"
value="4"
/>
<label className="radio-label" htmlFor="star4" title="4 stars">
4 stars
</label>

<input
className="radio-input"
type="radio"
id="star3"
name="rating"
value="3"
/>
<label className="radio-label" htmlFor="star3" title="3 stars">
3 stars
</label>

<input
className="radio-input"
type="radio"
id="star2"
name="rating"
value="2"
/>
<label className="radio-label" htmlFor="star2" title="2 stars">
2 stars
</label>

<input
className="radio-input"
type="radio"
id="star1"
name="rating"
value="1"
/>
<label className="radio-label" htmlFor="star1" title="1 star">
1 star
</label>
</p>
);
};

export default RatingPicker;
Loading

0 comments on commit c8f3a26

Please sign in to comment.