reformatting data flow on frontend
This commit is contained in:
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
18
client/src/context/AuthProvider.tsx
Normal file
18
client/src/context/AuthProvider.tsx
Normal 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;
|
||||||
@@ -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>
|
||||||
|
)
|
||||||
|
|||||||
@@ -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> {
|
||||||
@@ -149,4 +149,6 @@ export module API {
|
|||||||
super(APISTRING + "/app/grocery-list")
|
super(APISTRING + "/app/grocery-list")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default API
|
||||||
@@ -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);
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -46,4 +46,8 @@ export type ButtonComponent = FC<ButtonParams>
|
|||||||
export type ProtectPortal = FC<ProtectParams>
|
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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user