diff --git a/backend-start.sh b/backend-start.sh index 060fea534..75afb0a29 100755 --- a/backend-start.sh +++ b/backend-start.sh @@ -1,3 +1,3 @@ #!/bin/bash -sbt "~backend/reStart" +sbt "~backend/reStart" -mem 3000 diff --git a/ui/package.json b/ui/package.json index df88f6747..aaba10d83 100644 --- a/ui/package.json +++ b/ui/package.json @@ -19,7 +19,10 @@ "axios": "^0.20.0", "bootstrap": "^4.5.2", "formik": "^2.2.0", + "fp-ts": "^2.9.5", "immer": "^7.0.9", + "io-ts": "^2.2.14", + "io-ts-reporters": "^1.2.2", "jest-environment-jsdom-sixteen": "^1.0.3", "noop-ts": "^1.0.3", "react": "^16.13.1", diff --git a/ui/src/App.tsx b/ui/src/App.tsx index dcdab59f2..367aa4e4f 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -1,14 +1,24 @@ import React from "react"; import { BrowserRouter } from "react-router-dom"; import Main from "./main/Main/Main"; -import { UserContextProvider } from "./contexts/UserContext/UserContext"; +import {initialUserState, UserReducer, UserContext, matchUserState} from "./contexts/UserContext/UserContext"; +import {some} from "fp-ts/Option"; -const App: React.FC = () => ( - - -
- - -); +const App: React.FC = () => { + const [state, dispatch] = React.useReducer(UserReducer, initialUserState); + + return ( + + {matchUserState({ + LoggedIn: userLoggedIn => ( +
+ ), + LoggedOut: () =>

logged out

, + Unknown: () =>

unknown

, + Initial: () =>

initial

+ })} + + ); +} export default App; diff --git a/ui/src/contexts/UserContext/UserContext.tsx b/ui/src/contexts/UserContext/UserContext.tsx index e7964e724..d0df4fc05 100644 --- a/ui/src/contexts/UserContext/UserContext.tsx +++ b/ui/src/contexts/UserContext/UserContext.tsx @@ -1,66 +1,99 @@ import React from "react"; import immer from "immer"; import noop from "noop-ts"; +import { ApiKey, UserDetails } from "../../services/UserService/UserServiceFP"; +import { none, Option } from "fp-ts/es6/Option"; +import {some} from "fp-ts/Option"; -export interface UserDetails { - createdOn: string; - email: string; - login: string; +type LoginState = "logged_in" | "logged_out" | "unknown" | "initial"; + +interface UserLoginState { + tag: T; +} + +interface UserLoggedIn extends UserLoginState<"logged_in"> { + apiKey: ApiKey; + user: UserDetails; +} + +interface UserLoginStateUnknown extends UserLoginState<"unknown">{ + apiKey: ApiKey; } -export interface UserState { - apiKey: string | null; - user: UserDetails | null; - loggedIn: boolean | null; +type UserLoggedOut = UserLoginState<"logged_out">; + +type InitialLoginState = UserLoginState<"initial">; + +export type UserState = UserLoggedIn | UserLoggedOut | UserLoginStateUnknown | InitialLoginState; + +const isUserLoggedIn = (state: UserState): state is UserLoggedIn => "apiKey" in state && "user" in state; +const isUserLoginStateUnknown = (state: UserState): state is UserLoginStateUnknown => "apiKey" in state && state.tag === "unknown"; +const isUserLoggedOut = (state: UserState): state is UserLoggedOut => "tag" in state && state.tag === "logged_out"; +const isUserLoginStateInitial = (state: UserState): state is InitialLoginState => "tag" in state && state.tag === "initial"; + +interface UserStateMatcher { + LoggedIn: (state: UserLoggedIn) => T; + LoggedOut: (state: UserLoggedOut) => T; + Unknown: (state: UserLoginStateUnknown) => T; + Initial: (state: InitialLoginState) => T; } -export const initialUserState: UserState = { - apiKey: null, - user: null, - loggedIn: null, +export const matchUserState = (matcher: UserStateMatcher) => (state: UserState): T => { + if(isUserLoggedIn(state)) { + return matcher.LoggedIn(state); + } + if(isUserLoginStateUnknown(state)) { + return matcher.Unknown(state); + } + if(isUserLoginStateInitial(state)) { + return matcher.Initial(state); + } + return matcher.LoggedOut(state); }; + +export const initialUserState: UserState = { tag: "initial" }; + export type UserAction = - | { type: "SET_API_KEY"; apiKey: string | null } + | { type: "SET_LOGIN_STATUS_UNKNOWN"; apiKey: ApiKey } | { type: "UPDATE_USER_DATA"; user: Partial } - | { type: "LOG_IN"; user: UserDetails } + | { type: "LOG_IN"; payload: UserLoggedIn } | { type: "LOG_OUT" }; -const UserReducer = (state: UserState, action: UserAction): UserState => { +export const UserReducer = (state: UserState, action: UserAction): UserState => { switch (action.type) { - case "SET_API_KEY": - return immer(state, (draftState) => { - draftState.apiKey = action.apiKey; + case "SET_LOGIN_STATUS_UNKNOWN": + return immer(state, () => { + const userState: UserState = { tag: "unknown", apiKey: action.apiKey }; + return userState; }); case "UPDATE_USER_DATA": return immer(state, (draftState) => { - if (!draftState.user) return; - draftState.user = { ...draftState.user, ...action.user }; + if (isUserLoggedIn(draftState)) { + return ({ ...draftState, user: { + ...draftState.user, + ...action.user + }}) + } else { + return state; + } }); case "LOG_IN": return immer(state, (draftState) => { - draftState.user = action.user; - draftState.loggedIn = true; + return { user: action.payload.user, apiKey: action.payload.apiKey, tag: "logged_in" }; }); case "LOG_OUT": return immer(state, (draftState) => { - draftState.apiKey = null; - draftState.user = null; - draftState.loggedIn = false; + return { tag: "logged_out"}; }); } }; +type UserContextData = Omit; export const UserContext = React.createContext<{ - state: UserState; - dispatch: React.Dispatch; + user: Option; + dispatch: React.Dispatch, }>({ - state: initialUserState, + user: none, dispatch: noop, }); - -export const UserContextProvider: React.FC = ({ children }) => { - const [state, dispatch] = React.useReducer(UserReducer, initialUserState); - - return {children}; -}; diff --git a/ui/src/main/Main/Main.tsx b/ui/src/main/Main/Main.tsx index e257d1e9e..c9b08f93f 100644 --- a/ui/src/main/Main/Main.tsx +++ b/ui/src/main/Main/Main.tsx @@ -3,32 +3,25 @@ import Footer from "../Footer/Footer"; import Top from "../Top/Top"; import ForkMe from "../ForkMe/ForkMe"; import { UserContext } from "../../contexts/UserContext/UserContext"; -import Loader from "../Loader/Loader"; import Routes from "../Routes/Routes"; import useLoginOnApiKey from "./useLoginOnApiKey"; import useLocalStoragedApiKey from "./useLocalStoragedApiKey"; const Main: React.FC = () => { const { - state: { loggedIn }, + user, } = React.useContext(UserContext); useLocalStoragedApiKey(); useLoginOnApiKey(); - if (loggedIn === null) { - return ; - } - - return ( - <> - - - - -