feature/4-pilots---add

This commit is contained in:
2024-06-22 07:14:30 -05:00
parent 7bcddf9d13
commit 9d5a6f7d09
35 changed files with 1430 additions and 3201 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@@ -28,7 +28,7 @@
"@nestjs/core": "^10.0.0",
"@nestjs/passport": "^10.0.3",
"@nestjs/platform-express": "^10.0.0",
"@noahspan/noahspan-modules": "^0.2.7",
"@noahspan/noahspan-modules": "^0.3.4",
"@schematics/angular": "^17.3.7",
"dotenv": "^16.4.5",
"reflect-metadata": "0.1.13",

View File

@@ -1,4 +1,4 @@
import { Controller, Get, Query } from '@nestjs/common';
import { Controller, Get, Headers, Query } from '@nestjs/common';
import {
AppConfigService,
MsGraphService,
@@ -42,12 +42,14 @@ export class AppController {
async getProfilePhoto(@Query() query: any): Promise<any> {}
@Get('userProfile')
async getUserProfile(@Query() query: any) {
async getUserProfile(@Headers() headers: any) {
try {
const username: string = query.username;
const graphToken: string = await this.msGraphService.getMsGraphAuth(
headers.authorization.replace('Bearer ', '')
);
const client: MsGraphClient =
await this.msGraphService.getMsGraphClient();
const userProfile = await client.api(`users/${username}`).get();
await this.msGraphService.getMsGraphClientDelegated(graphToken);
const userProfile = await client.api(`me`).get();
return userProfile;
} catch (error) {

View File

@@ -9,6 +9,8 @@ import {
} from '@noahspan/noahspan-modules';
import { ConfigModule } from '@nestjs/config';
import { APP_GUARD } from '@nestjs/core';
import { PilotModule } from './pilot/pilot.module';
import { PilotController } from './pilot/pilot.controller';
@Module({
imports: [
@@ -28,9 +30,10 @@ import { APP_GUARD } from '@nestjs/core';
tenantId: process.env.TENANT_ID,
clientId: process.env.CLIENT_ID,
clientSecret: process.env.CLIENT_SECRET
})
}),
PilotModule
],
controllers: [AppController],
controllers: [AppController, PilotController],
providers: [
{
provide: APP_GUARD,

View File

@@ -0,0 +1,7 @@
export class Certificate {
partitionKey: string;
rowKey: string;
type: string;
issueDate: Date;
number?: string;
}

View File

@@ -0,0 +1,37 @@
import { Injectable } from '@nestjs/common';
import { Repository, InjectRepository } from '@nestjs/azure-database';
import { Certificate } from './certificate.entity';
@Injectable()
export class CertificateService {
private readonly partitionKey: string = 'certificate';
constructor(
@InjectRepository(Certificate)
private readonly certificateRepository: Repository<Certificate>
) {}
async find(rowKey: string): Promise<Certificate> {
return await this.certificateRepository.find(this.partitionKey, rowKey);
}
async findAll(): Promise<Certificate[]> {
return await this.certificateRepository.findAll();
}
async create(certificate: Certificate): Promise<Certificate> {
return await this.certificateRepository.create(certificate);
}
async update(rowKey: string, certificate: Certificate): Promise<Certificate> {
return await this.certificateRepository.update(
this.partitionKey,
rowKey,
certificate
);
}
async delete(rowKey: string): Promise<void> {
await this.certificateRepository.delete(this.partitionKey, rowKey);
}
}

View File

@@ -0,0 +1,6 @@
export class Endorsement {
partitionkey: string;
rowKey: string;
type: string;
issueDate: Date;
}

View File

@@ -0,0 +1,37 @@
import { InjectRepository, Repository } from '@nestjs/azure-database';
import { Injectable } from '@nestjs/common';
import { Endorsement } from './endorsement.entity';
@Injectable()
export class EndosementService {
private readonly partitionKey: string = 'endorsement';
constructor(
@InjectRepository(Endorsement)
private readonly endorsementRepository: Repository<Endorsement>
) {}
async find(rowKey: string): Promise<Endorsement> {
return await this.endorsementRepository.find(this.partitionKey, rowKey);
}
async findAll(): Promise<Endorsement[]> {
return await this.endorsementRepository.findAll();
}
async create(endorsement: Endorsement): Promise<Endorsement> {
return await this.endorsementRepository.create(endorsement);
}
async update(rowKey: string, endorsement: Endorsement): Promise<Endorsement> {
return await this.endorsementRepository.update(
this.partitionKey,
rowKey,
endorsement
);
}
async delete(rowKey: string): Promise<void> {
await this.endorsementRepository.delete(this.partitionKey, rowKey);
}
}

View File

@@ -0,0 +1,6 @@
export class Medical {
partitionKey: string;
rowKey: string;
certificateClass: string;
certificateExpiration: Date;
}

View File

@@ -0,0 +1,33 @@
import { Injectable } from '@nestjs/common';
import { Repository, InjectRepository } from '@nestjs/azure-database';
import { Medical } from './medical.entity';
@Injectable()
export class MedicalService {
private readonly partitionKey: string = 'medical';
constructor(
@InjectRepository(Medical)
private readonly profileRepository: Repository<Medical>
) {}
async find(rowKey: string): Promise<Medical> {
return this.profileRepository.find(this.partitionKey, rowKey);
}
async findAll(): Promise<Medical[]> {
return this.profileRepository.findAll();
}
async create(profile: Medical): Promise<Medical> {
return this.profileRepository.create(profile);
}
async update(rowKey: string, profile: Medical): Promise<Medical> {
return this.profileRepository.update(this.partitionKey, rowKey, profile);
}
async delete(rowKey: string) {
return this.profileRepository.delete(this.partitionKey, rowKey);
}
}

View File

@@ -0,0 +1,18 @@
import { Body, Controller, Get, Post, Put, Query } from '@nestjs/common';
import { Public } from '@noahspan/noahspan-modules';
import { PilotDTO } from './pilot.dto';
import { Pilot } from './pilot.entity';
@Controller('pilots')
export class PilotController {
constructor() {}
@Public()
@Post()
async createPilot(@Body() pilotDto: PilotDTO) {
const pilot = new Pilot();
Object.assign(pilot, pilotDto);
console.log(pilot);
}
}

View File

@@ -0,0 +1,11 @@
import { Certificate } from './certificate/certificate.entity';
import { Endorsement } from './endorsement/endorsement.entity';
import { Medical } from './medical/medical.entity';
import { Profile } from './profile/profile.entity';
export class PilotDTO {
profile?: Profile;
medical?: Medical;
certificate?: Certificate;
endosement?: Endorsement;
}

View File

@@ -0,0 +1,11 @@
import { Certificate } from './certificate/certificate.entity';
import { Endorsement } from './endorsement/endorsement.entity';
import { Medical } from './medical/medical.entity';
import { Profile } from './profile/profile.entity';
export class Pilot {
profile: Profile;
medical: Medical;
certificates: Certificate;
endorsements: Endorsement;
}

View File

@@ -0,0 +1,17 @@
import { Module } from '@nestjs/common';
import { PilotController } from './pilot.controller';
import { PilotService } from './pilot.service';
import { AzureTableStorageModule } from '@nestjs/azure-database';
import { Pilot } from './pilot.entity';
@Module({
imports: [
AzureTableStorageModule.forFeature(Pilot, {
table: 'Pilot',
createTableIfNotExists: true
})
],
controllers: [PilotController],
providers: [PilotService]
})
export class PilotModule {}

View File

@@ -0,0 +1,17 @@
import { Inject, Injectable } from '@nestjs/common';
import { Repository, InjectRepository } from '@nestjs/azure-database';
import { Medical } from './medical/medical.entity';
import { Profile } from './profile/profile.entity';
import { ProfileService } from './profile/profile.service';
import { Pilot } from './pilot.entity';
@Injectable()
export class PilotService {
constructor(
@InjectRepository(Pilot) private readonly pilotRepository: Repository<Pilot>
) {}
async create(pilot: Pilot): Promise<Pilot> {
return await this.pilotRepository.create(pilot);
}
}

View File

@@ -0,0 +1,11 @@
export class Profile {
partitionKey: string;
rowKey: string;
firstName: string;
lastName: string;
address: string;
city: string;
state: string;
postalCode: string;
lastFlightReview: Date;
}

View File

@@ -0,0 +1,37 @@
import { Injectable } from '@nestjs/common';
import { Repository, InjectRepository } from '@nestjs/azure-database';
import { Profile } from './profile.entity';
@Injectable()
export class ProfileService {
private readonly partitionKey: string = 'profile';
constructor(
@InjectRepository(Profile)
private readonly profileRepository: Repository<Profile>
) {}
async find(rowKey: string): Promise<Profile> {
return await this.profileRepository.find(this.partitionKey, rowKey);
}
async findAll(): Promise<Profile[]> {
return await this.profileRepository.findAll();
}
async create(profile: Profile): Promise<Profile> {
return await this.profileRepository.create(profile);
}
async update(rowKey: string, profile: Profile): Promise<Profile> {
return await this.profileRepository.update(
this.partitionKey,
rowKey,
profile
);
}
async delete(rowKey: string): Promise<void> {
await this.profileRepository.delete(this.partitionKey, rowKey);
}
}

View File

@@ -10,13 +10,13 @@
"preview": "vite preview"
},
"dependencies": {
"@azure/msal-react": "^2.0.15",
"@azure/msal-browser": "^3.17.0",
"@azure/msal-react": "^2.0.19",
"@fortawesome/fontawesome-svg-core": "^6.5.2",
"@fortawesome/free-regular-svg-icons": "^6.5.2",
"@fortawesome/free-solid-svg-icons": "^6.5.2",
"@fortawesome/react-fontawesome": "^0.2.2",
"@nextui-org/react": "^2.3.6",
"@noahspan/noahspan-components": "^0.2.5",
"@noahspan/noahspan-components": "^0.3.0",
"axios": "^1.7.2",
"framer-motion": "^11.1.7",
"react": "^18.2.0",
@@ -25,6 +25,7 @@
"react-router-dom": "^6.23.0"
},
"devDependencies": {
"@microsoft/microsoft-graph-types": "^2.40.0",
"@types/react": "^18.2.66",
"@types/react-dom": "^18.2.22",
"@vitejs/plugin-react": "^4.2.1",

View File

@@ -5,10 +5,25 @@ import { useAppContext } from './hooks/appContext/UseAppContext';
import { AxiosInstance, AxiosResponse } from 'axios';
import { useHttpClient } from './hooks/httpClient/UseHttpClient';
import { useFeatureFlag } from './hooks/featureFlag/UseFeatureFlag';
import { useMsal } from '@azure/msal-react';
import SiteNav from './components/siteNav/SiteNav';
import { EventMessage, EventPayload, EventType } from '@azure/msal-browser';
import { User } from '@microsoft/microsoft-graph-types';
type EventPayloadExtended = EventPayload & { accessToken: string };
const App: React.FC<unknown> = () => {
const httpClient: AxiosInstance = useHttpClient();
const appContext = useAppContext();
const { instance } = useMsal();
const handleSignIn = async () => {
await instance.loginRedirect({
scopes: [`api://${import.meta.env.VITE_CLIENT_ID}/user_impersonation`]
});
};
const handleSignOut = () => {
instance.logoutRedirect();
};
useEffect(() => {
const getFeatureFlags = async () => {
@@ -33,12 +48,51 @@ const App: React.FC<unknown> = () => {
getFeatureFlags();
}, []);
useEffect(() => {
const callback = instance.addEventCallback(
async (message: EventMessage) => {
if (message.eventType === EventType.LOGIN_SUCCESS) {
try {
const eventPayload: EventPayloadExtended =
message.payload as EventPayloadExtended;
const response: AxiosResponse = await httpClient.get(
`api/userProfile`,
{
headers: {
Authorization: `Bearer ${eventPayload.accessToken}`
}
}
);
const userProfile: User = response.data;
appContext.dispatch({
type: 'SET_USER_PROFILE',
payload: userProfile
});
} catch (error) {
console.log(error);
}
}
}
);
return () => {
if (callback) {
instance.removeEventCallback(callback);
appContext.dispatch({ type: 'SET_USER_PROFILE', payload: {} });
}
};
}, []);
return (
<Routes>
{useFeatureFlag('flying-pilots')?.enabled && (
<Route path="/" element={<Pilots />} />
)}
</Routes>
<>
<SiteNav handleSignIn={handleSignIn} handleSignOut={handleSignOut} />
<Routes>
{useFeatureFlag('flying-pilots')?.enabled && (
<Route path="/" element={<Pilots />} />
)}
</Routes>
</>
);
};

View File

@@ -1,9 +1,6 @@
import SiteNav from '../siteNav/SiteNav';
const Logbook: React.FC<unknown> = () => {
return (
<div>
<SiteNav />
<div>Logbook goes here</div>
</div>
);

View File

@@ -7,78 +7,150 @@
// Select,
// SelectItem
// } from '@nextui-org/react';
// import { useForm, Controller, SubmitHandler } from 'react-hook-form';
// import { useForm, Controller, FormProvider, SubmitHandler, Form } from 'react-hook-form';
// import PilotFormCertificates from '../pilotFormCertificates/PilotFormCertificates';
// const PilotForm: React.FC<unknown> = () => {
// const { control, handleSubmit } = useForm();
// const methods = useForm({
// defaultValues: {
// certificates: [
// {
// type: '',
// number: '',
// dateOfIssue: null
// }
// ]
// }
// });
// const { control, handleSubmit } = methods;
// const onSubmit = (data: unknown) => {
// console.log(data);
// };
// return (
// <form className="m-10" onSubmit={handleSubmit(onSubmit)}>
// <div className="grid grid-cols-2 gap-4">
// <div className="self-center">
// <label>First Name</label>
// </div>
// <div>
// <Controller
// name="firstName"
// control={control}
// render={({ field }) => <Input />}
// />
// </div>
// <div className="self-center">
// <label>Last Name</label>
// </div>
// <div>
// <Controller
// name="lastName"
// control={control}
// render={({ field }) => <Input />}
// />
// </div>
// </div>
// <Accordion>
// <AccordionItem title="Medical Certificate">
// <div className="grid grid-cols-2 gap-4">
// <div className="self-center">
// <label>Class</label>
// <FormProvider {...methods}>
// <form className="m-10" onSubmit={handleSubmit(onSubmit)}>
// <Accordion>
// <AccordionItem title='Profile'>
// <div className="grid grid-cols-3 gap-4">
// <div className="self-center">
// <label>First Name</label>
// </div>
// <div className='col-span-2'>
// <Controller
// name="firstName"
// control={control}
// render={({ field }) => <Input />}
// />
// </div>
// <div className="self-center">
// <label>Last Name</label>
// </div>
// <div className='col-span-2'>
// <Controller
// name="lastName"
// control={control}
// render={({ field }) => <Input />}
// />
// </div>
// <div className='self-center'>
// <label>Address</label>
// </div>
// <div className='col-span-2'>
// <Controller
// name='address'
// control={control}
// render={({ field }) => <Input />}
// />
// </div>
// <div className='self-center'>
// <label>City</label>
// </div>
// <div className='col-span-2'>
// <Controller
// name='city'
// control={control}
// render={({ field }) => <Input />}
// />
// </div>
// <div className='self-center'>
// <label>State</label>
// </div>
// <div className='col-span-2'>
// <Controller
// name='state'
// control={control}
// render={({ field }) => <Input />}
// />
// </div>
// <div className='self-center'>
// <label>Postal Code</label>
// </div>
// <div className='col-span-2'>
// <Controller
// name='postalCode'
// control={control}
// render={({ field }) => <Input />}
// />
// </div>
// </div>
// </AccordionItem>
// </Accordion>
// <Accordion>
// <AccordionItem title='Certificates'>
// <PilotFormCertificates certificates={[]}/>
// </AccordionItem>
// </Accordion>
// <Accordion>
// <AccordionItem title='Endorsements'>
// </AccordionItem>
// </Accordion>
// <Accordion>
// <AccordionItem title="Medical">
// <div className="grid grid-cols-2 gap-4">
// <div className="self-center">
// <label>Class</label>
// </div>
// <div>
// <Controller
// name="medicalClass"
// control={control}
// render={({ field }) => (
// <Select>
// <SelectItem key="first" value="First">
// First
// </SelectItem>
// <SelectItem key="second" value="Second">
// Second
// </SelectItem>
// <SelectItem key="third" value="Third">
// Third
// </SelectItem>
// <SelectItem key="basicMed" value="Basic Med">
// Basic Med
// </SelectItem>
// </Select>
// )}
// />
// </div>
// <div className="self-center">
// <label>Expires</label>
// </div>
// <div>
// <Controller
// name="medicalExpiration"
// control={control}
// render={({ field }) => <DatePicker />}
// />
// </div>
// </div>
// <div>
// <Controller
// name="medicalClass"
// control={control}
// render={({ field }) => (
// <Select>
// <SelectItem key="first" value="First">
// First
// </SelectItem>
// <SelectItem key="second" value="Second">
// Second
// </SelectItem>
// <SelectItem key="Third" value="Third">
// Third
// </SelectItem>
// </Select>
// )}
// />
// </div>
// <div className="self-center">
// <label>Expires</label>
// </div>
// <div>
// <Controller
// name="medicalExpiration"
// control={control}
// render={({ field }) => <DatePicker />}
// />
// </div>
// </div>
// </AccordionItem>
// </Accordion>
// </form>
// </AccordionItem>
// </Accordion>
// </form>
// </FormProvider>
// );
// };

View File

@@ -0,0 +1,5 @@
import { Certificate } from './certificate.type';
export interface IPilotFormCertificates {
certificates: Certificate[];
}

View File

@@ -0,0 +1,104 @@
// import { IPilotFormCertificates } from './IPilotFormCertificates';
// import { Button, DatePicker, Input, Select, SelectItem } from '@nextui-org/react';
// import { Controller, useFieldArray, useFormContext } from 'react-hook-form'
// const PilotFormCertificates: React.FC<IPilotFormCertificates> = ({ certificates }: IPilotFormCertificates) => {
// const { control, formState: { errors } } = useFormContext();
// const { fields, append, remove } = useFieldArray({
// name: 'certificates',
// control
// })
// return (
// <div className='grid grid-cols-4 gap-4'>
// <div className='self-center'>
// <label>Type</label>
// </div>
// <div className='self-center'>
// <label>Number</label>
// </div>
// <div className='self-center '>
// <label>Date of Issue</label>
// </div>
// <div className='self-center'>
// </div>
// {fields.map((field, index) => {
// return (
// <>
// {/* // <div key={field.id}> */}
// <div>
// <Controller
// name={`certificates.${index}.type`}
// control={control}
// render={({ field }) => {
// console.log(field)
// return (
// <Select {...field}>
// <SelectItem key="student" value="Student">
// Student
// </SelectItem>
// <SelectItem key="private" value="Private">
// Private
// </SelectItem>
// <SelectItem key="instrument" value="Instrument">
// Instrument
// </SelectItem>
// <SelectItem key="recreational" value="Recreational">
// Recreational
// </SelectItem>
// <SelectItem key="sport" value="Sport">
// Sport
// </SelectItem>
// </Select>
// )
// }}
// />
// </div>
// <div>
// <Controller
// name={`certificates.${index}.number`}
// control={control}
// render={({ field }) => {
// return (
// <Input {...field} />
// )
// }}
// />
// </div>
// <div>
// <Controller
// name={`certificates.${index}.dateOfIssue`}
// control={control}
// render={({ field }) => {
// return (
// <DatePicker {...field} />
// )
// }}
// />
// </div>
// <div>
// <Button onClick={() => remove(index)}>Remove</Button>
// </div>
// {/* </div> */}
// </>
// )
// })}
// <Button
// onClick={() => {
// append({
// type: '',
// number: '',
// dateOfIssue: null
// });
// }}
// >
// Add
// </Button>
// </div>
// )
// }
// export default PilotFormCertificates;

View File

@@ -0,0 +1,5 @@
export type Certificate = {
type: string;
number: string;
dateOfIssue: Date;
};

View File

@@ -1,16 +1,6 @@
import { useState } from 'react';
import SiteNav from '../siteNav/SiteNav';
// import PilotForm from '../pilotForm/PilotForm';
import { Button } from '@nextui-org/react';
import {
Drawer,
DrawerBody,
DrawerContent,
DrawerFooter,
DrawerHeader
} from '@noahspan/noahspan-components';
import { faPlus } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Button, Drawer } from '@noahspan/noahspan-components';
const Pilots: React.FC<unknown> = () => {
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
@@ -20,24 +10,26 @@ const Pilots: React.FC<unknown> = () => {
return (
<div className="container mx-auto">
<SiteNav />
blah
<div className="px-6">
<h1 role="heading">Pilots</h1>
<Button
color="default"
variant="light"
variant="filled"
onClick={onOpenCloseDrawer}
startContent={<FontAwesomeIcon icon={faPlus} />}
// startContent={<FontAwesomeIcon icon={faPlus} />}
data-testid="new-pilot-button"
>
New
</Button>
<Drawer isOpen={isDrawerOpen} data-testid="pilot-drawer">
<DrawerContent>
<Drawer open={isDrawerOpen} data-testid="pilot-drawer">
Blah
{/* <DrawerContent>
<DrawerHeader>
<h2>Add Pilot</h2>
</DrawerHeader>
<DrawerBody>{/* <PilotForm /> */}</DrawerBody>
<DrawerBody>
<PilotForm />
</DrawerBody>
<DrawerFooter>
<div className="flex gap-4 justify-end justify-self-center">
<div>
@@ -56,7 +48,7 @@ const Pilots: React.FC<unknown> = () => {
</div>
</div>
</DrawerFooter>
</DrawerContent>
</DrawerContent> */}
</Drawer>
</div>
</div>

View File

@@ -1,65 +1,116 @@
// import { Link as ReactRouterLink } from 'react-router-dom';
import { useAppContext } from '../../hooks/appContext/UseAppContext';
import {
Avatar,
Link,
Button,
Menu,
MenuHandler,
MenuItem,
MenuList,
Navbar,
NavbarBrand,
NavbarContent,
NavbarItem,
Button
} from '@nextui-org/react';
import { Link as ReactRouterLink } from 'react-router-dom';
import { useIsAuthenticated, useMsal } from '@azure/msal-react';
import { useAppContext } from '../../hooks/appContext/UseAppContext';
import { Logo, Plane } from '@noahspan/noahspan-components';
NavbarLinks,
NavbarMenu,
NavbarItemProps,
Plane,
Typography
} from '@noahspan/noahspan-components';
import { useIsAuthenticated } from '@azure/msal-react';
const SiteNav: React.FC<unknown> = () => {
const isAuthenticated = useIsAuthenticated();
const { accounts, instance } = useMsal();
const initializeLogin = () => {
instance.loginRedirect();
};
interface SiteNavProps {
handleSignIn: () => void;
handleSignOut: () => void;
}
const SiteNav: React.FC<SiteNavProps> = ({
handleSignIn,
handleSignOut
}: SiteNavProps) => {
const appContext = useAppContext();
const isAuthenticated = useIsAuthenticated();
const navItems: NavbarItemProps[] = [
{
name: 'Pilots',
url: '#'
}
];
return (
<Navbar
classNames={{
base: 'bg-transparent z-0'
}}
isBlurred={false}
maxWidth="full"
data-testid="flying-navbar"
>
<Navbar>
<NavbarBrand>
<Logo className="pr-3" height={50} width={50} data-testid="logo" />
<Plane size="2xl" />
<img height={40} width={40} src="noahspan-logo.png" />{' '}
<Plane size="2x" />
</NavbarBrand>
<NavbarContent justify="center">
<NavbarItem>
{appContext.state.featureFlags.find(
(featureFlag) => featureFlag.key === 'flying-pilots'
)?.enabled && (
<Link>
<ReactRouterLink to="/">Pilots</ReactRouterLink>
</Link>
<NavbarLinks items={navItems} />
<NavbarMenu>
<div className="flex items-center gap-2 hidden lg:inline-block">
{isAuthenticated && (
<Menu placement="bottom-end">
<MenuHandler>
<div>
<Typography>
{appContext.state.userProfile.displayName}
</Typography>
</div>
</MenuHandler>
<MenuList>
<MenuItem>
<Button variant="text" size="sm" onClick={handleSignOut}>
Sign Out
</Button>
</MenuItem>
</MenuList>
</Menu>
)}
</NavbarItem>
</NavbarContent>
<NavbarContent justify="end">
{!isAuthenticated && (
<Button
as={Link}
color="primary"
href="#"
onClick={initializeLogin}
data-testid="login-link"
>
Login
</Button>
)}
{isAuthenticated && <Avatar name={accounts[0]?.username} />}
</NavbarContent>
{!isAuthenticated && (
<Button variant="text" size="sm" onClick={handleSignIn}>
Sign In
</Button>
)}
</div>
{/* <IconButton
variant='text'
className='ml-auto h-6 w-6 text-inherit hover:bg-transparent focus:bg-transparent active:bg-transparent lg:hidden'
ripple={false}
onClick={() => setOpenNav(!openNav)}
>
<FontAwesomeIcon icon={faBars} size='2x' />
</IconButton> */}
</NavbarMenu>
</Navbar>
);
// return (
// <Navbar
// classNames={{
// base: 'bg-transparent z-0'
// }}
// isBlurred={false}
// maxWidth="full"
// data-testid="flying-navbar"
// >
// <NavbarBrand>
// <Logo className="pr-3" height={50} width={50} data-testid="logo" />
// <Plane size="2xl" />
// </NavbarBrand>
// <NavbarContent justify="center">
// <NavbarItem>
// {appContext.state.featureFlags.find(
// (featureFlag) => featureFlag.key === 'flying-pilots'
// )?.enabled && (
// <Link>
// <ReactRouterLink to="/">Pilots</ReactRouterLink>
// </Link>
// )}
// </NavbarItem>
// </NavbarContent>
// <NavbarContent justify='end'>
// <Login
// loginCompleted={loginCompleted}
// loginView='compact'
// />
// </NavbarContent>
// </Navbar>
// );
};
export default SiteNav;

View File

@@ -9,7 +9,8 @@ const AppContextProvider: React.FC<IAppContextProviderProps> = (
props: IAppContextProviderProps
) => {
const intialState: IAppContextState = {
featureFlags: []
featureFlags: [],
userProfile: {}
};
const [state, dispatch] = useReducer(reducer, intialState);
const contextValue: IAppContextProps = useMemo(() => {

View File

@@ -1,3 +1,6 @@
import { User } from '@microsoft/microsoft-graph-types';
export interface IAppContextState {
featureFlags: { key: string; enabled: boolean }[];
userProfile: User;
}

View File

@@ -1,9 +1,9 @@
import { User } from '@microsoft/microsoft-graph-types';
import { IAppContextState } from './IAppContextState';
export type Action = {
type: 'SET_FEATURE_FLAGS';
payload: { key: string; enabled: boolean }[];
};
export type Action =
| { type: 'SET_FEATURE_FLAGS'; payload: { key: string; enabled: boolean }[] }
| { type: 'SET_USER_PROFILE'; payload: User };
export const reducer = (
state: IAppContextState,
@@ -16,6 +16,12 @@ export const reducer = (
featureFlags: action.payload
};
}
case 'SET_USER_PROFILE': {
return {
...state,
userProfile: action.payload
};
}
default: {
return state;
}

View File

@@ -1,34 +1,28 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import { MsalProvider } from '@azure/msal-react';
import { Configuration, PublicClientApplication } from '@azure/msal-browser';
import { NextUIProvider } from '@nextui-org/react';
import App from './App.tsx';
import { BrowserRouter } from 'react-router-dom';
import AppContextProvider from './context/appContext/AppContextProvider.tsx';
import { BrowserRouter } from 'react-router-dom';
import './index.css';
import '@noahspan/noahspan-components/noahspan-components.css';
import { PublicClientApplication } from '@azure/msal-browser';
import { MsalProvider } from '@azure/msal-react';
const configuration: Configuration = {
const pca: PublicClientApplication = new PublicClientApplication({
auth: {
clientId: 'd3562a45-050d-4f9a-baed-0497c7156924',
authority:
'https://login.microsoftonline.com/0f23652e-4b15-420f-991e-3d6fc769a31d',
redirectUri: 'http://localhost:5173'
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
}
};
const pca = new PublicClientApplication(configuration);
});
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<MsalProvider instance={pca}>
<AppContextProvider>
<NextUIProvider>
<BrowserRouter>
<App />
</BrowserRouter>
</NextUIProvider>
<BrowserRouter>
<App />
</BrowserRouter>
</AppContextProvider>
</MsalProvider>
</React.StrictMode>

View File

@@ -2,6 +2,9 @@
interface ImportMetaEnv {
readonly VITE_API_URL: string;
readonly VITE_CLIENT_ID: string;
readonly VITE_TENANT_ID: string;
readonly VITE_REDIRECT_URL: string;
}
interface ImportMeta {

View File

@@ -1,15 +1,9 @@
const { nextui } = require('@nextui-org/react');
/** @type {import('tailwindcss').Config} */
export default {
content: [
'./index.html',
'./src/**/*.{js,ts,jsx,tsx}',
'../node_modules/@nextui-org/theme/dist/**/*.{js,ts,jsx,tsx}'
],
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {}
},
darkMode: 'class',
plugins: [nextui()]
plugins: []
};

View File

@@ -1,7 +1,23 @@
resource "azurerm_storage_account" "storage_account" {
name = var.STORAGE_ACCOUNT_NAME
resource "azurerm_storage_account" "storage_account_dev" {
name = "${var.STORAGE_ACCOUNT_NAME}dev"
resource_group_name = data.azurerm_resource_group.resource_group.name
location = data.azurerm_resource_group.resource_group.location
account_tier = "Standard"
account_replication_type = "GRS"
account_replication_type = "LRS"
}
resource "azurerm_storage_account" "storage_account_staging" {
name = "${var.STORAGE_ACCOUNT_NAME}staging"
resource_group_name = data.azurerm_resource_group.resource_group.name
location = data.azurerm_resource_group.resource_group.location
account_tier = "Standard"
account_replication_type = "LRS"
}
resource "azurerm_storage_account" "storage_account_prod" {
name = "${var.STORAGE_ACCOUNT_NAME}prod"
resource_group_name = data.azurerm_resource_group.resource_group.name
location = data.azurerm_resource_group.resource_group.location
account_tier = "Standard"
account_replication_type = "LRS"
}

3703
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -7,6 +7,7 @@
"tests"
],
"scripts": {
"start": "concurrently 'npm run start:azure -w api' 'wait-on tcp:0.0.0.0:7071 && npm run dev -w app'",
"format": "prettier --write \"**/src/**/*.ts\" \"**/test/**/*.ts\"",
"lint": "npm run lint -w api && npm run lint -w app",
"prepare": "husky || true"
@@ -14,15 +15,21 @@
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^7.2.0",
"concurrently": "^8.2.2",
"eslint": "^8.42.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.6",
"husky": "^9.0.11",
"lint-staged": "^15.2.2",
"prettier": "3.2.5"
"prettier": "3.2.5",
"wait-on": "^7.2.0"
},
"lint-staged": {
"**/*": "prettier --write \"**/src/**/*.ts\" \"**/test/**/*.ts\" --ignore-unknown"
},
"dependencies": {
"@microsoft/mgt-element": "^4.2.2",
"@microsoft/mgt-msal2-provider": "^4.2.2"
}
}