Refactoring

This commit is contained in:
2025-01-19 23:04:08 -06:00
parent a6b0c8c69f
commit e7b4bbb82b
4 changed files with 186 additions and 118 deletions

View File

@@ -13,6 +13,11 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
environment: ${{ inputs.calling_workflow }} 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: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
@@ -32,6 +37,7 @@ jobs:
- name: Build - name: Build
working-directory: app working-directory: app
run: | run: |
printenv
npm run build npm run build
- name: Setup Docker Buildx - name: Setup Docker Buildx
@@ -50,11 +56,6 @@ jobs:
tags: noahspan/flying-app:${{ inputs.version_number }} tags: noahspan/flying-app:${{ inputs.version_number }}
context: . context: .
target: app 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: deploy:
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@@ -5,13 +5,8 @@ COPY ./api/dist ./api/dist
COPY ./api/node_modules ./api/node_modules COPY ./api/node_modules ./api/node_modules
EXPOSE 3000 EXPOSE 3000
CMD ["node", "/api/dist/main.js"] CMD ["node", "/api/dist/main.js"]
# CMD ["tail", "-f", "/dev/null"]
FROM base AS app 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 RUN npm i -g serve
COPY ./app/dist ./app/dist COPY ./app/dist ./app/dist
EXPOSE 8080 EXPOSE 8080

View File

@@ -1,79 +1,179 @@
import { useEffect } from 'react'; import { useEffect, useState } from 'react';
import { Route, Routes } from 'react-router-dom'; import { ISiteNavProps } from './ISiteNavProps';
import Pilots from './components/pilots/Pilots'; import { useNavigate } from 'react-router-dom';
import Logbook from './components/logbook/Logbook'; import { useAppContext } from '../../hooks/appContext/UseAppContext';
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 { AxiosInstance, AxiosResponse } from 'axios';
import { useHttpClient } from './hooks/httpClient/UseHttpClient'; import { User } from '@microsoft/microsoft-graph-types';
import { useFeatureFlag } from './hooks/featureFlag/UseFeatureFlag'; import { EventMessage, EventPayload, EventType } from '@azure/msal-browser';
import SiteNav from './components/siteNav/SiteNav';
import { useMsal } from '@azure/msal-react';
import { Button } from '@noahspan/noahspan-components';
const App: React.FC<unknown> = () => { type EventPayloadExtended = EventPayload & { accessToken: string };
const SiteNav: React.FC<unknown> = () => {
const [loading, setLoading] = useState<boolean>(false);
const [userPhoto, setUserPhoto] = useState<string>();
const httpClient: AxiosInstance = useHttpClient(); const httpClient: AxiosInstance = useHttpClient();
const appContext = useAppContext(); const appContext = useAppContext();
const { instance } = useMsal(); const isAuthenticated = useIsAuthenticated();
const { getAccessToken } = useAccessToken();
// const handleSignInRedirect = () => { const { inProgress, instance } = useMsal();
// instance const navigate = useNavigate();
// .loginRedirect({ const pages = [
// scopes: [`api://${import.meta.env.VITE_CLIENT_ID}/user_impersonation`] {
// }) name: 'Logbook',
// .catch((error) => console.log(error)); url: '/'
// }; },
{
const handleSignInRedirect = () => { name: 'Pilots',
// instance.acquireTokenSilent({ url: '/pilots'
// scopes: [`api://${import.meta.env.VITE_CLIENT_ID}/user_impersonation`]
// })
instance.loginPopup({
scopes: [`api://${import.meta.env.VITE_CLIENT_ID}/user_impersonation`],
prompt: 'create'
})
} }
];
useEffect(() => { const handleSignIn = async () => {
const getFeatureFlags = async () => { await instance.loginRedirect({
try { scopes: [`api://${import.meta.env.VITE_CLIENT_ID}/user_impersonation`]
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
})
}
if (featureFlags.length > 0) {
appContext.dispatch({
type: 'SET_FEATURE_FLAGS',
payload: featureFlags
}); });
};
const handleSignOut = () => {
instance.logoutRedirect();
};
const getUserProfile = async (accessToken: string): Promise<User> => {
try {
const response: AxiosResponse = await httpClient.get(`api/userProfile`, {
headers: {
Authorization: accessToken
} }
});
const userProfile: User = response.data;
return userProfile;
} catch (error) {
throw new Error();
}
};
const getUserPhoto = async (accessToken: string): Promise<string> => {
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);
return imageUrl;
} catch (error) { } catch (error) {
console.log(error); console.log(error);
throw new Error();
}
};
const handlePageClick = (url: string) => {
navigate(url);
};
const Settings = () => {
return (
<MenuItem onClick={handleSignOut}>
<SignOutIcon />
<Typography sx={{ marginLeft: '10px', textAlign: 'center' }}>
Sign Out
</Typography>
</MenuItem>
);
};
useEffect(() => {
const callback = instance.addEventCallback(
async (message: EventMessage) => {
if (message.eventType === EventType.LOGIN_SUCCESS) {
try {
setLoading(true);
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 ( return (
<div> <Navbar
<Button onClick={handleSignInRedirect}>Sign On</Button> handlePageClick={handlePageClick}
{/* <SiteNav /> */} handleSignIn={handleSignIn}
<Routes> isAuthenticated={isAuthenticated}
{useFeatureFlag('pilots')?.enabled && ( logo={<PlaneIcon size="2x" />}
<Route path="/pilots" element={<Pilots />} /> pages={pages}
)} settings={<Settings />}
<Route path="/" element={<Logbook />} /> userPhoto={userPhoto}
</Routes> />
</div>
); );
}; };
export default App; export default SiteNav;

View File

@@ -5,49 +5,20 @@ import AppContextProvider from './context/appContext/AppContextProvider.tsx';
import { BrowserRouter } from 'react-router-dom'; import { BrowserRouter } from 'react-router-dom';
import './index.css'; import './index.css';
import '@noahspan/noahspan-components/noahspan-components.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'; import { MsalProvider } from '@azure/msal-react';
const msalConfig = { const msalInstance: PublicClientApplication = new PublicClientApplication({
auth: { auth: {
clientId: import.meta.env.VITE_CLIENT_ID, clientId: import.meta.env.VITE_CLIENT_ID,
authority: `https://login.microsoftonline.com/${import.meta.env.VITE_TENANT_ID}`, authority: `https://login.microsoftonline.com/${import.meta.env.VITE_TENANT_ID}`,
redirectUri: import.meta.env.VITE_REDIRECT_URL, 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( msalInstance.initialize().then(() => {
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<MsalProvider instance={msalInstance}> <MsalProvider instance={msalInstance}>
<AppContextProvider> <AppContextProvider>
<BrowserRouter> <BrowserRouter>
@@ -55,5 +26,6 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
</BrowserRouter> </BrowserRouter>
</AppContextProvider> </AppContextProvider>
</MsalProvider> </MsalProvider>
); </React.StrictMode>
);
});