reformatting data flow on frontend

This commit is contained in:
Mikayla Dobson
2023-02-11 20:50:16 -06:00
parent 514bcde809
commit 90a5bdf128
12 changed files with 122 additions and 107 deletions

View File

@@ -1,11 +1,10 @@
// framework tools and custom utils // framework tools and custom utils
import { useCallback, useContext, useEffect, useState } from 'react'; import { useEffect } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom'; import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { AuthContext, IAuthContext, useAuthContext } from './context/AuthContext'; import { useAuthContext } from './context/AuthContext';
import { attemptLogout, checkCredientials } from './util/apiUtils'; import jwtDecode from 'jwt-decode';
import { IUser } from './schemas';
// pages, ui, styles // pages, ui, components, styles
import Subscriptions from './components/pages/Subscriptions/Subscriptions'; import Subscriptions from './components/pages/Subscriptions/Subscriptions';
import Browser from './components/ui/Browser'; import Browser from './components/ui/Browser';
import Collection from './components/pages/Collection'; import Collection from './components/pages/Collection';
@@ -19,55 +18,41 @@ import CollectionBrowser from './components/pages/CollectionBrowser';
import { Navbar } from './components/ui'; import { Navbar } from './components/ui';
import GroceryList from './components/pages/GroceryList'; import GroceryList from './components/pages/GroceryList';
import GroceryListCollection from './components/pages/GroceryListCollection'; import GroceryListCollection from './components/pages/GroceryListCollection';
import { TokenType } from './util/types';
import './sass/App.scss'; import './sass/App.scss';
function App() { function App() {
const [user, setUser] = useState<any>(); const { setUser, setToken } = useAuthContext();
const parentState = { user, setUser };
const receiveChange = (() => {});
useEffect(() => { useEffect(() => {
const wrapper = async () => { if (document.cookie) {
try { const extractedToken: Partial<TokenType> = jwtDecode(document.cookie.split("=")[1]);
const result: IAuthContext | undefined = await checkCredientials(); setToken(document.cookie.split("=")[1]);
setUser(extractedToken.user);
if (result == undefined) {
setUser({ user: undefined });
} else {
setUser(result);
}
} catch(e) {
console.error(e);
}
} }
}, []);
wrapper();
}, [])
return ( return (
<BrowserRouter> <BrowserRouter>
<AuthContext.Provider value={ parentState }> <div className="App">
<div className="App"> <Navbar />
<Navbar receiveChange={receiveChange} /> <Routes>
<Routes> <Route path="/" element={<Welcome />} />
<Route path="/" element={<Welcome />} /> <Route path="/register" element={<Register />} />
<Route path="/register" element={<Register receiveChange={receiveChange} />} /> <Route path="/login" element={<Login />} />
<Route path="/login" element={<Login />} /> <Route path="/profile" element={<Profile />} />
<Route path="/profile" element={<Profile />} /> <Route path="/collections" element={<CollectionBrowser />} />
<Route path="/collections" element={<CollectionBrowser />} /> <Route path="/collections/:id" element={<Collection />} />
<Route path="/collections/:id" element={<Collection />} /> <Route path="/explore" element={<Browser header="" searchFunction={() => {}} />} />
<Route path="/explore" element={<Browser header="" searchFunction={() => {}} />} /> <Route path="/recipe/:id" element={<Recipe />} />
<Route path="/recipe/:id" element={<Recipe />} /> <Route path="/subscriptions" element={<Subscriptions />} />
<Route path="/subscriptions" element={<Subscriptions />} /> <Route path="/subscriptions/:id" element={<Collection />} />
<Route path="/subscriptions/:id" element={<Collection />} />
<Route path="/add-recipe" element={<AddRecipe />} /> <Route path="/add-recipe" element={<AddRecipe />} />
<Route path="/grocery-list" element={<GroceryListCollection />} /> <Route path="/grocery-list" element={<GroceryListCollection />} />
<Route path="/grocery-list/:id" element={<GroceryList />} /> <Route path="/grocery-list/:id" element={<GroceryList />} />
</Routes> </Routes>
</div> </div>
</AuthContext.Provider>
</BrowserRouter> </BrowserRouter>
) )
} }

View File

@@ -1,10 +1,10 @@
import { useCallback, useContext, useEffect, useState } from "react"; import { useCallback, useContext, useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { AuthContext, useAuthContext } from "../../context/AuthContext"; import { AuthContext, useAuthContext } from "../../context/AuthContext";
import { attemptLogin } from "../../util/apiUtils"; import { useNavigate, useParams } from "react-router-dom";
import { IUser, IUserAuth } from "../../schemas"; import { IUser, IUserAuth } from "../../schemas";
import { Button, Form, Page, Panel } from "../ui"; import { Button, Form, Page, Panel } from "../ui";
import { FormConfig } from "../ui/Form"; import { FormConfig } from "../ui/Form";
import API from "../../util/API";
export default function Login() { export default function Login() {
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search);
@@ -24,9 +24,12 @@ export default function Login() {
const handleLogin = async () => { const handleLogin = async () => {
if (!input.email || !input.password) return; if (!input.email || !input.password) return;
const { data, ok } = await attemptLogin(input); const result = await new API.Auth().login(input);
if (ok) setUser(data); console.log(result);
navigate(`/${redirect ?? ''}`);
// const { data, ok } = await attemptLogin(input);
// if (ok) setUser(data);
// navigate(`/${redirect ?? ''}`);
} }
// check for logged in user and mount form // check for logged in user and mount form

View File

@@ -6,7 +6,7 @@ import { attemptLogin, createNewCollection } from "../../../util/apiUtils";
import { Button, Divider, Page, Panel } from "../../ui"; import { Button, Divider, Page, Panel } from "../../ui";
import TextField from "../../ui/TextField"; import TextField from "../../ui/TextField";
const InitialCollection: RegisterVariantType = ({ transitionDisplay, receiveChange, input }) => { const InitialCollection: RegisterVariantType = ({ transitionDisplay, input }) => {
const [collectionName, setCollectionName] = useState<string>(); const [collectionName, setCollectionName] = useState<string>();
const [view, setView] = useState<JSX.Element>(<Page><h1>Loading...</h1></Page>); const [view, setView] = useState<JSX.Element>(<Page><h1>Loading...</h1></Page>);
const [user, setUser] = useState<IUser>(); const [user, setUser] = useState<IUser>();
@@ -45,8 +45,7 @@ const InitialCollection: RegisterVariantType = ({ transitionDisplay, receiveChan
} }
useEffect(() => { useEffect(() => {
if (user && receiveChange) { if (user) {
receiveChange(user);
setView( setView(
<Page> <Page>
<h1>Hi, {user.firstname}! Great to meet you.</h1> <h1>Hi, {user.firstname}! Great to meet you.</h1>

View File

@@ -19,17 +19,17 @@ export enum VariantLabel {
FinishUp FinishUp
} }
const Register: FC<{receiveChange: (change: IUser) => void}> = ({ receiveChange }) => { const Register = () => {
const [displayed, setDisplayed] = useState<JSX.Element>(); const [displayed, setDisplayed] = useState<JSX.Element>();
const authContext = useAuthContext(); const { user } = useAuthContext();
const transitionDisplay = (variant: number | VariantLabel, user?: IUser) => { const transitionDisplay = (variant: number | VariantLabel) => {
switch (variant) { switch (variant) {
case 0: case 0:
setDisplayed(<AboutYou transitionDisplay={transitionDisplay} />); setDisplayed(<AboutYou transitionDisplay={transitionDisplay} />);
break; break;
case 1: case 1:
setDisplayed(<InitialCollection transitionDisplay={transitionDisplay} input={user} receiveChange={receiveChange} />); setDisplayed(<InitialCollection transitionDisplay={transitionDisplay} input={user} />);
break; break;
case 2: case 2:
setDisplayed(<AddFriends transitionDisplay={transitionDisplay} input={user} />); setDisplayed(<AddFriends transitionDisplay={transitionDisplay} input={user} />);
@@ -38,7 +38,7 @@ const Register: FC<{receiveChange: (change: IUser) => void}> = ({ receiveChange
setDisplayed(<FinishUp transitionDisplay={transitionDisplay} />); setDisplayed(<FinishUp transitionDisplay={transitionDisplay} />);
break; break;
default: default:
setDisplayed(<AboutYou transitionDisplay={transitionDisplay} input={authContext.user || user} />); setDisplayed(<AboutYou transitionDisplay={transitionDisplay} input={user} />);
break; break;
} }
} }

View File

@@ -1,44 +1,25 @@
import { FC, useCallback, useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { LoggedIn, NotLoggedIn, Registering } from "./variants"; import { LoggedIn, NotLoggedIn, Registering } from "./variants";
import { useAuthContext } from "../../../context/AuthContext"; import { useAuthContext } from "../../../context/AuthContext";
import { IUser } from "../../../schemas";
import "/src/sass/components/Navbar.scss"; import "/src/sass/components/Navbar.scss";
const Navbar: FC<{receiveChange: (change: IUser) => void}> = ({ receiveChange }) => { const Navbar = () => {
// setup and local state // setup and local state
const navigate = useNavigate(); const { user } = useAuthContext();
const { user, setUser } = useAuthContext(); const [displayed, setDisplayed] = useState<JSX.Element>(<p>Loading...</p>);
const [received, setReceived] = useState<IUser | undefined>();
const [displayed, setDisplayed] = useState<JSX.Element>();
// lift and store state from navbar variants
const liftChange = useCallback((newValue: IUser | undefined) => {
if (!newValue) {
return;
}
setUser(newValue);
setReceived(newValue);
}, [])
const variants = { const variants = {
loggedin: <LoggedIn navigate={navigate} received={received} liftChange={liftChange} />, loggedin: <LoggedIn />,
notloggedin: <NotLoggedIn navigate={navigate} received={received} />, notloggedin: <NotLoggedIn />,
registering: <Registering navigate={navigate} received={received} /> registering: <Registering />
} }
// side effects for live rendering // side effects for live rendering
useEffect(() => { useEffect(() => {
user && setReceived(user); setDisplayed(user ? variants.loggedin : variants.notloggedin);
}, [user]) }, [user]);
useEffect(() => { return displayed;
if (received) receiveChange(received);
setDisplayed(received ? variants.loggedin : variants.notloggedin);
}, [received, setReceived]);
return displayed || <p>Loading...</p>;
} }
export default Navbar; export default Navbar;

View File

@@ -1,15 +1,26 @@
import { attemptLogout } from "../../../util/apiUtils"; import API from "../../../util/API";
import { NavbarType } from "../../../util/types"; import { NavbarType } from "../../../util/types";
import { Button, Dropdown } from '../.' import { Button, Dropdown } from '..'
import { useState } from "react"; import { useState } from "react";
import { useAuthContext } from "../../../context/AuthContext";
import { useNavigate } from "react-router-dom";
const LoggedIn = () => {
const { user, setUser, setToken } = useAuthContext();
const navigate = useNavigate();
const auth = new API.Auth();
const LoggedIn: NavbarType = ({ received, liftChange, navigate }) => {
const [dropdownActive, setDropdownActive] = useState(false); const [dropdownActive, setDropdownActive] = useState(false);
const [searchActive, setSearchActive] = useState(false); const [searchActive, setSearchActive] = useState(false);
const handleLogout = async () => { const handleLogout = async () => {
const success = await attemptLogout(); const success = await auth.logout();
if (success) liftChange!(undefined); console.log(success);
// nullify cookie and unset user/token data
document.cookie = `token=;expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
setUser(undefined);
setToken(undefined);
navigate('/'); navigate('/');
} }
@@ -36,7 +47,7 @@ const LoggedIn: NavbarType = ({ received, liftChange, navigate }) => {
<a onClick={() => navigate('/')}>RECIPIN</a> <a onClick={() => navigate('/')}>RECIPIN</a>
</div> </div>
<div className="navbar-block"> <div className="navbar-block">
<p>Hi, {received?.firstname}.</p> <p>Hi, {user?.firstname}.</p>
<span id="search-icon"></span> <span id="search-icon"></span>
<Button onClick={() => handleUIChange("SEARCH")}>Search</Button> <Button onClick={() => handleUIChange("SEARCH")}>Search</Button>
<Button onClick={() => handleUIChange("ACTIONS")}>Actions</Button> <Button onClick={() => handleUIChange("ACTIONS")}>Actions</Button>
@@ -64,7 +75,9 @@ const LoggedIn: NavbarType = ({ received, liftChange, navigate }) => {
) )
} }
const NotLoggedIn: NavbarType = ({ navigate }) => { const NotLoggedIn = () => {
const navigate = useNavigate();
return ( return (
<div id="navbar"> <div id="navbar">
<div className="navbar-block"> <div className="navbar-block">
@@ -77,14 +90,17 @@ const NotLoggedIn: NavbarType = ({ navigate }) => {
) )
} }
const Registering: NavbarType = ({ received, navigate }) => { const Registering = () => {
const { user } = useAuthContext();
const navigate = useNavigate();
return ( return (
<div id="navbar"> <div id="navbar">
<div className="navbar-block"> <div className="navbar-block">
<a onClick={() => navigate('/')}>RECIPIN</a> <a onClick={() => navigate('/')}>RECIPIN</a>
</div> </div>
<div className="navbar-block"> <div className="navbar-block">
<p>Hi, {received?.firstname}.</p> <p>Hi, {user?.firstname}.</p>
</div> </div>
</div> </div>
) )

View File

@@ -3,12 +3,16 @@ import { IUser } from "../schemas";
export interface IAuthContext { export interface IAuthContext {
user?: IUser user?: IUser
setUser: Dispatch<SetStateAction<IUser>> | VoidFunction setUser: Dispatch<SetStateAction<IUser | undefined>> | VoidFunction
token?: string
setToken: Dispatch<SetStateAction<string | undefined>> | VoidFunction
} }
export const defaultValue: IAuthContext = { export const defaultValue: IAuthContext = {
user: undefined, user: undefined,
setUser: () => {} setUser: () => {},
token: undefined,
setToken: () => {}
} }
export const AuthContext = createContext<IAuthContext>(defaultValue); export const AuthContext = createContext<IAuthContext>(defaultValue);

View File

@@ -0,0 +1,18 @@
import { FC, Provider, ReactPortal, useEffect, useState } from "react"
import { IUser } from "../schemas";
import { AuthContext, IAuthContext } from "./AuthContext";
const AuthProvider = ({ children }: any) => {
const [user, setUser] = useState<IUser>();
const [token, setToken] = useState<string>();
const value = { user, setUser, token, setToken }
return (
<AuthContext.Provider value={ value }>
{ children }
</AuthContext.Provider>
)
}
export default AuthProvider;

View File

@@ -1,6 +1,13 @@
import React from 'react' import React from 'react'
import ReactDOM from 'react-dom/client' import ReactDOM from 'react-dom/client'
import App from './App' import App from './App'
import AuthProvider from './context/AuthProvider'
import './sass/index.scss' import './sass/index.scss'
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(<App />) ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<AuthProvider>
<React.StrictMode>
<App />
</React.StrictMode>
</AuthProvider>
)

View File

@@ -2,7 +2,7 @@ import { AxiosHeaders, AxiosRequestHeaders } from "axios";
import { IUser, IUserAuth, IFriendship, IRecipe, IIngredient, ICollection, IGroceryList } from "../schemas"; import { IUser, IUserAuth, IFriendship, IRecipe, IIngredient, ICollection, IGroceryList } from "../schemas";
import { default as _instance } from "./axiosInstance"; import { default as _instance } from "./axiosInstance";
export module API { module API {
const APISTRING = import.meta.env.APISTRING || "http://localhost:8080"; const APISTRING = import.meta.env.APISTRING || "http://localhost:8080";
abstract class RestController<T> { abstract class RestController<T> {
@@ -150,3 +150,5 @@ export module API {
} }
} }
} }
export default API

View File

@@ -10,13 +10,9 @@ const instance = axios.create({
instance.interceptors.response.use((res: AxiosResponse<any,any>) => { instance.interceptors.response.use((res: AxiosResponse<any,any>) => {
if (res?.data.token) { if (res?.data.token) {
document.cookie = `token=${res.data.token}`; document.cookie = `token=${res.data.token}`;
return res;
} else {
console.error("Token was not found in response");
return res;
} }
return res;
}, (err) => { }, (err) => {
return Promise.reject(err); return Promise.reject(err);
}) })

View File

@@ -47,3 +47,7 @@ export type ProtectPortal = FC<ProtectParams>
export type UserCardType = FC<UserCardProps> export type UserCardType = FC<UserCardProps>
export type NavbarType = FC<NavbarProps> export type NavbarType = FC<NavbarProps>
export type CheckboxType = FC<CheckboxProps> export type CheckboxType = FC<CheckboxProps>
export interface TokenType {
user: IUser
}