diff --git a/src/TonalityApp.tsx b/src/TonalityApp.tsx index 18c5fcbc2a2be72b4ac630f3a0aa4087823ff423..e2a3d318b3ace2e8f026966b361533432beeded6 100644 --- a/src/TonalityApp.tsx +++ b/src/TonalityApp.tsx @@ -3,11 +3,6 @@ import React from "react"; import { RenderRoutes } from "@/routes/RenderRoutes.tsx"; import { routes } from "@/routes/routes.ts"; -export type UserContext = {token: string | null, username: string | null, onLogin: (accessToken: string, username: string) => void, onLogout: () => void} -export const AuthContext = React.createContext< - UserContext>(null as unknown as UserContext); - -export const useAuth = () => React.useContext(AuthContext); // @ts-expect-error error bg export const Routes: React.ReactNode = RenderRoutes(routes); diff --git a/src/components/delete-dialog-album.tsx b/src/components/delete-dialog-album.tsx index 437c99d3a53216b0100acbf320e59edd8a0423ca..c9166811882a6f9f1dc2e766d4ff1642a4f6e61f 100644 --- a/src/components/delete-dialog-album.tsx +++ b/src/components/delete-dialog-album.tsx @@ -1,13 +1,22 @@ import { useNavigate, useParams } from 'react-router-dom'; import api from "@/api/api.ts"; +import {StatusCodes} from "http-status-codes"; +import {useAuth} from "@/context/auth-context.tsx"; const DeleteAlbumDialog = () => { const { albumId } = useParams(); const navigate = useNavigate(); + const { onLogout } = useAuth(); const handleDeleteAlbum = async () => { try { - await api.delete( + api.post("/verify-token",).then((response) => { + if (response.status !== StatusCodes.OK) { + onLogout(); + } + }); + + await api.delete( `/premium-album/${albumId}` ); navigate(`/album`); diff --git a/src/components/delete-dialog-song.tsx b/src/components/delete-dialog-song.tsx index 25b0a14ac1425fecb2bb000f1d68cb665625c981..40f82d79670415e69e5982b046ed3e7c6e140784 100644 --- a/src/components/delete-dialog-song.tsx +++ b/src/components/delete-dialog-song.tsx @@ -1,11 +1,20 @@ import { useNavigate, useParams } from 'react-router'; import api from "@/api/api.ts"; +import {StatusCodes} from "http-status-codes"; +import {useAuth} from "@/context/auth-context.tsx"; const DeleteSongDialog = () => { + const { onLogout } = useAuth(); const { albumId, songId } = useParams(); const navigate = useNavigate(); const handleDeleteSong = async () => { try { + api.post("/verify-token",).then((response) => { + if (response.status !== StatusCodes.OK) { + onLogout(); + } + }); + const response = await api.delete( `/premium-album/${albumId}/song/${songId}` ); diff --git a/src/components/edit-album-form.tsx b/src/components/edit-album-form.tsx deleted file mode 100644 index 2fdc263cbbd227f892b3cc579d3d84414c6c5cd3..0000000000000000000000000000000000000000 --- a/src/components/edit-album-form.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import React, { useState } from 'react'; -import {FormState} from "@/types/premium-album-form.ts"; - -const initialFormState: FormState = { - albumName: '', - releaseDate: '', - genre: '', - artist: '', - coverFile: null, -}; - -const EditAlbum: React.FC = () => { - const [formData, setFormData] = useState<FormState>(initialFormState); - - const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => { - const { name, value } = e.target; - setFormData((prevData) => ({ ...prevData, [name]: value })); - }; - - const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => { - const file = e.target.files && e.target.files[0]; - setFormData((prevData) => ({ ...prevData, coverFile: file })); - }; - - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - // Handle form submission logic here, e.g., send data to server - console.log('Form data submitted:', formData); - }; - - return ( - <form onSubmit={handleSubmit}> - <div> - <label htmlFor="albumName">Album Name:</label> - <input - type="text" - id="albumName" - name="albumName" - value={formData.albumName} - onChange={handleChange} - required - /> - </div> - <div> - <label htmlFor="releaseDate">Release Date:</label> - <input - type="text" - id="releaseDate" - name="releaseDate" - value={formData.releaseDate} - onChange={handleChange} - required - /> - </div> - <div> - <label htmlFor="genre">Genre:</label> - <input - type="text" - id="genre" - name="genre" - value={formData.genre} - onChange={handleChange} - required - /> - </div> - <div> - <label htmlFor="artist">Artist:</label> - <input - type="text" - id="artist" - name="artist" - value={formData.artist} - onChange={handleChange} - required - /> - </div> - <div> - <label htmlFor="coverFile">Cover File:</label> - <input - type="file" - id="coverFile" - name="coverFile" - onChange={handleFileChange} - accept="image/*" // Specify the accepted file types, adjust as needed - required - /> - </div> - <button type="submit">Submit</button> - </form> - ); -}; - -export default EditAlbum; diff --git a/src/components/sidebar.tsx b/src/components/sidebar.tsx index 58576f31b3259127747d8d735d810562e844a096..487b6bb23189bdc478b588df25f6a88376e98bde 100644 --- a/src/components/sidebar.tsx +++ b/src/components/sidebar.tsx @@ -3,7 +3,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faPlayCircle, faUser, } from '@fortawesome/free-solid-svg-icons'; import {NavLink} from "react-router-dom"; import {cn} from "@/lib/utils.ts"; -import {useAuth} from "@/TonalityApp.tsx"; +import {useAuth} from "@/context/auth-context.tsx"; const Sidebar = () => { diff --git a/src/context/AuthProvider.tsx b/src/context/auth-context.tsx similarity index 53% rename from src/context/AuthProvider.tsx rename to src/context/auth-context.tsx index cec97b41de07be5b2bd5550b42d9738d9736a478..b1bb4a4af4ad7076f6993d6e9a037243e9419e6a 100644 --- a/src/context/AuthProvider.tsx +++ b/src/context/auth-context.tsx @@ -1,6 +1,19 @@ import React from "react"; -import { AuthContext } from "@/TonalityApp.tsx"; import {Outlet, useNavigate} from "react-router-dom"; +import api from "@/api/api.ts"; + +export type UserContext = { + token: string | null, + username: string | null, + onLogin: (accessToken: string, username: string) => void, + onLogout: () => void, +} + +export const AuthContext = React.createContext< + UserContext>(null as unknown as UserContext); + +export const useAuth = () => React.useContext(AuthContext); + const AuthProvider = () => { const navigate = useNavigate(); @@ -8,13 +21,23 @@ const AuthProvider = () => { const [accessToken, setAccessToken] = React.useState<string | null>(sessionStorage.getItem("accessToken") ?? null); - const [username, setUsername] = React.useState<string | null>(null); + const [username, setUsername] = + React.useState<string | null>(sessionStorage.getItem("username") ?? null); const handleLogin = (accessToken: string, username: string) => { sessionStorage.setItem("accessToken", accessToken); + sessionStorage.setItem("username", username); setAccessToken(accessToken); setUsername(username); - navigate("/album"); + api.interceptors.request.use( + (config) => { + config.headers["Authorization"] = "Bearer " + accessToken; + return config; + }, + (error) => { + Promise.reject(error).then(r => console.log(r)); + } + ) }; const handleLogout = () => { diff --git a/src/pages/AddAlbumPage.tsx b/src/pages/AddAlbumPage.tsx index ed4c12061ca645aa8d57c0e88239a25257cbe777..88f91eca69ec6cd0f1e41910a54f639fc7c53900 100644 --- a/src/pages/AddAlbumPage.tsx +++ b/src/pages/AddAlbumPage.tsx @@ -5,8 +5,11 @@ import {zodResolver} from "@hookform/resolvers/zod"; import {Form, FormControl, FormField, FormItem, FormLabel, FormMessage} from "@/components/ui/form.tsx"; import {Input} from "@/components/ui/input.tsx"; import {albumFormSchema} from "@/validations/premium-album-form-validation.ts"; +import {StatusCodes} from "http-status-codes"; +import {useAuth} from "@/context/auth-context.tsx"; export default function AddAlbum() { + const { onLogout } = useAuth(); const form = useForm({ resolver: zodResolver(albumFormSchema), defaultValues: { @@ -33,6 +36,12 @@ export default function AddAlbum() { formData.append("artist", data.artist); formData.append("coverFile", coverFile); + api.post("/verify-token",).then((response) => { + if (response.status !== StatusCodes.OK) { + onLogout(); + } + }); + await api.post("/premium-album", formData, { diff --git a/src/pages/AddSongPage.tsx b/src/pages/AddSongPage.tsx index f68e5cda0f4bd93e706227e8c21eb18539da9f4e..771714dc8108a28e8ecac8c8a6d3cdfaa5d023fd 100644 --- a/src/pages/AddSongPage.tsx +++ b/src/pages/AddSongPage.tsx @@ -6,9 +6,12 @@ import api from "@/api/api.ts"; import {Form, FormControl, FormField, FormItem, FormLabel, FormMessage} from "@/components/ui/form.tsx"; import {Input} from "@/components/ui/input.tsx"; import {songFormSchema} from "@/validations/premium-song-form-validation.ts"; +import {StatusCodes} from "http-status-codes"; +import {useAuth} from "@/context/auth-context.tsx"; const AddSong = () => { + const { onLogout } = useAuth(); const { albumId }= useParams(); const form = useForm({ resolver: zodResolver(songFormSchema), @@ -37,6 +40,12 @@ const AddSong = () => { formData.append("audioFile", audioFile); formData.append("albumId", albumId?.toString() ?? ""); + api.post("/verify-token",).then((response) => { + if (response.status !== StatusCodes.OK) { + onLogout(); + } + }); + await api.post(`/premium-album/${albumId}`, formData, { headers: { "Content-Type": "multipart/form-data", diff --git a/src/pages/AlbumPage.tsx b/src/pages/AlbumPage.tsx index b97b2ff52c29e5ddfc8b087082aebbbdef9dab01..42f16386a647ddc2856fe363455a2feee34ebc0c 100644 --- a/src/pages/AlbumPage.tsx +++ b/src/pages/AlbumPage.tsx @@ -5,9 +5,12 @@ import { useNavigate } from 'react-router-dom'; import { Button } from '@/components/ui/button'; import api from "@/api/api.ts"; import {PremiumAlbum} from "@/types/premium-album.ts"; +import {StatusCodes} from "http-status-codes"; +import {useAuth} from "@/context/auth-context.tsx"; // TODO : Prettier pagination const AlbumPage = () => { + const { onLogout } = useAuth(); const [dataAlbums, setDataAlbums] = useState<PremiumAlbum[]>([]); const [loading, setLoading] = useState(true); const [currentPage, setCurrentPage] = useState(1); @@ -16,9 +19,14 @@ const AlbumPage = () => { const fetchData = async (page: number) => { try { + api.post("/verify-token",).then((response) => { + if (response.status !== StatusCodes.OK) { + onLogout(); + } + }); + const response = await api.get( `/premium-album?page=${page}`); - console.log(response); setDataAlbums(() => [...response.data.data]); setTotalPages(response.data.paging.totalPages); @@ -30,7 +38,7 @@ const AlbumPage = () => { }; useEffect(() => { - const interval = setInterval(fetchData, 1000); // 100 milliseconds + const interval = setInterval(function () {fetchData(currentPage)}, 1000); // 100 milliseconds return () => clearInterval(interval); }, [dataAlbums]); diff --git a/src/pages/EditAlbumPage.tsx b/src/pages/EditAlbumPage.tsx index faa1f21341f48272e3986728767bfaef996a6065..b2f2bb4313c4977d51ac4183d697799c40480de9 100644 --- a/src/pages/EditAlbumPage.tsx +++ b/src/pages/EditAlbumPage.tsx @@ -7,9 +7,12 @@ import {useNavigate} from "react-router"; import {Form, FormControl, FormField, FormItem, FormLabel, FormMessage} from "@/components/ui/form.tsx"; import {Input} from "@/components/ui/input.tsx"; import {albumFormSchema} from "@/validations/premium-album-form-validation.ts"; +import {useAuth} from "@/context/auth-context.tsx"; +import {StatusCodes} from "http-status-codes"; export default function EditAlbum() { + const { onLogout } = useAuth(); const navigate = useNavigate(); const { albumId } = useParams(); const [coverFile, setCoverFile] = useState<File | null>(null); @@ -27,6 +30,12 @@ export default function EditAlbum() { useEffect(() => { const fetchAlbumData = async () => { try { + api.post("/verify-token",).then((response) => { + if (response.status !== StatusCodes.OK) { + onLogout(); + } + }); + const response = await api.get( `/premium-album/${albumId}` ); @@ -65,6 +74,12 @@ export default function EditAlbum() { formData.append("artist", data.artist); formData.append("coverFile", coverFile); + api.post("/verify-token",).then((response) => { + if (response.status !== StatusCodes.OK) { + onLogout(); + } + }); + await api.patch( `/premium-album/${albumId}`, formData, diff --git a/src/pages/EditSongPage.tsx b/src/pages/EditSongPage.tsx index 9b7e48d0067c17f4d1e2da78f539a1534a1065de..72ee0cd626a40f4679c3526f2a2d6b7e71e88a2f 100644 --- a/src/pages/EditSongPage.tsx +++ b/src/pages/EditSongPage.tsx @@ -6,8 +6,11 @@ import {useForm} from "react-hook-form"; import {songFormSchema} from "@/validations/premium-song-form-validation.ts"; import {Form, FormControl, FormField, FormItem, FormLabel, FormMessage} from "@/components/ui/form.tsx"; import {Input} from "@/components/ui/input.tsx"; +import {StatusCodes} from "http-status-codes"; +import {useAuth} from "@/context/auth-context.tsx"; const EditSong = () => { + const { onLogout } = useAuth(); const navigate = useNavigate(); const { albumId, songId } = useParams(); const [audioFile, setAudioFile] = useState<File | null>(null); @@ -26,6 +29,12 @@ const EditSong = () => { useEffect(() => { const fetchSongData = async () => { try { + api.post("/verify-token",).then((response) => { + if (response.status !== StatusCodes.OK) { + onLogout(); + } + }); + const response = await api.get( `/premium-album/${albumId}/song/${songId}` ); @@ -60,6 +69,12 @@ const EditSong = () => { formData.append("duration", data.duration); formData.append("audioFile", audioFile); + api.post("/verify-token",).then((response) => { + if (response.status !== StatusCodes.OK) { + onLogout(); + } + }); + await api.patch( `/premium-album/${albumId}/song/${songId}`, formData, diff --git a/src/pages/LoginPage.tsx b/src/pages/LoginPage.tsx index caaf61719da9f97b45da5b06a04dd4b5baff1ddd..f75274c6cc81b623e1468be706a74147cd60fa8e 100644 --- a/src/pages/LoginPage.tsx +++ b/src/pages/LoginPage.tsx @@ -14,12 +14,16 @@ import { Input } from "@/components/ui/input.tsx"; import { Button } from "@/components/ui/button.tsx"; import { Link } from "react-router-dom"; import { StatusCodes } from "http-status-codes"; -import {useAuth} from "@/TonalityApp.tsx"; -import api from "@/api/api.ts"; +import api from "@/api/api.ts"; import {loginFormSchema} from "@/validations/login-validation.ts"; +import {useAuth} from "@/context/auth-context.tsx"; +import { useNavigate } from "react-router-dom"; const LoginPage = () => { + const { token, onLogin, onLogout, } = useAuth(); + const navigate = useNavigate(); + // Define form const loginForm = useForm<z.infer<typeof loginFormSchema>>({ resolver: zodResolver(loginFormSchema), @@ -28,11 +32,20 @@ const LoginPage = () => { password: "", }, }); - const { onLogin } = useAuth(); + + + if (token) { + api.post("/verify-token",).then((response) => { + if (response.status !== StatusCodes.OK) { + onLogout(); + } else { + navigate("/album"); + } + }); + } // Define submit handler async function onSubmit(values: z.infer<typeof loginFormSchema>) { - console.log("values", values); try { const res = await api.post( "login", @@ -48,6 +61,7 @@ const LoginPage = () => { ); onLogin(res.data.accessToken, values.username); + navigate("/album"); } catch (err) { if ( axios.isAxiosError(err) && diff --git a/src/pages/SongsPage.tsx b/src/pages/SongsPage.tsx index b16c2e855449f30ca0db5400aad2c4d7a81c2ca3..81ff3cc028a4b343e8c5b7995481a533d7b50ccf 100644 --- a/src/pages/SongsPage.tsx +++ b/src/pages/SongsPage.tsx @@ -6,8 +6,11 @@ import { useNavigate, useParams } from 'react-router-dom'; import api, {restUrl} from "@/api/api.ts"; import {PremiumAlbum} from "@/types/premium-album.ts"; import {PremiumSong} from "@/types/premium-song.ts"; +import {StatusCodes} from "http-status-codes"; +import {useAuth} from "@/context/auth-context.tsx"; const SongsPage = () => { + const { onLogout } = useAuth(); const { albumId } = useParams<{ albumId: string }>(); const [albumData, setAlbumData] = useState<PremiumAlbum | null>(null); const [songsData, setSongsData] = useState<PremiumSong[]>([]); @@ -15,6 +18,12 @@ const SongsPage = () => { const fetchData = async () => { try { + api.post("/verify-token",).then((response) => { + if (response.status !== StatusCodes.OK) { + onLogout(); + } + }); + const responseAlbum = await api.get( `/premium-album/${albumId}`, ); @@ -27,7 +36,6 @@ const SongsPage = () => { ...responseSongs.data, ])); - console.log("response songs", responseSongs.data.data); setLoading(false); } catch (error) { diff --git a/src/pages/SubscriptionPage.tsx b/src/pages/SubscriptionPage.tsx index a81dc762fe5221aa52f3a123f823e54fcb7ce83d..1ea35a5b8f7c762447439fbc47ae4ce4c27884d5 100644 --- a/src/pages/SubscriptionPage.tsx +++ b/src/pages/SubscriptionPage.tsx @@ -2,8 +2,11 @@ import {useEffect, useState} from 'react'; import {SubscriptionTable} from "@/components/subscription-table.tsx"; import api from "@/api/api.ts"; import {Subscription} from "@/types/subscription.ts"; +import {StatusCodes} from "http-status-codes"; +import {useAuth} from "@/context/auth-context.tsx"; const SubscriptionPage = () => { + const { onLogout } = useAuth(); const [subscriptionData, setSubscriptionData] = useState<Subscription[]>([]); const [loading, setLoading] = useState(true); @@ -11,6 +14,12 @@ const SubscriptionPage = () => { const page = params.get('page') || 1; const update = async () => { try { + api.post("/verify-token",).then((response) => { + if (response.status !== StatusCodes.OK) { + onLogout(); + } + }); + const response = await api.get('subscription' + '?page=' + page,) setLoading(false); setSubscriptionData( diff --git a/src/routes/ProtectedRoute.tsx b/src/routes/ProtectedRoute.tsx index 27f466624a2bd34cfeec10711ee8de3e8dbf3cef..9a83a17efae6d94b49cfa25900a5b64bf2f5e626 100644 --- a/src/routes/ProtectedRoute.tsx +++ b/src/routes/ProtectedRoute.tsx @@ -1,9 +1,19 @@ -import { Navigate, Outlet } from "react-router-dom"; -import { useAuth } from "@/TonalityApp.tsx"; +import {Navigate, Outlet, } from "react-router-dom"; +import { useAuth } from "@/context/auth-context.tsx"; +import {StatusCodes} from "http-status-codes"; +import api from "@/api/api.ts"; const ProtectedRoute = ({ isPublic }: {isPublic: boolean}) => { - const isValidUser: boolean = useAuth().token !== null; + const { token, onLogout } = useAuth(); + + api.post("/verify-token",).then((response) => { + if (response.status !== StatusCodes.OK) { + onLogout(); + } + }); + + const isValidUser: boolean = token !== null; return isValidUser || isPublic ? <Outlet /> : <Navigate to="/login" />; }; diff --git a/src/routes/RenderRoutes.tsx b/src/routes/RenderRoutes.tsx index a4d3d798f9be2c9a4ceca5f3b757bb285730a3d6..d29649d4b9f10d2a3ea9db8ba08140687746fc98 100644 --- a/src/routes/RenderRoutes.tsx +++ b/src/routes/RenderRoutes.tsx @@ -2,7 +2,7 @@ import { Route, Routes } from "react-router-dom"; import ProtectedRoute from "@/routes/ProtectedRoute.tsx"; import { generateFlattenRoutes } from "@/lib/utils.ts"; import {RouteWithLayout} from "@/routes/routes.ts"; -import AuthProvider from "@/context/AuthProvider.tsx"; +import AuthContext from "@/context/auth-context.tsx"; export const RenderRoutes = (mainRoutes: RouteWithLayout[]) => { return () => { @@ -38,7 +38,7 @@ export const RenderRoutes = (mainRoutes: RouteWithLayout[]) => { }); return ( <Routes> - <Route element={<AuthProvider />}> + <Route element={<AuthContext />}> {layouts} </Route> </Routes>