Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(AuthContext): Refactor to TypeScript and change dir #404

Merged
merged 7 commits into from
Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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";
tiago-bacelar marked this conversation as resolved.
Show resolved Hide resolved

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(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've deleted this useMemo because it's generally used just for complex calcs (that is not this case).

This useMemo causes two renders, the first one because the states changed and second because the useMemo detects that someone from it's dependency array changed, causing unecessary renders.

() => ({
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
Loading