diff --git a/README.md b/README.md index 3f4f2cc..0e454cf 100644 --- a/README.md +++ b/README.md @@ -20,103 +20,6 @@ Users can search, filter and sort the artwork catalogue, browse results using pa For the third delivery, the project represents a first complete prototype of the application, with particular focus on catalogue functionality, frontend/backend integration, accessibility and sustainable web development. -## Main functionality - -The application currently supports: - -- Searching artworks by title or artist -- Case-insensitive search -- Search submission through a search button to avoid unnecessary requests -- Artist filtering with debounced artist search -- Sorting by title, artist and year -- Server-side filtering and sorting -- Pagination of artwork results -- Individual artwork detail pages -- Responsive layouts for different screen sizes -- User registration and login -- Protected profile pages for authenticated users -- Creation of personal galleries -- Adding artworks to galleries -- Persistent user-generated gallery data -- Profile and cover image customization -- Loading, empty and error states for relevant functionality - -Sorting, filtering and pagination are performed through the backend rather than only on the data currently loaded in the browser. This allows the solution to scale to larger datasets. - -## Technologies - -### Frontend - -- React -- TypeScript -- Vite -- Tailwind CSS -- React Router -- Zustand -- React Select -- Lucide React - -### Backend - -- Node.js -- TypeScript -- GraphQL -- Apollo Server -- MongoDB - -### Development tools - -- ESLint -- Prettier -- Git -- GitLab - -## Technical choices - -The project is divided into a frontend, backend and database. The frontend is built with React and TypeScript using Vite, while the backend is built with Node.js, TypeScript, GraphQL and Apollo Server. MongoDB is used to store artwork data, users and galleries. - -The frontend communicates with the backend through GraphQL. This allows the client to request only the fields it needs and makes it possible to perform pagination, search, filtering and sorting on the backend instead of loading the complete dataset into the browser. - -Artwork results are loaded using pagination. The frontend sends a `limit` and `offset` to the GraphQL backend, and the backend retrieves only the requested portion of the dataset from MongoDB. The catalogue currently displays 12 artworks per page. This approach was chosen with scalability in mind, since the application should be able to handle a significantly larger dataset without transferring all artwork data to the client at once. - -Filtering and sorting are also performed by the backend so that they apply to the complete result set rather than only the artworks currently displayed in the browser. The application supports sorting by title, artist and year in ascending or descending order. - -The main artwork search is submitted explicitly instead of sending a request for every character typed into the search field. Artist search uses a debounce before requesting matching artists from the backend. These choices reduce unnecessary network requests while still providing a responsive user experience. - -Zustand is used for shared frontend state. Authentication state, including the current user and authentication token, is stored in a central Zustand store and shared across login, registration, protected routes, profile navigation and authenticated gallery functionality. The store is persisted using `sessionStorage`, allowing the user to remain logged in when refreshing the page during the same browser session. - -State that only belongs to a single component remains managed using React state. Form input, modal visibility, loading states and locally fetched gallery data are therefore kept local instead of being moved into global state unnecessarily. - -Artex also includes persistent user-generated data through personal galleries. Authenticated users can create galleries and add artworks to them. Gallery information and artwork IDs are stored in MongoDB, so the data remains available after refreshing or logging in again. When an artwork is added to a gallery, set-based database updates are used to avoid storing the same artwork multiple times in the same gallery. - -The design is based on our Figma prototype and is implemented responsively so that the layout adapts to different screen sizes. The frontend is divided into reusable components and hooks to separate responsibilities and make the code easier to maintain and test. - -## Accessibility - -Accessibility has been considered throughout the frontend implementation. - -Examples of accessibility choices include semantic HTML elements such as `main`, `section`, `form`, `button` and links, labels connected to form inputs, `aria-label` for icon-only controls and accessible attributes for expandable filters and dialogs. - -The catalogue search uses `role="search"`, while relevant feedback is presented using `role="alert"` and `role="status"`. Meaningful images use alternative text, while decorative images use empty alternative text where appropriate. Interactive elements use keyboard-accessible buttons and links, and visible focus styles are included for relevant controls. - -The application also provides loading states, empty-result messages and error feedback so that users receive clear information about what is happening. - -Accessibility will continue to be reviewed throughout the remaining development process, particularly keyboard navigation, focus handling in dialogs and general usability. - -## Sustainable web development - -Sustainable web development has influenced several technical and design choices in Artex. - -Pagination ensures that only a limited number of artworks are retrieved at a time instead of transferring the complete database to the browser. Server-side filtering and sorting further reduce unnecessary client-side processing and data transfer. - -The main artwork search is triggered by form submission rather than every keyboard input, while artist search uses debouncing. This reduces unnecessary network requests. - -GraphQL allows each view to request only the fields it needs. The catalogue view therefore retrieves only the information required for artwork cards, while additional artwork information is fetched when the user opens an individual detail page. - -Artwork listings use smaller artwork image variants where available rather than unnecessarily requesting higher-resolution images. Missing images are handled using placeholders. - -Together, these choices reduce network traffic, browser memory usage and unnecessary client-side computation while also improving the scalability of the application. - ## Getting started Before running the project, make sure Node.js and npm are installed. @@ -225,6 +128,103 @@ Testing will be expanded further towards the final delivery. The final project w For the third delivery, the primary focus has been completing and validating the functional prototype, including search, pagination, filtering, sorting, authentication, galleries, accessibility and sustainable development choices. +## Main functionality + +The application currently supports: + +- Searching artworks by title or artist +- Case-insensitive search +- Search submission through a search button to avoid unnecessary requests +- Artist filtering with debounced artist search +- Sorting by title, artist and year +- Server-side filtering and sorting +- Pagination of artwork results +- Individual artwork detail pages +- Responsive layouts for different screen sizes +- User registration and login +- Protected profile pages for authenticated users +- Creation of personal galleries +- Adding artworks to galleries +- Persistent user-generated gallery data +- Profile and cover image customization +- Loading, empty and error states for relevant functionality + +Sorting, filtering and pagination are performed through the backend rather than only on the data currently loaded in the browser. This allows the solution to scale to larger datasets. + +## Technologies + +### Frontend + +- React +- TypeScript +- Vite +- Tailwind CSS +- React Router +- Zustand +- React Select +- Lucide React + +### Backend + +- Node.js +- TypeScript +- GraphQL +- Apollo Server +- MongoDB + +### Development tools + +- ESLint +- Prettier +- Git +- GitLab + +## Technical choices + +The project is divided into a frontend, backend and database. The frontend is built with React and TypeScript using Vite, while the backend is built with Node.js, TypeScript, GraphQL and Apollo Server. MongoDB is used to store artwork data, users and galleries. + +The frontend communicates with the backend through GraphQL. This allows the client to request only the fields it needs and makes it possible to perform pagination, search, filtering and sorting on the backend instead of loading the complete dataset into the browser. + +Artwork results are loaded using pagination. The frontend sends a `limit` and `offset` to the GraphQL backend, and the backend retrieves only the requested portion of the dataset from MongoDB. The catalogue currently displays 12 artworks per page. This approach was chosen with scalability in mind, since the application should be able to handle a significantly larger dataset without transferring all artwork data to the client at once. + +Filtering and sorting are also performed by the backend so that they apply to the complete result set rather than only the artworks currently displayed in the browser. The application supports sorting by title, artist and year in ascending or descending order. + +The main artwork search is submitted explicitly instead of sending a request for every character typed into the search field. Artist search uses a debounce before requesting matching artists from the backend. These choices reduce unnecessary network requests while still providing a responsive user experience. + +Zustand is used for shared frontend state. Authentication state, including the current user and authentication token, is stored in a central Zustand store and shared across login, registration, protected routes, profile navigation and authenticated gallery functionality. The store is persisted using `sessionStorage`, allowing the user to remain logged in when refreshing the page during the same browser session. + +State that only belongs to a single component remains managed using React state. Form input, modal visibility, loading states and locally fetched gallery data are therefore kept local instead of being moved into global state unnecessarily. + +Artex also includes persistent user-generated data through personal galleries. Authenticated users can create galleries and add artworks to them. Gallery information and artwork IDs are stored in MongoDB, so the data remains available after refreshing or logging in again. When an artwork is added to a gallery, set-based database updates are used to avoid storing the same artwork multiple times in the same gallery. + +The design is based on our Figma prototype and is implemented responsively so that the layout adapts to different screen sizes. The frontend is divided into reusable components and hooks to separate responsibilities and make the code easier to maintain and test. + +## Accessibility + +Accessibility has been considered throughout the frontend implementation. + +Examples of accessibility choices include semantic HTML elements such as `main`, `section`, `form`, `button` and links, labels connected to form inputs, `aria-label` for icon-only controls and accessible attributes for expandable filters and dialogs. + +The catalogue search uses `role="search"`, while relevant feedback is presented using `role="alert"` and `role="status"`. Meaningful images use alternative text, while decorative images use empty alternative text where appropriate. Interactive elements use keyboard-accessible buttons and links, and visible focus styles are included for relevant controls. + +The application also provides loading states, empty-result messages and error feedback so that users receive clear information about what is happening. + +Accessibility will continue to be reviewed throughout the remaining development process, particularly keyboard navigation, focus handling in dialogs and general usability. + +## Sustainable web development + +Sustainable web development has influenced several technical and design choices in Artex. + +Pagination ensures that only a limited number of artworks are retrieved at a time instead of transferring the complete database to the browser. Server-side filtering and sorting further reduce unnecessary client-side processing and data transfer. + +The main artwork search is triggered by form submission rather than every keyboard input, while artist search uses debouncing. This reduces unnecessary network requests. + +GraphQL allows each view to request only the fields it needs. The catalogue view therefore retrieves only the information required for artwork cards, while additional artwork information is fetched when the user opens an individual detail page. + +Artwork listings use smaller artwork image variants where available rather than unnecessarily requesting higher-resolution images. Missing images are handled using placeholders. + +Together, these choices reduce network traffic, browser memory usage and unnecessary client-side computation while also improving the scalability of the application. + ## Use of AI AI has been used as a supporting tool during the development of Artex. @@ -242,4 +242,4 @@ AI is therefore used primarily as a development and learning aid rather than as - Emma Fredriksen - Mina Ellingsen - Karen Skauge -- Gurveer Singh \ No newline at end of file +- Gurveer Singh diff --git a/frontend/src/components/AddToGalleryModal.tsx b/frontend/src/components/AddToGalleryModal.tsx index de1cccf..ca0f28f 100644 --- a/frontend/src/components/AddToGalleryModal.tsx +++ b/frontend/src/components/AddToGalleryModal.tsx @@ -45,7 +45,7 @@ export default function AddToGalleryModal({ `; try { - const response = await fetch("/graphql", { + const response = await fetch(`${import.meta.env.BASE_URL}graphql`, { method: "POST", headers: { "Content-Type": "application/json", diff --git a/frontend/src/components/CreateGalleryModal.tsx b/frontend/src/components/CreateGalleryModal.tsx index 48c22fa..1c2e6c3 100644 --- a/frontend/src/components/CreateGalleryModal.tsx +++ b/frontend/src/components/CreateGalleryModal.tsx @@ -35,7 +35,7 @@ export default function CreateGalleryModal({ `; try { - const response = await fetch("/graphql", { + const response = await fetch(`${import.meta.env.BASE_URL}graphql`, { method: "POST", headers: { "Content-Type": "application/json", diff --git a/frontend/src/hooks/useGalleryCoverImages.ts b/frontend/src/hooks/useGalleryCoverImages.ts index 321d892..960e9b7 100644 --- a/frontend/src/hooks/useGalleryCoverImages.ts +++ b/frontend/src/hooks/useGalleryCoverImages.ts @@ -36,16 +36,19 @@ export function useGalleryCoverImages(galleries: Gallery[]) { `; try { - const response = await fetch("/graphql", { - method: "POST", - headers: { - "Content-Type": "application/json", + const response = await fetch( + `${import.meta.env.BASE_URL}graphql`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + query, + variables: { id: artworkId }, + }), }, - body: JSON.stringify({ - query, - variables: { id: artworkId }, - }), - }); + ); if (!response.ok) { throw new Error("Could not fetch gallery cover image"); diff --git a/frontend/src/pages/CreateAccountPage.tsx b/frontend/src/pages/CreateAccountPage.tsx index f4387cb..ba67638 100644 --- a/frontend/src/pages/CreateAccountPage.tsx +++ b/frontend/src/pages/CreateAccountPage.tsx @@ -42,7 +42,7 @@ export default function CreateAccount() { `; try { - const response = await fetch("/graphql", { + const response = await fetch(`${import.meta.env.BASE_URL}graphql`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query, variables: { username, password } }), @@ -72,7 +72,7 @@ export default function CreateAccount() { } `; - const loginResponse = await fetch("/graphql", { + const loginResponse = await fetch(`${import.meta.env.BASE_URL}graphql`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ diff --git a/frontend/src/pages/LoginPage.tsx b/frontend/src/pages/LoginPage.tsx index e33bb1f..4c1c1b8 100644 --- a/frontend/src/pages/LoginPage.tsx +++ b/frontend/src/pages/LoginPage.tsx @@ -40,7 +40,7 @@ function Login() { `; try { - const response = await fetch("/graphql", { + const response = await fetch(`${import.meta.env.BASE_URL}graphql`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ query, variables: { username, password } }), diff --git a/frontend/src/pages/ProfilePage.tsx b/frontend/src/pages/ProfilePage.tsx index a140cc3..9c14832 100644 --- a/frontend/src/pages/ProfilePage.tsx +++ b/frontend/src/pages/ProfilePage.tsx @@ -50,7 +50,7 @@ export default function ProfilePage() { `; try { - const response = await fetch("/graphql", { + const response = await fetch(`${import.meta.env.BASE_URL}graphql`, { method: "POST", headers: { "Content-Type": "application/json",