mirror of
https://github.com/rjNemo/meal_planner
synced 2026-06-10 20:36:39 +00:00
feat: remove login and profile
This commit is contained in:
parent
09b69aeccb
commit
0ce8b1cfc2
15 changed files with 16 additions and 390 deletions
24
src/App.tsx
24
src/App.tsx
|
|
@ -1,21 +1,11 @@
|
|||
import { PreLoader } from "./components/PreLoader";
|
||||
import "./index.css";
|
||||
import MainLayout from "./layouts/MainLayout";
|
||||
import { AppRouter, Router } from "./router";
|
||||
import { useAuth0 } from "./utils/auth0-spa";
|
||||
|
||||
export const App = () => {
|
||||
const { loading } = useAuth0();
|
||||
|
||||
return loading ? (
|
||||
<div className="container center-align valign-wrapper">
|
||||
<PreLoader />
|
||||
</div>
|
||||
) : (
|
||||
<Router>
|
||||
<MainLayout>
|
||||
<AppRouter />
|
||||
</MainLayout>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
export const App = () => (
|
||||
<Router>
|
||||
<MainLayout>
|
||||
<AppRouter />
|
||||
</MainLayout>
|
||||
</Router>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
import { MouseEventHandler } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { buttonURL, links } from "../constants";
|
||||
import ChefImage from "../images/chef.svg";
|
||||
import SpecialEventImage from "../images/special_event.svg";
|
||||
import { useAuth0 } from "../utils/auth0-spa";
|
||||
import { FooterLink } from "./FooterLink";
|
||||
import { RandomButton } from "./RandomButton";
|
||||
|
||||
|
|
@ -13,7 +11,6 @@ type Props = {
|
|||
};
|
||||
|
||||
export const SideNav = ({ showNav, closeNavClick }: Props) => {
|
||||
const { isAuthenticated, user } = useAuth0();
|
||||
let transformStyle = {
|
||||
transform: showNav ? "translateX(0%)" : "translateX(-105%)",
|
||||
transition: "0.5s",
|
||||
|
|
@ -44,18 +41,6 @@ export const SideNav = ({ showNav, closeNavClick }: Props) => {
|
|||
<i className="material-icons right" onClick={closeNavClick}>
|
||||
close
|
||||
</i>
|
||||
|
||||
<Link to="/profile">
|
||||
{isAuthenticated ? (
|
||||
<div>
|
||||
<img className="circle" src={user.picture} alt="user_avatar" />
|
||||
<span className="white-text name">{user.name}</span>
|
||||
<span className="white-text email">{user.email}</span>
|
||||
</div>
|
||||
) : (
|
||||
<img className="circle" src={ChefImage} alt="user_avatar" />
|
||||
)}
|
||||
</Link>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
|
|
@ -73,7 +58,6 @@ export const SideNav = ({ showNav, closeNavClick }: Props) => {
|
|||
{links.map((link, i) => (
|
||||
<FooterLink key={i} link={link} />
|
||||
))}
|
||||
{isAuthenticated && <FooterLink link="profile" />}
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,14 +7,13 @@ type Props = {
|
|||
ingredients: string[][];
|
||||
recipe: string;
|
||||
meal: Meal;
|
||||
handleFavChange: () => void;
|
||||
};
|
||||
|
||||
export const MealPage = ({ meal, ingredients, recipe, handleFavChange }: Props) => (
|
||||
export const MealPage = ({ meal, ingredients, recipe }: Props) => (
|
||||
<section className="container">
|
||||
<div className="row">
|
||||
<div className="col s12 l6">
|
||||
<MealPresentation meal={meal} handleFavChange={handleFavChange} />
|
||||
<MealPresentation meal={meal} />
|
||||
</div>
|
||||
<div className="col s12 l6">
|
||||
<MealIngredientList ingredients={ingredients} />
|
||||
|
|
|
|||
|
|
@ -3,12 +3,10 @@ import { Meal } from "../../../types/meal";
|
|||
|
||||
type Props = {
|
||||
meal: Meal;
|
||||
handleFavChange: () => void;
|
||||
};
|
||||
|
||||
export const MealPresentation = ({
|
||||
meal: { mealName, imgAddress, videoAddress, mealCategory, mealArea, isFav },
|
||||
handleFavChange,
|
||||
meal: { mealName, imgAddress, videoAddress, mealCategory, mealArea },
|
||||
}: Props) => {
|
||||
return (
|
||||
<div className="row">
|
||||
|
|
@ -36,17 +34,6 @@ export const MealPresentation = ({
|
|||
<div className="chip">
|
||||
<b>Origin:</b> {mealArea}
|
||||
</div>
|
||||
|
||||
<div className="chip">
|
||||
<b>{isFav ? "Remove from favourites" : "Add to favourites"}:</b>
|
||||
|
||||
<Link to="#">
|
||||
{" "}
|
||||
<i className="material-icons tiny" onClick={handleFavChange}>
|
||||
{isFav ? "favorite" : "favorite_border"}
|
||||
</i>
|
||||
</Link>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,21 +1,15 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useFirebase } from "../../services/Firebase";
|
||||
import { useMeal } from "../../store/meal";
|
||||
import { fetchMeal, fetchRandomMeal } from "../../store/meal/async";
|
||||
import { useAuth0 } from "../../utils/auth0-spa";
|
||||
import { NotFound } from "../NotFound";
|
||||
import { MealPage } from "./components/MealPage";
|
||||
import { buildIngredientList, buildMealProps } from "./service";
|
||||
|
||||
export const Meal = () => {
|
||||
// hooks
|
||||
const { user, isAuthenticated } = useAuth0();
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const { state, dispatch } = useMeal();
|
||||
const fb = useFirebase();
|
||||
// local state
|
||||
const [isFav, setIsFav] = useState<boolean>();
|
||||
// variables
|
||||
const mealItem = state.meals?.[0];
|
||||
// effects
|
||||
|
|
@ -24,30 +18,7 @@ export const Meal = () => {
|
|||
!id ? fetchRandomMeal(dispatch) : fetchMeal(dispatch, id);
|
||||
}, [id, dispatch]);
|
||||
|
||||
/** Updates fav status in db */
|
||||
useEffect(() => {
|
||||
if (isAuthenticated) {
|
||||
fb?.isFav(user.email, mealItem?.idMeal).then((res) => setIsFav(res));
|
||||
}
|
||||
}, [user, fb, mealItem?.idMeal, isAuthenticated]);
|
||||
// other logic
|
||||
const handleFavChange = () => {
|
||||
if (isAuthenticated) {
|
||||
setIsFav(!isFav);
|
||||
// Send !isFav because state is not yet updated
|
||||
fb.addToFavs(
|
||||
user?.email,
|
||||
mealItem?.idMeal,
|
||||
mealItem?.strMeal,
|
||||
mealItem?.strMealThumb,
|
||||
!isFav
|
||||
);
|
||||
} else {
|
||||
window.alert("You must be authenticated to add to favourites.");
|
||||
}
|
||||
};
|
||||
|
||||
const item = buildMealProps(mealItem, isFav!);
|
||||
const item = buildMealProps(mealItem);
|
||||
const ingredients = buildIngredientList(mealItem);
|
||||
|
||||
return !!state.meals ? (
|
||||
|
|
@ -55,7 +26,6 @@ export const Meal = () => {
|
|||
meal={item}
|
||||
ingredients={ingredients}
|
||||
recipe={mealItem?.strInstructions}
|
||||
handleFavChange={handleFavChange}
|
||||
/>
|
||||
) : (
|
||||
<NotFound />
|
||||
|
|
|
|||
|
|
@ -14,11 +14,10 @@ export const buildIngredientList = (mealItem: MealApi): string[][] => {
|
|||
return ingredients;
|
||||
};
|
||||
|
||||
export const buildMealProps = (mealItem: MealApi, isFav: boolean) => ({
|
||||
export const buildMealProps = (mealItem: MealApi) => ({
|
||||
mealName: mealItem?.strMeal,
|
||||
imgAddress: mealItem?.strMealThumb,
|
||||
videoAddress: mealItem?.strYoutube,
|
||||
mealCategory: mealItem?.strCategory,
|
||||
mealArea: mealItem?.strArea,
|
||||
isFav,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
import { CardEntry } from "../../../components/CardEntry";
|
||||
import { MealSummary } from "../../../types/meal";
|
||||
|
||||
type Props = {
|
||||
user: any;
|
||||
meals: MealSummary[];
|
||||
};
|
||||
|
||||
export const ProfilePage = ({ user, meals }: Props) => (
|
||||
<div className="container">
|
||||
<div className="row valign-wrapper">
|
||||
<img className="left circle responsive-img" src={user.picture} alt="Avatar" width="15%" />
|
||||
<h2 className="col s9">{user.name}</h2>
|
||||
</div>
|
||||
<div className="row">
|
||||
<b>Email: </b>
|
||||
{user.email}
|
||||
<h3>Favourites meals</h3>
|
||||
<ul>
|
||||
{meals?.map((meal) => (
|
||||
<CardEntry key={meal.idMeal} meal={meal} />
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { PreLoader } from "../../components/PreLoader";
|
||||
import { useFirebase } from "../../services/Firebase";
|
||||
import { MealSummary } from "../../types/meal";
|
||||
import { useAuth0 } from "../../utils/auth0-spa";
|
||||
import { ProfilePage } from "./components/ProfilePage";
|
||||
|
||||
export const Profile = () => {
|
||||
const { loading, user } = useAuth0();
|
||||
const [favs, setFavs] = useState([] as MealSummary[]);
|
||||
const db = useFirebase();
|
||||
|
||||
useEffect(() => {
|
||||
db.getByEmail(user.email).then((res) => {
|
||||
setFavs(res.favs);
|
||||
});
|
||||
}, [db, user.email]);
|
||||
|
||||
return loading || !user ? ( // is caught by PrivateRoute
|
||||
<div className="container center-align">
|
||||
<PreLoader />
|
||||
</div>
|
||||
) : (
|
||||
<ProfilePage user={user} meals={favs} />
|
||||
);
|
||||
};
|
||||
|
|
@ -3,33 +3,12 @@ import ReactDOM from "react-dom";
|
|||
import "./index.css";
|
||||
import { App } from "./App";
|
||||
import * as serviceWorker from "./serviceWorker";
|
||||
import { Auth0Provider } from "./utils/auth0-spa";
|
||||
import history from "./utils/history";
|
||||
import { FirebaseContext } from "./services/Firebase";
|
||||
import { AppProvider } from "./store/meal";
|
||||
|
||||
const onRedirectCallBack = (appState) => {
|
||||
history.push(
|
||||
appState && appState.targetUrl
|
||||
? appState.targetUrl
|
||||
: window.location.pathname
|
||||
);
|
||||
};
|
||||
|
||||
ReactDOM.render(
|
||||
<Auth0Provider
|
||||
domain={process.env.REACT_APP_DOMAIN}
|
||||
client_id={process.env.REACT_APP_CLIENT_ID}
|
||||
redirect_uri={window.location.origin}
|
||||
onRedirectCallBack={onRedirectCallBack}
|
||||
>
|
||||
{/*<FirebaseContext.Provider value={new Firebase()}> todo fix Firebase app*/}
|
||||
<FirebaseContext.Provider>
|
||||
<AppProvider>
|
||||
<App />
|
||||
</AppProvider>
|
||||
</FirebaseContext.Provider>
|
||||
</Auth0Provider>,
|
||||
<AppProvider>
|
||||
<App />
|
||||
</AppProvider>,
|
||||
document.getElementById("root")
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -6,9 +6,7 @@ import { Contact } from "../containers/Contact";
|
|||
import { Home } from "../containers/Home";
|
||||
import { Meal } from "../containers/Meal";
|
||||
import { NotFound } from "../containers/NotFound";
|
||||
import { Profile } from "../containers/Profile";
|
||||
import { Search } from "../containers/Search";
|
||||
import { PrivateRoute } from "./PrivateRoute";
|
||||
|
||||
export const AppRouter = () => (
|
||||
<Switch>
|
||||
|
|
@ -16,8 +14,6 @@ export const AppRouter = () => (
|
|||
<Home />
|
||||
</Route>
|
||||
|
||||
<PrivateRoute exact path="/profile" component={Profile} />
|
||||
|
||||
<Route exact path={buttonURL}>
|
||||
<Meal />
|
||||
</Route>
|
||||
|
|
|
|||
|
|
@ -1,22 +0,0 @@
|
|||
import { FC, useEffect } from "react";
|
||||
import { Route, RouteProps } from "react-router-dom";
|
||||
import { useAuth0 } from "../utils/auth0-spa";
|
||||
|
||||
type Props = { component: FC } & RouteProps;
|
||||
|
||||
export const PrivateRoute = ({ component: Component, path, ...rest }: Props) => {
|
||||
const { loading, isAuthenticated, loginWithRedirect } = useAuth0();
|
||||
|
||||
useEffect(() => {
|
||||
// catches infinite loading when no user is logged in
|
||||
if (loading || isAuthenticated) {
|
||||
return;
|
||||
}
|
||||
const fn = async () => await loginWithRedirect({ appState: { targetUrl: path } });
|
||||
fn();
|
||||
}, [loading, isAuthenticated, loginWithRedirect, path]);
|
||||
|
||||
const render = (props: any) => (isAuthenticated ? <Component {...props} /> : null);
|
||||
|
||||
return <Route path={path} render={render} {...rest} />;
|
||||
};
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
import { createContext, useContext } from "react";
|
||||
import Firebase from "./firebase";
|
||||
|
||||
// create a Firebase context to make state available anywhere in the App.
|
||||
const FirebaseContext = createContext(new Firebase());
|
||||
|
||||
export const useFirebase = () => useContext(FirebaseContext);
|
||||
export default FirebaseContext;
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
import app from "firebase/app";
|
||||
import "firebase/firestore";
|
||||
|
||||
const CONFIG = {
|
||||
apiKey: process.env.REACT_APP_API_KEY,
|
||||
authDomain: process.env.REACT_APP_AUTH_DOMAIN,
|
||||
databaseURL: process.env.REACT_APP_DB_URL,
|
||||
projectId: process.env.REACT_APP_PROJECT_ID,
|
||||
storageBucket: process.env.REACT_APP_STORAGE_BUCKET,
|
||||
messagingSenderId: process.env.REACT_APP_MSG_SENDER_ID,
|
||||
appId: process.env.REACT_APP_APP_ID,
|
||||
measurementId: process.env.REACT_APP_MEASUREMENT_ID,
|
||||
};
|
||||
|
||||
const FAVS = "favs";
|
||||
|
||||
/**
|
||||
* Firebase initializes the Application and provides method to interact with
|
||||
* Firebase services as auth and firestore.
|
||||
*/
|
||||
export default class Firebase {
|
||||
#db: any;
|
||||
#collection: any;
|
||||
|
||||
constructor() {
|
||||
app.initializeApp(CONFIG);
|
||||
this.#db = app.firestore();
|
||||
this.#collection = this.#db.collection("mealPlannerUsers");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get infos for user 'email'.
|
||||
*/
|
||||
getByEmail = async (email: string) => {
|
||||
const infos = await this.#collection
|
||||
.where("email", "==", email)
|
||||
.limit(1)
|
||||
.get();
|
||||
|
||||
const favs = await this.getFavsByEmail(email);
|
||||
|
||||
return { infos, favs };
|
||||
};
|
||||
|
||||
/**
|
||||
* Get user's favourite recipes
|
||||
* */
|
||||
getFavsByEmail = async (email: string) => {
|
||||
let favs = [] as any[];
|
||||
const query = await this.#collection
|
||||
.doc(email)
|
||||
.collection(FAVS)
|
||||
.where("isFav", "==", true)
|
||||
.limit(10)
|
||||
.get();
|
||||
|
||||
query.forEach((doc: any) => favs.push(doc.data()));
|
||||
|
||||
return favs;
|
||||
};
|
||||
|
||||
isFav = async (email: string, idMeal: string) => {
|
||||
const query = await this.#collection
|
||||
.doc(email)
|
||||
.collection(FAVS)
|
||||
.doc(idMeal)
|
||||
.get();
|
||||
|
||||
const obj = query.data();
|
||||
return obj?.isFav;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create or update favourite status for an authenticated user.
|
||||
*/
|
||||
addToFavs = async (
|
||||
email: string,
|
||||
idMeal: string,
|
||||
strMeal: string,
|
||||
strMealThumb: string,
|
||||
isFav: boolean
|
||||
) => {
|
||||
this.#collection
|
||||
.doc(email)
|
||||
.collection(FAVS)
|
||||
.doc(idMeal)
|
||||
.set({
|
||||
email,
|
||||
idMeal,
|
||||
strMeal,
|
||||
strMealThumb,
|
||||
isFav,
|
||||
})
|
||||
.catch((err: any) => console.error("Error adding favs to database", err));
|
||||
};
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
// This file centralizes all Firebase related exports
|
||||
|
||||
import Firebase from "./firebase";
|
||||
import FirebaseContext, { useFirebase } from "./context";
|
||||
|
||||
export default Firebase;
|
||||
export { FirebaseContext, useFirebase };
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
// adapted from: https://auth0.com/docs/quickstart/spa/react
|
||||
import React, { useContext, useState, useEffect } from "react";
|
||||
import createAuth0Client from "@auth0/auth0-spa-js";
|
||||
|
||||
const DEFAULT_REDIRECT_CALLBACK = () => {
|
||||
window.history.replaceState({}, document.title, window.location.pathname);
|
||||
};
|
||||
|
||||
export const Auth0Context = React.createContext();
|
||||
export const useAuth0 = () => useContext(Auth0Context);
|
||||
|
||||
export const Auth0Provider = ({
|
||||
children,
|
||||
onRedirectCallBack = DEFAULT_REDIRECT_CALLBACK,
|
||||
...initOptions
|
||||
}) => {
|
||||
const [isAuthenticated, setIsAuthenticated] = useState();
|
||||
const [user, setUser] = useState();
|
||||
const [auth0Client, setAuth0] = useState();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [popUpOpen, setPopUpOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const initAuth0 = async () => {
|
||||
const auth0FromHook = await createAuth0Client(initOptions);
|
||||
setAuth0(auth0FromHook);
|
||||
|
||||
if (
|
||||
window.location.search.includes("code=") &&
|
||||
window.location.search.includes("state=")
|
||||
) {
|
||||
const { appState } = await auth0FromHook.handleRedirectCallback();
|
||||
onRedirectCallBack(appState);
|
||||
}
|
||||
|
||||
const isAuthenticated = await auth0FromHook.isAuthenticated();
|
||||
setIsAuthenticated(isAuthenticated);
|
||||
|
||||
if (isAuthenticated) {
|
||||
const user = await auth0FromHook.getUser();
|
||||
setUser(user);
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
};
|
||||
initAuth0();
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
|
||||
const loginWithPopUp = async (params = {}) => {
|
||||
setPopUpOpen(true);
|
||||
try {
|
||||
await auth0Client.loginWithPopUp(params);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
setPopUpOpen(false);
|
||||
}
|
||||
const user = auth0Client.getUser();
|
||||
setUser(user);
|
||||
setIsAuthenticated(true);
|
||||
};
|
||||
|
||||
const handleRedirectCallback = async () => {
|
||||
setLoading(true);
|
||||
await auth0Client.handleRedirectCallback();
|
||||
const user = auth0Client.getUser();
|
||||
setLoading(false);
|
||||
setIsAuthenticated(true);
|
||||
setUser(user);
|
||||
};
|
||||
|
||||
return (
|
||||
<Auth0Context.Provider
|
||||
value={{
|
||||
isAuthenticated,
|
||||
user,
|
||||
loading,
|
||||
popUpOpen,
|
||||
loginWithPopUp,
|
||||
handleRedirectCallback,
|
||||
getIdTokenClaims: (...props) => auth0Client.getIdTokenClaims(...props),
|
||||
loginWithRedirect: (...props) =>
|
||||
auth0Client.loginWithRedirect(...props),
|
||||
getTokenWithPopUp: (...props) =>
|
||||
auth0Client.getTokenWithPopUp(...props),
|
||||
logout: (...props) => auth0Client.logout(...props)
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Auth0Context.Provider>
|
||||
);
|
||||
};
|
||||
Loading…
Reference in a new issue