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} | ||||
|                     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
	 Rodolfo Ruiz
					Rodolfo Ruiz