diff --git a/src/components/Header/tests/AccountBar.test.tsx b/src/components/Header/tests/AccountBar.test.tsx
new file mode 100644
index 0000000..b592cec
--- /dev/null
+++ b/src/components/Header/tests/AccountBar.test.tsx
@@ -0,0 +1,50 @@
+import { render, screen } from "@testing-library/react";
+import "@testing-library/jest-dom";
+import { describe, it, expect } from "vitest";
+import { BrowserRouter } from "react-router-dom";
+import { AuthContext } from "../../../context/AuthContext";
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import AccountBar from "../AccountBar.tsx";
+
+describe("Accountbar Component", () => {
+ const mockAuthContext = {
+ accessToken: "mockAccessToken",
+ login: async () => {},
+ logout: () => {},
+ refreshToken: "mockRefresh",
+ };
+ const dashboardComponent = () => {
+ const queryClient = new QueryClient();
+
+ render(
+
+
+
+
+
+
+ ,
+ );
+ };
+ it("renders without crashing", () => {
+ dashboardComponent();
+ expect(screen.getByTestId("accountbar-element")).toBeInTheDocument();
+ });
+
+ it("contains avatar", () => {
+ dashboardComponent();
+ expect(screen.getByTestId("avatar-element")).toBeInTheDocument();
+ });
+
+ it("contains dropdown", () => {
+ dashboardComponent();
+ expect(screen.getByTestId("dropdown-element")).toBeInTheDocument();
+ });
+});
diff --git a/src/components/Header/tests/Header.test.tsx b/src/components/Header/tests/Header.test.tsx
new file mode 100644
index 0000000..06376c2
--- /dev/null
+++ b/src/components/Header/tests/Header.test.tsx
@@ -0,0 +1,51 @@
+import { render, screen } from "@testing-library/react";
+import "@testing-library/jest-dom";
+import { describe, it, expect } from "vitest";
+import { BrowserRouter } from "react-router-dom";
+import { AuthContext } from "../../../context/AuthContext";
+import Header from "../Header.tsx";
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+
+describe("Header Component", () => {
+ const mockAuthContext = {
+ accessToken: "mockAccessToken",
+ login: async () => {},
+ logout: () => {},
+ refreshToken: "mockRefresh",
+ getRefreshToken: async () => {},
+ };
+ const dashboardComponent = () => {
+ const queryClient = new QueryClient();
+
+ render(
+
+
+
+
+
+
+ ,
+ );
+ };
+ it("renders without crashing", () => {
+ dashboardComponent();
+ expect(screen.getByTestId("header-element")).toBeInTheDocument();
+ });
+
+ it("contains searchbar", () => {
+ dashboardComponent();
+ expect(screen.getByTestId("searchbar-element")).toBeInTheDocument();
+ });
+
+ it("contains accountbar", () => {
+ dashboardComponent();
+ expect(screen.getByTestId("accountbar-element")).toBeInTheDocument();
+ });
+});
diff --git a/src/components/MainContent.tsx b/src/components/MainContent.tsx
index bb1513f..221ab3f 100644
--- a/src/components/MainContent.tsx
+++ b/src/components/MainContent.tsx
@@ -1,10 +1,42 @@
+import { useContentStore } from "../stores/useContentStore.ts";
+import { useProfileQuery } from "../api/user";
+import { useAuth } from "../context/AuthContext.tsx";
+import { MainContent as MC } from "../data-objects/enum";
+import ReactCountryFlag from "react-country-flag";
+
const MainContent = () => {
+ const { currentContent } = useContentStore();
+ const { accessToken } = useAuth();
+ const { data, isPending } = useProfileQuery(accessToken ?? "");
return (
-
-
-
Card title!
-
If a dog chews shoes whose shoes does he choose?
-
+
+ {currentContent === MC.PROFILE && (
+
+ {!isPending && data && (
+
+
+
+
Profile
+
{data.display_name}
+
{data.followers.total} Followers
+
+
+
+ )}
+
+
+ )}
);
};
diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx
index 1823826..27b7169 100644
--- a/src/components/Sidebar.tsx
+++ b/src/components/Sidebar.tsx
@@ -1,6 +1,9 @@
const Sidebar = () => {
return (
-
+
diff --git a/src/context/AuthProvider.tsx b/src/context/AuthProvider.tsx
index 4e24c9c..5bae0ac 100644
--- a/src/context/AuthProvider.tsx
+++ b/src/context/AuthProvider.tsx
@@ -1,6 +1,5 @@
-import { useEffect, useState } from "react";
+import { useEffect } from "react";
import {
- getRefreshToken,
getToken,
redirectToSpotifyAuthorize,
} from "../api/auth/service/auth.service.ts";
@@ -9,8 +8,8 @@ import { AuthContext } from "./AuthContext";
export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({
children,
}) => {
- const [accessToken, setAccessToken] = useState
(null);
- const [refreshToken, setRefreshToken] = useState(null);
+ const accessToken = localStorage.getItem("access_token");
+ const refreshToken = localStorage.getItem("refresh_token");
const args = new URLSearchParams(window.location.search);
const code = args.get("code");
@@ -19,10 +18,6 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({
if (code) {
const token = await getToken(code);
- console.log("token", token);
-
- setAccessToken(token.access_token);
- setRefreshToken(token.refresh_token);
localStorage.setItem("access_token", token.access_token);
localStorage.setItem("refresh_token", token.refresh_token);
localStorage.setItem("expires_in", token.expires_in.toString());
@@ -34,7 +29,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({
const url = new URL(window.location.href);
url.searchParams.delete("code");
- const updatedUrl = url.search ? url.href : url.href.replace("?", "");
+ const updatedUrl = url.search ? url.href : url.href.replace("?", "/");
window.history.replaceState({}, document.title, updatedUrl);
}
};
@@ -42,13 +37,13 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({
fetchToken();
}, [code]);
+ // TODO: Implement refresh token logic, automatically refresh the token when it expires
+
const login = async () => {
await redirectToSpotifyAuthorize();
};
const logout = () => {
- setAccessToken(null);
- setRefreshToken(null);
localStorage.removeItem("access_token");
localStorage.removeItem("refresh_token");
localStorage.removeItem("expires_in");
@@ -57,9 +52,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({
};
return (
-
+
{children}
);
diff --git a/src/data-objects/enum/index.ts b/src/data-objects/enum/index.ts
new file mode 100644
index 0000000..4e8958d
--- /dev/null
+++ b/src/data-objects/enum/index.ts
@@ -0,0 +1,8 @@
+export const enum MainContent {
+ PROFILE = "Profile",
+ SETTINGS = "Settings",
+ ALBUMS = "Albums",
+ PLAYLISTS = "Playlists",
+ TRACKS = "Tracks",
+ BROWSE = "Browse",
+}
diff --git a/src/data-objects/interface/auth-interface.ts b/src/data-objects/interface/auth-interface.ts
new file mode 100644
index 0000000..24f7005
--- /dev/null
+++ b/src/data-objects/interface/auth-interface.ts
@@ -0,0 +1,20 @@
+export interface TokenResponse {
+ access_token: string;
+ refresh_token: string;
+ expires_in: number;
+}
+
+export interface TokenScopeResponse {
+ access_token: string;
+ token_type: string;
+ expires_in: number;
+ refresh_token: string;
+ scope: string;
+}
+
+export interface AuthContextType {
+ accessToken: string | null;
+ login: () => Promise;
+ logout: () => void;
+ refreshToken: string | null;
+}
diff --git a/src/data-objects/interface/index.ts b/src/data-objects/interface/index.ts
index 0541642..7c6567b 100644
--- a/src/data-objects/interface/index.ts
+++ b/src/data-objects/interface/index.ts
@@ -1,21 +1,2 @@
-export interface TokenResponse {
- access_token: string;
- refresh_token: string;
- expires_in: number;
-}
-
-export interface TokenScopeResponse {
- access_token: string;
- token_type: string;
- expires_in: number;
- refresh_token: string;
- scope: string;
-}
-
-export interface AuthContextType {
- accessToken: string | null;
- login: () => Promise;
- logout: () => void;
- refreshToken: string | null;
- getRefreshToken: () => Promise;
-}
+export * from "./auth-interface.ts";
+export * from "./profile-interface.ts";
diff --git a/src/data-objects/interface/profile-interface.ts b/src/data-objects/interface/profile-interface.ts
new file mode 100644
index 0000000..62e2ef3
--- /dev/null
+++ b/src/data-objects/interface/profile-interface.ts
@@ -0,0 +1,34 @@
+export interface ProfileInterface {
+ display_name: string;
+ external_urls: ExternalUrls;
+ href: string;
+ id: string;
+ images: Image[];
+ type: string;
+ uri: string;
+ followers: Followers;
+ country: string;
+ product: string;
+ explicit_content: ExplicitContent;
+ email: string;
+}
+
+export interface ExternalUrls {
+ spotify: string;
+}
+
+export interface Image {
+ url: string;
+ height: number;
+ width: number;
+}
+
+export interface Followers {
+ href: string;
+ total: number;
+}
+
+export interface ExplicitContent {
+ filter_enabled: boolean;
+ filter_locked: boolean;
+}
diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx
index 8b6c076..23203fa 100644
--- a/src/pages/Dashboard.tsx
+++ b/src/pages/Dashboard.tsx
@@ -3,14 +3,8 @@ import Footer from "../components/Footer/Footer.tsx";
import Sidebar from "../components/Sidebar.tsx";
import MainContent from "../components/MainContent.tsx";
import ExpandContent from "../components/ExpandContent.tsx";
-import { Navigate } from "react-router-dom";
-import { useAuth } from "../context/AuthContext.tsx";
const Dashboard = () => {
- const { accessToken } = useAuth();
- if (!accessToken) {
- return ;
- }
return (
diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx
index 6e27d40..a0bfa33 100644
--- a/src/pages/Login.tsx
+++ b/src/pages/Login.tsx
@@ -1,11 +1,7 @@
import { useAuth } from "../context/AuthContext.tsx";
-import { Navigate } from "react-router-dom";
const Login = () => {
- const { accessToken, login } = useAuth();
- if (accessToken) {
- return
;
- }
+ const { login } = useAuth();
return (
{
const mockAuthContext = {
@@ -11,27 +12,54 @@ describe("Dashboard Component", () => {
login: async () => {},
logout: () => {},
refreshToken: "mockRefresh",
- getRefreshToken: async () => {},
};
const dashboardComponent = () => {
+ const queryClient = new QueryClient();
render(
-
-
-
-
- ,
+
+
+
+
+
+
+ ,
+ ,
);
};
it("renders without crashing", () => {
dashboardComponent();
expect(screen.getByTestId("dashboard-element")).toBeInTheDocument();
});
+
+ it("contains a header", () => {
+ dashboardComponent();
+ expect(screen.getByTestId("header-element")).toBeInTheDocument();
+ });
+
+ it("contains a footer", () => {
+ dashboardComponent();
+ expect(screen.getByTestId("footer-element")).toBeInTheDocument();
+ });
+
+ it("contains a sidebar", () => {
+ dashboardComponent();
+ expect(screen.getByTestId("sidebar-element")).toBeInTheDocument();
+ });
+
+ it("contains a main content", () => {
+ dashboardComponent();
+ expect(screen.getByTestId("main-content-element")).toBeInTheDocument();
+ });
+
+ it("contains a expand content", () => {
+ dashboardComponent();
+ expect(screen.getByTestId("expand-content-element")).toBeInTheDocument();
+ });
});
diff --git a/src/pages/tests/Login.test.tsx b/src/pages/tests/Login.test.tsx
index 30637ed..61aaa49 100644
--- a/src/pages/tests/Login.test.tsx
+++ b/src/pages/tests/Login.test.tsx
@@ -21,7 +21,6 @@ describe("Login Component", () => {
login: mockAuthContext.login,
logout: mockAuthContext.logout,
refreshToken: mockAuthContext.refreshToken,
- getRefreshToken: mockAuthContext.getRefreshToken,
}}
>
diff --git a/src/stores/useContentStore.ts b/src/stores/useContentStore.ts
new file mode 100644
index 0000000..82c8294
--- /dev/null
+++ b/src/stores/useContentStore.ts
@@ -0,0 +1,10 @@
+import { create } from "zustand";
+import { MainContent } from "../data-objects/enum";
+
+export const useContentStore = create<{
+ currentContent: MainContent;
+ setCurrentContent: (content: MainContent) => void;
+}>((set) => ({
+ currentContent: MainContent.PROFILE,
+ setCurrentContent: (content: MainContent) => set({ currentContent: content }),
+}));
diff --git a/vite.config.ts b/vite.config.ts
index b4cfcf3..474641a 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,14 +1,13 @@
-import { defineConfig } from 'vite'
-import react from '@vitejs/plugin-react'
-import tailwindcss from 'tailwindcss'
+import { defineConfig } from "vite";
+import react from "@vitejs/plugin-react";
+import tailwindcss from "tailwindcss";
// https://vitejs.dev/config/
export default defineConfig({
server: {
- host: 'localhost',
- fs: {
- allow: [".."]
- }
+ cors: {
+ origin: false,
+ },
},
plugins: [react()],
css: {
@@ -16,4 +15,4 @@ export default defineConfig({
plugins: [tailwindcss()],
},
},
-})
+});