) => {
setStrings(
@@ -185,13 +251,24 @@ function App() {
removeAlert,
}), [alerts, addAlert, updateAlert, removeAlert]);
+ if (meLoading) {
+ return (
+ // FIXME: Use translation
+
+ Checking user session...
+
+ );
+ }
+
return (
-
-
-
-
-
+
+
+
+
+
+
+
);
}
diff --git a/src/App/routes/common.tsx b/src/App/routes/common.tsx
new file mode 100644
index 00000000..bfad45ea
--- /dev/null
+++ b/src/App/routes/common.tsx
@@ -0,0 +1,47 @@
+import {
+ type MyInputIndexRouteObject,
+ type MyInputNonIndexRouteObject,
+ type MyOutputIndexRouteObject,
+ type MyOutputNonIndexRouteObject,
+ wrapRoute,
+} from '#utils/routes';
+import { Component as RootLayout } from '#views/RootLayout';
+
+import Auth from '../Auth';
+import PageError from '../PageError';
+
+export type ExtendedProps = {
+ title: string,
+ visibility: 'is-authenticated' | 'is-not-authenticated' | 'anything',
+ permissions?: (
+ params: Record | undefined | null,
+ ) => boolean;
+};
+
+interface CustomWrapRoute {
+ (
+ myRouteOptions: MyInputIndexRouteObject
+ ): MyOutputIndexRouteObject
+ (
+ myRouteOptions: MyInputNonIndexRouteObject
+ ): MyOutputNonIndexRouteObject
+}
+
+export const customWrapRoute: CustomWrapRoute = wrapRoute;
+
+// NOTE: We should not use layout or index routes in links
+
+export const rootLayout = customWrapRoute({
+ path: '/',
+ errorElement: ,
+ component: {
+ eagerLoad: true,
+ render: RootLayout,
+ props: {},
+ },
+ wrapperComponent: Auth,
+ context: {
+ title: 'IFRC Alert Hub',
+ visibility: 'anything',
+ },
+});
diff --git a/src/App/routes/index.tsx b/src/App/routes/index.tsx
index e8d8987c..e194c249 100644
--- a/src/App/routes/index.tsx
+++ b/src/App/routes/index.tsx
@@ -6,11 +6,13 @@ import {
MyOutputIndexRouteObject,
MyOutputNonIndexRouteObject,
unwrapRoute,
- wrapRoute,
} from '#utils/routes';
-import { Component as RootLayout } from '#views/RootLayout';
-import PageError from '../PageError';
+import Auth from '../Auth';
+import {
+ customWrapRoute,
+ rootLayout,
+} from './common';
// NOTE: setting default ExtendedProps
export type ExtendedProps = {
@@ -27,22 +29,6 @@ export interface MyWrapRoute {
): MyOutputNonIndexRouteObject
}
-const customWrapRoute: MyWrapRoute = wrapRoute;
-
-const rootLayout = customWrapRoute({
- path: '/',
- errorElement: ,
- component: {
- render: RootLayout,
- eagerLoad: true,
- props: {},
- },
- context: {
- title: 'IFRC Alert Hub',
- visibility: 'anything',
- },
-});
-
type DefaultHomeChild = 'map';
const homeLayout = customWrapRoute({
parent: rootLayout,
@@ -51,6 +37,7 @@ const homeLayout = customWrapRoute({
render: () => import('#views/Home'),
props: {},
},
+ wrapperComponent: Auth,
context: {
title: 'IFRC Alert Hub',
visibility: 'anything',
@@ -64,6 +51,7 @@ const mySubscription = customWrapRoute({
render: () => import('#views/MySubscription'),
props: {},
},
+ wrapperComponent: Auth,
context: {
title: 'My Subscriptions',
// TODO: Change visibility after login feature
@@ -82,6 +70,7 @@ const homeIndex = customWrapRoute({
replace: true,
},
},
+ wrapperComponent: Auth,
context: {
title: 'IFRC Alert Hub',
visibility: 'anything',
@@ -95,6 +84,7 @@ const homeMap = customWrapRoute({
render: () => import('#views/Home/AlertsMap'),
props: {},
},
+ wrapperComponent: Auth,
context: {
title: 'IFRC Alert Hub - Map',
visibility: 'anything',
@@ -108,6 +98,7 @@ const homeTable = customWrapRoute({
render: () => import('#views/Home/AlertsTable'),
props: {},
},
+ wrapperComponent: Auth,
context: {
title: 'IFRC Alert Hub - Table',
visibility: 'anything',
@@ -121,6 +112,7 @@ const preferences = customWrapRoute({
render: () => import('#views/Preferences'),
props: {},
},
+ wrapperComponent: Auth,
context: {
title: 'Preferences',
visibility: 'anything',
@@ -134,6 +126,7 @@ const historicalAlerts = customWrapRoute({
render: () => import('#views/HistoricalAlerts'),
props: {},
},
+ wrapperComponent: Auth,
context: {
title: 'Historical Alerts',
visibility: 'anything',
@@ -147,6 +140,7 @@ const about = customWrapRoute({
render: () => import('#views/About'),
props: {},
},
+ wrapperComponent: Auth,
context: {
title: 'About',
visibility: 'anything',
@@ -160,6 +154,7 @@ const resources = customWrapRoute({
render: () => import('#views/Resources'),
props: {},
},
+ wrapperComponent: Auth,
context: {
title: 'Resources',
visibility: 'anything',
@@ -173,6 +168,7 @@ const alertDetails = customWrapRoute({
render: () => import('#views/AlertDetails'),
props: {},
},
+ wrapperComponent: Auth,
context: {
title: 'Alert Details',
visibility: 'anything',
@@ -186,6 +182,7 @@ const allSourcesFeeds = customWrapRoute({
render: () => import('#views/AllSourcesFeeds'),
props: {},
},
+ wrapperComponent: Auth,
context: {
title: 'Sources Feeds',
visibility: 'anything',
@@ -199,6 +196,7 @@ const pageNotFound = customWrapRoute({
render: () => import('#views/PageNotFound'),
props: {},
},
+ wrapperComponent: Auth,
context: {
title: '404',
visibility: 'anything',
@@ -212,6 +210,7 @@ const login = customWrapRoute({
render: () => import('#views/Login'),
props: {},
},
+ wrapperComponent: Auth,
context: {
title: 'Login',
visibility: 'is-not-authenticated',
@@ -225,6 +224,7 @@ const recoverAccount = customWrapRoute({
render: () => import('#views/RecoverAccount'),
props: {},
},
+ wrapperComponent: Auth,
context: {
title: 'Recover Account',
visibility: 'is-not-authenticated',
@@ -238,6 +238,7 @@ const resendValidationEmail = customWrapRoute({
render: () => import('#views/ResendValidationEmail'),
props: {},
},
+ wrapperComponent: Auth,
context: {
title: 'Resend Validation Email',
visibility: 'is-not-authenticated',
@@ -251,6 +252,7 @@ const cookiePolicy = customWrapRoute({
render: () => import('#views/CookiePolicy'),
props: {},
},
+ wrapperComponent: Auth,
context: {
title: 'Cookie Policy',
visibility: 'anything',
diff --git a/src/App/styles.module.css b/src/App/styles.module.css
new file mode 100644
index 00000000..62cf9777
--- /dev/null
+++ b/src/App/styles.module.css
@@ -0,0 +1,7 @@
+.loading {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 100vw;
+ height: 100vh;
+}
diff --git a/src/components/Link/index.tsx b/src/components/Link/index.tsx
index b85ac36a..1f26d4c5 100644
--- a/src/components/Link/index.tsx
+++ b/src/components/Link/index.tsx
@@ -21,7 +21,7 @@ import {
} from '@togglecorp/fujs';
import RouteContext from '#contexts/route';
-import useAuth from '#hooks/useAuth';
+import useAuth from '#hooks/domain/useAuth';
import { type WrappedRoutes } from '../../App/routes';
diff --git a/src/components/Navbar/i18n.json b/src/components/Navbar/i18n.json
index 7ffcd233..117178a3 100644
--- a/src/components/Navbar/i18n.json
+++ b/src/components/Navbar/i18n.json
@@ -7,6 +7,8 @@
"appResources": "Resources",
"headerMenuHome": "Home",
"headerMenuMySubscription": "My Subscriptions",
- "historicalAlerts": "Historical Alerts"
+ "historicalAlerts": "Historical Alerts",
+ "logoutFailure": "Failed to logout",
+ "userLogout":"Logout"
}
}
diff --git a/src/components/Navbar/index.tsx b/src/components/Navbar/index.tsx
index eb1ebc2b..06221e83 100644
--- a/src/components/Navbar/index.tsx
+++ b/src/components/Navbar/index.tsx
@@ -1,4 +1,10 @@
+import { useContext } from 'react';
import {
+ gql,
+ useMutation,
+} from '@apollo/client';
+import {
+ Button,
Heading,
NavigationTabList,
PageContainer,
@@ -9,18 +15,67 @@ import { _cs } from '@togglecorp/fujs';
import goLogo from '#assets/icons/go-logo-2020.svg';
import Link from '#components/Link';
import NavigationTab from '#components/NavigationTab';
+import UserContext from '#contexts/user';
+import { LogoutMutation } from '#generated/types/graphql';
+import useAuth from '#hooks/domain/useAuth';
+import useAlert from '#hooks/useAlert';
import LangaugeDropdown from './LanguageDropdown';
import i18n from './i18n.json';
import styles from './styles.module.css';
+const LOGOUT = gql`
+ mutation Logout {
+ private {
+ logout {
+ ok
+ errors
+ }
+ }
+ }
+`;
+
interface Props {
className?: string;
}
function Navbar(props: Props) {
const { className } = props;
const strings = useTranslation(i18n);
+ const { isAuthenticated } = useAuth();
+ const alert = useAlert();
+
+ const {
+ removeUserAuth: removeUser,
+ } = useContext(UserContext);
+
+ const [
+ triggerLogout,
+ { loading: logoutPending },
+ ] = useMutation(
+ LOGOUT,
+ {
+ onCompleted: (logoutResponse) => {
+ const response = logoutResponse?.private?.logout;
+ if (response.ok) {
+ window.location.reload();
+ removeUser();
+ } else {
+ alert.show(
+ strings.logoutFailure,
+ { variant: 'danger' },
+ );
+ }
+ },
+ onError: () => {
+ alert.show(
+ strings.logoutFailure,
+ { variant: 'danger' },
+ );
+ },
+ },
+ );
+
return (