Skip to content
Merged
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
32 changes: 31 additions & 1 deletion frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
"react-router-dom": "^7.18.4",
"react-select": "^5.10.2",
"rxjs": "^7.8.2",
"tailwindcss": "^4.3.3"
"tailwindcss": "^4.3.3",
"zustand": "^5.0.16"
},
"devDependencies": {
"@babel/core": "^7.29.7",
Expand Down
9 changes: 4 additions & 5 deletions frontend/src/components/AddToGalleryModal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useEffect, useState } from "react";
import type { Gallery } from "../types/Gallery";
import { useAuthStore } from "../store/authStore";

type AddToGalleryModalProps = {
artworkId: number;
Expand Down Expand Up @@ -28,6 +29,8 @@ export default function AddToGalleryModal({
const [loading, setLoading] = useState(true);
const [message, setMessage] = useState("");

const token = useAuthStore((state) => state.token);

useEffect(() => {
async function fetchGalleries() {
const query = `
Expand All @@ -42,8 +45,6 @@ export default function AddToGalleryModal({
`;

try {
const token = sessionStorage.getItem("token");

const response = await fetch("/graphql", {
method: "POST",
headers: {
Expand Down Expand Up @@ -74,7 +75,7 @@ export default function AddToGalleryModal({
}

void fetchGalleries();
}, []);
}, [token]);

async function handleAddToGallery(galleryId: string) {
const mutation = `
Expand All @@ -93,8 +94,6 @@ export default function AddToGalleryModal({
try {
setMessage("");

const token = sessionStorage.getItem("token");

const response = await fetch("/graphql", {
method: "POST",
headers: {
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/components/CreateGalleryModal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { LucideX } from "lucide-react";
import { useState } from "react";
import { useAuthStore } from "../store/authStore";

type CreateGalleryModalProps = {
onClose: () => void;
Expand All @@ -14,6 +15,8 @@ export default function CreateGalleryModal({
const [isCreating, setIsCreating] = useState(false);
const [error, setError] = useState("");

const token = useAuthStore((state) => state.token);

async function handleCreateGallery() {
if (!title.trim() || isCreating) return;

Expand All @@ -32,8 +35,6 @@ export default function CreateGalleryModal({
`;

try {
const token = sessionStorage.getItem("token");

const response = await fetch("/graphql", {
method: "POST",
headers: {
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/components/Hero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import heroImageBack from "../assets/hero2.png";
import heroImageFront from "../assets/hero1.png";
import { CircleUserRound } from "lucide-react";
import { Link } from "react-router-dom";
import { useAuthStore } from "../store/authStore";

function Hero() {
const isLoggedIn = Boolean(sessionStorage.getItem("token"));
const isLoggedIn = useAuthStore((state) => Boolean(state.token));

return (
<section className="relative mx-auto flex min-h-152 max-w-350 items-center justify-between gap-12 px-10 py-16 pt-30">
Expand Down
6 changes: 4 additions & 2 deletions frontend/src/components/ProtectedRoute.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { Navigate } from "react-router-dom";
import type { ReactNode } from "react";
import { useAuthStore } from "../store/authStore";

type ProtectedRouteProps = {
children: React.ReactNode;
children: ReactNode;
};

export default function ProtectedRoute({ children }: ProtectedRouteProps) {
const token = sessionStorage.getItem("token");
const token = useAuthStore((state) => state.token);

if (!token) {
return <Navigate to="/login" replace />;
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/pages/CreateAccountPage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useState, type SubmitEvent as ReactSubmitEvent } from "react";
import { Link, useNavigate } from "react-router-dom";
import type { User } from "../types/User";
import { useAuthStore } from "../store/authStore";

type RegisterResponse = {
data?: { register?: User };
Expand All @@ -19,6 +20,7 @@ type LoginResponse = {

export default function CreateAccount() {
const navigate = useNavigate();
const setAuth = useAuthStore((state) => state.setAuth);

const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
Expand Down Expand Up @@ -95,8 +97,7 @@ export default function CreateAccount() {

const { user, token } = loginResult.data.login;

sessionStorage.setItem("user", JSON.stringify(user));
sessionStorage.setItem("token", token);
setAuth(user, token);

navigate("/profile");
} catch (error) {
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/pages/LoginPage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Link, useNavigate } from "react-router-dom";
import { useState, type SubmitEvent as ReactSubmitEvent } from "react";
import type { User } from "../types/User";
import { useAuthStore } from "../store/authStore";

type LoginResponse = {
data?: {
Expand All @@ -14,6 +15,7 @@ type LoginResponse = {

function Login() {
const navigate = useNavigate();
const setAuth = useAuthStore((state) => state.setAuth);

const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
Expand Down Expand Up @@ -58,8 +60,7 @@ function Login() {

const { user, token } = result.data.login;

sessionStorage.setItem("user", JSON.stringify(user));
sessionStorage.setItem("token", token);
setAuth(user, token);

navigate("/profile");
} catch (error) {
Expand Down
25 changes: 8 additions & 17 deletions frontend/src/pages/ProfilePage.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { User } from "../types/User";
import GalleryCard from "../components/GalleryCard";
import { LucidePlus } from "lucide-react";
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";
import { useAuthStore } from "../store/authStore";

const coverColors = [
"#F3CA34", // Yellow
Expand All @@ -19,6 +19,10 @@ const coverColors = [
export default function ProfilePage() {
const navigate = useNavigate();

const user = useAuthStore((state) => state.user);
const token = useAuthStore((state) => state.token);
const logout = useAuthStore((state) => state.logout);

const [coverColor] = useState(() => {
const randomIndex = Math.floor(Math.random() * coverColors.length);
return coverColors[randomIndex];
Expand All @@ -28,8 +32,6 @@ export default function ProfilePage() {
const [showCreateGallery, setShowCreateGallery] = useState(false);
const [profileImage, setProfileImage] = useState<string | null>(null);

const storedUser = sessionStorage.getItem("user");

const [galleries, setGalleries] = useState<Gallery[]>([]);
const galleryCoverImages = useGalleryCoverImages(galleries);
const [isLoading, setIsLoading] = useState(true); // Galleries
Expand All @@ -48,8 +50,6 @@ export default function ProfilePage() {
`;

try {
const token = sessionStorage.getItem("token");

const response = await fetch("/graphql", {
method: "POST",
headers: {
Expand Down Expand Up @@ -79,7 +79,7 @@ export default function ProfilePage() {
}

void fetchProfileImage();
}, []);
}, [token]);

useEffect(() => {
async function fetchGalleries() {
Expand All @@ -95,8 +95,6 @@ export default function ProfilePage() {
`;

try {
const token = sessionStorage.getItem("token");

const response = await fetch("/graphql", {
method: "POST",
headers: {
Expand Down Expand Up @@ -125,9 +123,7 @@ export default function ProfilePage() {
}

void fetchGalleries();
}, [refreshGalleries]);

const user: User | null = storedUser ? JSON.parse(storedUser) : null;
}, [refreshGalleries, token]);

async function handleCoverImageChange(
event: React.ChangeEvent<HTMLInputElement>,
Expand Down Expand Up @@ -160,8 +156,6 @@ export default function ProfilePage() {
`;

try {
const token = sessionStorage.getItem("token");

const response = await fetch("/graphql", {
method: "POST",
headers: {
Expand Down Expand Up @@ -227,8 +221,6 @@ export default function ProfilePage() {
`;

try {
const token = sessionStorage.getItem("token");

const response = await fetch("/graphql", {
method: "POST",
headers: {
Expand Down Expand Up @@ -262,8 +254,7 @@ export default function ProfilePage() {
}

function handleLogout() {
sessionStorage.removeItem("user");
sessionStorage.removeItem("token");
logout();
navigate("/");
}

Expand Down
31 changes: 31 additions & 0 deletions frontend/src/store/authStore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { create } from "zustand";
import { createJSONStorage, persist } from "zustand/middleware";
import type { User } from "../types/User";

type AuthState = {
user: User | null;
token: string | null;
setAuth: (user: User, token: string) => void;
logout: () => void;
};

export const useAuthStore = create<AuthState>()(
persist(
(set) => ({
user: null,
token: null,

setAuth: (user, token) => {
set({ user, token });
},

logout: () => {
set({ user: null, token: null });
},
}),
{
name: "auth",
storage: createJSONStorage(() => sessionStorage),
},
),
);