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 React, { useMemo, useState, useEffect } from 'react'; | ||||||
| import { | import { | ||||||
|     Drawer, List, ListItemButton, ListItemIcon, ListItemText, |     Drawer, List, ListItemButton, ListItemIcon, ListItemText, | ||||||
| @@ -6,7 +5,6 @@ import { | |||||||
| } from '@mui/material'; | } from '@mui/material'; | ||||||
| import { useTheme } from '@mui/material/styles'; | import { useTheme } from '@mui/material/styles'; | ||||||
|  |  | ||||||
| // MUI icons (you can replace with your PNGs later) |  | ||||||
| import InsightsIcon from '@mui/icons-material/Insights'; | import InsightsIcon from '@mui/icons-material/Insights'; | ||||||
| import Inventory2Icon from '@mui/icons-material/Inventory2'; | import Inventory2Icon from '@mui/icons-material/Inventory2'; | ||||||
| import PeopleAltIcon from '@mui/icons-material/PeopleAlt'; | import PeopleAltIcon from '@mui/icons-material/PeopleAlt'; | ||||||
| @@ -19,7 +17,6 @@ import ExpandMore from '@mui/icons-material/ExpandMore'; | |||||||
| const OPEN_WIDTH = 400; | const OPEN_WIDTH = 400; | ||||||
| const MINI_WIDTH = 72; | const MINI_WIDTH = 72; | ||||||
|  |  | ||||||
| // ---- Hierarchy (from your diagram). Leaves are "CRUD" pages (clickables). ---- |  | ||||||
| const menuData = [ | const menuData = [ | ||||||
|     { |     { | ||||||
|         title: 'Business Intelligence', |         title: 'Business Intelligence', | ||||||
| @@ -100,28 +97,24 @@ const menuData = [ | |||||||
| ]; | ]; | ||||||
|  |  | ||||||
| export default function MenuDrawerPrivate({ | export default function MenuDrawerPrivate({ | ||||||
|     open,                // optional: for mobile temporary drawer |     open, | ||||||
|     onClose,             // optional: for mobile temporary drawer |     onClose, | ||||||
|     onSelect,            // (title) => void |     onSelect, | ||||||
|     onExpandedChange,    // (boolean) => void   // optional: tell header if expanded/collapsed |     onExpandedChange, | ||||||
| }) { | }) { | ||||||
|     const theme = useTheme(); |     const theme = useTheme(); | ||||||
|     const isMobile = useMediaQuery('(max-width:900px)'); |     const isMobile = useMediaQuery('(max-width:900px)'); | ||||||
|  |  | ||||||
|     const [collapsed, setCollapsed] = useState(false); |     const [collapsed, setCollapsed] = useState(false); | ||||||
|     // open states per branch key |  | ||||||
|     const [openMap, setOpenMap] = useState({}); |     const [openMap, setOpenMap] = useState({}); | ||||||
|  |  | ||||||
|     // keep collapsed in sync only if "open" prop provided (mobile) |  | ||||||
|     useEffect(() => { |     useEffect(() => { | ||||||
|         if (!isMobile) return; |         if (!isMobile) return; | ||||||
|         if (typeof open === 'boolean') { |         if (typeof open === 'boolean') { | ||||||
|             // temporary drawer: collapsed UI is not shown on mobile, treat as expanded |  | ||||||
|             setCollapsed(false); |             setCollapsed(false); | ||||||
|         } |         } | ||||||
|     }, [open, isMobile]); |     }, [open, isMobile]); | ||||||
|  |  | ||||||
|     // inform parent of expanded/collapsed (desktop) |  | ||||||
|     useEffect(() => { |     useEffect(() => { | ||||||
|         onExpandedChange?.(isMobile ? true : !collapsed); |         onExpandedChange?.(isMobile ? true : !collapsed); | ||||||
|     }, [collapsed, isMobile, onExpandedChange]); |     }, [collapsed, isMobile, onExpandedChange]); | ||||||
| @@ -131,7 +124,6 @@ export default function MenuDrawerPrivate({ | |||||||
|     const toggleCollapse = () => setCollapsed(c => !c); |     const toggleCollapse = () => setCollapsed(c => !c); | ||||||
|  |  | ||||||
|     const handleToggleNode = (key) => { |     const handleToggleNode = (key) => { | ||||||
|         // if rail collapsed, expand rail first to reveal submenu |  | ||||||
|         if (!isMobile && collapsed) { |         if (!isMobile && collapsed) { | ||||||
|             setCollapsed(false); |             setCollapsed(false); | ||||||
|             setOpenMap((m) => ({ ...m, [key]: true })); |             setOpenMap((m) => ({ ...m, [key]: true })); | ||||||
|   | |||||||
| @@ -1,46 +1,56 @@ | |||||||
|  | import React, { useState } from 'react'; | ||||||
| import { GoogleLogin } from '@react-oauth/google'; | import { GoogleLogin } from '@react-oauth/google'; | ||||||
| import { jwtDecode } from 'jwt-decode'; | import { jwtDecode } from 'jwt-decode'; | ||||||
|  | import ThalosTokenConnector from '../auth/ThalosTokenConnector'; | ||||||
| import { useAuth } from '../context/AuthContext'; | import { useAuth } from '../context/AuthContext'; | ||||||
|  | import { Box, Paper, Typography } from '@mui/material'; | ||||||
| import { useNavigate } from 'react-router-dom'; | import { useNavigate } from 'react-router-dom'; | ||||||
| import { Box, Typography } from '@mui/material'; |  | ||||||
|  |  | ||||||
| export default function LoginPage() { | export default function LoginPage() { | ||||||
|  |     const [googleIdToken, setGoogleIdToken] = useState(null); | ||||||
|  |     const [googleProfile, setGoogleProfile] = useState(null); | ||||||
|     const { login } = useAuth(); |     const { login } = useAuth(); | ||||||
|     const navigate = useNavigate(); |     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 ( |     return ( | ||||||
|         <Box display="flex" flexDirection="column" alignItems="center" mt={10}> |         <Box display="flex" justifyContent="center" alignItems="center" minHeight="100vh"> | ||||||
|             <Typography variant="h4" gutterBottom> |             <Paper sx={{ p: 4, borderRadius: 2, boxShadow: 3, textAlign: 'center' }}> | ||||||
|                 Sign in with Google |                 <Typography variant="h5" mb={2}>Login with Google</Typography> | ||||||
|             </Typography> |  | ||||||
|  |  | ||||||
|                 <GoogleLogin |                 <GoogleLogin | ||||||
|                 onSuccess={handleSuccess} |                     onSuccess={(cred) => { | ||||||
|                 onError={handleError} |                         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> |         </Box> | ||||||
|     ); |     ); | ||||||
| } | } | ||||||
		Reference in New Issue
	
	Block a user
	 Rodolfo Ruiz
					Rodolfo Ruiz