Refactoring
This commit is contained in:
11
.github/workflows/app.yaml
vendored
11
.github/workflows/app.yaml
vendored
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
216
app/src/App.tsx
216
app/src/App.tsx
@@ -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 { 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<User> => {
|
||||||
|
try {
|
||||||
|
const response: AxiosResponse = await httpClient.get(`api/userProfile`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: accessToken
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const userProfile: User = response.data;
|
||||||
|
|
||||||
// const handleSignInRedirect = () => {
|
return userProfile;
|
||||||
// instance
|
} catch (error) {
|
||||||
// .loginRedirect({
|
throw new Error();
|
||||||
// scopes: [`api://${import.meta.env.VITE_CLIENT_ID}/user_impersonation`]
|
}
|
||||||
// })
|
};
|
||||||
// .catch((error) => console.log(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);
|
||||||
|
|
||||||
const handleSignInRedirect = () => {
|
return imageUrl;
|
||||||
// instance.acquireTokenSilent({
|
} catch (error) {
|
||||||
// scopes: [`api://${import.meta.env.VITE_CLIENT_ID}/user_impersonation`]
|
console.log(error);
|
||||||
// })
|
throw new Error();
|
||||||
instance.loginPopup({
|
}
|
||||||
scopes: [`api://${import.meta.env.VITE_CLIENT_ID}/user_impersonation`],
|
};
|
||||||
prompt: 'create'
|
const handlePageClick = (url: string) => {
|
||||||
})
|
navigate(url);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const Settings = () => {
|
||||||
|
return (
|
||||||
|
<MenuItem onClick={handleSignOut}>
|
||||||
|
<SignOutIcon />
|
||||||
|
<Typography sx={{ marginLeft: '10px', textAlign: 'center' }}>
|
||||||
|
Sign Out
|
||||||
|
</Typography>
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getFeatureFlags = async () => {
|
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 {
|
try {
|
||||||
const featureFlags: { key: string; enabled: boolean }[] = []
|
setLoading(true);
|
||||||
const response: AxiosResponse = await httpClient.get(
|
|
||||||
'api/featureFlags'
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const featureFlag of response.data) {
|
const accessToken: string = await getAccessToken();
|
||||||
featureFlags.push({
|
const userProfile = await getUserProfile(accessToken);
|
||||||
key: featureFlag.rowKey,
|
const userPhoto = await getUserPhoto(accessToken);
|
||||||
enabled: featureFlag.active === 'true' ? true : false
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (featureFlags.length > 0) {
|
setUserPhoto(userPhoto);
|
||||||
appContext.dispatch({
|
|
||||||
type: 'SET_FEATURE_FLAGS',
|
appContext.dispatch({
|
||||||
payload: featureFlags
|
type: 'SET_USER_PROFILE',
|
||||||
});
|
payload: userProfile
|
||||||
}
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(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;
|
||||||
@@ -5,55 +5,27 @@ 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(
|
|
||||||
<MsalProvider instance={msalInstance}>
|
|
||||||
<AppContextProvider>
|
|
||||||
<BrowserRouter>
|
|
||||||
<App />
|
|
||||||
</BrowserRouter>
|
|
||||||
</AppContextProvider>
|
|
||||||
</MsalProvider>
|
|
||||||
);
|
|
||||||
|
|
||||||
|
msalInstance.initialize().then(() => {
|
||||||
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<MsalProvider instance={msalInstance}>
|
||||||
|
<AppContextProvider>
|
||||||
|
<BrowserRouter>
|
||||||
|
<App />
|
||||||
|
</BrowserRouter>
|
||||||
|
</AppContextProvider>
|
||||||
|
</MsalProvider>
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user