Skip to content

Commit

Permalink
refactor(AuthContext): Refactor to TypeScript and change dir (#404)
Browse files Browse the repository at this point in the history
* Rebase (squashed Enzo's commits)

* Change import to lib alias

* Improve login/logout logic

* Fix lint error

* Remove withoutAuth

* Add types to AuthContext

* Fix type errors

---------

Co-authored-by: tiago-bacelar <pereira.migueltiago@gmail.com>
  • Loading branch information
EnzoVieira and tiago-bacelar authored Jan 24, 2024
1 parent 945c6a8 commit 12fc798
Show file tree
Hide file tree
Showing 28 changed files with 219 additions and 148 deletions.
181 changes: 132 additions & 49 deletions context/Auth/AuthProvider.js → context/Auth/AuthContext.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,95 @@
import { createContext, useEffect, useMemo, useState } from "react";
import { createContext, useContext, useEffect, useState } from "react";
import { useRouter } from "next/router";
import API from "/lib/api";
import * as api from "/lib/api";
import * as USER from "/lib/user";
import API from "@lib/api";
import * as api from "@lib/api";
import * as USER from "@lib/user";

export const AuthContext = createContext();
interface ILoginDTO {
email: string;
password: string;
}

export interface IBadge {
avatar: string | null;
begin: string;
description: string;
end: string;
id: number;
name: string;
tokens: number;
type: number;
}

export interface IPrize {
avatar: string;
id: number;
is_redeemable: boolean;
name: string;
not_redeemed: number;
}

export interface IAbstractUser {
email: string;
type: string;
}

export interface IAttendee extends IAbstractUser {
avatar: string | null;
badge_count: number;
badges: IBadge[];
course: number;
cv: string | null;
entries: number;
id: string;
name: string;
nickname: string;
prizes: IPrize[];
redeemables: IPrize[];
token_balance: number;
}

export interface IStaff extends IAbstractUser {
id: number;
}

export interface ISponsor extends IAbstractUser {
badge_id: number;
/** The id of the company */
id: number;
name: string;
sponsorship: string;
/** The id of the user */
user_id: number;
}

export type IUser = IAttendee | ISponsor | IStaff;

interface IAuthContext {
user: IUser | null;
errors?: string;

// Booleans
isAuthenticated: boolean;
isLoading: boolean;

// Updates user in state
updateUser: (user: IUser | null) => void;
refetchUser: () => void;

// Api calls
login: (params: ILoginDTO) => void;
logout: () => void;
editUser: (username: FormData) => void;
sign_up: (fields: any) => void;
}

export const AuthContext = createContext({} as IAuthContext);

const TOKEN_KEY_NAME = "safira_token";

// Function that consumes the context
export const useAuth = () => useContext(AuthContext);

export function AuthProvider({ children }) {
const router = useRouter();
const [user, setUser] = useState(null);
Expand All @@ -19,6 +101,7 @@ export function AuthProvider({ children }) {

useEffect(() => {
if (user) {
console.log(user);
setAuthenticated(true);
} else {
setAuthenticated(false);
Expand All @@ -44,7 +127,7 @@ export function AuthProvider({ children }) {
})
.catch((_errors) => {
// It means the jwt is expired
localStorage.clear();
localStorage.removeItem(TOKEN_KEY_NAME);
delete API.defaults.headers.common["Authorization"];
})
.finally(() => setFirstLoading(false));
Expand Down Expand Up @@ -77,38 +160,41 @@ export function AuthProvider({ children }) {
})
.catch((e) => {
setErrors("Something went wrong. Check your input and try again"); //e.message
setUser(undefined);
setUser(null);
setLoading(false);
});
}

function login({ email, password }) {
setLoading(true);

api
.sign_in({ email, password })
.then(({ jwt }) => {
localStorage.setItem(TOKEN_KEY_NAME, jwt);
API.defaults.headers.common["Authorization"] = `Bearer ${jwt}`;
api.getCurrentUser().then((response) => {
setUser(response);
switch (response.type) {
case USER.ROLES.ATTENDEE:
router.push("/attendee/profile");
break;
case USER.ROLES.SPONSOR:
router.push("/sponsor/scanner");
break;
case USER.ROLES.STAFF:
router.push("/staff/badges");
break;
default:
throw new Error(`Unknown USER TYPE: ${response.type}`);
api.getCurrentUser().then(async (response) => {
if (router.query.from == undefined) {
switch (response.type) {
case USER.ROLES.ATTENDEE:
await router.push({ query: { from: "/attendee/profile" } });
break;
case USER.ROLES.SPONSOR:
await router.push({ query: { from: "/sponsor/scanner" } });
break;
case USER.ROLES.STAFF:
await router.push({ query: { from: "/staff/badges" } });
break;
}
}
setUser(response);
});
})
.catch((errors) => {
if (errors.response) {
setErrors("Invalid credentials");
if (errors.response?.data?.error) {
setErrors(errors.response.data.error);
} else if (errors.response) {
setErrors("Request denied by the server");
} else if (errors.request) {
setErrors(
"No connection to the server. Please check your internet connection and try again later"
Expand All @@ -118,30 +204,28 @@ export function AuthProvider({ children }) {
"Something went wrong :/ Please check your internet connection and try again later"
);
}

setUser(undefined);
setUser(null);
setLoading(false);
});
}

function logout() {
setLoading(true);
localStorage.clear();
localStorage.removeItem(TOKEN_KEY_NAME);
delete API.defaults.headers.common["Authorization"];
setUser(undefined);
router.push("/");
router.push("/").finally(() => setUser(null));
}

function editUser(formData) {
function editUser(nickname) {
setLoading(true);

api
.editUser(user.id, formData)
.editUser(user.id, nickname)
.then((at) => {
setUser((oldUser) => ({ ...oldUser, ...at }));
})
.catch((errors) => {
setUser(undefined);
setUser(null);
setErrors(
"Something went wrong :/ Please check your internet connection and try again later"
);
Expand All @@ -152,26 +236,25 @@ export function AuthProvider({ children }) {
setRefetch((needsRefetch) => !needsRefetch);
}

// Make the provider update only when it should
const values = useMemo(
() => ({
user,
isAuthenticated,
isLoading,
setUser,
errors,
login,
logout,
editUser,
refetchUser,
sign_up,
}),
// eslint-disable-next-line
[user, isAuthenticated, isLoading, errors]
);
function updateUser(updatedUser: IUser | null) {
setUser(updatedUser);
}

return (
<AuthContext.Provider value={values}>
<AuthContext.Provider
value={{
user,
isAuthenticated,
isLoading,
updateUser: updateUser,
errors,
login,
logout,
editUser,
refetchUser,
sign_up,
}}
>
{!isFirstLoading && children}
</AuthContext.Provider>
);
Expand Down
4 changes: 0 additions & 4 deletions context/Auth/index.js

This file was deleted.

13 changes: 13 additions & 0 deletions context/Auth/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export type {
IBadge,
IPrize,
IAbstractUser,
IAttendee,
IStaff,
ISponsor,
IUser,
} from "./AuthContext";

export { AuthProvider } from "./AuthContext";
export { useAuth } from "./AuthContext";
export { withAuth } from "./withAuth";
4 changes: 0 additions & 4 deletions context/Auth/useAuth.js

This file was deleted.

13 changes: 8 additions & 5 deletions context/Auth/withAuth.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useRouter } from "next/router";
import { useAuth } from "./useAuth";
import { useAuth } from ".";
import * as USER from "/lib/user";

export function withAuth(WrappedComponent) {
Expand All @@ -9,7 +9,7 @@ export function withAuth(WrappedComponent) {
const { user } = useAuth();

if (!user) {
router.replace("/signup");
router.replace(`/login?from=${encodeURIComponent(router.asPath)}`);
return null;
}

Expand All @@ -29,7 +29,8 @@ export function withAuth(WrappedComponent) {
"/product/[slug]",
].includes(router.pathname)
) {
return router.replace("/404");
router.replace("/404");
return null;
}
break;
case USER.ROLES.STAFF:
Expand All @@ -43,7 +44,8 @@ export function withAuth(WrappedComponent) {
"/attendees/[uuid]",
].includes(router.pathname)
) {
return router.replace("/404");
router.replace("/404");
return null;
}
break;
case USER.ROLES.SPONSOR:
Expand All @@ -55,7 +57,8 @@ export function withAuth(WrappedComponent) {
"/sponsor/visitors",
].includes(router.pathname)
) {
return router.replace("/404");
router.replace("/404");
return null;
}
break;
}
Expand Down
16 changes: 0 additions & 16 deletions context/Auth/withoutAuth.js

This file was deleted.

9 changes: 5 additions & 4 deletions generator/templates/layouts/layout.tsx.hbs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
{{#if shouldAddAuth}}withAuth{{else}}withoutAuth{{/if}}
} from "@context/Auth";

{{#if shouldAddAuth}}
import { withAuth } from "@context/Auth";
{{/if}}

{{#if shouldAddComponents}}
import { Component } from "./components";
Expand All @@ -14,4 +15,4 @@ type
<h1>{{pascalCase name}}</h1>
</div>
); } export default
{{#if shouldAddAuth}}withAuth{{else}}withoutAuth{{/if}}({{pascalCase name}});
{{#if shouldAddAuth}}withAuth({{pascalCase name}}){{else}}{{pascalCase name}}{{/if}};
20 changes: 4 additions & 16 deletions layout/Attendee/Badgedex/Badgedex.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,15 @@ import ErrorMessage from "@components/ErrorMessage";
import Badge from "@components/Badge";
import BadgeFilter from "@components/BadgeFilter";
import GoToTop from "@components/GoToTop";

export interface Badges {
avatar: string;
begin: string;
description: string;
end: string;
id: number;
name: string;
tokens: number;
type: number;
}
import { IBadge } from "@context/Auth";

interface UserWithBadges {
user: {
badges: Badges[];
};
badges: IBadge[];
}

function Badgedex() {
const { user }: UserWithBadges = useAuth();
const [allBadges, updateAllBadges] = useState<Badges[]>([]);
const { user } = useAuth() as { user: UserWithBadges };
const [allBadges, updateAllBadges] = useState<IBadge[]>([]);
const [all, updateAll] = useState(true);
const [filter, updateFilter] = useState(null);
const [error, setError] = useState();
Expand Down
Loading

0 comments on commit 12fc798

Please sign in to comment.