From e7b4bbb82b59234ef8066b79ec553a7b362e12e3 Mon Sep 17 00:00:00 2001 From: Noah Spannbauer Date: Sun, 19 Jan 2025 23:04:08 -0600 Subject: [PATCH] Refactoring --- .github/workflows/app.yaml | 11 +- Dockerfile | 5 - app/src/App.tsx | 218 +++++++++++++++++++++++++++---------- app/src/main.tsx | 70 ++++-------- 4 files changed, 186 insertions(+), 118 deletions(-) diff --git a/.github/workflows/app.yaml b/.github/workflows/app.yaml index 6911295..759249e 100644 --- a/.github/workflows/app.yaml +++ b/.github/workflows/app.yaml @@ -13,6 +13,11 @@ jobs: build: runs-on: ubuntu-latest environment: ${{ inputs.calling_workflow }} + env: + VITE_API_URL: ${{ secrets.VITE_API_URL }} + VITE_CLIENT_ID: ${{ secrets.VITE_CLIENT_ID }} + VITE_TENANT_ID: ${{ secrets.VITE_TENANT_ID }} + VITE_REDIRECT_URL: ${{ secrets.VITE_REDIRECT_URL }} steps: - uses: actions/checkout@v4 with: @@ -32,6 +37,7 @@ jobs: - name: Build working-directory: app run: | + printenv npm run build - name: Setup Docker Buildx @@ -50,11 +56,6 @@ jobs: tags: noahspan/flying-app:${{ inputs.version_number }} context: . target: app - build-args: | - VITE_API_URL=${{ secrets.VITE_API_URL }} - VITE_CLIENT_ID=${{ secrets.VITE_CLIENT_ID }} - VITE_TENANT_ID=${{ secrets.VITE_TENANT_ID }} - VITE_REDIRECT_URL=${{ secrets.VITE_REDIRECT_URL }} deploy: runs-on: ubuntu-latest diff --git a/Dockerfile b/Dockerfile index dcfa3cd..a43d4a5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,13 +5,8 @@ COPY ./api/dist ./api/dist COPY ./api/node_modules ./api/node_modules EXPOSE 3000 CMD ["node", "/api/dist/main.js"] -# CMD ["tail", "-f", "/dev/null"] FROM base AS app -ARG VITE_API_URL -ARG VITE_CLIENT_ID -ARG VITE_TENANT_ID -ARG VITE_REDIRECT_URL RUN npm i -g serve COPY ./app/dist ./app/dist EXPOSE 8080 diff --git a/app/src/App.tsx b/app/src/App.tsx index c85d853..50eb147 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -1,79 +1,179 @@ -import { useEffect } from 'react'; -import { Route, Routes } from 'react-router-dom'; -import Pilots from './components/pilots/Pilots'; -import Logbook from './components/logbook/Logbook'; -import { useAppContext } from './hooks/appContext/UseAppContext'; +import { useEffect, useState } from 'react'; +import { ISiteNavProps } from './ISiteNavProps'; +import { useNavigate } from 'react-router-dom'; +import { useAppContext } from '../../hooks/appContext/UseAppContext'; +import { + Avatar, + Button, + IconButton, + Menu, + MenuItem, + Navbar, + PlaneIcon, + SignOutIcon, + Spinner, + Typography +} from '@noahspan/noahspan-components'; +import { useIsAuthenticated, useMsal } from '@azure/msal-react'; +import { InteractionStatus } from '@azure/msal-browser'; +import { useAccessToken } from '../../hooks/accessToken/UseAcessToken'; +import { useHttpClient } from '../../hooks/httpClient/UseHttpClient'; import { AxiosInstance, AxiosResponse } from 'axios'; -import { useHttpClient } from './hooks/httpClient/UseHttpClient'; -import { useFeatureFlag } from './hooks/featureFlag/UseFeatureFlag'; -import SiteNav from './components/siteNav/SiteNav'; -import { useMsal } from '@azure/msal-react'; -import { Button } from '@noahspan/noahspan-components'; +import { User } from '@microsoft/microsoft-graph-types'; +import { EventMessage, EventPayload, EventType } from '@azure/msal-browser'; -const App: React.FC = () => { +type EventPayloadExtended = EventPayload & { accessToken: string }; + +const SiteNav: React.FC = () => { + const [loading, setLoading] = useState(false); + const [userPhoto, setUserPhoto] = useState(); const httpClient: AxiosInstance = useHttpClient(); const appContext = useAppContext(); - const { instance } = useMsal(); + const isAuthenticated = useIsAuthenticated(); + const { getAccessToken } = useAccessToken(); + const { inProgress, instance } = useMsal(); + const navigate = useNavigate(); + const pages = [ + { + name: 'Logbook', + url: '/' + }, + { + name: 'Pilots', + url: '/pilots' + } + ]; + const handleSignIn = async () => { + await instance.loginRedirect({ + scopes: [`api://${import.meta.env.VITE_CLIENT_ID}/user_impersonation`] + }); + }; + const handleSignOut = () => { + instance.logoutRedirect(); + }; + const getUserProfile = async (accessToken: string): Promise => { + try { + const response: AxiosResponse = await httpClient.get(`api/userProfile`, { + headers: { + Authorization: accessToken + } + }); + const userProfile: User = response.data; - // const handleSignInRedirect = () => { - // instance - // .loginRedirect({ - // scopes: [`api://${import.meta.env.VITE_CLIENT_ID}/user_impersonation`] - // }) - // .catch((error) => console.log(error)); - // }; + return userProfile; + } catch (error) { + throw new Error(); + } + }; + const getUserPhoto = async (accessToken: string): Promise => { + try { + const response: AxiosResponse = await httpClient.get(`api/userPhoto`, { + headers: { + Authorization: accessToken + }, + responseType: 'arraybuffer' + }); + const arrayBufferView = new Uint8Array(response.data); + const blob = new Blob([arrayBufferView], { type: 'image/png' }); + const imageUrl = window.URL.createObjectURL(blob); - const handleSignInRedirect = () => { - // instance.acquireTokenSilent({ - // scopes: [`api://${import.meta.env.VITE_CLIENT_ID}/user_impersonation`] - // }) - instance.loginPopup({ - scopes: [`api://${import.meta.env.VITE_CLIENT_ID}/user_impersonation`], - prompt: 'create' - }) - } + return imageUrl; + } catch (error) { + console.log(error); + throw new Error(); + } + }; + const handlePageClick = (url: string) => { + navigate(url); + }; + + const Settings = () => { + return ( + + + + Sign Out + + + ); + }; useEffect(() => { - const getFeatureFlags = async () => { - try { - const featureFlags: { key: string; enabled: boolean }[] = [] - const response: AxiosResponse = await httpClient.get( - 'api/featureFlags' - ); - - for (const featureFlag of response.data) { - featureFlags.push({ - key: featureFlag.rowKey, - enabled: featureFlag.active === 'true' ? true : false - }) - } + const callback = instance.addEventCallback( + async (message: EventMessage) => { + if (message.eventType === EventType.LOGIN_SUCCESS) { + try { + setLoading(true); - if (featureFlags.length > 0) { - appContext.dispatch({ - type: 'SET_FEATURE_FLAGS', - payload: featureFlags - }); + const eventPayload: EventPayloadExtended = + message.payload as EventPayloadExtended; + const userProfile: User = await getUserProfile( + eventPayload.accessToken + ); + const userPhoto = await getUserPhoto(eventPayload.accessToken); + + appContext.dispatch({ + type: 'SET_USER_PROFILE', + payload: userProfile + }); + } catch (error) { + console.log(error); + } finally { + setLoading(false); + } } + } + ); + + return () => { + if (callback) { + instance.removeEventCallback(callback); + appContext.dispatch({ type: 'SET_USER_PROFILE', payload: {} }); + } + }; + }, []); + + useEffect(() => { + const setUserProfile = async () => { + try { + setLoading(true); + + const accessToken: string = await getAccessToken(); + const userProfile = await getUserProfile(accessToken); + const userPhoto = await getUserPhoto(accessToken); + + setUserPhoto(userPhoto); + + appContext.dispatch({ + type: 'SET_USER_PROFILE', + payload: userProfile + }); } catch (error) { console.log(error); + } finally { + setLoading(false); } }; - getFeatureFlags(); - }, []); + if ( + isAuthenticated && + Object.keys(appContext.state.userProfile).length === 0 + ) { + setUserProfile(); + } + }, [isAuthenticated]); return ( -
- - {/* */} - - {useFeatureFlag('pilots')?.enabled && ( - } /> - )} - } /> - -
+ } + pages={pages} + settings={} + userPhoto={userPhoto} + /> ); }; -export default App; +export default SiteNav; \ No newline at end of file diff --git a/app/src/main.tsx b/app/src/main.tsx index 3a855ea..63d5d57 100644 --- a/app/src/main.tsx +++ b/app/src/main.tsx @@ -5,55 +5,27 @@ import AppContextProvider from './context/appContext/AppContextProvider.tsx'; import { BrowserRouter } from 'react-router-dom'; import './index.css'; import '@noahspan/noahspan-components/noahspan-components.css'; -import { LogLevel, PublicClientApplication } from '@azure/msal-browser'; +import { PublicClientApplication } from '@azure/msal-browser'; import { MsalProvider } from '@azure/msal-react'; -const msalConfig = { - auth: { - clientId: import.meta.env.VITE_CLIENT_ID, - authority: `https://login.microsoftonline.com/${import.meta.env.VITE_TENANT_ID}`, - redirectUri: import.meta.env.VITE_REDIRECT_URL, - }, - // cache: { - // cacheLocation: "sessionStorage", // This configures where your cache will be stored - // storeAuthStateInCookie: false, // Set this to "true" if you are having issues on IE11 or Edge - // }, - // system: { - // loggerOptions: { - // loggerCallback: (level: any, message: any, containsPii: any) => { - // if (containsPii) { - // return; - // } - // switch (level) { - // case LogLevel.Error: - // console.error(message); - // return; - // case LogLevel.Info: - // console.info(message); - // return; - // case LogLevel.Verbose: - // console.debug(message); - // return; - // case LogLevel.Warning: - // console.warn(message); - // return; - // default: - // return; - // } - // } - // } - // } -}; -console.log(msalConfig) -const msalInstance = new PublicClientApplication(msalConfig) - -ReactDOM.createRoot(document.getElementById('root')!).render( - - - - - - - -); +const msalInstance: PublicClientApplication = new PublicClientApplication({ + auth: { + clientId: import.meta.env.VITE_CLIENT_ID, + authority: `https://login.microsoftonline.com/${import.meta.env.VITE_TENANT_ID}`, + redirectUri: import.meta.env.VITE_REDIRECT_URL + } +}); +msalInstance.initialize().then(() => { + ReactDOM.createRoot(document.getElementById('root')!).render( + + + + + + + + + + ); +}); \ No newline at end of file