diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 5817264..6f18051 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -6,6 +6,7 @@ import CreateAccount from "./pages/CreateAccountPage";
import ProfilePage from "./pages/ProfilePage";
import ArtworkDetailPage from "./pages/ArtworkDetailPage";
import Footer from "./components/Footer";
+import ProtectedRoute from "./components/ProtectedRoute";
function App() {
// No footers for Login and CreateAccount Pages
@@ -19,7 +20,14 @@ function App() {
} />
} />
} />
- } />
+
+
+
+ }
+ />
} />
diff --git a/frontend/src/components/Hero.tsx b/frontend/src/components/Hero.tsx
index 8eecfb6..7422ed4 100644
--- a/frontend/src/components/Hero.tsx
+++ b/frontend/src/components/Hero.tsx
@@ -4,11 +4,13 @@ import { CircleUserRound } from "lucide-react";
import { Link } from "react-router-dom";
function Hero() {
+ const isLoggedIn = Boolean(sessionStorage.getItem("token"));
+
return (
diff --git a/frontend/src/components/ProtectedRoute.tsx b/frontend/src/components/ProtectedRoute.tsx
new file mode 100644
index 0000000..e01f97e
--- /dev/null
+++ b/frontend/src/components/ProtectedRoute.tsx
@@ -0,0 +1,15 @@
+import { Navigate } from "react-router-dom";
+
+type ProtectedRouteProps = {
+ children: React.ReactNode;
+};
+
+export default function ProtectedRoute({ children }: ProtectedRouteProps) {
+ const token = sessionStorage.getItem("token");
+
+ if (!token) {
+ return ;
+ }
+
+ return children;
+}
\ No newline at end of file
diff --git a/frontend/src/pages/ArtworkDetailPage.tsx b/frontend/src/pages/ArtworkDetailPage.tsx
index 0cad80c..f6ff566 100644
--- a/frontend/src/pages/ArtworkDetailPage.tsx
+++ b/frontend/src/pages/ArtworkDetailPage.tsx
@@ -55,7 +55,7 @@ export default function ArtworkDetailPage() {
type="button"
aria-label="Add artwork to gallery"
onClick={() => setShowGalleryModal(true)}
- className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full border border-gray-300 text-2xl font-light text-gray-500 transition hover:border-gray-500 hover:text-black"
+ className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full border border-gray-300 text-2xl font-light text-gray-500 transition cursor-pointer hover:border-gray-500 hover:text-black"
>
+
diff --git a/frontend/src/pages/CreateAccountPage.tsx b/frontend/src/pages/CreateAccountPage.tsx
index 71069b5..1bc91d7 100644
--- a/frontend/src/pages/CreateAccountPage.tsx
+++ b/frontend/src/pages/CreateAccountPage.tsx
@@ -1,5 +1,5 @@
import { useState, type SubmitEvent as ReactSubmitEvent } from "react";
-import { Link } from "react-router-dom";
+import { Link, useNavigate } from "react-router-dom";
import type { User } from "../types/User";
type RegisterResponse = {
@@ -7,7 +7,19 @@ type RegisterResponse = {
errors?: Array<{ message: string }>;
};
+type LoginResponse = {
+ data?: {
+ login: {
+ user: User;
+ token: string;
+ };
+ };
+ errors?: { message: string }[];
+};
+
export default function CreateAccount() {
+ const navigate = useNavigate();
+
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [message, setMessage] = useState("");
@@ -46,8 +58,47 @@ export default function CreateAccount() {
throw new Error("The server did not return the new account.");
}
- setMessage(`Account created for ${result.data.register.username}.`);
- setPassword("");
+ const loginQuery = `
+ mutation Login($username: String!, $password: String!) {
+ login(username: $username, password: $password) {
+ user {
+ id
+ username
+ }
+ token
+ }
+ }
+ `;
+
+ const loginResponse = await fetch("/graphql", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ query: loginQuery,
+ variables: { username, password },
+ }),
+ });
+
+ if (!loginResponse.ok) {
+ throw new Error(`Server error (${loginResponse.status}).`);
+ }
+
+ const loginResult = (await loginResponse.json()) as LoginResponse;
+
+ if (loginResult.errors?.length) {
+ throw new Error(loginResult.errors[0].message);
+ }
+
+ if (!loginResult.data?.login) {
+ throw new Error("Account was created, but login failed.");
+ }
+
+ const { user, token } = loginResult.data.login;
+
+ sessionStorage.setItem("user", JSON.stringify(user));
+ sessionStorage.setItem("token", token);
+
+ navigate("/profile");
} catch (error) {
setMessage(
error instanceof Error ? error.message : "Something went wrong.",
diff --git a/frontend/src/pages/LoginPage.tsx b/frontend/src/pages/LoginPage.tsx
index 247a4ef..dbd8203 100644
--- a/frontend/src/pages/LoginPage.tsx
+++ b/frontend/src/pages/LoginPage.tsx
@@ -1,4 +1,4 @@
-import { Link } from "react-router-dom";
+import { Link, useNavigate } from "react-router-dom";
import { useState, type SubmitEvent as ReactSubmitEvent } from "react";
import type { User } from "../types/User";
@@ -13,6 +13,8 @@ type LoginResponse = {
};
function Login() {
+ const navigate = useNavigate();
+
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [message, setMessage] = useState("");
@@ -59,8 +61,7 @@ function Login() {
sessionStorage.setItem("user", JSON.stringify(user));
sessionStorage.setItem("token", token);
- setMessage(`Welcome back, ${user.username}.`);
- setPassword("");
+ navigate("/profile");
} catch (error) {
setMessage(
error instanceof Error ? error.message : "Something went wrong.",
diff --git a/frontend/src/pages/ProfilePage.tsx b/frontend/src/pages/ProfilePage.tsx
index cb75cb2..447da68 100644
--- a/frontend/src/pages/ProfilePage.tsx
+++ b/frontend/src/pages/ProfilePage.tsx
@@ -5,6 +5,7 @@ import { useEffect, useState } from "react";
import type { Gallery } from "../types/Gallery";
import CreateGalleryModal from "../components/CreateGalleryModal";
import { useGalleryCoverImages } from "../hooks/useGalleryCoverImages";
+import { Link, useNavigate } from "react-router-dom";
const coverColors = [
"#F3CA34", // Yellow
@@ -16,6 +17,8 @@ const coverColors = [
];
export default function ProfilePage() {
+ const navigate = useNavigate();
+
const [coverColor] = useState(() => {
const randomIndex = Math.floor(Math.random() * coverColors.length);
return coverColors[randomIndex];
@@ -258,8 +261,29 @@ export default function ProfilePage() {
reader.readAsDataURL(file);
}
+ function handleLogout() {
+ sessionStorage.removeItem("user");
+ sessionStorage.removeItem("token");
+ navigate("/");
+ }
+
return (