Testing menu button
This commit is contained in:
@@ -22,6 +22,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@azure/data-tables": "^13.2.2",
|
"@azure/data-tables": "^13.2.2",
|
||||||
"@azure/functions": "^1.0.3",
|
"@azure/functions": "^1.0.3",
|
||||||
|
"@nestjs/axios": "^3.0.3",
|
||||||
"@nestjs/azure-database": "^3.0.0",
|
"@nestjs/azure-database": "^3.0.0",
|
||||||
"@nestjs/azure-func-http": "^0.10.0",
|
"@nestjs/azure-func-http": "^0.10.0",
|
||||||
"@nestjs/common": "^10.0.0",
|
"@nestjs/common": "^10.0.0",
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import {
|
|||||||
import { ConfigModule } from '@nestjs/config';
|
import { ConfigModule } from '@nestjs/config';
|
||||||
import { APP_GUARD } from '@nestjs/core';
|
import { APP_GUARD } from '@nestjs/core';
|
||||||
import { PilotModule } from './pilot/pilot.module';
|
import { PilotModule } from './pilot/pilot.module';
|
||||||
|
import { APP_FILTER } from '@nestjs/core';
|
||||||
|
import { HttpExceptionFilter } from './filters/http-exception.filter';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -38,6 +40,10 @@ import { PilotModule } from './pilot/pilot.module';
|
|||||||
provide: APP_GUARD,
|
provide: APP_GUARD,
|
||||||
useClass: AuthGuard
|
useClass: AuthGuard
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: APP_FILTER,
|
||||||
|
useClass: HttpExceptionFilter
|
||||||
|
},
|
||||||
AppService
|
AppService
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,8 +1,25 @@
|
|||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
|
import { HttpService } from '@nestjs/axios';
|
||||||
|
import { HttpExceptionFilter } from './filters/http-exception.filter';
|
||||||
|
import { InternalServerErrorException } from '@nestjs/common';
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
|
const httpService = new HttpService();
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create(AppModule);
|
||||||
|
|
||||||
|
app.useGlobalFilters(new HttpExceptionFilter());
|
||||||
|
httpService.axiosRef.interceptors.response.use(
|
||||||
|
(response) => {
|
||||||
|
return response;
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.error('Internal server error exception', error);
|
||||||
|
|
||||||
|
throw new InternalServerErrorException();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
await app.listen(3000);
|
await app.listen(3000);
|
||||||
}
|
}
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { HttpException, Injectable } from '@nestjs/common';
|
||||||
// import { Repository, InjectRepository } from '@nestjs/azure-database';
|
// import { Repository, InjectRepository } from '@nestjs/azure-database';
|
||||||
import { PilotInfoDto } from './pilot-info.dto';
|
import { PilotInfoDto } from './pilot-info.dto';
|
||||||
import { PilotInfoEntity } from './pilot-info.entity';
|
import { PilotInfoEntity } from './pilot-info.entity';
|
||||||
import { TableClient, TableService } from '@noahspan/noahspan-modules';
|
import { TableClient, TableService } from '@noahspan/noahspan-modules';
|
||||||
|
import { RestError, TableInsertEntityHeaders } from '@azure/data-tables';
|
||||||
|
import { CustomError } from '../../customError/CustomError';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PilotInfoService {
|
export class PilotInfoService {
|
||||||
@@ -22,7 +24,7 @@ export class PilotInfoService {
|
|||||||
// return await this.pilotInfoRepository.findAll();
|
// return await this.pilotInfoRepository.findAll();
|
||||||
// }
|
// }
|
||||||
|
|
||||||
async create(pilotInfoData: PilotInfoDto): Promise<void> {
|
async create(pilotInfoData: PilotInfoDto): Promise<TableInsertEntityHeaders> {
|
||||||
const client: TableClient =
|
const client: TableClient =
|
||||||
await this.tableService.getTableClient('Pilots');
|
await this.tableService.getTableClient('Pilots');
|
||||||
const pilotInfo: PilotInfoEntity = new PilotInfoEntity();
|
const pilotInfo: PilotInfoEntity = new PilotInfoEntity();
|
||||||
@@ -30,11 +32,17 @@ export class PilotInfoService {
|
|||||||
Object.assign(pilotInfo, pilotInfoData);
|
Object.assign(pilotInfo, pilotInfoData);
|
||||||
pilotInfo.partitionKey = 'pilot';
|
pilotInfo.partitionKey = 'pilot';
|
||||||
pilotInfo.rowKey = pilotInfo.id;
|
pilotInfo.rowKey = pilotInfo.id;
|
||||||
console.log(pilotInfo);
|
|
||||||
try {
|
try {
|
||||||
await client.createEntity(pilotInfo);
|
return await client.createEntity(pilotInfo);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return error;
|
const restError: RestError = error as RestError;
|
||||||
|
|
||||||
|
throw new CustomError(
|
||||||
|
restError.details['odataError']['message']['value'],
|
||||||
|
restError.details['odataError']['code'],
|
||||||
|
restError.statusCode
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,8 @@
|
|||||||
import {
|
import { Body, Controller, HttpException, Post } from '@nestjs/common';
|
||||||
Body,
|
|
||||||
Controller,
|
|
||||||
Get,
|
|
||||||
Post,
|
|
||||||
Put,
|
|
||||||
Query,
|
|
||||||
UnprocessableEntityException
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { PilotInfoService } from './info/pilot-info.service';
|
import { PilotInfoService } from './info/pilot-info.service';
|
||||||
import { PilotInfoDto } from './info/pilot-info.dto';
|
import { PilotInfoDto } from './info/pilot-info.dto';
|
||||||
|
import { TableInsertEntityHeaders } from '@azure/data-tables';
|
||||||
|
import { CustomError } from '../customError/CustomError';
|
||||||
|
|
||||||
@Controller('pilots')
|
@Controller('pilots')
|
||||||
export class PilotController {
|
export class PilotController {
|
||||||
@@ -17,9 +11,23 @@ export class PilotController {
|
|||||||
@Post()
|
@Post()
|
||||||
async createPilot(@Body() pilotInfoData: PilotInfoDto): Promise<void> {
|
async createPilot(@Body() pilotInfoData: PilotInfoDto): Promise<void> {
|
||||||
try {
|
try {
|
||||||
return await this.pilotInfoService.create(pilotInfoData);
|
const response: TableInsertEntityHeaders =
|
||||||
|
await this.pilotInfoService.create(pilotInfoData);
|
||||||
|
|
||||||
|
console.log(`Not Broken: ${response}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new UnprocessableEntityException(error);
|
const customError = error as CustomError;
|
||||||
|
console.log(customError.name);
|
||||||
|
throw new HttpException(customError.message, customError.statusCode, {
|
||||||
|
cause: customError.name
|
||||||
|
});
|
||||||
|
|
||||||
|
// throw new HttpException({
|
||||||
|
// status: customError.statusCode,
|
||||||
|
// error: customError.message
|
||||||
|
// }, customError.statusCode, {
|
||||||
|
// cause: customError.name
|
||||||
|
// });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ type EventPayloadExtended = EventPayload & { accessToken: string };
|
|||||||
const App: React.FC<unknown> = () => {
|
const App: React.FC<unknown> = () => {
|
||||||
const httpClient: AxiosInstance = useHttpClient();
|
const httpClient: AxiosInstance = useHttpClient();
|
||||||
const appContext = useAppContext();
|
const appContext = useAppContext();
|
||||||
const { instance } = useMsal();
|
const { inProgress, instance } = useMsal();
|
||||||
const handleSignIn = async () => {
|
const handleSignIn = async () => {
|
||||||
await instance.loginRedirect({
|
await instance.loginRedirect({
|
||||||
scopes: [`api://${import.meta.env.VITE_CLIENT_ID}/user_impersonation`]
|
scopes: [`api://${import.meta.env.VITE_CLIENT_ID}/user_impersonation`]
|
||||||
@@ -32,7 +32,6 @@ const App: React.FC<unknown> = () => {
|
|||||||
const response: AxiosResponse = await httpClient.get(
|
const response: AxiosResponse = await httpClient.get(
|
||||||
`api/featureFlags?keys=${featureFlagKeys}&label=${import.meta.env.MODE}`
|
`api/featureFlags?keys=${featureFlagKeys}&label=${import.meta.env.MODE}`
|
||||||
);
|
);
|
||||||
console.log(response.data);
|
|
||||||
const featureFlags: { key: string; enabled: boolean }[] = response.data;
|
const featureFlags: { key: string; enabled: boolean }[] = response.data;
|
||||||
|
|
||||||
if (featureFlags.length > 0) {
|
if (featureFlags.length > 0) {
|
||||||
@@ -87,7 +86,11 @@ const App: React.FC<unknown> = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto">
|
<div className="container mx-auto">
|
||||||
<SiteNav handleSignIn={handleSignIn} handleSignOut={handleSignOut} />
|
<SiteNav
|
||||||
|
handleSignIn={handleSignIn}
|
||||||
|
handleSignOut={handleSignOut}
|
||||||
|
inProgress={inProgress}
|
||||||
|
/>
|
||||||
<Routes>
|
<Routes>
|
||||||
{useFeatureFlag('flying-pilots')?.enabled && (
|
{useFeatureFlag('flying-pilots')?.enabled && (
|
||||||
<Route path="/" element={<Pilots />} />
|
<Route path="/" element={<Pilots />} />
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import { IPilotFormProps } from './IPilotFormProps';
|
|||||||
import PilotFormCertificates from '../pilotFormCertificates/PilotFormCertificates';
|
import PilotFormCertificates from '../pilotFormCertificates/PilotFormCertificates';
|
||||||
import PilotFormEndorsements from '../pilotFormEndorsements/PilotFormEndorsements';
|
import PilotFormEndorsements from '../pilotFormEndorsements/PilotFormEndorsements';
|
||||||
import { Person } from '@microsoft/microsoft-graph-types';
|
import { Person } from '@microsoft/microsoft-graph-types';
|
||||||
import { AxiosInstance, AxiosResponse } from 'axios';
|
import axios, { AxiosError, AxiosInstance, AxiosResponse } from 'axios';
|
||||||
import { useHttpClient } from '../../hooks/httpClient/UseHttpClient';
|
import { useHttpClient } from '../../hooks/httpClient/UseHttpClient';
|
||||||
import { useAccessToken } from '../../hooks/accessToken/UseAcessToken';
|
import { useAccessToken } from '../../hooks/accessToken/UseAcessToken';
|
||||||
import { IPilotFormCertificates } from '../pilotFormCertificates/IPilotFormCertificates';
|
import { IPilotFormCertificates } from '../pilotFormCertificates/IPilotFormCertificates';
|
||||||
@@ -41,6 +41,7 @@ const PilotForm: React.FC<IPilotFormProps> = ({
|
|||||||
const [peoplePickerResults, setPeoplePickerResults] = useState<Person[]>([]);
|
const [peoplePickerResults, setPeoplePickerResults] = useState<Person[]>([]);
|
||||||
const [isPeoplePickerLoading, setIsPeoplePickerLoading] =
|
const [isPeoplePickerLoading, setIsPeoplePickerLoading] =
|
||||||
useState<boolean>(false);
|
useState<boolean>(false);
|
||||||
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
const { getAccessToken } = useAccessToken();
|
const { getAccessToken } = useAccessToken();
|
||||||
const methods = useForm();
|
const methods = useForm();
|
||||||
|
|
||||||
@@ -82,14 +83,31 @@ const PilotForm: React.FC<IPilotFormProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onSubmit = async (data: unknown) => {
|
const onSubmit = async (data: unknown) => {
|
||||||
console.log(data);
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
const accessToken: string = await getAccessToken();
|
const accessToken: string = await getAccessToken();
|
||||||
const response: AxiosResponse = await httpClient.post(`api/pilots`, data, {
|
const response: AxiosResponse = await httpClient.post(
|
||||||
headers: {
|
`api/pilots`,
|
||||||
Authorization: accessToken
|
data,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: accessToken
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response) console.log(response);
|
||||||
|
} catch (error) {
|
||||||
|
if (axios.isAxiosError(error)) {
|
||||||
|
const errResp = error.response;
|
||||||
|
|
||||||
|
console.log(errResp?.data.message);
|
||||||
|
} else {
|
||||||
}
|
}
|
||||||
});
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -428,6 +446,7 @@ const PilotForm: React.FC<IPilotFormProps> = ({
|
|||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
className="flex items-center gap-3"
|
className="flex items-center gap-3"
|
||||||
|
loading={isLoading}
|
||||||
variant="filled"
|
variant="filled"
|
||||||
type="submit"
|
type="submit"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -4,7 +4,11 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
Typography
|
Typography,
|
||||||
|
Menu,
|
||||||
|
MenuItem,
|
||||||
|
MenuHandler,
|
||||||
|
MenuList
|
||||||
} from '@noahspan/noahspan-components';
|
} from '@noahspan/noahspan-components';
|
||||||
|
|
||||||
const Pilots: React.FC<unknown> = () => {
|
const Pilots: React.FC<unknown> = () => {
|
||||||
@@ -14,26 +18,37 @@ const Pilots: React.FC<unknown> = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className="mt-6 p-6">
|
// <Card className="mt-6 p-6">
|
||||||
<div className="grid grid-cols-1 gap-4">
|
<div className="grid grid-cols-1 gap-4">
|
||||||
|
<div className="col-span-1">
|
||||||
<Typography variant="h2">Pilots</Typography>
|
<Typography variant="h2">Pilots</Typography>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-1 justify-self-end">
|
||||||
<Button
|
<Button
|
||||||
className="flex items-center gap-3"
|
className="flex justify-center gap-3"
|
||||||
variant="filled"
|
variant="filled"
|
||||||
onClick={onOpenCloseDrawer}
|
onClick={onOpenCloseDrawer}
|
||||||
data-testid="add-pilot-button"
|
data-testid="add-pilot-button"
|
||||||
fullWidth={true}
|
|
||||||
>
|
>
|
||||||
<PlusIcon size="lg" />
|
<PlusIcon size="lg" />
|
||||||
Add Pilot
|
Add Pilot
|
||||||
</Button>
|
</Button>
|
||||||
|
<Menu>
|
||||||
<PilotForm
|
<MenuHandler>
|
||||||
isDrawerOpen={isDrawerOpen}
|
<Button>Menu</Button>
|
||||||
onOpenCloseDrawer={onOpenCloseDrawer}
|
</MenuHandler>
|
||||||
/>
|
<MenuList>
|
||||||
|
<MenuItem>List Item 1</MenuItem>
|
||||||
|
</MenuList>
|
||||||
|
</Menu>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
|
||||||
|
<PilotForm
|
||||||
|
isDrawerOpen={isDrawerOpen}
|
||||||
|
onOpenCloseDrawer={onOpenCloseDrawer}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
//</Card>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { ISiteNavProps } from './ISiteNavProps';
|
||||||
// import { Link as ReactRouterLink } from 'react-router-dom';
|
// import { Link as ReactRouterLink } from 'react-router-dom';
|
||||||
import { useAppContext } from '../../hooks/appContext/UseAppContext';
|
import { useAppContext } from '../../hooks/appContext/UseAppContext';
|
||||||
import {
|
import {
|
||||||
@@ -12,19 +13,17 @@ import {
|
|||||||
NavbarMenu,
|
NavbarMenu,
|
||||||
NavbarItemProps,
|
NavbarItemProps,
|
||||||
PlaneIcon,
|
PlaneIcon,
|
||||||
|
Spinner,
|
||||||
Typography
|
Typography
|
||||||
} from '@noahspan/noahspan-components';
|
} from '@noahspan/noahspan-components';
|
||||||
import { useIsAuthenticated } from '@azure/msal-react';
|
import { useIsAuthenticated } from '@azure/msal-react';
|
||||||
|
import { InteractionStatus } from '@azure/msal-browser';
|
||||||
|
|
||||||
interface SiteNavProps {
|
const SiteNav: React.FC<ISiteNavProps> = ({
|
||||||
handleSignIn: () => void;
|
|
||||||
handleSignOut: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SiteNav: React.FC<SiteNavProps> = ({
|
|
||||||
handleSignIn,
|
handleSignIn,
|
||||||
handleSignOut
|
handleSignOut,
|
||||||
}: SiteNavProps) => {
|
inProgress
|
||||||
|
}: ISiteNavProps) => {
|
||||||
const appContext = useAppContext();
|
const appContext = useAppContext();
|
||||||
const isAuthenticated = useIsAuthenticated();
|
const isAuthenticated = useIsAuthenticated();
|
||||||
const navItems: NavbarItemProps[] = [
|
const navItems: NavbarItemProps[] = [
|
||||||
@@ -47,22 +46,25 @@ const SiteNav: React.FC<SiteNavProps> = ({
|
|||||||
<Menu placement="bottom-end">
|
<Menu placement="bottom-end">
|
||||||
<MenuHandler>
|
<MenuHandler>
|
||||||
<div>
|
<div>
|
||||||
<Typography>
|
<Button variant="text" size="sm">
|
||||||
{appContext.state.userProfile.displayName}
|
{appContext.state.userProfile.displayName}
|
||||||
</Typography>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</MenuHandler>
|
</MenuHandler>
|
||||||
<MenuList>
|
<MenuList>
|
||||||
<MenuItem>
|
<MenuItem onClick={handleSignOut}>
|
||||||
<Button variant="text" size="sm" onClick={handleSignOut}>
|
<Typography variant="small">Sign Out</Typography>
|
||||||
Sign Out
|
|
||||||
</Button>
|
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</MenuList>
|
</MenuList>
|
||||||
</Menu>
|
</Menu>
|
||||||
)}
|
)}
|
||||||
{!isAuthenticated && (
|
{!isAuthenticated && (
|
||||||
<Button variant="text" size="sm" onClick={handleSignIn}>
|
<Button
|
||||||
|
variant="text"
|
||||||
|
size="sm"
|
||||||
|
onClick={handleSignIn}
|
||||||
|
loading={inProgress === InteractionStatus.Login ? true : false}
|
||||||
|
>
|
||||||
Sign In
|
Sign In
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export const useAccessToken = () => {
|
|||||||
try {
|
try {
|
||||||
const response: AuthenticationResult =
|
const response: AuthenticationResult =
|
||||||
await instance.acquireTokenSilent(tokenRequest);
|
await instance.acquireTokenSilent(tokenRequest);
|
||||||
console.log(response.accessToken);
|
|
||||||
return `Bearer ${response.accessToken}`;
|
return `Bearer ${response.accessToken}`;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof InteractionRequiredAuthError) {
|
if (error instanceof InteractionRequiredAuthError) {
|
||||||
|
|||||||
11
package-lock.json
generated
11
package-lock.json
generated
@@ -32,6 +32,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@azure/data-tables": "^13.2.2",
|
"@azure/data-tables": "^13.2.2",
|
||||||
"@azure/functions": "^1.0.3",
|
"@azure/functions": "^1.0.3",
|
||||||
|
"@nestjs/axios": "^3.0.3",
|
||||||
"@nestjs/azure-database": "^3.0.0",
|
"@nestjs/azure-database": "^3.0.0",
|
||||||
"@nestjs/azure-func-http": "^0.10.0",
|
"@nestjs/azure-func-http": "^0.10.0",
|
||||||
"@nestjs/common": "^10.0.0",
|
"@nestjs/common": "^10.0.0",
|
||||||
@@ -3065,6 +3066,16 @@
|
|||||||
"tslib": "^2.3.1"
|
"tslib": "^2.3.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@nestjs/axios": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-h6TCn3yJwD6OKqqqfmtRS5Zo4E46Ip2n+gK1sqwzNBC+qxQ9xpCu+ODVRFur6V3alHSCSBxb3nNtt73VEdluyA==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0",
|
||||||
|
"axios": "^1.3.1",
|
||||||
|
"rxjs": "^6.0.0 || ^7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@nestjs/azure-database": {
|
"node_modules/@nestjs/azure-database": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/azure-database/-/azure-database-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/azure-database/-/azure-database-3.0.0.tgz",
|
||||||
|
|||||||
Reference in New Issue
Block a user