protected routes, 404 page, auth with localstorage, login redirects
This commit is contained in:
1 parent
207a5394f5
commit
e183e60fe2
6 files changed
+141
-61
No files matched your search
@@ -1,7 +1,7 @@
|
||||
import {
|
||||
createContext,
|
||||
useEffect,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState,
|
||||
ReactNode,
|
||||
Dispatch,
|
||||
@@ -12,52 +12,43 @@ import { useCookies } from 'react-cookie';
|
||||
import {
|
||||
SessionResponse,
|
||||
AuthState,
|
||||
STORAGE_KEYS
|
||||
} from "./types";
|
||||
|
||||
import { CreateSession, DestroySession, GetSession } from "./utilities";
|
||||
|
||||
export const AuthenticationContext = createContext<{
|
||||
authState?: AuthState,
|
||||
setAuthState?: Dispatch<SetStateAction<AuthState | undefined>>,
|
||||
persistLoginOrOut?: ()=>void,
|
||||
authState: AuthState,
|
||||
setAuthState?: Dispatch<SetStateAction<AuthState>>,
|
||||
loginAndGetUserInfo?: (
|
||||
usernameOrEmail: string,
|
||||
password:string,
|
||||
failureCallback?:()=>void
|
||||
)=>void;
|
||||
logout?: ()=>void;
|
||||
}>({});
|
||||
}>({
|
||||
authState:{}
|
||||
});
|
||||
|
||||
export const AuthenticationProvider = ({children}:{children:ReactNode})=>{
|
||||
const [authCookies, setAuthCookie, removeAuthCookie] = useCookies(['userInfo']);
|
||||
const [authState, setAuthState] = useState<AuthState | undefined>(undefined);
|
||||
console.log('auth provider rerender');
|
||||
const [authCookies, setAuthCookie, removeAuthCookie] = useCookies([STORAGE_KEYS.USER_INFO]);
|
||||
const [authState, setAuthState] = useState<AuthState>({});
|
||||
|
||||
const persistLoginOrOut = useCallback(()=>{
|
||||
GetSession().then((
|
||||
res: SessionResponse
|
||||
)=>{
|
||||
if(res.success && res.user){
|
||||
setAuthCookie('userInfo', res.user);
|
||||
setAuthState({
|
||||
isLoggedIn: true,
|
||||
userInfo: res.user
|
||||
});
|
||||
}else{
|
||||
removeAuthCookie('userInfo');
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
const loginAndGetUserInfo = (usernameOrEmail: string, password:string, failureCallback?:()=>void)=>{
|
||||
const loginAndGetUserInfo = useCallback((
|
||||
usernameOrEmail: string,
|
||||
password:string,
|
||||
failureCallback?:()=>void
|
||||
)=>{
|
||||
CreateSession({usernameOrEmail, password})
|
||||
.then((respond)=>{
|
||||
GetSession().then((
|
||||
res: SessionResponse
|
||||
)=>{
|
||||
if(res.success && res.user && setAuthState){
|
||||
setAuthCookie('userInfo', res.user);
|
||||
setAuthState({
|
||||
isLoggedIn: true,
|
||||
sessionData: JSON.stringify(res.user),
|
||||
userInfo: res.user
|
||||
});
|
||||
}else{
|
||||
@@ -65,29 +56,69 @@ export const AuthenticationProvider = ({children}:{children:ReactNode})=>{
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const logout = ()=>{
|
||||
DestroySession().then((res)=>{
|
||||
if (res && setAuthState) {
|
||||
setAuthState({
|
||||
isLoggedIn: false,
|
||||
userInfo: undefined
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
useEffect(()=>{
|
||||
if (authCookies !== undefined && authState===undefined){
|
||||
persistLoginOrOut();
|
||||
}
|
||||
},[]);
|
||||
|
||||
const setLogoutState = useCallback(()=>{
|
||||
setAuthState({
|
||||
isLoggedIn: false,
|
||||
sessionData: null,
|
||||
userInfo: undefined
|
||||
});
|
||||
},[]);
|
||||
|
||||
const logout = useCallback(()=>{
|
||||
DestroySession().then((res)=>{
|
||||
if (res) setLogoutState();
|
||||
});
|
||||
},[]);
|
||||
|
||||
//Set Session upon Auth State Changes
|
||||
useEffect(()=>{
|
||||
//On First Load
|
||||
if(authState.isLoggedIn === undefined){
|
||||
//Persist Login if Session Data exist
|
||||
const sessionData = localStorage.getItem(STORAGE_KEYS.USER_INFO);
|
||||
console.log(`Session Data: ${sessionData?.substring(0, 30)}${sessionData ? '...':''}`);
|
||||
if (sessionData !== null){
|
||||
console.log('Getting session to persist login');
|
||||
GetSession().then((
|
||||
res: SessionResponse
|
||||
)=>{
|
||||
console.log('Session Returned');
|
||||
console.log(res);
|
||||
if(res.success && res.user){
|
||||
console.log('Setting Auth State');
|
||||
setAuthState({
|
||||
isLoggedIn: true,
|
||||
sessionData: JSON.stringify(res.user),
|
||||
userInfo: res.user
|
||||
});
|
||||
}else{
|
||||
console.log('Session expired, setting State');
|
||||
setLogoutState();
|
||||
}
|
||||
});
|
||||
}else{
|
||||
setLogoutState();
|
||||
}
|
||||
}
|
||||
|
||||
// Propagate session data to localstorage
|
||||
if(authState.sessionData !== null && authState.sessionData !== undefined){
|
||||
localStorage.setItem(STORAGE_KEYS.USER_INFO, authState.sessionData);
|
||||
setAuthCookie(STORAGE_KEYS.USER_INFO, authState.userInfo);
|
||||
}
|
||||
// Propagate Logout
|
||||
else if (authState.sessionData === null){
|
||||
localStorage.removeItem(STORAGE_KEYS.USER_INFO);
|
||||
removeAuthCookie(STORAGE_KEYS.USER_INFO);
|
||||
}
|
||||
},[authState]);
|
||||
|
||||
return(
|
||||
<AuthenticationContext.Provider value={{
|
||||
authState,
|
||||
setAuthState,
|
||||
persistLoginOrOut,
|
||||
loginAndGetUserInfo,
|
||||
logout,
|
||||
}}>
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
export enum STORAGE_KEYS{
|
||||
USER_INFO = "user_info",
|
||||
}
|
||||
export enum AUTH_STATUS {
|
||||
LOGGED_IN = "logged_in",
|
||||
LOGGING = "logging",
|
||||
@@ -5,7 +8,8 @@ export enum AUTH_STATUS {
|
||||
}
|
||||
|
||||
export type AuthState = {
|
||||
isLoggedIn: boolean;
|
||||
isLoggedIn?: boolean;
|
||||
sessionData?: string | null;
|
||||
userInfo?: UserInfo;
|
||||
}
|
||||
export interface UserInfo {
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { useContext, useEffect, ComponentType } from "react";
|
||||
import { useNavigate, useLocation } from "@remix-run/react";
|
||||
import { AuthenticationContext } from "~/contexts/Authentication";
|
||||
|
||||
export const withProtectionRoute = <P extends {}>(Component: ComponentType<P>) => (rest:P) => {
|
||||
const { authState } = useContext(AuthenticationContext);
|
||||
console.log(authState);
|
||||
if (authState.isLoggedIn === undefined ) {
|
||||
return <>waiting</>;
|
||||
}
|
||||
if (authState.isLoggedIn === false) {
|
||||
return <RedirectToLogin />
|
||||
}
|
||||
return (
|
||||
<Component {...rest}/>
|
||||
);
|
||||
}
|
||||
|
||||
const RedirectToLogin = ()=>{
|
||||
const navigate = useNavigate();
|
||||
const {pathname} = useLocation();
|
||||
useEffect(()=>{
|
||||
navigate(`/login?redirect=${pathname}`);
|
||||
},[]);
|
||||
return <>redirecting</>
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { json } from "@remix-run/router";
|
||||
import { withProtectionRoute } from "~/modules/withProtectedRoute";
|
||||
|
||||
import { faChevronLeft } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { P, H1, ButtonLink } from "~/components";
|
||||
export const loader = () => {
|
||||
return json(null, { status: 404 });
|
||||
};
|
||||
|
||||
const Page404 = withProtectionRoute(()=>{
|
||||
return(
|
||||
<div
|
||||
className="fixed w-full overflow-scroll"
|
||||
style={{height: "calc(100% - 72px)"}}
|
||||
>
|
||||
<div
|
||||
className='bg-ui-panel w-10/12 max-w-7xl h-1/2 mx-auto my-24 rounded-lg p-6 flex flex-col items-center justify-center gap-8 text-center'
|
||||
>
|
||||
<P>Oops, seems like this is not a page that exists!</P>
|
||||
<H1>Server Error: 404</H1>
|
||||
|
||||
<ButtonLink to="/" icon={faChevronLeft}>Back to Dashboard</ButtonLink>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
});
|
||||
export default Page404;
|
||||
@@ -1,5 +1,5 @@
|
||||
import { PageEnigma } from "../pages/PageEnigma";
|
||||
import { withProtectionRoute } from "~/modules/withProtectedRoute";
|
||||
import { PageEnigma } from "~/pages/PageEnigma";
|
||||
|
||||
export default function IdealEnigma() {
|
||||
return <PageEnigma />;
|
||||
}
|
||||
const IdealEnigma = withProtectionRoute(()=><PageEnigma />);
|
||||
export default IdealEnigma;
|
||||
+6
-14
@@ -1,29 +1,19 @@
|
||||
import { useContext, useEffect, useState, useRef } from "react";
|
||||
import { useNavigate } from "@remix-run/react";
|
||||
import { useCookies } from 'react-cookie';
|
||||
import { useNavigate, useSearchParams } from "@remix-run/react";
|
||||
import { faKey, faUser } from "@fortawesome/pro-solid-svg-icons";
|
||||
|
||||
import { AuthenticationContext } from "~/contexts/Authentication";
|
||||
|
||||
import {
|
||||
Button,
|
||||
H1,
|
||||
Input,
|
||||
Link,
|
||||
P,
|
||||
} from '~/components';
|
||||
|
||||
import { Button,H1,Input,Link,P } from '~/components';
|
||||
import { LoadingDots } from "~/components";
|
||||
|
||||
export default function LoginScreen() {
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
const [showLoader, setShowLoader] = useState<string|undefined>(undefined);
|
||||
const formRef = useRef<HTMLFormElement | null>(null);
|
||||
const [authCookies, setAuthCookie, removeAuthCookie] = useCookies(['userInfo']);
|
||||
const {authState, loginAndGetUserInfo} = useContext(AuthenticationContext);
|
||||
|
||||
|
||||
const handleOnSumbit = (ev: React.FormEvent<HTMLFormElement>)=>{
|
||||
ev.preventDefault();
|
||||
if(formRef.current){
|
||||
@@ -43,8 +33,9 @@ export default function LoginScreen() {
|
||||
}// end handleOnSubmit
|
||||
|
||||
useEffect(()=>{
|
||||
const redirectPath = searchParams.get('redirect');
|
||||
if(authState && authState.isLoggedIn)
|
||||
navigate("/");
|
||||
navigate(redirectPath ? redirectPath:'/');
|
||||
},[authState]);
|
||||
|
||||
return (
|
||||
@@ -64,6 +55,7 @@ export default function LoginScreen() {
|
||||
icon={faUser}
|
||||
name="usernameOrEmail"
|
||||
placeholder="Username or Email"
|
||||
autoComplete="username"
|
||||
required
|
||||
/>
|
||||
<br />
|
||||
|
||||
Reference in new issue
Block a user