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

feature-user-jwt-auth #20

Closed
wants to merge 2 commits into from
Closed
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
21 changes: 12 additions & 9 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import "bootstrap/dist/css/bootstrap.min.css";
import LoadingMask from "components/LoadingMask";
import TopNavbar from "components/nav/TopNavbar";
import NotFoundRedirect from "components/NotFoundRedirect";
import { AuthProvider } from "contexts/AuthContext";
import { LoadingProvider } from "contexts/LoadingContext";
import { AlertQueue, AlertQueueProvider } from "hooks/alerts";
import { AuthenticationProvider, OneTimePasswordWrapper } from "hooks/auth";
import { ThemeProvider } from "hooks/theme";
import CollectionPage from "pages/Collection";
import Collections from "pages/Collections";
Expand All @@ -18,10 +20,10 @@ const App = () => {
return (
<Router>
<ThemeProvider>
<AuthenticationProvider>
<AlertQueueProvider>
<AlertQueue>
<OneTimePasswordWrapper>
<LoadingProvider>
<AuthProvider>
<AlertQueueProvider>
<AlertQueue>
<Container>
<Routes>
<Route path="/" element={<Home />} />
Expand All @@ -42,10 +44,11 @@ const App = () => {
</Routes>
</Container>
<TopNavbar />
</OneTimePasswordWrapper>
</AlertQueue>
</AlertQueueProvider>
</AuthenticationProvider>
</AlertQueue>
</AlertQueueProvider>
</AuthProvider>
<LoadingMask />
</LoadingProvider>
</ThemeProvider>
</Router>
);
Expand Down
34 changes: 8 additions & 26 deletions frontend/src/api/auth.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,29 @@
// src/api/auth.ts
import axios from "axios";
import {
SigninData,
SignupData,
SignupResponse,
SignupResponseBusiness,
} from "types/auth";
import { Response, SigninData, SignupData } from "types/auth";

const API_URL = process.env.REACT_APP_BACKEND_URL || "https://localhost:8080";

export const signup = async (data: SignupData): Promise<SignupResponse> => {
export const signup = async (data: SignupData): Promise<Response> => {
const response = await axios.post(`${API_URL}/signup`, data);
return response.data;
};
export const read_me = async (token: string): Promise<SignupResponse> => {
const response = await axios.get(`${API_URL}/api/user/me`, {
export const read_me = async (token: string): Promise<Response> => {
const response = await axios.get(`${API_URL}/me`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
return response.data;
};
export const read_me_business = async (
token: string,
): Promise<SignupResponseBusiness> => {
const response = await axios.get(`${API_URL}/api/business/me`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
return response.data;
};
export const signin = async (data: SigninData): Promise<SignupResponse> => {
const params = new URLSearchParams();
params.append("username", data.email);
params.append("password", data.password);
const response = await axios.post(`${API_URL}/api/user/token`, params);
export const signin = async (data: SigninData): Promise<Response> => {
const response = await axios.post(`${API_URL}/signin`, data);
console.log(response);
return response.data;
};
export const social_facebook_login = async (
token: string,
): Promise<SignupResponse> => {
): Promise<Response> => {
console.log(token);
const response = await axios.post(
`${API_URL}/api/facebook/callback`,
Expand All @@ -58,7 +40,7 @@ export const social_facebook_login = async (
};
export const social_Instagram_login = async (
token: string,
): Promise<SignupResponse> => {
): Promise<Response> => {
const response = await axios.post(
`${API_URL}/api/instagram/callback`,
{
Expand Down
19 changes: 19 additions & 0 deletions frontend/src/components/LoadingMask.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// src/components/LoadingMask.tsx
import { useLoading } from "contexts/LoadingContext";
import React from "react";

const LoadingMask: React.FC = () => {
const { loading } = useLoading();

if (!loading) return null;

return (
<div className="fixed inset-0 flex items-center justify-center bg-gray-800 bg-opacity-50 z-50">
<div className="spinner-border text-white" role="status">
<span className="visually-hidden">Loading...</span>
</div>
</div>
);
};

export default LoadingMask;
70 changes: 39 additions & 31 deletions frontend/src/components/nav/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import clsx from "clsx";
import { useAuth } from "contexts/AuthContext";
import {
FaHome,
FaLock,
Expand All @@ -8,8 +10,6 @@ import {
} from "react-icons/fa";
import { useNavigate } from "react-router-dom";

import clsx from "clsx";

interface SidebarItemProps {
title: string;
icon?: JSX.Element;
Expand Down Expand Up @@ -70,7 +70,7 @@ interface SidebarProps {

const Sidebar = ({ show, onClose }: SidebarProps) => {
const navigate = useNavigate();

const { is_auth, signout } = useAuth();
return (
<div>
{show ? (
Expand Down Expand Up @@ -106,15 +106,19 @@ const Sidebar = ({ show, onClose }: SidebarProps) => {
}}
size="md"
/>
<SidebarItem
title="Collections"
icon={<FaThList />}
onClick={() => {
navigate("/collections");
onClose();
}}
size="md"
/>
{is_auth ? (
<SidebarItem
title="Collections"
icon={<FaThList />}
onClick={() => {
navigate("/collections");
onClose();
}}
size="md"
/>
) : (
<></>
)}
<SidebarItem
title="Privacy"
icon={<FaLock />}
Expand All @@ -130,25 +134,29 @@ const Sidebar = ({ show, onClose }: SidebarProps) => {
<hr className="my-4 border-gray-300 dark:border-gray-600" />

<ul className="space-y-1">
<SidebarItem
title="Login / Sign Up"
icon={<FaSignInAlt />}
onClick={() => {
navigate("/login");
onClose();
}}
size="md"
/>
<SidebarItem
title="Logout"
icon={<FaSignOutAlt />}
onClick={() => {
// Handle logout logic here
navigate("/login");
onClose();
}}
size="md"
/>
{is_auth ? (
<SidebarItem
title="Logout"
icon={<FaSignOutAlt />}
onClick={() => {
// Handle logout logic here
signout();
navigate("/login");
onClose();
}}
size="md"
/>
) : (
<SidebarItem
title="Login / Sign Up"
icon={<FaSignInAlt />}
onClick={() => {
navigate("/login");
onClose();
}}
size="md"
/>
)}
</ul>
</div>
</div>
Expand Down
66 changes: 18 additions & 48 deletions frontend/src/contexts/AuthContext.tsx
Original file line number Diff line number Diff line change
@@ -1,84 +1,54 @@
// src/context/AuthContext.tsx
import { read_me, read_me_business } from "api/auth";
import { read_me } from "api/auth";
import React, {
createContext,
ReactNode,
useContext,
useEffect,
useState,
} from "react";
import { SignupResponse, SignupResponseBusiness } from "types/auth";
import { Response } from "types/auth";

interface AuthContextType {
auth: SignupResponse | null;
auth_business: SignupResponseBusiness | null;
auth_type: "user" | "business";
setAuth: React.Dispatch<React.SetStateAction<SignupResponse | null>>;
setAuthBusiness: React.Dispatch<
React.SetStateAction<SignupResponseBusiness | null>
>;
setAuthType: React.Dispatch<React.SetStateAction<"user" | "business">>;
is_auth: boolean;
auth: Response | null;
setAuth: React.Dispatch<React.SetStateAction<Response | null>>;
signout: () => void;
}

const AuthContext = createContext<AuthContextType | undefined>(undefined);

const AuthProvider = ({ children }: { children: ReactNode }) => {
const [auth, setAuth] = useState<SignupResponse | null>(null);
const [auth_business, setAuthBusiness] =
useState<SignupResponseBusiness | null>(null);
const [auth_type, setAuthType] = useState<"user" | "business">("user");
const [auth, setAuth] = useState<Response | null>(null);
const [is_auth, setFlag] = useState<boolean>(false);
const signout = () => {
localStorage.removeItem("token");
localStorage.removeItem("type");
setAuth({});
setAuthBusiness({});
setFlag(false);
};
useEffect(() => {
console.log("1");
const token = localStorage.getItem("token");
const auth_type = localStorage.getItem("type");
if (token) {
if (auth_type == "user") {
const fetch_data = async (token: string) => {
const response = await read_me(token);
console.log(response);
if (response) setAuth(response);
};
fetch_data(token);
} else {
const fetch_data = async (token: string) => {
const response = await read_me_business(token);
console.log(response);
if (response) setAuthBusiness(response);
};
fetch_data(token);
}
const fetch_data = async (token: string) => {
const response = await read_me(token);
console.log(response);
if (response) setAuth(response);
};
fetch_data(token);
} else signout();
}, []);
useEffect(() => {
console.log("2");
if (auth?.access_token) {
localStorage.setItem("token", auth.access_token);
localStorage.setItem("type", "user");
if (auth?.token) {
localStorage.setItem("token", auth.token);
setFlag(true);
}
}, [auth]);
useEffect(() => {
console.log("3");
if (auth_business?.access_token) {
localStorage.setItem("token", auth_business.access_token);
localStorage.setItem("type", "business");
}
}, [auth_business]);
return (
<AuthContext.Provider
value={{
auth,
auth_business,
auth_type,
is_auth,
setAuth,
setAuthBusiness,
setAuthType,
signout,
}}
>
Expand Down
37 changes: 37 additions & 0 deletions frontend/src/contexts/LoadingContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// src/context/LoadingContext.tsx
import React, { createContext, ReactNode, useContext, useState } from "react";

interface LoadingContextType {
loading: boolean;
startLoading: () => void;
stopLoading: () => void;
}

const LoadingContext = createContext<LoadingContextType | undefined>(undefined);

export const useLoading = (): LoadingContextType => {
const context = useContext(LoadingContext);
if (!context) {
throw new Error("useLoading must be used within a LoadingProvider");
}
return context;
};

interface LoadingProviderProps {
children: ReactNode;
}

export const LoadingProvider: React.FC<LoadingProviderProps> = ({
children,
}) => {
const [loading, setLoading] = useState<boolean>(false);

const startLoading = () => setLoading(true);
const stopLoading = () => setLoading(false);

return (
<LoadingContext.Provider value={{ loading, startLoading, stopLoading }}>
{children}
</LoadingContext.Provider>
);
};
Loading
Loading