From 3115d4513599fdb3f3eb7edabbe74265fcb76e32 Mon Sep 17 00:00:00 2001 From: Rodolfo Ruiz Date: Fri, 29 Aug 2025 21:14:25 -0600 Subject: [PATCH] chore: get token from google, add the thalos connector and get and persist the thalos token id for future use in other endpoints --- src/auth/ThalosTokenConnector.jsx | 59 ++++++++++++++++++++++ src/components/MenuDrawerPrivate.jsx | 16 ++---- src/private/LoginPage.jsx | 74 ++++++++++++++++------------ 3 files changed, 105 insertions(+), 44 deletions(-) create mode 100644 src/auth/ThalosTokenConnector.jsx diff --git a/src/auth/ThalosTokenConnector.jsx b/src/auth/ThalosTokenConnector.jsx new file mode 100644 index 0000000..dc279d3 --- /dev/null +++ b/src/auth/ThalosTokenConnector.jsx @@ -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, +}; \ No newline at end of file diff --git a/src/components/MenuDrawerPrivate.jsx b/src/components/MenuDrawerPrivate.jsx index 50b5d35..9256c63 100644 --- a/src/components/MenuDrawerPrivate.jsx +++ b/src/components/MenuDrawerPrivate.jsx @@ -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 })); diff --git a/src/private/LoginPage.jsx b/src/private/LoginPage.jsx index c021e3f..44d79c6 100644 --- a/src/private/LoginPage.jsx +++ b/src/private/LoginPage.jsx @@ -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 ( - - - Sign in with Google - + + + Login with Google - + { + 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 && ( + { + login({ + ...googleProfile, + idToken: googleIdToken, + thalosToken: payload?.token || '', + }); + navigate('/'); + }} + onError={(err) => { + console.error('Thalos exchange failed:', err); + localStorage.removeItem('thalosToken'); + }} + /> + )} + ); } \ No newline at end of file