diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 156bd060..df5480ed 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -27,6 +27,7 @@
"mantine-form-zod-resolver": "^1.1.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
+ "react-error-boundary": "^4.1.2",
"react-icons": "^5.3.0",
"react-router-dom": "^6.26.2",
"recharts": "^2.13.3",
@@ -5460,6 +5461,17 @@
"react": "^18.3.1"
}
},
+ "node_modules/react-error-boundary": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-4.1.2.tgz",
+ "integrity": "sha512-GQDxZ5Jd+Aq/qUxbCm1UtzmL/s++V7zKgE8yMktJiCQXCCFZnMZh9ng+6/Ne6PjNSXH0L9CjeOEREfRnq6Duag==",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5"
+ },
+ "peerDependencies": {
+ "react": ">=16.13.1"
+ }
+ },
"node_modules/react-icons": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.3.0.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index a924337d..e53bdcd0 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -36,6 +36,7 @@
"mantine-form-zod-resolver": "^1.1.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
+ "react-error-boundary": "^4.1.2",
"react-icons": "^5.3.0",
"react-router-dom": "^6.26.2",
"recharts": "^2.13.3",
diff --git a/frontend/src/Layout.tsx b/frontend/src/Layout.tsx
index 88ecebf7..840ffff8 100644
--- a/frontend/src/Layout.tsx
+++ b/frontend/src/Layout.tsx
@@ -13,13 +13,15 @@ const Layout = () => {
}}
>
-
-
+
);
diff --git a/frontend/src/components/DashboardLayout.tsx b/frontend/src/components/DashboardLayout.tsx
index c8bb395b..1232644c 100644
--- a/frontend/src/components/DashboardLayout.tsx
+++ b/frontend/src/components/DashboardLayout.tsx
@@ -3,7 +3,7 @@ import { Outlet } from 'react-router-dom';
import Navbar from './navbar/Navbar.tsx';
import DashboardFooter from './footer/DashboardFooter';
import { Box, Flex } from '@mantine/core';
-import { ScrollToTop } from '../components/ScrollToTop';
+import { ScrollToTop } from './ScrollToTop.tsx';
const dashboardFlex = {
flex: 'auto',
diff --git a/frontend/src/components/DefaultNavbar.tsx b/frontend/src/components/DefaultNavbar.tsx
index 310ba8c5..2d50f2fe 100644
--- a/frontend/src/components/DefaultNavbar.tsx
+++ b/frontend/src/components/DefaultNavbar.tsx
@@ -20,7 +20,7 @@ const LeftNavItems = () => {
const RightNavItems = () => {
return (
-
+
);
diff --git a/frontend/src/components/fallbacks/ProfilePictureFallback.tsx b/frontend/src/components/fallbacks/ProfilePictureFallback.tsx
new file mode 100644
index 00000000..404be7f2
--- /dev/null
+++ b/frontend/src/components/fallbacks/ProfilePictureFallback.tsx
@@ -0,0 +1,26 @@
+import { Box, Image } from '@mantine/core';
+import React from 'react';
+
+export const ProfilePictureFallback = ({ error }) => {
+ console.error('ProfilePictureFallback:', error.message);
+ return (
+
+
+
+ );
+};
diff --git a/frontend/src/components/fallbacks/RateLimitStatusFallback.tsx b/frontend/src/components/fallbacks/RateLimitStatusFallback.tsx
new file mode 100644
index 00000000..7100e39a
--- /dev/null
+++ b/frontend/src/components/fallbacks/RateLimitStatusFallback.tsx
@@ -0,0 +1,17 @@
+import { HoverCard } from '@mantine/core';
+import { IconBrandReddit } from '@tabler/icons-react';
+import React from 'react';
+
+export const RateLimitStatusFallback = ({ error }) => {
+ console.error('RateLimitStatusFallback:', error.message);
+ return (
+
+
+
+
+
+ Error occurred while fetching rate limit status: {error.message}
+
+
+ );
+};
diff --git a/frontend/src/components/fallbacks/UserMenuFallback.tsx b/frontend/src/components/fallbacks/UserMenuFallback.tsx
new file mode 100644
index 00000000..5f319341
--- /dev/null
+++ b/frontend/src/components/fallbacks/UserMenuFallback.tsx
@@ -0,0 +1,11 @@
+import { logout } from '../../utility/logout.ts';
+import { useNavigate } from 'react-router-dom';
+
+export const UserMenuFallback = ({ error, resetErrorBoundary }) => {
+ resetErrorBoundary();
+ console.error('UserMenuFallback:', error.message);
+ return (
+ // TODO: will have to get more info how to handle UserMenu from the team
+ <>>
+ );
+};
diff --git a/frontend/src/components/navbar/RateLimitStatus.tsx b/frontend/src/components/navbar/RateLimitStatus.tsx
index 1e74a456..a6a42588 100644
--- a/frontend/src/components/navbar/RateLimitStatus.tsx
+++ b/frontend/src/components/navbar/RateLimitStatus.tsx
@@ -6,6 +6,8 @@ import React, { useEffect } from 'react';
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
import duration from 'dayjs/plugin/duration';
+import { ErrorBoundary } from 'react-error-boundary';
+import { RateLimitStatusFallback } from '../fallbacks/RateLimitStatusFallback.tsx';
dayjs.extend(duration);
dayjs.extend(relativeTime);
@@ -125,4 +127,10 @@ const RateLimitStatus = () => {
);
};
-export default RateLimitStatus;
+export default function () {
+ return (
+
+
+
+ );
+}
diff --git a/frontend/src/components/navbar/UserMenu.tsx b/frontend/src/components/navbar/UserMenu.tsx
index 95352140..ee6e81e3 100644
--- a/frontend/src/components/navbar/UserMenu.tsx
+++ b/frontend/src/components/navbar/UserMenu.tsx
@@ -2,10 +2,13 @@ import Cookies from 'js-cookie';
import { useNavigate } from 'react-router-dom';
import { Avatar, Menu } from '@mantine/core';
import authFetch from '../../rmoods/client/authFetch.ts';
-import { useEffect, useState } from 'react';
import { jwtDecode } from 'jwt-decode';
import { JwtClaims } from '../../rmoods/jwt.ts';
import { changeDefaultGoogleProfilePictureSize } from '../../utility/changeDefaultGoogleProfilePictureSize.ts';
+import { useQuery } from '@tanstack/react-query';
+import { ErrorBoundary } from 'react-error-boundary';
+import { UserMenuFallback } from '../fallbacks/UserMenuFallback.tsx';
+import { logout } from '../../utility/logout.ts';
/**
* User interface representing the user data.
@@ -15,56 +18,47 @@ interface User {
picture: string;
}
+/**
+ * Fetches the user data from the server.
+ * @returns {Promise} The user data.
+ */
+const fetchUserData = async (): Promise => {
+ const token = Cookies.get('RMOODS_JWT');
+ if (!token) {
+ throw new Error('No JWT token found');
+ }
+
+ const data = jwtDecode(token);
+ const id = data.userInfo.id;
+ const response = await authFetch(`http://localhost:8001/api/user?id=${id}`);
+ if (!response.ok) {
+ throw new Error('Failed to fetch user data');
+ }
+
+ return response.json();
+};
+
/**
* UserMenu component that displays the user menu.
* @returns {JSX.Element} The UserMenu component.
*/
const UserMenu = () => {
const navigate = useNavigate();
- const [user, setUser] = useState(null);
- const [loading, setLoading] = useState(true);
-
- useEffect(() => {
- const token = Cookies.get('RMOODS_JWT');
- if (!token) {
- navigate('/login');
- return;
- }
- let isMounted = true;
- try {
- const data = jwtDecode(token);
- const id = data.userInfo.id;
+ const { data, error } = useQuery({
+ queryKey: ['userData'],
+ queryFn: fetchUserData,
+ });
- authFetch('http://localhost:8001/api/user?id=' + id).then((response) => {
- response.json().then((data) => {
- if (isMounted) {
- setUser(data);
- setLoading(false);
- }
- });
- });
- } catch (error) {
- console.error('JWT token could not be decoded.', error);
- if (isMounted) {
- setLoading(false);
- }
- }
- return () => {
- isMounted = false;
- };
- }, [navigate]);
-
- const handleLogout = () => {
- console.log('Logging out');
- Cookies.remove('RMOODS_JWT');
- navigate('/login');
- };
+ if (error) {
+ throw new Error('Failed to fetch user data');
+ }
const size = 45;
- const resizedPicture = user
- ? changeDefaultGoogleProfilePictureSize(user.picture, size)
+ const resizedPicture = data?.picture
+ ? changeDefaultGoogleProfilePictureSize(data.picture, size)
: '';
+
return (
);
};
-export default UserMenu;
+export default function () {
+ const navigate = useNavigate();
+ return (
+ {
+ logout(navigate);
+ }}
+ >
+
+
+ );
+}
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
index 80f74ee4..c34b2a3c 100644
--- a/frontend/src/main.tsx
+++ b/frontend/src/main.tsx
@@ -6,11 +6,15 @@ import './index.css';
import { RouterProvider } from 'react-router-dom';
import router from './router.tsx';
import Providers from './providers/Providers.tsx';
+import { ErrorBoundary } from 'react-error-boundary';
+import { MainFallback } from './routes/fallbacks/MainFallback.tsx';
createRoot(document.getElementById('root')!).render(
-
+
+
+
);
diff --git a/frontend/src/rmoods/client/types.ts b/frontend/src/rmoods/client/types.ts
index 7adb9d56..074bfc1b 100644
--- a/frontend/src/rmoods/client/types.ts
+++ b/frontend/src/rmoods/client/types.ts
@@ -54,11 +54,11 @@ export const DataSourceSchema = z.object({
const GoogleUserInfoSchema = z.object({
sub: z.string(),
name: z.string(),
- given_name: z.string(),
- family_name: z.string().nullable(),
+ givenName: z.string(),
+ familyName: z.string().nullable(),
picture: z.string().url(),
email: z.string().email(),
- email_verified: z.boolean(),
+ emailVerified: z.boolean(),
});
const LanguageResponseSchema = z.object({
@@ -67,7 +67,7 @@ const LanguageResponseSchema = z.object({
});
const NlpMetadataSchema = z.object({
- generated_in: z.number(),
+ generatedIn: z.number(),
});
const NlpAnalysisSchema = z.object({
@@ -76,9 +76,9 @@ const NlpAnalysisSchema = z.object({
});
const ReportMetadataSchema = z.object({
- created_at: z.number(),
- user_info: GoogleUserInfoSchema,
- is_public: z.boolean(),
+ createdAt: z.number(),
+ userInfo: GoogleUserInfoSchema,
+ isPublic: z.boolean(),
});
const ReportResponseSchema = z.object({
diff --git a/frontend/src/router.tsx b/frontend/src/router.tsx
index cb614264..b797493d 100644
--- a/frontend/src/router.tsx
+++ b/frontend/src/router.tsx
@@ -50,7 +50,6 @@ const router = createHashRouter([
path: '/about',
element: ,
},
-
{
path: '/login',
element: ,
diff --git a/frontend/src/routes/about/page.tsx b/frontend/src/routes/about/page.tsx
index ae4775de..5c89fe4d 100644
--- a/frontend/src/routes/about/page.tsx
+++ b/frontend/src/routes/about/page.tsx
@@ -1,5 +1,7 @@
import Section from './Section';
import { Box } from '@mantine/core';
+import { ErrorBoundary } from 'react-error-boundary';
+import { PageFallback } from '../fallbacks/PageFallback.tsx';
/**
* About page, contains information about the project and the team.
@@ -41,4 +43,10 @@ const About = () => {
);
};
-export default About;
+export default function () {
+ return (
+
+
+
+ );
+}
diff --git a/frontend/src/routes/dashboard/page.tsx b/frontend/src/routes/dashboard/page.tsx
index ed2bdde4..1f73d93e 100644
--- a/frontend/src/routes/dashboard/page.tsx
+++ b/frontend/src/routes/dashboard/page.tsx
@@ -1,4 +1,6 @@
import { Title } from '@mantine/core';
+import { ErrorBoundary } from 'react-error-boundary';
+import { PageFallback } from '../fallbacks/PageFallback.tsx';
/**
* Dashboard page, gets user info asynchonously
@@ -8,52 +10,14 @@ const Dashboard = () => {
return (
<>
Dashboard
- {/*/!* Lorem ipsum added for testing purposes*!/purposes**/}
- {/*Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas a ligula nec augue consequat venenatis. Fusce tellus sapien, dignissim non hendrerit at, auctor sed diam. Maecenas et lacus consequat, gravida orci vitae, accumsan ipsum. Duis imperdiet tellus sit amet imperdiet placerat. Phasellus tempor at odio at fermentum. Nunc non hendrerit dui. Nunc dapibus sed purus ut euismod. Phasellus mattis viverra velit et lacinia. Donec nec congue massa, finibus pretium turpis.*/}
-
- {/*Quisque nec dolor eget risus varius pharetra. Ut tempor aliquet nisi sed pellentesque. Morbi faucibus dui at condimentum rhoncus. Quisque vehicula pharetra magna lacinia vestibulum. Aenean non purus tincidunt, cursus quam vitae, eleifend nibh. Aliquam tristique sem ut felis sagittis dignissim. Vestibulum bibendum auctor eros, ut viverra leo aliquam vel. Proin iaculis porta sollicitudin. Praesent quis sem posuere, suscipit nisi non, efficitur neque. Aenean imperdiet sollicitudin elementum. Etiam placerat dignissim elit nec sollicitudin. Suspendisse potenti. Donec luctus condimentum neque vitae molestie. Sed vehicula laoreet lectus, id aliquam dolor euismod in. Aliquam accumsan bibendum odio. Vivamus ac est quis turpis fringilla pharetra posuere id est.*/}
-
- {/*Etiam sed iaculis dui. Integer lacinia laoreet orci, in dapibus diam tincidunt quis. Aenean laoreet nisi et dolor euismod, ac commodo urna imperdiet. Maecenas a tempor orci. Donec vehicula justo tristique metus posuere, at pellentesque dolor imperdiet. Donec aliquet gravida feugiat. Mauris dictum urna ac elit convallis varius. Phasellus nec varius massa. Mauris ultricies lacus quis est tempor, nec finibus arcu ornare. Quisque urna libero, luctus venenatis semper non, tincidunt nec felis. Quisque ultrices quam pellentesque tortor tempor rutrum. Aliquam rhoncus eros dui, sit amet sagittis elit semper non. Nullam iaculis elementum diam, ut ornare ipsum varius sed.*/}
-
- {/*Aliquam pretium vehicula ipsum, nec venenatis turpis viverra elementum. Ut iaculis felis nec leo elementum, eget lobortis est mollis. Etiam tincidunt ligula pellentesque, pharetra diam ut, aliquam ex. Morbi urna massa, iaculis vel suscipit eu, facilisis in turpis. Donec nec lectus eu nibh elementum tincidunt. Duis vel felis metus. Nulla faucibus quam at congue tincidunt. Aliquam quis aliquam ipsum. Proin at lorem iaculis, dictum urna et, rhoncus diam. Sed molestie accumsan purus, at blandit sapien consequat sed. Sed felis magna, pulvinar in consequat sed, venenatis non elit. Vestibulum metus libero, aliquet a dolor quis, dignissim dictum est. Quisque consectetur blandit iaculis.*/}
-
- {/*Etiam vehicula, erat a ullamcorper egestas, ante eros feugiat quam, sed ultricies quam tortor ac risus. Fusce blandit lorem non ultrices malesuada. Integer pellentesque eu urna nec rutrum. Aenean ultrices dui id nunc fermentum sagittis. Nam tristique dolor risus, nec ullamcorper lectus gravida sit amet. Cras vitae ante et lacus suscipit egestas. Quisque molestie magna ligula, egestas finibus ante posuere sed.*/}
-
- {/*Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas a ligula nec augue consequat venenatis. Fusce tellus sapien, dignissim non hendrerit at, auctor sed diam. Maecenas et lacus consequat, gravida orci vitae, accumsan ipsum. Duis imperdiet tellus sit amet imperdiet placerat. Phasellus tempor at odio at fermentum. Nunc non hendrerit dui. Nunc dapibus sed purus ut euismod. Phasellus mattis viverra velit et lacinia. Donec nec congue massa, finibus pretium turpis.*/}
-
- {/*Quisque nec dolor eget risus varius pharetra. Ut tempor aliquet nisi sed pellentesque. Morbi faucibus dui at condimentum rhoncus. Quisque vehicula pharetra magna lacinia vestibulum. Aenean non purus tincidunt, cursus quam vitae, eleifend nibh. Aliquam tristique sem ut felis sagittis dignissim. Vestibulum bibendum auctor eros, ut viverra leo aliquam vel. Proin iaculis porta sollicitudin. Praesent quis sem posuere, suscipit nisi non, efficitur neque. Aenean imperdiet sollicitudin elementum. Etiam placerat dignissim elit nec sollicitudin. Suspendisse potenti. Donec luctus condimentum neque vitae molestie. Sed vehicula laoreet lectus, id aliquam dolor euismod in. Aliquam accumsan bibendum odio. Vivamus ac est quis turpis fringilla pharetra posuere id est.*/}
-
- {/*Etiam sed iaculis dui. Integer lacinia laoreet orci, in dapibus diam tincidunt quis. Aenean laoreet nisi et dolor euismod, ac commodo urna imperdiet. Maecenas a tempor orci. Donec vehicula justo tristique metus posuere, at pellentesque dolor imperdiet. Donec aliquet gravida feugiat. Mauris dictum urna ac elit convallis varius. Phasellus nec varius massa. Mauris ultricies lacus quis est tempor, nec finibus arcu ornare. Quisque urna libero, luctus venenatis semper non, tincidunt nec felis. Quisque ultrices quam pellentesque tortor tempor rutrum. Aliquam rhoncus eros dui, sit amet sagittis elit semper non. Nullam iaculis elementum diam, ut ornare ipsum varius sed.*/}
-
- {/*Aliquam pretium vehicula ipsum, nec venenatis turpis viverra elementum. Ut iaculis felis nec leo elementum, eget lobortis est mollis. Etiam tincidunt ligula pellentesque, pharetra diam ut, aliquam ex. Morbi urna massa, iaculis vel suscipit eu, facilisis in turpis. Donec nec lectus eu nibh elementum tincidunt. Duis vel felis metus. Nulla faucibus quam at congue tincidunt. Aliquam quis aliquam ipsum. Proin at lorem iaculis, dictum urna et, rhoncus diam. Sed molestie accumsan purus, at blandit sapien consequat sed. Sed felis magna, pulvinar in consequat sed, venenatis non elit. Vestibulum metus libero, aliquet a dolor quis, dignissim dictum est. Quisque consectetur blandit iaculis.*/}
-
- {/*Etiam vehicula, erat a ullamcorper egestas, ante eros feugiat quam, sed ultricies quam tortor ac risus. Fusce blandit lorem non ultrices malesuada. Integer pellentesque eu urna nec rutrum. Aenean ultrices dui id nunc fermentum sagittis. Nam tristique dolor risus, nec ullamcorper lectus gravida sit amet. Cras vitae ante et lacus suscipit egestas. Quisque molestie magna ligula, egestas finibus ante posuere sed.*/}
- {/*Lorem impsum added for testing purposes*/}
- {/*Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas a ligula nec augue consequat venenatis. Fusce tellus sapien, dignissim non hendrerit at, auctor sed diam. Maecenas et lacus consequat, gravida orci vitae, accumsan ipsum. Duis imperdiet tellus sit amet imperdiet placerat. Phasellus tempor at odio at fermentum. Nunc non hendrerit dui. Nunc dapibus sed purus ut euismod. Phasellus mattis viverra velit et lacinia. Donec nec congue massa, finibus pretium turpis.*/}
-
- {/*Quisque nec dolor eget risus varius pharetra. Ut tempor aliquet nisi sed pellentesque. Morbi faucibus dui at condimentum rhoncus. Quisque vehicula pharetra magna lacinia vestibulum. Aenean non purus tincidunt, cursus quam vitae, eleifend nibh. Aliquam tristique sem ut felis sagittis dignissim. Vestibulum bibendum auctor eros, ut viverra leo aliquam vel. Proin iaculis porta sollicitudin. Praesent quis sem posuere, suscipit nisi non, efficitur neque. Aenean imperdiet sollicitudin elementum. Etiam placerat dignissim elit nec sollicitudin. Suspendisse potenti. Donec luctus condimentum neque vitae molestie. Sed vehicula laoreet lectus, id aliquam dolor euismod in. Aliquam accumsan bibendum odio. Vivamus ac est quis turpis fringilla pharetra posuere id est.*/}
-
- {/*Etiam sed iaculis dui. Integer lacinia laoreet orci, in dapibus diam tincidunt quis. Aenean laoreet nisi et dolor euismod, ac commodo urna imperdiet. Maecenas a tempor orci. Donec vehicula justo tristique metus posuere, at pellentesque dolor imperdiet. Donec aliquet gravida feugiat. Mauris dictum urna ac elit convallis varius. Phasellus nec varius massa. Mauris ultricies lacus quis est tempor, nec finibus arcu ornare. Quisque urna libero, luctus venenatis semper non, tincidunt nec felis. Quisque ultrices quam pellentesque tortor tempor rutrum. Aliquam rhoncus eros dui, sit amet sagittis elit semper non. Nullam iaculis elementum diam, ut ornare ipsum varius sed.*/}
-
- {/*Aliquam pretium vehicula ipsum, nec venenatis turpis viverra elementum. Ut iaculis felis nec leo elementum, eget lobortis est mollis. Etiam tincidunt ligula pellentesque, pharetra diam ut, aliquam ex. Morbi urna massa, iaculis vel suscipit eu, facilisis in turpis. Donec nec lectus eu nibh elementum tincidunt. Duis vel felis metus. Nulla faucibus quam at congue tincidunt. Aliquam quis aliquam ipsum. Proin at lorem iaculis, dictum urna et, rhoncus diam. Sed molestie accumsan purus, at blandit sapien consequat sed. Sed felis magna, pulvinar in consequat sed, venenatis non elit. Vestibulum metus libero, aliquet a dolor quis, dignissim dictum est. Quisque consectetur blandit iaculis.*/}
-
- {/*Etiam vehicula, erat a ullamcorper egestas, ante eros feugiat quam, sed ultricies quam tortor ac risus. Fusce blandit lorem non ultrices malesuada. Integer pellentesque eu urna nec rutrum. Aenean ultrices dui id nunc fermentum sagittis. Nam tristique dolor risus, nec ullamcorper lectus gravida sit amet. Cras vitae ante et lacus suscipit egestas. Quisque molestie magna ligula, egestas finibus ante posuere sed.*/}
- {/**/}
-
- {/*Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas a ligula nec augue consequat venenatis. Fusce tellus sapien, dignissim non hendrerit at, auctor sed diam. Maecenas et lacus consequat, gravida orci vitae, accumsan ipsum. Duis imperdiet tellus sit amet imperdiet placerat. Phasellus tempor at odio at fermentum. Nunc non hendrerit dui. Nunc dapibus sed purus ut euismod. Phasellus mattis viverra velit et lacinia. Donec nec congue massa, finibus pretium turpis.*/}
-
- {/*Quisque nec dolor eget risus varius pharetra. Ut tempor aliquet nisi sed pellentesque. Morbi faucibus dui at condimentum rhoncus. Quisque vehicula pharetra magna lacinia vestibulum. Aenean non purus tincidunt, cursus quam vitae, eleifend nibh. Aliquam tristique sem ut felis sagittis dignissim. Vestibulum bibendum auctor eros, ut viverra leo aliquam vel. Proin iaculis porta sollicitudin. Praesent quis sem posuere, suscipit nisi non, efficitur neque. Aenean imperdiet sollicitudin elementum. Etiam placerat dignissim elit nec sollicitudin. Suspendisse potenti. Donec luctus condimentum neque vitae molestie. Sed vehicula laoreet lectus, id aliquam dolor euismod in. Aliquam accumsan bibendum odio. Vivamus ac est quis turpis fringilla pharetra posuere id est.*/}
-
- {/*Etiam sed iaculis dui. Integer lacinia laoreet orci, in dapibus diam tincidunt quis. Aenean laoreet nisi et dolor euismod, ac commodo urna imperdiet. Maecenas a tempor orci. Donec vehicula justo tristique metus posuere, at pellentesque dolor imperdiet. Donec aliquet gravida feugiat. Mauris dictum urna ac elit convallis varius. Phasellus nec varius massa. Mauris ultricies lacus quis est tempor, nec finibus arcu ornare. Quisque urna libero, luctus venenatis semper non, tincidunt nec felis. Quisque ultrices quam pellentesque tortor tempor rutrum. Aliquam rhoncus eros dui, sit amet sagittis elit semper non. Nullam iaculis elementum diam, ut ornare ipsum varius sed.*/}
-
- {/*Aliquam pretium vehicula ipsum, nec venenatis turpis viverra elementum. Ut iaculis felis nec leo elementum, eget lobortis est mollis. Etiam tincidunt ligula pellentesque, pharetra diam ut, aliquam ex. Morbi urna massa, iaculis vel suscipit eu, facilisis in turpis. Donec nec lectus eu nibh elementum tincidunt. Duis vel felis metus. Nulla faucibus quam at congue tincidunt. Aliquam quis aliquam ipsum. Proin at lorem iaculis, dictum urna et, rhoncus diam. Sed molestie accumsan purus, at blandit sapien consequat sed. Sed felis magna, pulvinar in consequat sed, venenatis non elit. Vestibulum metus libero, aliquet a dolor quis, dignissim dictum est. Quisque consectetur blandit iaculis.*/}
-
- {/*Etiam vehicula, erat a ullamcorper egestas, ante eros feugiat quam, sed ultricies quam tortor ac risus. Fusce blandit lorem non ultrices malesuada.malesuada*/}
- {/*
-
- */}
>
);
};
-export default Dashboard;
+export default function () {
+ return (
+
+
+
+ );
+}
diff --git a/frontend/src/routes/fallbacks/MainFallback.tsx b/frontend/src/routes/fallbacks/MainFallback.tsx
new file mode 100644
index 00000000..dc941954
--- /dev/null
+++ b/frontend/src/routes/fallbacks/MainFallback.tsx
@@ -0,0 +1,34 @@
+import React from 'react';
+import { Card, Center, Stack, Text, Title } from '@mantine/core';
+import { IconAlertCircle } from '@tabler/icons-react';
+
+export const MainFallback = ({ error }) => {
+ console.error('MainFallback:', error.message);
+ return (
+
+
+
+
+
+
+
+
+ Critical failure! Something went seriously wrong
+
+
+
+
+ The app crashed. The devs will be notified and investigate the
+ issue.
+
+
+
+
+ {error.message}
+
+
+
+
+
+ );
+};
diff --git a/frontend/src/routes/fallbacks/PageFallback.tsx b/frontend/src/routes/fallbacks/PageFallback.tsx
new file mode 100644
index 00000000..81740c16
--- /dev/null
+++ b/frontend/src/routes/fallbacks/PageFallback.tsx
@@ -0,0 +1,30 @@
+import { Center, Stack, Title, Text, Card } from '@mantine/core';
+import { IconAlertCircle } from '@tabler/icons-react';
+
+export const PageFallback = ({ error }) => {
+ console.error('PageFallback:', error.message);
+ return (
+
+
+
+
+
+
+
+ Oops! Something went wrong
+
+
+
+ The page encountered an error and couldn't be displayed.
+
+
+
+
+ {error.message}
+
+
+
+
+
+ );
+};
diff --git a/frontend/src/routes/login/page.tsx b/frontend/src/routes/login/page.tsx
index 03fea040..f3fbeecd 100644
--- a/frontend/src/routes/login/page.tsx
+++ b/frontend/src/routes/login/page.tsx
@@ -1,11 +1,13 @@
import { useGoogleLogin } from '@react-oauth/google';
import GoogleSignInButton from './GoogleSignInButton';
-import { useAtom, useSetAtom } from 'jotai';
+import { useSetAtom } from 'jotai';
import Cookies from 'js-cookie';
import { userInfoAtom } from '../../atoms';
import { useNavigate } from 'react-router-dom';
-import { Center, Paper, Stack, Title, Box } from '@mantine/core';
+import { Center, Paper, Stack, Title, Box, Card } from '@mantine/core';
import { notifications } from '@mantine/notifications';
+import { ErrorBoundary } from 'react-error-boundary';
+import { PageFallback } from '../fallbacks/PageFallback.tsx';
/**
* Login card with Google sign in button
@@ -57,18 +59,16 @@ const LoginCard = () => {
});
return (
-
-
-
-
-
- Welcome to RMoods!
-
-
-
-
-
-
+
+
+
+
+ Welcome to RMoods!
+
+
+
+
+
);
};
@@ -78,10 +78,16 @@ const LoginCard = () => {
*/
const Login = () => {
return (
-
+
);
};
-export default Login;
+export default function () {
+ return (
+
+
+
+ );
+}
diff --git a/frontend/src/routes/page.tsx b/frontend/src/routes/page.tsx
index a2841c2b..7f503a37 100644
--- a/frontend/src/routes/page.tsx
+++ b/frontend/src/routes/page.tsx
@@ -1,5 +1,7 @@
import Demo from '../components/Demo';
import { Center, Container, Title } from '@mantine/core';
+import { ErrorBoundary } from 'react-error-boundary';
+import { PageFallback } from './fallbacks/PageFallback.tsx';
const Root = () => {
return (
@@ -12,4 +14,10 @@ const Root = () => {
);
};
-export default Root;
+export default function () {
+ return (
+
+
+
+ );
+}
diff --git a/frontend/src/routes/report/page.tsx b/frontend/src/routes/report/page.tsx
index cdf414ee..1880f42b 100644
--- a/frontend/src/routes/report/page.tsx
+++ b/frontend/src/routes/report/page.tsx
@@ -24,6 +24,8 @@ import {
} from './types.ts';
import { transformJson } from './transformJson.ts';
import { RMoodsClient } from '../../rmoods/client/RMoodsClient.ts';
+import { ErrorBoundary } from 'react-error-boundary';
+import { PageFallback } from '../fallbacks/PageFallback.tsx';
/**
* Report component for creating a new report.
@@ -287,4 +289,10 @@ const Report = () => {
);
};
-export default Report;
+export default function () {
+ return (
+
+
+
+ );
+}
diff --git a/frontend/src/routes/settings/page.tsx b/frontend/src/routes/settings/page.tsx
index 771aa3fe..23e5c052 100644
--- a/frontend/src/routes/settings/page.tsx
+++ b/frontend/src/routes/settings/page.tsx
@@ -1,7 +1,15 @@
import { Box } from '@mantine/core';
+import { ErrorBoundary } from 'react-error-boundary';
+import { PageFallback } from '../fallbacks/PageFallback.tsx';
const Settings = () => {
- return Work in progress
+ return Work in progress;
};
-export default Settings;
\ No newline at end of file
+export default function () {
+ return (
+
+
+
+ );
+}
diff --git a/frontend/src/routes/user/ProfilePicture.tsx b/frontend/src/routes/user/ProfilePicture.tsx
index 53198a47..f387465a 100644
--- a/frontend/src/routes/user/ProfilePicture.tsx
+++ b/frontend/src/routes/user/ProfilePicture.tsx
@@ -1,6 +1,8 @@
import React from 'react';
import { Image, Box } from '@mantine/core';
import { changeDefaultGoogleProfilePictureSize } from '../../utility/changeDefaultGoogleProfilePictureSize.ts';
+import { ErrorBoundary } from 'react-error-boundary';
+import { ProfilePictureFallback } from '../../components/fallbacks/ProfilePictureFallback.tsx';
/**
* Props for the ProfilePicture component.
@@ -42,4 +44,10 @@ const ProfilePicture: React.FC = ({ src, alt }) => {
);
};
-export default ProfilePicture;
+export default function ({ src, alt }) {
+ return (
+
+
+
+ );
+}
diff --git a/frontend/src/routes/user/UserCard.tsx b/frontend/src/routes/user/UserCard.tsx
index 66fe8899..c0f9df02 100644
--- a/frontend/src/routes/user/UserCard.tsx
+++ b/frontend/src/routes/user/UserCard.tsx
@@ -30,11 +30,11 @@ const UserCard: React.FC = ({ user }) => {
{user.name}
-
- {user.givenName}
+
+ {user.given_name}
-
-
+
+
{user.email}
diff --git a/frontend/src/routes/user/page.tsx b/frontend/src/routes/user/page.tsx
index a19b2f7d..b43c2446 100644
--- a/frontend/src/routes/user/page.tsx
+++ b/frontend/src/routes/user/page.tsx
@@ -1,18 +1,20 @@
-import { Box, Text } from '@mantine/core';
+import { Box, Text, Loader } from '@mantine/core';
import Cookies from 'js-cookie';
import { jwtDecode } from 'jwt-decode';
-import { useEffect, useState } from 'react';
import UserCard from './UserCard';
import StatisticItem from './StatisticItem';
import { JwtClaims } from '../../rmoods/jwt.ts';
import authFetch from '../../rmoods/client/authFetch.ts';
+import { useQuery } from '@tanstack/react-query';
+import { ErrorBoundary } from 'react-error-boundary';
+import { PageFallback } from '../fallbacks/PageFallback.tsx';
/**
* User interface representing the user data.
*/
export interface User {
name: string;
- givenName: string;
+ given_name: string;
email: string;
picture: string;
}
@@ -31,37 +33,53 @@ const statistics = {
longestReportTime: '2 hours',
};
+/**
+ * Fetches the user data from the server.
+ * @returns {Promise} The user data.
+ */
+const fetchUserData = async (): Promise => {
+ const token = Cookies.get('RMOODS_JWT');
+ if (!token) {
+ throw new Error('No JWT token found');
+ }
+
+ const data = jwtDecode(token);
+ const id = data.userInfo.id;
+ const response = await authFetch(`http://localhost:8001/api/user?id=${id}`);
+ if (!response.ok) {
+ throw new Error('Failed to fetch user data');
+ }
+
+ return response.json();
+};
+
/**
* UserPage component that displays the user's profile and statistics.
* @returns {JSX.Element} The UserPage component.
*/
const UserPage = () => {
- const [user, setUser] = useState(null);
+ const { data, error, isLoading } = useQuery({
+ queryKey: ['userData'],
+ queryFn: fetchUserData,
+ });
- useEffect(() => {
- const token = Cookies.get('RMOODS_JWT');
- if (!token) {
- console.error('No JWT token found');
- throw new Error('No JWT token found');
- }
-
- try {
- const data = jwtDecode(token);
- const id = data.userInfo.id;
-
- authFetch('http://localhost:8001/api/user?id=' + id).then((response) => {
- response.json().then((data) => {
- setUser(data);
- });
- });
- } catch (error) {
- console.error('JWT token could not be decoded.', error);
- throw new Error('JWT token could not be decoded.');
- }
- }, []);
+ if (isLoading) {
+ return (
+
+
+
+ );
+ }
- if (!user) {
- return Loading...;
+ if (error) {
+ return Error: {error.message};
}
return (
@@ -69,43 +87,43 @@ const UserPage = () => {
style={{
display: 'flex',
justifyContent: 'space-between',
- alignItems: 'flex-start'
+ alignItems: 'flex-start',
}}
>
-
+ {data && }
-
+
@@ -114,4 +132,10 @@ const UserPage = () => {
);
};
-export default UserPage;
+export default function () {
+ return (
+
+
+
+ );
+}
diff --git a/frontend/src/utility/logout.ts b/frontend/src/utility/logout.ts
new file mode 100644
index 00000000..ed3aa072
--- /dev/null
+++ b/frontend/src/utility/logout.ts
@@ -0,0 +1,7 @@
+import { NavigateFunction } from 'react-router-dom';
+import Cookies from 'js-cookie';
+
+export const logout = (navigate: NavigateFunction) => {
+ Cookies.remove('RMOODS_JWT');
+ navigate('/login');
+};
diff --git a/frontend/tsconfig.app.json b/frontend/tsconfig.app.json
index c39a9d22..26c231e9 100644
--- a/frontend/tsconfig.app.json
+++ b/frontend/tsconfig.app.json
@@ -18,7 +18,8 @@
"strict": true,
"noUnusedLocals": false,
"noUnusedParameters": true,
- "noFallthroughCasesInSwitch": true
+ "noFallthroughCasesInSwitch": true,
+ "noImplicitAny": false,
},
- "include": ["src"]
+ "include": ["src"],
}
diff --git a/frontend/tsconfig.vite.json b/frontend/tsconfig.vite.json
index 7247e79b..49e1d2b4 100644
--- a/frontend/tsconfig.vite.json
+++ b/frontend/tsconfig.vite.json
@@ -19,5 +19,5 @@
"noFallthroughCasesInSwitch": true,
"noImplicitAny": false,
},
- "include": ["vite.config.ts"]
+ "include": ["vite.config.ts"],
}