Skip to content

Commit

Permalink
feature-user-jwt-auth
Browse files Browse the repository at this point in the history
  • Loading branch information
codernesty committed Sep 6, 2024
1 parent 9b3a6b3 commit c0998bb
Show file tree
Hide file tree
Showing 13 changed files with 158 additions and 132 deletions.
20 changes: 12 additions & 8 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import NotFoundRedirect from "components/NotFoundRedirect";
import { AlertQueue, AlertQueueProvider } from "hooks/alerts";
import { AuthenticationProvider, OneTimePasswordWrapper } from "hooks/auth";
import { ThemeProvider } from "hooks/theme";
import { LoadingProvider } from "contexts/LoadingContext";
import { AuthProvider } from "contexts/AuthContext";
import LoadingMask from "components/LoadingMask";
import CollectionPage from "pages/Collection";
import Collections from "pages/Collections";
import Home from "pages/Home";
Expand All @@ -18,10 +21,10 @@ const App = () => {
return (
<Router>
<ThemeProvider>
<AuthenticationProvider>
<AlertQueueProvider>
<AlertQueue>
<OneTimePasswordWrapper>
<LoadingProvider>
<AuthProvider>
<AlertQueueProvider>
<AlertQueue>
<Container>
<Routes>
<Route path="/" element={<Home />} />
Expand All @@ -42,10 +45,11 @@ const App = () => {
</Routes>
</Container>
<TopNavbar />
</OneTimePasswordWrapper>
</AlertQueue>
</AlertQueueProvider>
</AuthenticationProvider>
</AlertQueue>
</AlertQueueProvider>
</AuthProvider>
<LoadingMask />
</LoadingProvider>
</ThemeProvider>
</Router>
);
Expand Down
30 changes: 8 additions & 22 deletions frontend/src/api/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,31 @@ import axios from "axios";
import {
SigninData,
SignupData,
SignupResponse,
SignupResponseBusiness,
Response,
} 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 +44,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 React from 'react';
import { useLoading } from 'contexts/LoadingContext';

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;
52 changes: 29 additions & 23 deletions frontend/src/components/nav/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
FaTimes,
} from "react-icons/fa";
import { useNavigate } from "react-router-dom";

import { useAuth } from "contexts/AuthContext";
import clsx from "clsx";

interface SidebarItemProps {
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,16 @@ const Sidebar = ({ show, onClose }: SidebarProps) => {
}}
size="md"
/>
<SidebarItem
{
is_auth?<SidebarItem
title="Collections"
icon={<FaThList />}
onClick={() => {
navigate("/collections");
onClose();
}}
size="md"
/>
/>:<></>}
<SidebarItem
title="Privacy"
icon={<FaLock />}
Expand All @@ -130,25 +131,30 @@ 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
55 changes: 13 additions & 42 deletions frontend/src/contexts/AuthContext.tsx
Original file line number Diff line number Diff line change
@@ -1,84 +1,55 @@
// 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);
}
} else signout();
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
35 changes: 35 additions & 0 deletions frontend/src/contexts/LoadingContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// src/context/LoadingContext.tsx
import React, { createContext, useState, useContext, ReactNode } 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>
);
};
23 changes: 20 additions & 3 deletions frontend/src/pages/Login.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,42 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import { Google } from "react-bootstrap-icons";
import { useLoading } from "contexts/LoadingContext";
import { signup, signin } from "api/auth";
import { useAuth } from "contexts/AuthContext";
import { useNavigate } from "react-router-dom"
const LoginPage: React.FC = () => {
const [isSignup, setIsSignup] = useState(false);
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [username, setName] = useState("");
const {startLoading, stopLoading} = useLoading();
const {is_auth, setAuth} = useAuth()
const navigate = useNavigate();
useEffect(()=>{
if(is_auth)
navigate('/collections');
},[is_auth])
// Toggle between login and signup forms
const handleSwitch = () => {
setIsSignup(!isSignup);
};

// Handle form submission
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();

// Add your logic for login/signup here
if (isSignup) {
// You can call your API for sign-up
// const user = await signup({email, password, username})
startLoading();
const user = await signup({email, password, username})
setAuth(user)
stopLoading();
} else {
// You can call your API for login
startLoading();
const user = await signin({email, password})
setAuth(user)
stopLoading();
}
};

Expand Down
Loading

0 comments on commit c0998bb

Please sign in to comment.