From c6df7f757177247d1ebce8601d2091177f513fd0 Mon Sep 17 00:00:00 2001 From: Jackson Williams Date: Sat, 13 Apr 2024 15:15:05 -0400 Subject: [PATCH 01/25] started adding data for testing --- FU.SPA/tests/setup-tests.js | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/FU.SPA/tests/setup-tests.js b/FU.SPA/tests/setup-tests.js index 04848af3..ea57f46f 100644 --- a/FU.SPA/tests/setup-tests.js +++ b/FU.SPA/tests/setup-tests.js @@ -6,9 +6,23 @@ const API_BASE_URL = process.env.API_BASE_URL; const setup = async () => { console.log('Setting up'); - - const response = await fetch(`${API_BASE_URL}/Games`); - console.log(await response.json()); + const gameData = { + name: "Insurgency", + id: "1" + }; + try { + const response = await fetch(`${API_BASE_URL}/Games`, { + method: 'POST', + headers: { + 'content-type': 'application/json', + }, + body: JSON.stringify(gameData), + }); + const result = await response.json(); + console.log('Data inserted:', result); + } catch (error) { + console.error('Failed to insert data:', error); + } console.log('Setup done'); }; From be3a609179b0c792b82f9aa836cefb5fb0476fb3 Mon Sep 17 00:00:00 2001 From: Jackson Williams Date: Wed, 17 Apr 2024 22:19:39 -0400 Subject: [PATCH 02/25] stores auth token to post game data --- FU.SPA/tests/setup-tests.js | 92 ++++++++++++++++++++++++++++++------- 1 file changed, 75 insertions(+), 17 deletions(-) diff --git a/FU.SPA/tests/setup-tests.js b/FU.SPA/tests/setup-tests.js index ea57f46f..16d6ba32 100644 --- a/FU.SPA/tests/setup-tests.js +++ b/FU.SPA/tests/setup-tests.js @@ -4,28 +4,86 @@ console.log('Entering setup script'); const API_BASE_URL = process.env.API_BASE_URL; +let tokenStorage = {}; + +const signUp = async (credentials) => { + const response = await fetch(`${API_BASE_URL}/Accounts`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(credentials) + }); + + if (!response.ok) { + console.log('Response Status:', response.status); + const errorText = await response.text(); + console.error('Sign-up Error Response:', errorText); + throw new Error(`Failed to sign up: ${errorText}`); + } + + console.log('Sign-up successful.'); +}; + +// sign in and retrieve a token +const signIn = async (credentials) => { + const response = await fetch(`${API_BASE_URL}/Accounts/auth`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(credentials) + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Authentication failed: ${errorText}`); + } + + const data = await response.json(); + tokenStorage.token = data.token; + console.log('Authentication successful, token obtained.'); + return data.token; +}; + +const postGameData = async (gameData) => { + const token = tokenStorage.token; + const response = await fetch(`${API_BASE_URL}/Games`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + }, + body: JSON.stringify(gameData) + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Failed to post game data: ${errorText}`); + } + + const result = await response.json(); + console.log('Game data inserted:', result); +}; + const setup = async () => { - console.log('Setting up'); - const gameData = { - name: "Insurgency", - id: "1" - }; + console.log('setting up'); try { - const response = await fetch(`${API_BASE_URL}/Games`, { - method: 'POST', - headers: { - 'content-type': 'application/json', - }, - body: JSON.stringify(gameData), - }); - const result = await response.json(); - console.log('Data inserted:', result); + const credentials = { username: 'user', password: 'pass', email: 'user@example.com' }; + + await signUp(credentials); + await signIn(credentials); + const gameData = [ + { name: "Insurgency", id: "1" }, + { name: "RainBow Six Siege", id: "2" }, + { name: "Rocket League", id: "3" } + ]; + + for (const game of gameData) { + await postGameData(game); + } } catch (error) { - console.error('Failed to insert data:', error); + console.error('Setup failed:', error); } - console.log('Setup done'); + console.log('Setup complete'); }; -// Run setup after 3 second delay to give api time to startup +// Run the setup after a delay to give the API time to start up setTimeout(setup, 3000); From 2cc88e8ede41133db1065d0183f2666c7cbe135e Mon Sep 17 00:00:00 2001 From: Jackson Williams Date: Thu, 18 Apr 2024 01:29:29 -0400 Subject: [PATCH 03/25] added tags, more games, and create post data/API call --- FU.SPA/tests/setup-tests.js | 85 ++++++++++++++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 2 deletions(-) diff --git a/FU.SPA/tests/setup-tests.js b/FU.SPA/tests/setup-tests.js index 16d6ba32..a9fa93d4 100644 --- a/FU.SPA/tests/setup-tests.js +++ b/FU.SPA/tests/setup-tests.js @@ -48,7 +48,7 @@ const postGameData = async (gameData) => { method: 'POST', headers: { 'Content-Type': 'application/json', - 'Authorization': `Bearer ${token}` + 'Authorization': `Bearer ${token}`, }, body: JSON.stringify(gameData) }); @@ -62,6 +62,57 @@ const postGameData = async (gameData) => { console.log('Game data inserted:', result); }; +const postTagData = async (tagData) => { + const token = tokenStorage.token; + const response = await fetch(`${API_BASE_URL}/Tags`, { + method: 'POST', + headers: { + 'content-type': 'application/json', + 'Authorization': `Bearer ${token}`, + }, + body: JSON.stringify(tagData), + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Failed to post tag data: ${errorText}`); + } + + const result = await response.json(); + console.log('Tag data inserted:', result); +}; + + +const createPost = async (postData) => { + const token = tokenStorage.token; + + if (postData.StartTime && postData.EndTime) { + postData.StartTime = new Date(postData.StartTime).toISOString(); + postData.EndTime = new Date(postData.EndTime).toISOString(); + } else { + console.error("Invalid or missing date fields"); + throw new Error("Invalid or missing date fields"); + } + + const response = await fetch(`${API_BASE_URL}/Posts`, { + method: 'POST', + headers: { + 'content-type': 'application/json', + 'Authorization': `Bearer ${token}`, + }, + body: JSON.stringify(postData), + }); + + if (!response.ok) { + const errorText = await response.text(); + console.error(`Failed to create post data: ${errorText}`); + throw new Error(`Failed to create post data: ${errorText}`); + } + + const result = await response.json(); + console.log('post data inserted:', result); +}; + const setup = async () => { console.log('setting up'); try { @@ -69,15 +120,44 @@ const setup = async () => { await signUp(credentials); await signIn(credentials); + const gameData = [ { name: "Insurgency", id: "1" }, { name: "RainBow Six Siege", id: "2" }, - { name: "Rocket League", id: "3" } + { name: "Rocket League", id: "3" }, + { name: "Call of Duty", id: "4" }, + { name: "Counter Strike 2", id: "5" } + ]; + + const tagData = [ + { name: "mic", id: "1" }, + { name: "fun", id: "2"}, + { name: "casual", id: "3"}, + { name: "east", id: "4"}, + { name: "open", id: "5"} ]; + const postData = { + Title: "Exciting Game Night", + Description: "Join us for an exciting night of gaming!", + GameId: 1, + StartTime: "2024-07-20T18:00:00", + EndTime: "2024-07-20T21:00:00", + MaxPlayers: 10, + TagIds: [1, 3, 5] + }; + + for (const game of gameData) { await postGameData(game); } + + for (const tag of tagData) { + await postTagData(tag); + } + + await createPost(postData); + } catch (error) { console.error('Setup failed:', error); } @@ -85,5 +165,6 @@ const setup = async () => { console.log('Setup complete'); }; + // Run the setup after a delay to give the API time to start up setTimeout(setup, 3000); From e7471ad6c27b9f1ef89ea2e9750e00da08c3fcc6 Mon Sep 17 00:00:00 2001 From: Jackson Williams Date: Thu, 18 Apr 2024 23:32:48 -0400 Subject: [PATCH 04/25] add more users --- FU.SPA/tests/setup-tests.js | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/FU.SPA/tests/setup-tests.js b/FU.SPA/tests/setup-tests.js index a9fa93d4..d7ef4781 100644 --- a/FU.SPA/tests/setup-tests.js +++ b/FU.SPA/tests/setup-tests.js @@ -116,11 +116,21 @@ const createPost = async (postData) => { const setup = async () => { console.log('setting up'); try { - const credentials = { username: 'user', password: 'pass', email: 'user@example.com' }; - - await signUp(credentials); - await signIn(credentials); + + const numberOfUsers = 25; +const credentials = Array.from({ length: numberOfUsers }, (v, i) => ({ + username: `user${i + 1}`, + password: `password${i + 1}`, + email: `user${i + 1}@example.com` +})); + + for(const user of credentials) { + await signUp(user); + } + for(const user of credentials) { + await signIn(user); + } const gameData = [ { name: "Insurgency", id: "1" }, { name: "RainBow Six Siege", id: "2" }, From 62febfa3d699b76233157863c0fe3af1b1b6e5cc Mon Sep 17 00:00:00 2001 From: Jackson Williams Date: Fri, 19 Apr 2024 00:04:04 -0400 Subject: [PATCH 05/25] generates 48 test post templates --- FU.SPA/tests/setup-tests.js | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/FU.SPA/tests/setup-tests.js b/FU.SPA/tests/setup-tests.js index d7ef4781..3b8835e1 100644 --- a/FU.SPA/tests/setup-tests.js +++ b/FU.SPA/tests/setup-tests.js @@ -147,15 +147,17 @@ const credentials = Array.from({ length: numberOfUsers }, (v, i) => ({ { name: "open", id: "5"} ]; - const postData = { - Title: "Exciting Game Night", - Description: "Join us for an exciting night of gaming!", - GameId: 1, - StartTime: "2024-07-20T18:00:00", - EndTime: "2024-07-20T21:00:00", - MaxPlayers: 10, - TagIds: [1, 3, 5] - }; + const NewPostData = (index) => { + return { + Title: `Exciting Game Night ${index}`, + Description: `Join us for an exciting night of gaming at event ${index}!`, + GameId: (index % 5) + 1, + StartTime: `2024-07-${20 + (index % 2)}T18:00:00`, + EndTime: `2024-07-${20 + (index % 2)}T21:00:00`, + MaxPlayers: 10 + (index % 10), + TagIds: [(index % 5) + 1, ((index + 1) % 5) + 1, ((index + 2) % 5) + 1] + }; + }; for (const game of gameData) { @@ -166,7 +168,20 @@ const credentials = Array.from({ length: numberOfUsers }, (v, i) => ({ await postTagData(tag); } - await createPost(postData); +// Function to post all generated posts +const AllGeneratedPosts = async () => { + for (let i = 0; i < 48; i++) { + const postData = NewPostData(i + 1); + try { + await createPost(postData); + console.log(`Post ${i + 1} created successfully.`); + } catch (error) { + console.error(`Failed to create post ${i + 1}:`, error); + } + } +}; + +AllGeneratedPosts(); } catch (error) { console.error('Setup failed:', error); From 77c24cc57e7e594102e0a4a9547d57251ab4ebc3 Mon Sep 17 00:00:00 2001 From: Jackson Williams Date: Fri, 19 Apr 2024 00:31:24 -0400 Subject: [PATCH 06/25] added more variety to the posts dates and times for filtering test. Also ran format --- FU.SPA/tests/setup-tests.js | 108 +++++++++++++++++++----------------- 1 file changed, 56 insertions(+), 52 deletions(-) diff --git a/FU.SPA/tests/setup-tests.js b/FU.SPA/tests/setup-tests.js index 3b8835e1..a59c57e7 100644 --- a/FU.SPA/tests/setup-tests.js +++ b/FU.SPA/tests/setup-tests.js @@ -10,7 +10,7 @@ const signUp = async (credentials) => { const response = await fetch(`${API_BASE_URL}/Accounts`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(credentials) + body: JSON.stringify(credentials), }); if (!response.ok) { @@ -28,7 +28,7 @@ const signIn = async (credentials) => { const response = await fetch(`${API_BASE_URL}/Accounts/auth`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(credentials) + body: JSON.stringify(credentials), }); if (!response.ok) { @@ -48,9 +48,9 @@ const postGameData = async (gameData) => { method: 'POST', headers: { 'Content-Type': 'application/json', - 'Authorization': `Bearer ${token}`, + Authorization: `Bearer ${token}`, }, - body: JSON.stringify(gameData) + body: JSON.stringify(gameData), }); if (!response.ok) { @@ -68,7 +68,7 @@ const postTagData = async (tagData) => { method: 'POST', headers: { 'content-type': 'application/json', - 'Authorization': `Bearer ${token}`, + Authorization: `Bearer ${token}`, }, body: JSON.stringify(tagData), }); @@ -82,7 +82,6 @@ const postTagData = async (tagData) => { console.log('Tag data inserted:', result); }; - const createPost = async (postData) => { const token = tokenStorage.token; @@ -90,15 +89,15 @@ const createPost = async (postData) => { postData.StartTime = new Date(postData.StartTime).toISOString(); postData.EndTime = new Date(postData.EndTime).toISOString(); } else { - console.error("Invalid or missing date fields"); - throw new Error("Invalid or missing date fields"); + console.error('Invalid or missing date fields'); + throw new Error('Invalid or missing date fields'); } const response = await fetch(`${API_BASE_URL}/Posts`, { method: 'POST', headers: { 'content-type': 'application/json', - 'Authorization': `Bearer ${token}`, + Authorization: `Bearer ${token}`, }, body: JSON.stringify(postData), }); @@ -116,50 +115,57 @@ const createPost = async (postData) => { const setup = async () => { console.log('setting up'); try { - - const numberOfUsers = 25; + const numberOfUsers = 25; -const credentials = Array.from({ length: numberOfUsers }, (v, i) => ({ - username: `user${i + 1}`, - password: `password${i + 1}`, - email: `user${i + 1}@example.com` -})); + const credentials = Array.from({ length: numberOfUsers }, (v, i) => ({ + username: `user${i + 1}`, + password: `password${i + 1}`, + email: `user${i + 1}@example.com`, + })); - for(const user of credentials) { - await signUp(user); - } - for(const user of credentials) { - await signIn(user); - } + for (const user of credentials) { + await signUp(user); + } + for (const user of credentials) { + await signIn(user); + } const gameData = [ - { name: "Insurgency", id: "1" }, - { name: "RainBow Six Siege", id: "2" }, - { name: "Rocket League", id: "3" }, - { name: "Call of Duty", id: "4" }, - { name: "Counter Strike 2", id: "5" } + { name: 'Insurgency', id: '1' }, + { name: 'RainBow Six Siege', id: '2' }, + { name: 'Rocket League', id: '3' }, + { name: 'Call of Duty', id: '4' }, + { name: 'Counter Strike 2', id: '5' }, ]; - const tagData = [ - { name: "mic", id: "1" }, - { name: "fun", id: "2"}, - { name: "casual", id: "3"}, - { name: "east", id: "4"}, - { name: "open", id: "5"} + const tagData = [ + { name: 'mic', id: '1' }, + { name: 'fun', id: '2' }, + { name: 'casual', id: '3' }, + { name: 'east', id: '4' }, + { name: 'open', id: '5' }, ]; const NewPostData = (index) => { + const month = Math.floor(Math.random() * 12) + 1; + const day = Math.floor(Math.random() * 28) + 1; + const hourStart = Math.floor(Math.random() * 5) + 16; + const hourEnd = hourStart + Math.floor(Math.random() * 3) + 1; + return { Title: `Exciting Game Night ${index}`, Description: `Join us for an exciting night of gaming at event ${index}!`, GameId: (index % 5) + 1, - StartTime: `2024-07-${20 + (index % 2)}T18:00:00`, - EndTime: `2024-07-${20 + (index % 2)}T21:00:00`, + StartTime: `2024-${month.toString().padStart(2, '0')}-${day + .toString() + .padStart(2, '0')}T${hourStart.toString().padStart(2, '0')}:00:00`, + EndTime: `2024-${month.toString().padStart(2, '0')}-${day + .toString() + .padStart(2, '0')}T${hourEnd.toString().padStart(2, '0')}:00:00`, MaxPlayers: 10 + (index % 10), - TagIds: [(index % 5) + 1, ((index + 1) % 5) + 1, ((index + 2) % 5) + 1] + TagIds: [(index % 5) + 1, ((index + 1) % 5) + 1, ((index + 2) % 5) + 1], }; }; - for (const game of gameData) { await postGameData(game); } @@ -168,21 +174,20 @@ const credentials = Array.from({ length: numberOfUsers }, (v, i) => ({ await postTagData(tag); } -// Function to post all generated posts -const AllGeneratedPosts = async () => { - for (let i = 0; i < 48; i++) { - const postData = NewPostData(i + 1); - try { - await createPost(postData); - console.log(`Post ${i + 1} created successfully.`); - } catch (error) { - console.error(`Failed to create post ${i + 1}:`, error); - } - } -}; - -AllGeneratedPosts(); + // Function to post all generated posts + const AllGeneratedPosts = async () => { + for (let i = 0; i < 48; i++) { + const postData = NewPostData(i + 1); + try { + await createPost(postData); + console.log(`Post ${i + 1} created successfully.`); + } catch (error) { + console.error(`Failed to create post ${i + 1}:`, error); + } + } + }; + AllGeneratedPosts(); } catch (error) { console.error('Setup failed:', error); } @@ -190,6 +195,5 @@ AllGeneratedPosts(); console.log('Setup complete'); }; - // Run the setup after a delay to give the API time to start up setTimeout(setup, 3000); From d0a39d7a55cf6deffe761c2cb46a405482115cc0 Mon Sep 17 00:00:00 2001 From: Jackson Williams Date: Fri, 19 Apr 2024 16:17:45 -0400 Subject: [PATCH 07/25] updated time and date, prevents from making dates in the past --- FU.SPA/tests/setup-tests.js | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/FU.SPA/tests/setup-tests.js b/FU.SPA/tests/setup-tests.js index a59c57e7..af81cf27 100644 --- a/FU.SPA/tests/setup-tests.js +++ b/FU.SPA/tests/setup-tests.js @@ -146,19 +146,31 @@ const setup = async () => { ]; const NewPostData = (index) => { - const month = Math.floor(Math.random() * 12) + 1; - const day = Math.floor(Math.random() * 28) + 1; + const currentDate = new Date(); + const currentYear = currentDate.getFullYear(); + const currentMonth = currentDate.getMonth() + 1; + const currentDay = currentDate.getDate(); + + const month = Math.floor(Math.random() * (12 - currentMonth + 1)) + currentMonth; + + let day; + if (month === currentMonth) { + day = Math.floor(Math.random() * (28 - currentDay)) + currentDay + 1; + } else { + day = Math.floor(Math.random() * 28) + 1; + } + const hourStart = Math.floor(Math.random() * 5) + 16; const hourEnd = hourStart + Math.floor(Math.random() * 3) + 1; - + return { Title: `Exciting Game Night ${index}`, Description: `Join us for an exciting night of gaming at event ${index}!`, GameId: (index % 5) + 1, - StartTime: `2024-${month.toString().padStart(2, '0')}-${day + StartTime: `${currentYear}-${month.toString().padStart(2, '0')}-${day .toString() .padStart(2, '0')}T${hourStart.toString().padStart(2, '0')}:00:00`, - EndTime: `2024-${month.toString().padStart(2, '0')}-${day + EndTime: `${currentYear}-${month.toString().padStart(2, '0')}-${day .toString() .padStart(2, '0')}T${hourEnd.toString().padStart(2, '0')}:00:00`, MaxPlayers: 10 + (index % 10), From 7b5622ea7ff68eccd732000df8990b185f6f886d Mon Sep 17 00:00:00 2001 From: Jackson Williams Date: Fri, 19 Apr 2024 16:18:20 -0400 Subject: [PATCH 08/25] format --- FU.SPA/tests/setup-tests.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/FU.SPA/tests/setup-tests.js b/FU.SPA/tests/setup-tests.js index af81cf27..4716f84c 100644 --- a/FU.SPA/tests/setup-tests.js +++ b/FU.SPA/tests/setup-tests.js @@ -150,19 +150,20 @@ const setup = async () => { const currentYear = currentDate.getFullYear(); const currentMonth = currentDate.getMonth() + 1; const currentDay = currentDate.getDate(); - - const month = Math.floor(Math.random() * (12 - currentMonth + 1)) + currentMonth; - + + const month = + Math.floor(Math.random() * (12 - currentMonth + 1)) + currentMonth; + let day; if (month === currentMonth) { day = Math.floor(Math.random() * (28 - currentDay)) + currentDay + 1; } else { day = Math.floor(Math.random() * 28) + 1; } - + const hourStart = Math.floor(Math.random() * 5) + 16; const hourEnd = hourStart + Math.floor(Math.random() * 3) + 1; - + return { Title: `Exciting Game Night ${index}`, Description: `Join us for an exciting night of gaming at event ${index}!`, From f8db826d602771a9f1db359c383a423a70ced777 Mon Sep 17 00:00:00 2001 From: epadams Date: Sat, 20 Apr 2024 14:28:00 -0400 Subject: [PATCH 09/25] Added comments and update README.md --- FU.SPA/README.md | 39 ++++++++++++++++++- FU.SPA/src/App.jsx | 4 ++ FU.SPA/src/Theme.js | 2 + FU.SPA/src/components/Chat.jsx | 1 + FU.SPA/src/components/CreateGroup.jsx | 10 +---- FU.SPA/src/components/Navbar.jsx | 2 + FU.SPA/src/components/PostCard.jsx | 5 +++ FU.SPA/src/components/PostForm.jsx | 5 +++ FU.SPA/src/components/PostUsersList.jsx | 3 ++ FU.SPA/src/components/ProtectedRoute.jsx | 4 +- FU.SPA/src/components/TextSearch.jsx | 1 + FU.SPA/src/components/UserCard.jsx | 3 ++ .../src/components/pages/AccountSettings.jsx | 1 + FU.SPA/src/components/pages/CreatePost.jsx | 1 + FU.SPA/src/components/pages/Discover.jsx | 14 ++++++- FU.SPA/src/components/pages/EditPost.jsx | 1 + FU.SPA/src/components/pages/Home.jsx | 1 + FU.SPA/src/components/pages/NoPage.jsx | 1 + FU.SPA/src/components/pages/PostPage.jsx | 1 + .../src/components/pages/ProfileSettings.jsx | 3 ++ FU.SPA/src/components/pages/SignIn.jsx | 1 + FU.SPA/src/components/pages/Social.jsx | 8 +++- FU.SPA/src/components/pages/UserProfile.jsx | 3 ++ FU.SPA/src/config.js | 8 ++++ FU.SPA/src/context/userContext.js | 2 + FU.SPA/src/helpers/dateUtils.js | 4 ++ FU.SPA/src/helpers/requestBuilder.js | 2 + FU.SPA/src/main.jsx | 2 + FU.SPA/src/services/authService.js | 9 +++++ FU.SPA/src/services/avatarService.js | 2 + FU.SPA/src/services/gameService.js | 4 ++ FU.SPA/src/services/postService.js | 3 ++ FU.SPA/src/services/relationService.js | 1 + FU.SPA/src/services/searchService.js | 1 + FU.SPA/src/services/signalrService.js | 3 +- FU.SPA/src/services/tagService.js | 3 ++ FU.SPA/src/services/userService.js | 7 ++++ 37 files changed, 149 insertions(+), 16 deletions(-) diff --git a/FU.SPA/README.md b/FU.SPA/README.md index 92d66f2d..d2bc3598 100644 --- a/FU.SPA/README.md +++ b/FU.SPA/README.md @@ -1,10 +1,45 @@ # SPA Overview +## Tech Stack + +- SPA is built with React +- - Material UI is used for common components, some components are custom or modified versions of MUI components +- - Vite is used for local development hosting of the SPA +- NPM for package management + +### Understanding Components + +Components are rendered and displayed on the page. They integrate JavaScript code and React's JSX syntax to allow for complex functionality to be added to webpages. +Components are displayed in the Document Object Model, or DOM. Each component is a child of the root of the DOM, and components can be children of other comopnents. +This allows passing properties and information down to components, such as passing a title/username from a parent component down to a child. + +### Understanding Contexts + +Contexts are a way to pass data through the component tree/DOM without having to do it at every level. This simplifies a lot of logic for several different scenarios. +Things like a username, authentication token, login status, and more are not easily passed down through the DOM. For example, our `UserContext` looks like this: + +``` +const UserContext = createContext({ + user: null, + token: null, + login: () => {}, + logout: () => {}, + refreshUser: () => {}, +}); +``` + +This allows us to keep track of the user and call its properties at any level, as well as the user's authentication token. + +### Understanding Services + +Services are just thin wrappers used by the SPA to ease calling the API and allow for reuse of common API calls. +See the (API services)[https://github.com/SCCapstone/PalmettoProgrammers/blob/main/FU.API/README.md#understanding-services] for understanding of how they work. + ## Development Install [npm](https://www.npmjs.com/package/npm) and [Docker](https://www.docker.com/get-started/). -## Configure the API URL +### Configure the API URL Config settings are loaded from environment variables. To automatically load environment variables from a file, create a `.env` file in this folder. @@ -14,7 +49,7 @@ Set the `VITE_API_URL` environment variable by adding the following to `.env`. If there are CORS errors, change the URL to match `http://` instead of `https://` and this may resolve the issue. -## Running with docker +### Running with Docker Add the following to your `hosts` file (`/etc/hosts` on Linux and `C:\Windows\System32\drivers\etc` on Windows). diff --git a/FU.SPA/src/App.jsx b/FU.SPA/src/App.jsx index 374f7438..280b676a 100644 --- a/FU.SPA/src/App.jsx +++ b/FU.SPA/src/App.jsx @@ -20,6 +20,10 @@ import EditPost from './components/pages/EditPost'; import { ReactNotifications } from 'react-notifications-component'; import 'react-notifications-component/dist/theme.css'; +/* Top level of application + * Provides theme, notifications, general CSS, User Context, Navbar, + * and top level routes + */ function App() { return ( diff --git a/FU.SPA/src/Theme.js b/FU.SPA/src/Theme.js index 3a794d3a..c2bcfb26 100644 --- a/FU.SPA/src/Theme.js +++ b/FU.SPA/src/Theme.js @@ -1,5 +1,6 @@ import { createTheme } from '@mui/material/styles'; +// Main color palatte for the application const COLORS = { PRIMARY_MAIN: '#e354dc', SECONDARY_MAIN: '#4290f5', @@ -7,6 +8,7 @@ const COLORS = { BACKGROUND_DEFAULT: '#23194f', }; +// Create Theme for the ThemeProvider const Theme = createTheme({ palette: { mode: 'dark', diff --git a/FU.SPA/src/components/Chat.jsx b/FU.SPA/src/components/Chat.jsx index 2db1895c..d3a8e245 100644 --- a/FU.SPA/src/components/Chat.jsx +++ b/FU.SPA/src/components/Chat.jsx @@ -44,6 +44,7 @@ export default function Chat({ chatId }) { } }; + // Handles receiving messages const handleReceiveMessage = (receivedMessage) => { setMessages((prevMessages) => [...prevMessages, receivedMessage]); diff --git a/FU.SPA/src/components/CreateGroup.jsx b/FU.SPA/src/components/CreateGroup.jsx index 80f4a982..f2069205 100644 --- a/FU.SPA/src/components/CreateGroup.jsx +++ b/FU.SPA/src/components/CreateGroup.jsx @@ -1,3 +1,4 @@ +// NOTE: unused component and deprecated import { Button, TextField, @@ -11,15 +12,6 @@ import { TextareaAutosize } from '@mui/base/TextareaAutosize'; import { createTheme, ThemeProvider } from '@mui/material/styles'; import Radio from '@mui/material/Radio'; -// TODO remove, this demo shouldn't need to reset the theme. - -//const defaultTheme = createTheme(); - -//Look at changing to const CreatePost = () => { -// CreatingPost(); -//} or something similiar. -//Design of the page.// TODO START OF PAGE CODE, WHICH I'M CHANGING TO CREATE GROUP, BEFORE MAKING IT A BUTTON FUNCTION (semi-completed) -// need to add a group image and an upload button to the left under the buttons that will be placed there, as well. export default function CreateGroup() { return ( diff --git a/FU.SPA/src/components/Navbar.jsx b/FU.SPA/src/components/Navbar.jsx index 4ab3031d..30474941 100644 --- a/FU.SPA/src/components/Navbar.jsx +++ b/FU.SPA/src/components/Navbar.jsx @@ -83,6 +83,7 @@ export default function Navbar() { }, })); + // Renders the profile picture and username on the navbar const renderProfile = () => ( <>
@@ -182,6 +183,7 @@ export default function Navbar() { setAnchorElUser(null); }; + // Displays navbar component return ( diff --git a/FU.SPA/src/components/PostCard.jsx b/FU.SPA/src/components/PostCard.jsx index 0d562bdb..98f32a40 100644 --- a/FU.SPA/src/components/PostCard.jsx +++ b/FU.SPA/src/components/PostCard.jsx @@ -16,6 +16,7 @@ import dayjs from 'dayjs'; import { Done } from '@mui/icons-material'; import ChatMessagePreview from './ChatMessagePreview'; +// Function that displays a card with details of a given post const PostCard = ({ post, showActions, onTagClick, showJoinedStatus }) => { const navigate = useNavigate(); const user = post.creator; @@ -24,6 +25,7 @@ const PostCard = ({ post, showActions, onTagClick, showJoinedStatus }) => { showActions = true; } + // const handleTagClick = (tag) => { if (onTagClick) { onTagClick(tag); @@ -49,6 +51,7 @@ const PostCard = ({ post, showActions, onTagClick, showJoinedStatus }) => { let postEndDateTime = dayjs(post.endTime); let startDate = dayjs(post.startTime).format('MMM D, YYYY'); + // This block handles formatting of start date display on card if (postStartDateTime < startOfToday) { // Use default } else if (postStartDateTime < startOfToday.add(1, 'day')) { @@ -64,6 +67,7 @@ const PostCard = ({ post, showActions, onTagClick, showJoinedStatus }) => { let endDate = ''; if (!postEndDateTime.isSame(postStartDateTime, 'day')) { endDate = dayjs(post.endTime).format('MMM D, YYYY'); + // This block handles formatting of end date display on card if (postEndDateTime < startOfToday) { // Use default } else if (postEndDateTime < startOfToday.add(1, 'day')) { @@ -109,6 +113,7 @@ const PostCard = ({ post, showActions, onTagClick, showJoinedStatus }) => { return color; }; + // Returns card with post details to be displayed return ( diff --git a/FU.SPA/src/components/PostForm.jsx b/FU.SPA/src/components/PostForm.jsx index 52489ff8..8aaf75e7 100644 --- a/FU.SPA/src/components/PostForm.jsx +++ b/FU.SPA/src/components/PostForm.jsx @@ -18,6 +18,7 @@ import GameService from '../services/gameService'; import UserContext from '../context/userContext'; import dayjs from 'dayjs'; +// Function that displays a post form when creating or editing posts const PostForm = ({ onSubmit, submitButtonText, initialValue }) => { const { user } = useContext(UserContext); @@ -107,6 +108,7 @@ const PostForm = ({ onSubmit, submitButtonText, initialValue }) => { } }; + // Handles description change state error const handleDescriptionChange = (e) => { if (e.length > 1500) { setDescriptionError('Description cannot exceed 1500 characters'); @@ -169,6 +171,7 @@ const PostForm = ({ onSubmit, submitButtonText, initialValue }) => { return tags?.map((tag) => tag.name); }; + // Returns form component to be displayed return (
@@ -334,6 +337,7 @@ const GameSelector = ({ onChange, initialValue }) => { return filtered; }; + // Returns Game selector field return ( { return filtered; }; + // Returns tag selector field return ( { return initials; }; + // Display profile picture and names of users in a post const renderPfp = () => { return defaultPfp ? ( { ); }; + // Display status of users in a post const renderOnlineStatus = (isOnline) => { if (!isOnline) return; diff --git a/FU.SPA/src/components/ProtectedRoute.jsx b/FU.SPA/src/components/ProtectedRoute.jsx index 4bf12d33..5ce2a702 100644 --- a/FU.SPA/src/components/ProtectedRoute.jsx +++ b/FU.SPA/src/components/ProtectedRoute.jsx @@ -3,6 +3,7 @@ import { Navigate } from 'react-router-dom'; import UserContext from '../context/userContext'; import config from '../config'; +// Function that handles protecting routes based on user authorization level export const ProtectedRoute = ({ children }) => { const { user } = useContext(UserContext); const [isLoading, setIsLoading] = useState(true); @@ -12,7 +13,8 @@ export const ProtectedRoute = ({ children }) => { useEffect(() => { const delay = async () => { - // See #281: We need to wait for the user to be set before rendering the children + // See #281: We need to wait for the user to be set before rendering the + // children await new Promise((resolve) => setTimeout(resolve, config.WAIT_TIME)); setIsLoading(false); }; diff --git a/FU.SPA/src/components/TextSearch.jsx b/FU.SPA/src/components/TextSearch.jsx index b27dbd9e..a54be9f6 100644 --- a/FU.SPA/src/components/TextSearch.jsx +++ b/FU.SPA/src/components/TextSearch.jsx @@ -2,6 +2,7 @@ import { TextField, InputAdornment, IconButton } from '@mui/material'; import { useEffect, useState } from 'react'; import SearchIcon from '@mui/icons-material/Search'; +// Component search bar that handles searching of posts and users function SearchBar({ searchText, onSearchSubmit }) { const [localSearchText, setLocalSearchText] = useState(searchText); diff --git a/FU.SPA/src/components/UserCard.jsx b/FU.SPA/src/components/UserCard.jsx index beec3201..9dc2bf51 100644 --- a/FU.SPA/src/components/UserCard.jsx +++ b/FU.SPA/src/components/UserCard.jsx @@ -14,6 +14,7 @@ import { useNavigate } from 'react-router-dom'; import { People, PendingActions, CallMade } from '@mui/icons-material'; import ChatMessagePreview from './ChatMessagePreview'; +// Function that displays a card with details of a given user const UserCard = ({ user, showRelationStatus, showActions }) => { if (showRelationStatus === undefined) { showRelationStatus = false; @@ -42,6 +43,7 @@ const UserCard = ({ user, showRelationStatus, showActions }) => { (today.getTime() - dob.getTime()) / (1000 * 3600 * 24 * 365), ); + // Handles displaying relationship status with another user const renderRelationStatus = () => { if (!showRelationStatus) { return null; @@ -70,6 +72,7 @@ const UserCard = ({ user, showRelationStatus, showActions }) => { } }; + // Return card to be displayed return ( Are you sure you want to delete your account? diff --git a/FU.SPA/src/components/pages/CreatePost.jsx b/FU.SPA/src/components/pages/CreatePost.jsx index eac05bec..249d1ced 100644 --- a/FU.SPA/src/components/pages/CreatePost.jsx +++ b/FU.SPA/src/components/pages/CreatePost.jsx @@ -3,6 +3,7 @@ import { useNavigate } from 'react-router-dom'; import { Store } from 'react-notifications-component'; import PostForm from '../PostForm'; +// Create post page export default function CreatePost() { const navigate = useNavigate(); diff --git a/FU.SPA/src/components/pages/Discover.jsx b/FU.SPA/src/components/pages/Discover.jsx index f5521372..b3557e4e 100644 --- a/FU.SPA/src/components/pages/Discover.jsx +++ b/FU.SPA/src/components/pages/Discover.jsx @@ -33,6 +33,7 @@ const paramKey = { userSort: 'usort', }; +// Converts a search paramater to its dayjs equivalent const paramToDayjs = (searchParams, paramKey) => { let paramValue = searchParams.get(paramKey); if (!paramValue || !dayjs(paramValue).isValid()) return undefined; @@ -45,6 +46,7 @@ export default function Discover() { Users: 'Users', }; + // STATE VARIABLES START const queryLimit = 12; const [totalResults, setTotalResults] = useState(0); const [searchParams, setSearchParams] = useSearchParams(); @@ -112,11 +114,17 @@ export default function Discover() { const [endTime, setEndTime] = useState( paramToDayjs(searchParams, paramKey.endTime), ); + // STATE VARIABLES END + // useEffect to update search params useEffect(() => { const updateSearchParams = async () => { setSearchParams( (params) => { + /* This large block sets the search paramters for a query + * Also handles error checking so that invalid params cannot + * be included + */ if ( dateRangeRadioValue === DateFilterRadioValues.between && startDate?.isValid() @@ -189,7 +197,6 @@ export default function Discover() { ); }; - //TODO pull this out to directly call const updateSearchResults = async () => { if (tabOption === tabOptions.Posts) { const query = { @@ -318,6 +325,7 @@ export default function Discover() { } }; + // Render posts or users based on tab option selected const renderTabContent = () => { if (tabOption === tabOptions.Posts) { return ( @@ -328,6 +336,7 @@ export default function Discover() { } }; + // Displays post sort selector for sort options const renderPostSortSelector = () => { return ( { return ( { return (
{ return ( diff --git a/FU.SPA/src/components/pages/NoPage.jsx b/FU.SPA/src/components/pages/NoPage.jsx index 75bff163..b4389d0d 100644 --- a/FU.SPA/src/components/pages/NoPage.jsx +++ b/FU.SPA/src/components/pages/NoPage.jsx @@ -1,3 +1,4 @@ +// 404 error page export default function NoPage() { return

404

; } diff --git a/FU.SPA/src/components/pages/PostPage.jsx b/FU.SPA/src/components/pages/PostPage.jsx index 311e0e49..20c164ac 100644 --- a/FU.SPA/src/components/pages/PostPage.jsx +++ b/FU.SPA/src/components/pages/PostPage.jsx @@ -105,6 +105,7 @@ const PostPage = () => { setLeaveDialogOpen(false); }; + // Displays leave confirmation Dialog for a post return ( <> { const [file, setFile] = useState(); const [uploadedImageUrl, setUploadedImageUrl] = useState(); @@ -228,6 +229,7 @@ const UploadAvatar = ({ onNewPreview }) => { setLoading(true); setFile(event.target.files[0]); + // Try to upload image and throw error if fail try { const response = await AvatarService.upload(event.target.files[0]); setUploadedImageUrl(response.imageUrl); @@ -244,6 +246,7 @@ const UploadAvatar = ({ onNewPreview }) => { setFile(); }; + // Display component return ( <>