Skip to content

Commit

Permalink
Merge branch 'NetManagerMapFunc' of github.com:AKATWIJUKA-ELIA/AirQo-…
Browse files Browse the repository at this point in the history
…frontend into NetManagerMapFunc
  • Loading branch information
AKATWIJUKA-ELIA committed Feb 28, 2025
2 parents 95c73e0 + 688af45 commit eaad0b8
Show file tree
Hide file tree
Showing 5 changed files with 8,345 additions and 4 deletions.
2 changes: 1 addition & 1 deletion k8s/platform/values-stage.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ replicaCount: 1
image:
repository: eu.gcr.io/airqo-250220/airqo-stage-next-platform
pullPolicy: Always
tag: stage-07e1532b-1740386563
tag: stage-963fc29b-1740488187
imagePullSecrets: []
nameOverride: ''
fullnameOverride: ''
Expand Down
1 change: 1 addition & 0 deletions src/platform/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"fuse.js": "^7.0.0",
"html2canvas": "^1.4.1",
"i18n-iso-countries": "^7.7.0",
"js-cookie": "^3.0.5",
"json2csv": "^6.0.0-alpha.2",
"jspdf": "^2.5.2",
"jspdf-autotable": "^3.8.4",
Expand Down
79 changes: 77 additions & 2 deletions src/platform/src/core/utils/protectedRoute.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,87 @@ import React, { useEffect } from 'react';
import { useRouter } from 'next/router';
import { useSelector, useDispatch } from 'react-redux';
import LogoutUser from '@/core/utils/LogoutUser';
import Cookies from 'js-cookie';
import jwt_decode from 'jwt-decode';
import {
setUserInfo,
setSuccess,
} from '@/lib/store/services/account/LoginSlice';
import { getIndividualUserPreferences } from '@/lib/store/services/account/UserDefaultsSlice';
import { getUserDetails } from '@/core/apis/Account';
import Spinner from '../../common/components/Spinner';

const MAX_RETRIES = 3;
const RETRY_DELAY = 1000;

export default function withAuth(Component) {
return function WithAuthComponent(props) {
const dispatch = useDispatch();
const router = useRouter();
const userCredentials = useSelector((state) => state.login);
const [isRedirecting, setIsRedirecting] = React.useState(
router.query.success === 'google',
);

const retryWithDelay = async (fn, retries = MAX_RETRIES) => {
try {
return await fn();
} catch (error) {
if (retries > 0 && error.response?.status === 429) {
await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY));
return retryWithDelay(fn, retries - 1);
}
throw error;
}
};

const setupUserSession = async (user) => {
if (!user.groups[0]?.grp_title) {
throw new Error(
'Server error. Contact support to add you to the AirQo Organisation',
);
}

localStorage.setItem('loggedUser', JSON.stringify(user));

const preferencesResponse = await retryWithDelay(() =>
dispatch(getIndividualUserPreferences({ identifier: user._id })),
);
if (preferencesResponse.payload.success) {
const preferences = preferencesResponse.payload.preferences;
const activeGroup = preferences[0]?.group_id
? user.groups.find((group) => group._id === preferences[0].group_id)
: user.groups.find((group) => group.grp_title === 'airqo');
localStorage.setItem('activeGroup', JSON.stringify(activeGroup));
}

dispatch(setUserInfo(user));
dispatch(setSuccess(true));
};

useEffect(() => {
if (typeof window !== 'undefined') {
const storedUserGroup = localStorage.getItem('activeGroup');
// Handle Google redirect first
if (router.query.success === 'google') {
const token = Cookies.get('access_token');
if (token) {
localStorage.setItem('token', token);
const decoded = jwt_decode(token);
retryWithDelay(() => getUserDetails(decoded._id, token))
.then((response) => setupUserSession(response.users[0]))
.catch((error) => {
console.error('Google auth error:', error);
setIsRedirecting(false);
router.push('/account/login');
});
} else {
setIsRedirecting(false);
router.push('/account/login');
}
return; // Exit early to prevent further checks until redirect is resolved
}

const storedUserGroup = localStorage.getItem('activeGroup');
if (!userCredentials.success) {
router.push('/account/login');
}
Expand All @@ -21,7 +91,12 @@ export default function withAuth(Component) {
LogoutUser(dispatch, router);
}
}
}, [userCredentials, dispatch, router]);
}, [userCredentials, dispatch, router, retryWithDelay, isRedirecting]);

// Block rendering until redirect is handled
if (isRedirecting) {
return <Spinner width={20} height={20} />;
}

// Render the component if the user is authenticated
return userCredentials.success ? <Component {...props} /> : null;
Expand Down
24 changes: 24 additions & 0 deletions src/platform/src/pages/account/login/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
} from '@/lib/store/services/account/LoginSlice';
import { getIndividualUserPreferences } from '@/lib/store/services/account/UserDefaultsSlice';
import { postUserLoginDetails, getUserDetails } from '@/core/apis/Account';
import { GOOGLE_AUTH_URL } from '@/core/urls/authentication';

const MAX_RETRIES = 3;
const RETRY_DELAY = 1000;
Expand Down Expand Up @@ -110,6 +111,15 @@ const UserLogin = () => {
dispatch(setUserData({ key, value }));
};

const handleGoogleLogin = async () => {
try {
// Redirect to Google auth URL
window.location.href = GOOGLE_AUTH_URL;
} catch (error) {
console.error('Login error:', error);
}
};

return (
<AccountPageLayout
pageTitle="AirQo Analytics | Login"
Expand Down Expand Up @@ -168,6 +178,7 @@ const UserLogin = () => {
</div>
</div>
</div>

<div className="mt-10">
<button
data-testid="login-btn"
Expand All @@ -181,6 +192,19 @@ const UserLogin = () => {
'Login'
)}
</button>

<button
data-testid="google-login-btn"
className="w-full btn border-blue-900 rounded-[12px] text-white text-sm outline-none border"
disabled={loading}
onClick={handleGoogleLogin}
>
{loading ? (
<Spinner data-testid="spinner" width={25} height={25} />
) : (
'Login with Google'
)}
</button>
</div>
</form>
<div className="mt-8 w-full flex justify-center">
Expand Down
Loading

0 comments on commit eaad0b8

Please sign in to comment.