chore: get token from google, add the thalos connector and get and persist the thalos token id for future use in other endpoints
This commit is contained in:
59
src/auth/ThalosTokenConnector.jsx
Normal file
59
src/auth/ThalosTokenConnector.jsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export default function ThalosTokenConnector({ googleIdToken, onSuccess, onError }) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!googleIdToken) return;
|
||||
|
||||
let cancelled = false;
|
||||
const run = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await fetch(
|
||||
'https://thalos-bff.dream-views.com/api/v1/Authentication/GenerateToken',
|
||||
{
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${googleIdToken}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
const text = await res.text();
|
||||
throw new Error(`Auth exchange failed (${res.status}): ${text || 'No details'}`);
|
||||
}
|
||||
|
||||
const payload = await res.json();
|
||||
if (cancelled) return;
|
||||
|
||||
if (payload?.token) {
|
||||
localStorage.setItem('thalosToken', payload.token);
|
||||
}
|
||||
|
||||
onSuccess?.(payload);
|
||||
} catch (err) {
|
||||
if (!cancelled) onError?.(err);
|
||||
console.error('Thalos token exchange error:', err);
|
||||
} finally {
|
||||
if (!cancelled) setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
run();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [googleIdToken, onSuccess, onError]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
ThalosTokenConnector.propTypes = {
|
||||
googleIdToken: PropTypes.string.isRequired,
|
||||
onSuccess: PropTypes.func,
|
||||
onError: PropTypes.func,
|
||||
};
|
||||
@@ -1,4 +1,3 @@
|
||||
// src/components/MenuDrawerPrivate.jsx
|
||||
import React, { useMemo, useState, useEffect } from 'react';
|
||||
import {
|
||||
Drawer, List, ListItemButton, ListItemIcon, ListItemText,
|
||||
@@ -6,7 +5,6 @@ import {
|
||||
} from '@mui/material';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
|
||||
// MUI icons (you can replace with your PNGs later)
|
||||
import InsightsIcon from '@mui/icons-material/Insights';
|
||||
import Inventory2Icon from '@mui/icons-material/Inventory2';
|
||||
import PeopleAltIcon from '@mui/icons-material/PeopleAlt';
|
||||
@@ -19,7 +17,6 @@ import ExpandMore from '@mui/icons-material/ExpandMore';
|
||||
const OPEN_WIDTH = 400;
|
||||
const MINI_WIDTH = 72;
|
||||
|
||||
// ---- Hierarchy (from your diagram). Leaves are "CRUD" pages (clickables). ----
|
||||
const menuData = [
|
||||
{
|
||||
title: 'Business Intelligence',
|
||||
@@ -100,28 +97,24 @@ const menuData = [
|
||||
];
|
||||
|
||||
export default function MenuDrawerPrivate({
|
||||
open, // optional: for mobile temporary drawer
|
||||
onClose, // optional: for mobile temporary drawer
|
||||
onSelect, // (title) => void
|
||||
onExpandedChange, // (boolean) => void // optional: tell header if expanded/collapsed
|
||||
open,
|
||||
onClose,
|
||||
onSelect,
|
||||
onExpandedChange,
|
||||
}) {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery('(max-width:900px)');
|
||||
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
// open states per branch key
|
||||
const [openMap, setOpenMap] = useState({});
|
||||
|
||||
// keep collapsed in sync only if "open" prop provided (mobile)
|
||||
useEffect(() => {
|
||||
if (!isMobile) return;
|
||||
if (typeof open === 'boolean') {
|
||||
// temporary drawer: collapsed UI is not shown on mobile, treat as expanded
|
||||
setCollapsed(false);
|
||||
}
|
||||
}, [open, isMobile]);
|
||||
|
||||
// inform parent of expanded/collapsed (desktop)
|
||||
useEffect(() => {
|
||||
onExpandedChange?.(isMobile ? true : !collapsed);
|
||||
}, [collapsed, isMobile, onExpandedChange]);
|
||||
@@ -131,7 +124,6 @@ export default function MenuDrawerPrivate({
|
||||
const toggleCollapse = () => setCollapsed(c => !c);
|
||||
|
||||
const handleToggleNode = (key) => {
|
||||
// if rail collapsed, expand rail first to reveal submenu
|
||||
if (!isMobile && collapsed) {
|
||||
setCollapsed(false);
|
||||
setOpenMap((m) => ({ ...m, [key]: true }));
|
||||
|
||||
@@ -1,46 +1,56 @@
|
||||
import React, { useState } from 'react';
|
||||
import { GoogleLogin } from '@react-oauth/google';
|
||||
import { jwtDecode } from 'jwt-decode';
|
||||
import ThalosTokenConnector from '../auth/ThalosTokenConnector';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import { Box, Paper, Typography } from '@mui/material';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Box, Typography } from '@mui/material';
|
||||
|
||||
export default function LoginPage() {
|
||||
const [googleIdToken, setGoogleIdToken] = useState(null);
|
||||
const [googleProfile, setGoogleProfile] = useState(null);
|
||||
const { login } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleSuccess = (credentialResponse) => {
|
||||
try {
|
||||
const token = credentialResponse.credential;
|
||||
const decoded = jwtDecode(token);
|
||||
console.log('Google user decoded:', decoded);
|
||||
|
||||
// save user in context
|
||||
login({
|
||||
name: decoded.name,
|
||||
email: decoded.email,
|
||||
picture: decoded.picture,
|
||||
token,
|
||||
});
|
||||
console.log('User logged in and saved to context token:', token);
|
||||
} catch (err) {
|
||||
console.error('Token decode failed:', err);
|
||||
}
|
||||
};
|
||||
|
||||
const handleError = () => {
|
||||
console.error('Google login failed');
|
||||
};
|
||||
|
||||
return (
|
||||
<Box display="flex" flexDirection="column" alignItems="center" mt={10}>
|
||||
<Typography variant="h4" gutterBottom>
|
||||
Sign in with Google
|
||||
</Typography>
|
||||
<Box display="flex" justifyContent="center" alignItems="center" minHeight="100vh">
|
||||
<Paper sx={{ p: 4, borderRadius: 2, boxShadow: 3, textAlign: 'center' }}>
|
||||
<Typography variant="h5" mb={2}>Login with Google</Typography>
|
||||
|
||||
<GoogleLogin
|
||||
onSuccess={handleSuccess}
|
||||
onError={handleError}
|
||||
/>
|
||||
<GoogleLogin
|
||||
onSuccess={(cred) => {
|
||||
const idToken = cred?.credential;
|
||||
if (!idToken) return;
|
||||
|
||||
const decoded = jwtDecode(idToken);
|
||||
setGoogleIdToken(idToken);
|
||||
setGoogleProfile({
|
||||
name: decoded?.name || '',
|
||||
email: decoded?.email || '',
|
||||
picture: decoded?.picture || '',
|
||||
});
|
||||
}}
|
||||
onError={() => console.error('Google login failed')}
|
||||
/>
|
||||
|
||||
{googleIdToken && (
|
||||
<ThalosTokenConnector
|
||||
googleIdToken={googleIdToken}
|
||||
onSuccess={(payload) => {
|
||||
login({
|
||||
...googleProfile,
|
||||
idToken: googleIdToken,
|
||||
thalosToken: payload?.token || '',
|
||||
});
|
||||
navigate('/');
|
||||
}}
|
||||
onError={(err) => {
|
||||
console.error('Thalos exchange failed:', err);
|
||||
localStorage.removeItem('thalosToken');
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user