adding app context and feature flag hooks

This commit is contained in:
2024-05-28 12:33:39 -05:00
parent bea4e7f04b
commit 4d9c15bc8a
12 changed files with 93 additions and 44 deletions

View File

@@ -26,7 +26,7 @@
"@nestjs/common": "^10.0.0", "@nestjs/common": "^10.0.0",
"@nestjs/core": "^10.0.0", "@nestjs/core": "^10.0.0",
"@nestjs/platform-express": "^10.0.0", "@nestjs/platform-express": "^10.0.0",
"@noahspan/noahspan-modules": "^0.1.1", "@noahspan/noahspan-modules": "^0.1.2",
"@schematics/angular": "^17.3.7", "@schematics/angular": "^17.3.7",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"reflect-metadata": "0.1.13", "reflect-metadata": "0.1.13",

View File

@@ -10,8 +10,8 @@ export class AppController {
private readonly authService: AuthService private readonly authService: AuthService
) {} ) {}
@Get('featureFlag') @Get('featureFlags')
async getFeatureFlag(@Query() query: any): Promise<FeatureFlagValue[]> { async getFeatureFlags(@Query() query: any): Promise<FeatureFlagValue[]> {
try { try {
const token: string = await this.authService.getToken( const token: string = await this.authService.getToken(
'client_credentials', 'client_credentials',

View File

@@ -12,6 +12,7 @@
"dependencies": { "dependencies": {
"@azure/msal-react": "^2.0.15", "@azure/msal-react": "^2.0.15",
"@nextui-org/react": "^2.3.6", "@nextui-org/react": "^2.3.6",
"axios": "^1.7.2",
"framer-motion": "^11.1.7", "framer-motion": "^11.1.7",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",

View File

@@ -1,12 +1,40 @@
import { useEffect } from 'react';
import { Route, Routes } from 'react-router-dom'; import { Route, Routes } from 'react-router-dom';
import Logbook from './components/logbook/Logbook';
import Pilots from './components/pilots/Pilots'; import Pilots from './components/pilots/Pilots';
import { useAppContext } from './hooks/appContext/UseAppContext';
import axios, { AxiosResponse } from 'axios';
import { useFeatureFlag } from './hooks/featureFlag/UseFeatureFlag';
const App: React.FC<unknown> = () => { const App: React.FC<unknown> = () => {
const appContext = useAppContext();
useEffect(() => {
const getFeatureFlags = async () => {
try {
const response: AxiosResponse = await axios.get(
`http://localhost:7071/api/featureFlags?key=flying*&label=${process.env.NODE_ENV}`
);
const featureFlags: unknown[] = response.data;
if (featureFlags.length > 0) {
appContext.dispatch({
type: 'SET_FEATURE_FLAGS',
payload: featureFlags
});
}
} catch (error) {
console.log(error);
}
};
getFeatureFlags();
}, []);
return ( return (
<Routes> <Routes>
<Route path="logbook" element={<Logbook />} /> {useFeatureFlag('flying-pilots')?.enabled && (
<Route path="pilots" element={<Pilots />} /> <Route path="/" element={<Pilots />} />
)}
</Routes> </Routes>
); );
}; };

View File

@@ -9,6 +9,7 @@ import {
} from '@nextui-org/react'; } from '@nextui-org/react';
import { Link as ReactRouterLink } from 'react-router-dom'; import { Link as ReactRouterLink } from 'react-router-dom';
import { useIsAuthenticated, useMsal } from '@azure/msal-react'; import { useIsAuthenticated, useMsal } from '@azure/msal-react';
import { useAppContext } from '../../hooks/appContext/UseAppContext';
const SiteNav: React.FC<unknown> = () => { const SiteNav: React.FC<unknown> = () => {
const isAuthenticated = useIsAuthenticated(); const isAuthenticated = useIsAuthenticated();
@@ -16,6 +17,7 @@ const SiteNav: React.FC<unknown> = () => {
const initializeLogin = () => { const initializeLogin = () => {
instance.loginRedirect(); instance.loginRedirect();
}; };
const appContext = useAppContext();
console.log(accounts); console.log(accounts);
@@ -24,24 +26,13 @@ const SiteNav: React.FC<unknown> = () => {
<NavbarBrand>Site Logo Goes Here</NavbarBrand> <NavbarBrand>Site Logo Goes Here</NavbarBrand>
<NavbarContent justify="center"> <NavbarContent justify="center">
<NavbarItem> <NavbarItem>
<Link> {appContext.state.featureFlags.find(
<ReactRouterLink to="/">Flights</ReactRouterLink> (featureFlag) => featureFlag.id === 'flying-pilots'
</Link> )?.enabled && (
</NavbarItem> <Link>
<NavbarItem> <ReactRouterLink to="/">Pilots</ReactRouterLink>
<Link> </Link>
<ReactRouterLink to="/logbook">Logbook</ReactRouterLink> )}
</Link>
</NavbarItem>
<NavbarItem>
<Link>
<ReactRouterLink to="/checklists">Checklists</ReactRouterLink>
</Link>
</NavbarItem>
<NavbarItem>
<Link>
<ReactRouterLink to="/pilots">Pilots</ReactRouterLink>
</Link>
</NavbarItem> </NavbarItem>
</NavbarContent> </NavbarContent>
<NavbarContent justify="end"> <NavbarContent justify="end">

View File

@@ -1,5 +1,9 @@
import { useMemo, useReducer } from 'react';
import { AppContext } from './AppContext';
import { IAppContextProviderProps } from './IAppContextProviderProps'; import { IAppContextProviderProps } from './IAppContextProviderProps';
import { IAppContextProps } from './IAppContextProps';
import { IAppContextState } from './IAppContextState'; import { IAppContextState } from './IAppContextState';
import { reducer } from './reducer';
const AppContextProvider: React.FC<IAppContextProviderProps> = ( const AppContextProvider: React.FC<IAppContextProviderProps> = (
props: IAppContextProviderProps props: IAppContextProviderProps
@@ -8,15 +12,12 @@ const AppContextProvider: React.FC<IAppContextProviderProps> = (
featureFlags: [] featureFlags: []
}; };
const [state, dispatch] = useReducer(reducer, intialState); const [state, dispatch] = useReducer(reducer, intialState);
const contextValue = (IAppContextProps = useMemo(() => { const contextValue: IAppContextProps = useMemo(() => {
return ( return {
{ state,
state, dispatch
dispatch };
}, }, [state, dispatch]);
[state, dispatch]
);
}));
return ( return (
<AppContext.Provider value={contextValue}> <AppContext.Provider value={contextValue}>

View File

@@ -1,3 +1,6 @@
import { Action } from './reducer';
import { IAppContextState } from './IAppContextState';
export interface IAppContextProps { export interface IAppContextProps {
state: IAppContextState; state: IAppContextState;
dispatch: React.Dispatch<Action>; dispatch: React.Dispatch<Action>;

View File

@@ -0,0 +1,11 @@
import { useContext } from 'react';
import { AppContext } from '../../context/appContext/AppContext';
export const useAppContext = () => {
const { state, dispatch } = useContext(AppContext);
return {
state,
dispatch
};
};

View File

@@ -0,0 +1,10 @@
import { useAppContext } from '../appContext/UseAppContext';
export const useFeatureFlag = (featureFlagId: string) => {
const appContext = useAppContext();
const featureFlag = appContext.state.featureFlags.find(
(featureFlag) => featureFlag.id === featureFlagId
);
return featureFlag;
};

View File

@@ -4,8 +4,9 @@ import { MsalProvider } from '@azure/msal-react';
import { Configuration, PublicClientApplication } from '@azure/msal-browser'; import { Configuration, PublicClientApplication } from '@azure/msal-browser';
import { NextUIProvider } from '@nextui-org/react'; import { NextUIProvider } from '@nextui-org/react';
import App from './App.tsx'; import App from './App.tsx';
import './index.css';
import { BrowserRouter } from 'react-router-dom'; import { BrowserRouter } from 'react-router-dom';
import AppContextProvider from './context/appContext/AppContextProvider.tsx';
import './index.css';
const configuration: Configuration = { const configuration: Configuration = {
auth: { auth: {
@@ -21,11 +22,13 @@ const pca = new PublicClientApplication(configuration);
ReactDOM.createRoot(document.getElementById('root')!).render( ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode> <React.StrictMode>
<MsalProvider instance={pca}> <MsalProvider instance={pca}>
<NextUIProvider> <AppContextProvider>
<BrowserRouter> <NextUIProvider>
<App /> <BrowserRouter>
</BrowserRouter> <App />
</NextUIProvider> </BrowserRouter>
</NextUIProvider>
</AppContextProvider>
</MsalProvider> </MsalProvider>
</React.StrictMode> </React.StrictMode>
); );

View File

@@ -5,7 +5,7 @@ export default {
content: [ content: [
'./index.html', './index.html',
'./src/**/*.{js,ts,jsx,tsx}', './src/**/*.{js,ts,jsx,tsx}',
'./node_modules/@nextui-org/theme/dist/**/*.{js,ts,jsx,tsx}' '../node_modules/@nextui-org/theme/dist/**/*.{js,ts,jsx,tsx}'
], ],
theme: { theme: {
extend: {} extend: {}

9
package-lock.json generated
View File

@@ -37,7 +37,7 @@
"@nestjs/common": "^10.0.0", "@nestjs/common": "^10.0.0",
"@nestjs/core": "^10.0.0", "@nestjs/core": "^10.0.0",
"@nestjs/platform-express": "^10.0.0", "@nestjs/platform-express": "^10.0.0",
"@noahspan/noahspan-modules": "^0.1.1", "@noahspan/noahspan-modules": "^0.1.2",
"@schematics/angular": "^17.3.7", "@schematics/angular": "^17.3.7",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"reflect-metadata": "0.1.13", "reflect-metadata": "0.1.13",
@@ -67,6 +67,7 @@
"dependencies": { "dependencies": {
"@azure/msal-react": "^2.0.15", "@azure/msal-react": "^2.0.15",
"@nextui-org/react": "^2.3.6", "@nextui-org/react": "^2.3.6",
"axios": "^1.7.2",
"framer-motion": "^11.1.7", "framer-motion": "^11.1.7",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
@@ -4402,9 +4403,9 @@
"link": true "link": true
}, },
"node_modules/@noahspan/noahspan-modules": { "node_modules/@noahspan/noahspan-modules": {
"version": "0.1.1", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/@noahspan/noahspan-modules/-/noahspan-modules-0.1.1.tgz", "resolved": "https://registry.npmjs.org/@noahspan/noahspan-modules/-/noahspan-modules-0.1.2.tgz",
"integrity": "sha512-9rljhDl2AZeIMWZgTR59pFvC7N3i+j332vsfNnHMAh5ApaNjgmWZ6La/8MkGJmcZ8pxT9cqXDaH27A8YWKbJBQ==", "integrity": "sha512-cD1PUgcFvy/9LTvxfzjk43+n+wZ4eTfWIZk1XFyM+NjjvP9cks2QnpQJ+di+7LYuzO8EPRX0/Ln34ywwwOCZaQ==",
"dependencies": { "dependencies": {
"@azure/app-configuration": "^1.6.0", "@azure/app-configuration": "^1.6.0",
"@nestjs/common": "^10.0.0", "@nestjs/common": "^10.0.0",