{
const { userId } = useParams();
const { user } = useContext(UserContext);
@@ -40,6 +42,7 @@ const UserProfile = () => {
update();
}, [userId, update]);
+ // Renders a chat if on another user's profile
const renderChat = () => {
if (isOwnProfile) {
return null;
@@ -99,36 +102,55 @@ const SocialRelationActionButton = ({ requesteeId }) => {
// don't render if viewing your own profile
if (requesteeId === currentUser.id) return;
- if (relationStatus === RelationService.STATUS.NONE) {
- buttonText = 'Send Friend Request';
- handleClick = async () => {
- await RelationService.postRelation(
- requesteeId,
- RelationService.ACTIONS.FRIEND,
- );
- UpdateStatus();
- };
- } else if (relationStatus === RelationService.STATUS.REQUESTED) {
- buttonText = 'Cancel Friend Request';
- handleClick = async () => {
- await RelationService.removeRelation(requesteeId);
- UpdateStatus();
- };
- } else if (relationStatus === RelationService.STATUS.PENDING) {
- buttonText = 'Accept Friend Request';
- handleClick = async () => {
- await RelationService.postRelation(
- requesteeId,
- RelationService.ACTIONS.FRIEND,
- );
- UpdateStatus();
- };
- } else if (relationStatus === RelationService.STATUS.FRIENDS) {
- buttonText = 'Unfriend';
- handleClick = async () => {
- await RelationService.removeRelation(requesteeId);
- UpdateStatus();
- };
+ // Render friend button options and try to handle requests
+ try {
+ if (relationStatus === RelationService.STATUS.NONE) {
+ buttonText = 'Send Friend Request';
+ handleClick = async () => {
+ await RelationService.postRelation(
+ requesteeId,
+ RelationService.ACTIONS.FRIEND,
+ );
+ UpdateStatus();
+ };
+ } else if (relationStatus === RelationService.STATUS.REQUESTED) {
+ buttonText = 'Cancel Friend Request';
+ handleClick = async () => {
+ await RelationService.removeRelation(requesteeId);
+ UpdateStatus();
+ };
+ } else if (relationStatus === RelationService.STATUS.PENDING) {
+ buttonText = 'Accept Friend Request';
+ handleClick = async () => {
+ await RelationService.postRelation(
+ requesteeId,
+ RelationService.ACTIONS.FRIEND,
+ );
+ UpdateStatus();
+ };
+ } else if (relationStatus === RelationService.STATUS.FRIENDS) {
+ buttonText = 'Unfriend';
+ handleClick = async () => {
+ await RelationService.removeRelation(requesteeId);
+ UpdateStatus();
+ };
+ }
+ } catch (e) {
+ // Error notification
+ Store.addNotification({
+ title: 'Error has occured',
+ message: 'An error has occured.\n' + e,
+ type: 'danger',
+ insert: 'bottom',
+ container: 'bottom-right',
+ animationIn: ['animate__animated', 'animate__fadeIn'],
+ animationOut: ['animate__animated', 'animate__fadeOut'],
+ dismiss: {
+ duration: 8000,
+ onScreen: true,
+ },
+ });
+ console.error('Error in UserProfile: ', e);
}
return (
diff --git a/FU.SPA/src/config.js b/FU.SPA/src/config.js
index 4a20aa3b..3ce1fe9c 100644
--- a/FU.SPA/src/config.js
+++ b/FU.SPA/src/config.js
@@ -1,24 +1,32 @@
+// Array of possible sort options for posts
const POST_SORT_OPTIONS = [
{ value: 'newest', label: 'Created Date' },
{ value: 'soonest', label: 'Start Time' },
{ value: 'title', label: 'Title' },
];
+// Array of possible sort options for posts in the Social component
const SOCIAL_POST_SORT_OPTIONS = [
...POST_SORT_OPTIONS,
{ value: 'chatactivity', label: 'Chat Activity' },
];
+// Array of possible sort options for users in the Discover component
const USER_SORT_OPTIONS = [
{ value: 'username', label: 'Name' },
{ value: 'dob', label: 'DOB' },
];
+// Array of possible sort options for users in the Social component
const SOCIAL_USER_SORT_OPTIONS = [
...USER_SORT_OPTIONS,
{ value: 'chatactivity', label: 'Chat Activity' },
];
+/* Exports config variables and arrays
+ * If running locally, will use the local running API, otherwise used the hosted
+ * API
+ */
const config = {
API_URL:
window.location.hostname === 'jolly-glacier-0ae92c40f.4.azurestaticapps.net'
diff --git a/FU.SPA/src/context/userContext.js b/FU.SPA/src/context/userContext.js
index b024b167..99704037 100644
--- a/FU.SPA/src/context/userContext.js
+++ b/FU.SPA/src/context/userContext.js
@@ -1,5 +1,7 @@
import { createContext } from 'react';
+// Creates a context for users that keeps track of the current user and
+// their auth token
const UserContext = createContext({
user: null,
token: null,
diff --git a/FU.SPA/src/helpers/dateUtils.js b/FU.SPA/src/helpers/dateUtils.js
index 0f773cbc..7807d096 100644
--- a/FU.SPA/src/helpers/dateUtils.js
+++ b/FU.SPA/src/helpers/dateUtils.js
@@ -1,3 +1,7 @@
+/* Calculates time difference for chat messages based on when they were sent
+ * Ie a message sent 3 days ago should display '3 days' rather than
+ * '4320 minutes'
+ */
const timeDifference = (timestamp) => {
const messageDate = new Date(timestamp);
const currentTimestamp = Date.now();
diff --git a/FU.SPA/src/helpers/requestBuilder.js b/FU.SPA/src/helpers/requestBuilder.js
index efa27d99..ed5419bc 100644
--- a/FU.SPA/src/helpers/requestBuilder.js
+++ b/FU.SPA/src/helpers/requestBuilder.js
@@ -1,3 +1,4 @@
+// Builds a URL query string for post searching based on input arguments
const buildPostQueryString = (query) => {
let queryString = '';
if (query.keywords) {
@@ -49,6 +50,7 @@ const buildPostQueryString = (query) => {
return queryString;
};
+// Builds a URL query string for user searching based on input arguments
const buildUserQueryString = (query) => {
let queryString = '';
if (query && query.keywords) {
diff --git a/FU.SPA/src/main.jsx b/FU.SPA/src/main.jsx
index e2a73f79..ae1285e5 100644
--- a/FU.SPA/src/main.jsx
+++ b/FU.SPA/src/main.jsx
@@ -4,6 +4,8 @@ import App from './App.jsx';
import './index.css';
import { BrowserRouter } from 'react-router-dom';
+// Create root of application
+// Also starts the BrowserRouter for routing URLs
ReactDOM.createRoot(document.getElementById('root')).render(
diff --git a/FU.SPA/src/services/authService.js b/FU.SPA/src/services/authService.js
index 28e86f53..9777b7eb 100644
--- a/FU.SPA/src/services/authService.js
+++ b/FU.SPA/src/services/authService.js
@@ -10,6 +10,7 @@ const signIn = async (credentials) => {
body: JSON.stringify(credentials),
});
+ // Error checking
if (!response.ok) {
const errorText = await response.text();
throw new Error(errorText || 'Error in sign in');
@@ -31,6 +32,7 @@ const signUp = async (credentials) => {
body: JSON.stringify(credentials),
});
+ // Error checking
if (!response.ok) {
// Get the error message
const errorText = await response.text();
@@ -38,6 +40,7 @@ const signUp = async (credentials) => {
}
};
+// Function for email account confirmation
const confirmAccount = async (token) => {
const response = await fetch(`${API_BASE_URL}/Accounts/confirm`, {
method: 'POST',
@@ -46,6 +49,7 @@ const confirmAccount = async (token) => {
},
});
+ // Error checking
if (!response.ok) {
const errorText = await response.text();
throw new Error(errorText || 'Error in confirming account');
@@ -55,6 +59,7 @@ const confirmAccount = async (token) => {
return jsonResponse;
};
+// Function for resending confirmation email
const resendConfirmation = async (resendConfirmationData) => {
const response = await fetch(`${API_BASE_URL}/Accounts/resendconfirmation`, {
method: 'POST',
@@ -62,17 +67,21 @@ const resendConfirmation = async (resendConfirmationData) => {
body: JSON.stringify(resendConfirmationData),
});
+ // Error checking
if (!response.ok) {
const errorText = await response.text();
throw new Error(errorText || 'Error in resending confirmation');
}
};
+// Function that retrieves users' auth token from localStorage
const getToken = () => {
const token = localStorage.getItem(LOCAL_STORAGE_TOKEN_KEY);
return token;
};
+// Function that gets a logged in users account token and easily formats it
+// for API calls
const getAuthHeader = () => {
const token = getToken();
return { Authorization: `Bearer ${token}` };
diff --git a/FU.SPA/src/services/avatarService.js b/FU.SPA/src/services/avatarService.js
index a955a69d..326a4aff 100644
--- a/FU.SPA/src/services/avatarService.js
+++ b/FU.SPA/src/services/avatarService.js
@@ -2,6 +2,7 @@ import config from '../config';
const API_BASE_URL = config.API_URL;
import AuthService from './authService';
+// Function that uploads an image to be used as a profile picture
const upload = async (file) => {
const formData = new FormData();
formData.append('avatarFile', file);
@@ -14,6 +15,7 @@ const upload = async (file) => {
},
});
+ // Error checking
if (response.status === 422) {
throw new Error('Invalid file format');
} else if (!response.ok) {
diff --git a/FU.SPA/src/services/gameService.js b/FU.SPA/src/services/gameService.js
index 8ecf6f1c..b86949c3 100644
--- a/FU.SPA/src/services/gameService.js
+++ b/FU.SPA/src/services/gameService.js
@@ -14,6 +14,7 @@ const searchGames = async (keyword) => {
return await response.json();
};
+// Finds a game by its title
const findGameByTitle = async (title) => {
let games = await searchGames(title);
@@ -27,9 +28,11 @@ const findGameByTitle = async (title) => {
return game;
};
+// Finds a game by title, and if it doesn't exist, creates the game
const findOrCreateGameByTitle = async (title) => {
let game = await findGameByTitle(title);
+ // Creates a game if it does not exist
if (!game) {
game = await createGame({ Name: title });
}
@@ -48,6 +51,7 @@ const createGame = async (params) => {
body: JSON.stringify(params),
});
+ // Error checking
if (!response.ok) {
throw new Error('Error in game creation');
}
diff --git a/FU.SPA/src/services/postService.js b/FU.SPA/src/services/postService.js
index 24f3ed4c..999345ee 100644
--- a/FU.SPA/src/services/postService.js
+++ b/FU.SPA/src/services/postService.js
@@ -13,6 +13,7 @@ const createPost = async (params) => {
body: JSON.stringify(params),
});
+ // Error checking
if (!response.ok) {
throw new Error('Error in post creation');
}
@@ -32,6 +33,7 @@ const updatePost = async (params, postId) => {
body: JSON.stringify(params),
});
+ // Error checking
if (!response.ok) {
throw new Error('Error in updating post');
}
@@ -49,6 +51,7 @@ const getPostDetails = async (postId) => {
},
});
+ // Error checking
if (!response.ok) {
throw new Error('Error in post creation');
}
diff --git a/FU.SPA/src/services/relationService.js b/FU.SPA/src/services/relationService.js
index e446d377..f45c367c 100644
--- a/FU.SPA/src/services/relationService.js
+++ b/FU.SPA/src/services/relationService.js
@@ -112,6 +112,7 @@ const getRelations = async (userId, query) => {
},
);
+ // Error checking
if (!response.ok) {
throw new Error('Error in getting relation status');
}
diff --git a/FU.SPA/src/services/searchService.js b/FU.SPA/src/services/searchService.js
index 5749ed65..5b289a44 100644
--- a/FU.SPA/src/services/searchService.js
+++ b/FU.SPA/src/services/searchService.js
@@ -29,6 +29,7 @@ const searchPosts = async (query) => {
return { data: responseData, totalCount: totalCount };
};
+// Search users based on query
const searchUsers = async (query) => {
var queryString = RequestBuilder.buildUserQueryString(query);
diff --git a/FU.SPA/src/services/signalrService.js b/FU.SPA/src/services/signalrService.js
index e58dd576..4142e82e 100644
--- a/FU.SPA/src/services/signalrService.js
+++ b/FU.SPA/src/services/signalrService.js
@@ -8,6 +8,7 @@ export const hubConnection = new signalR.HubConnectionBuilder()
})
.build();
+// Function that tries to start a connection to a signalR hub
export const startConnection = async () => {
try {
// Only start if connection is not in the Connected state
@@ -18,7 +19,7 @@ export const startConnection = async () => {
// console.error('Error starting SignalR connection:', err);
}
};
-
+// Function that tries to stop a connection to a signalR hub
export const stopConnection = async () => {
try {
// Only stop if connection is not in the Disconnected state
diff --git a/FU.SPA/src/services/tagService.js b/FU.SPA/src/services/tagService.js
index 0a946114..ca487c77 100644
--- a/FU.SPA/src/services/tagService.js
+++ b/FU.SPA/src/services/tagService.js
@@ -7,6 +7,7 @@ import AuthService from './authService';
keywords: "",
}
*/
+// Searches for given tag(s)
const searchTags = async (keyword) => {
keyword = encodeURIComponent(keyword);
const response = await fetch(`${API_BASE_URL}/tags?$keyword=${keyword}`);
@@ -14,6 +15,7 @@ const searchTags = async (keyword) => {
return await response.json();
};
+// Function that searchs for a tag
const findTagByName = async (name) => {
let tags = await searchTags(name);
@@ -26,6 +28,7 @@ const findTagByName = async (name) => {
return tag;
};
+// Finds a tag by title, and if it doesn't exist, creates the tag
const findOrCreateTagByName = async (name) => {
let tag = await findTagByName(name);
diff --git a/FU.SPA/src/services/userService.js b/FU.SPA/src/services/userService.js
index a69b87e0..59d978d2 100644
--- a/FU.SPA/src/services/userService.js
+++ b/FU.SPA/src/services/userService.js
@@ -11,6 +11,7 @@ const getConnectedPosts = async (query) => {
{ headers: { ...AuthService.getAuthHeader() } },
);
+ // Error checking
if (!response.ok) {
throw new Error('Error getting posts');
}
@@ -26,6 +27,7 @@ const getConnectedGroups = async () => {
{ headers: { ...AuthService.getAuthHeader() } },
);
+ // Error checking
if (!response.ok) {
throw new Error('Error getting groups');
}
@@ -33,11 +35,13 @@ const getConnectedGroups = async () => {
return await response.json();
};
+// Gets a given user's profile
const getUserprofile = async (userString) => {
const response = await fetch(`${API_BASE_URL}/users/${userString}`, {
headers: { ...AuthService.getAuthHeader() },
});
+ // Error checking
if (!response.ok) {
throw new Error('Error getting groups');
}
@@ -53,6 +57,7 @@ const getUserIdJson = async () => {
method: 'GET',
});
+ // Error checking
if (!response.ok) {
throw new Error('Error in retrieving ID');
}
@@ -86,6 +91,7 @@ const updateAccountInfo = async (data) => {
body: JSON.stringify(data),
});
+ // Error checking
if (!response.ok) {
const errorText = await response.text();
throw new Error(errorText || 'Error in resending confirmation');
@@ -103,6 +109,7 @@ const deleteAccount = async (credentials) => {
body: JSON.stringify(credentials),
});
+ // Error checking
if (!response.ok) {
throw new Error('Error in deleting account');
}
diff --git a/FU.SPA/tests/setup-tests.js b/FU.SPA/tests/setup-tests.js
index 04848af3..8ae1dfe4 100644
--- a/FU.SPA/tests/setup-tests.js
+++ b/FU.SPA/tests/setup-tests.js
@@ -4,14 +4,209 @@
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 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');
+ console.log('setting up');
+ try {
+ const numberOfUsers = 25;
+
+ const credentials = Array.from({ length: numberOfUsers }, (v, i) => ({
+ username: `user${i + 1}`,
+ password: `pass_word${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' },
+ { 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 NewPostData = (index) => {
+ 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: `${currentYear}-${month.toString().padStart(2, '0')}-${day
+ .toString()
+ .padStart(2, '0')}T${hourStart.toString().padStart(2, '0')}:00:00`,
+ EndTime: `${currentYear}-${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],
+ };
+ };
+
+ for (const game of gameData) {
+ await postGameData(game);
+ }
+
+ for (const tag of tagData) {
+ 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);
+ }
+ }
+ };
- const response = await fetch(`${API_BASE_URL}/Games`);
- console.log(await response.json());
+ AllGeneratedPosts();
+ } catch (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);