Setup Playwright (#14)
* setting up playwright * renaming client to app * updating node version * updating node version * fixing husky * fixing husky * fixing husky * fixing husky
This commit was merged in pull request #14.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
.vscode/*
|
.vscode/*
|
||||||
node_modules
|
node_modules
|
||||||
|
**/.env
|
||||||
@@ -1 +1,4 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
. "$(dirname -- "$0")/_/husky.sh"
|
||||||
|
|
||||||
npx lint-staged
|
npx lint-staged
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ module.exports = {
|
|||||||
extends: [
|
extends: [
|
||||||
'eslint:recommended',
|
'eslint:recommended',
|
||||||
'plugin:@typescript-eslint/recommended',
|
'plugin:@typescript-eslint/recommended',
|
||||||
'plugin:react-hooks/recommended',
|
'plugin:react-hooks/recommended'
|
||||||
],
|
],
|
||||||
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
||||||
parser: '@typescript-eslint/parser',
|
parser: '@typescript-eslint/parser',
|
||||||
@@ -12,7 +12,7 @@ module.exports = {
|
|||||||
rules: {
|
rules: {
|
||||||
'react-refresh/only-export-components': [
|
'react-refresh/only-export-components': [
|
||||||
'warn',
|
'warn',
|
||||||
{ allowConstantExport: true },
|
{ allowConstantExport: true }
|
||||||
],
|
]
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
0
client/.gitignore → app/.gitignore
vendored
0
client/.gitignore → app/.gitignore
vendored
@@ -20,8 +20,8 @@ export default {
|
|||||||
ecmaVersion: 'latest',
|
ecmaVersion: 'latest',
|
||||||
sourceType: 'module',
|
sourceType: 'module',
|
||||||
project: ['./tsconfig.json', './tsconfig.node.json'],
|
project: ['./tsconfig.json', './tsconfig.node.json'],
|
||||||
tsconfigRootDir: __dirname,
|
tsconfigRootDir: __dirname
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -27,5 +27,8 @@
|
|||||||
"tailwindcss": "^3.4.3",
|
"tailwindcss": "^3.4.3",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
"vite": "^5.2.0"
|
"vite": "^5.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
export default {
|
export default {
|
||||||
plugins: {
|
plugins: {
|
||||||
tailwindcss: {},
|
tailwindcss: {},
|
||||||
autoprefixer: {},
|
autoprefixer: {}
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
@@ -4,16 +4,15 @@ import Logbook from './components/logbook/Logbook';
|
|||||||
import Checklists from './components/checklists/Checklists';
|
import Checklists from './components/checklists/Checklists';
|
||||||
import Pilots from './components/pilots/Pilots';
|
import Pilots from './components/pilots/Pilots';
|
||||||
|
|
||||||
|
|
||||||
const App: React.FC<unknown> = () => {
|
const App: React.FC<unknown> = () => {
|
||||||
return (
|
return (
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path='/' element={<Flights />} />
|
<Route path="/" element={<Flights />} />
|
||||||
<Route path='logbook' element={<Logbook />} />
|
<Route path="logbook" element={<Logbook />} />
|
||||||
<Route path='checklists' element={<Checklists />} />
|
<Route path="checklists" element={<Checklists />} />
|
||||||
<Route path='pilots' element={<Pilots />} />
|
<Route path="pilots" element={<Pilots />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
12
app/src/components/logbook/Logbook.tsx
Normal file
12
app/src/components/logbook/Logbook.tsx
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import SiteNav from '../siteNav/SiteNav';
|
||||||
|
|
||||||
|
const Logbook: React.FC<unknown> = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<SiteNav />
|
||||||
|
<div>Logbook goes here</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Logbook;
|
||||||
122
app/src/components/logbookEntryForm/LogbookEntryForm.tsx
Normal file
122
app/src/components/logbookEntryForm/LogbookEntryForm.tsx
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionItem,
|
||||||
|
Button,
|
||||||
|
DatePicker,
|
||||||
|
Input
|
||||||
|
} from '@nextui-org/react';
|
||||||
|
import { useForm, Controller, SubmitHandler } from 'react-hook-form';
|
||||||
|
|
||||||
|
const LogbookEntryForm: React.FC<unknown> = () => {
|
||||||
|
const { control, handleSubmit } = useForm();
|
||||||
|
|
||||||
|
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>Date</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Controller
|
||||||
|
name="date"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => <DatePicker labelPlacement="outside-left" />}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="self-center">
|
||||||
|
<label>Aircraft Type</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Controller
|
||||||
|
name="aircraftType"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<Input fullWidth={true} labelPlacement="outside-left" />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="self-center">
|
||||||
|
<label>Aircraft Identity</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Controller
|
||||||
|
name="aircraftIdent"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<Input fullWidth={true} labelPlacement="outside-left" />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="self-center">
|
||||||
|
<label>Route To</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Controller
|
||||||
|
name="routeTo"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => <Input />}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="self-center">
|
||||||
|
<label>Route From</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Controller
|
||||||
|
name="routeFrom"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => <Input />}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="self-center">
|
||||||
|
<label>Duration of Flight</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Controller
|
||||||
|
name="flightDuration"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => <Input />}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="self-center">
|
||||||
|
<label>Single Engine Land</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Controller
|
||||||
|
name="aircraftSEL"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => <Input />}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-2">
|
||||||
|
<Accordion>
|
||||||
|
<AccordionItem title="Aircraft Category and Class">
|
||||||
|
<div className="self-center">
|
||||||
|
<label>Single Engine Land</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Controller
|
||||||
|
name="aircraftSEL"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => <Input />}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-2 justify-self-end">
|
||||||
|
<Button color="default">Cancel</Button>
|
||||||
|
<Button className="ml-10" color="primary">
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LogbookEntryForm;
|
||||||
85
app/src/components/pilotForm/PilotForm.tsx
Normal file
85
app/src/components/pilotForm/PilotForm.tsx
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionItem,
|
||||||
|
Button,
|
||||||
|
DatePicker,
|
||||||
|
Input,
|
||||||
|
Select,
|
||||||
|
SelectItem
|
||||||
|
} from '@nextui-org/react';
|
||||||
|
import { useForm, Controller, SubmitHandler } from 'react-hook-form';
|
||||||
|
|
||||||
|
const PilotForm: React.FC<unknown> = () => {
|
||||||
|
const { control, handleSubmit } = useForm();
|
||||||
|
|
||||||
|
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>
|
||||||
|
</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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PilotForm;
|
||||||
15
app/src/components/pilots/Pilots.tsx
Normal file
15
app/src/components/pilots/Pilots.tsx
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import SiteNav from '../siteNav/SiteNav';
|
||||||
|
import PilotForm from '../pilotForm/PilotForm';
|
||||||
|
|
||||||
|
const Pilots: React.FC<unknown> = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<SiteNav />
|
||||||
|
<div>
|
||||||
|
<PilotForm />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Pilots;
|
||||||
59
app/src/components/siteNav/SiteNav.tsx
Normal file
59
app/src/components/siteNav/SiteNav.tsx
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
Link,
|
||||||
|
Navbar,
|
||||||
|
NavbarBrand,
|
||||||
|
NavbarContent,
|
||||||
|
NavbarItem,
|
||||||
|
Button
|
||||||
|
} from '@nextui-org/react';
|
||||||
|
import { Link as ReactRouterLink } from 'react-router-dom';
|
||||||
|
import { useIsAuthenticated, useMsal } from '@azure/msal-react';
|
||||||
|
|
||||||
|
const SiteNav: React.FC<unknown> = () => {
|
||||||
|
const isAuthenticated = useIsAuthenticated();
|
||||||
|
const { accounts, instance } = useMsal();
|
||||||
|
const initializeLogin = () => {
|
||||||
|
instance.loginRedirect();
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log(accounts);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Navbar isBordered>
|
||||||
|
<NavbarBrand>Site Logo Goes Here</NavbarBrand>
|
||||||
|
<NavbarContent justify="center">
|
||||||
|
<NavbarItem>
|
||||||
|
<Link>
|
||||||
|
<ReactRouterLink to="/">Flights</ReactRouterLink>
|
||||||
|
</Link>
|
||||||
|
</NavbarItem>
|
||||||
|
<NavbarItem>
|
||||||
|
<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>
|
||||||
|
</NavbarContent>
|
||||||
|
<NavbarContent justify="end">
|
||||||
|
{!isAuthenticated && (
|
||||||
|
<Button as={Link} color="primary" href="#" onClick={initializeLogin}>
|
||||||
|
Login
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{isAuthenticated && <Avatar name={accounts[0]?.username} />}
|
||||||
|
</NavbarContent>
|
||||||
|
</Navbar>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SiteNav;
|
||||||
@@ -10,10 +10,11 @@ import { BrowserRouter } from 'react-router-dom';
|
|||||||
const configuration: Configuration = {
|
const configuration: Configuration = {
|
||||||
auth: {
|
auth: {
|
||||||
clientId: 'd3562a45-050d-4f9a-baed-0497c7156924',
|
clientId: 'd3562a45-050d-4f9a-baed-0497c7156924',
|
||||||
authority: 'https://login.microsoftonline.com/0f23652e-4b15-420f-991e-3d6fc769a31d',
|
authority:
|
||||||
|
'https://login.microsoftonline.com/0f23652e-4b15-420f-991e-3d6fc769a31d',
|
||||||
redirectUri: 'http://localhost:5173'
|
redirectUri: 'http://localhost:5173'
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const pca = new PublicClientApplication(configuration);
|
const pca = new PublicClientApplication(configuration);
|
||||||
|
|
||||||
@@ -26,5 +27,5 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
|
|||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</NextUIProvider>
|
</NextUIProvider>
|
||||||
</MsalProvider>
|
</MsalProvider>
|
||||||
</React.StrictMode>,
|
</React.StrictMode>
|
||||||
);
|
);
|
||||||
@@ -5,11 +5,11 @@ 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: {}
|
||||||
},
|
},
|
||||||
darkMode: 'class',
|
darkMode: 'class',
|
||||||
plugins: [nextui()],
|
plugins: [nextui()]
|
||||||
};
|
};
|
||||||
@@ -3,5 +3,5 @@ import react from '@vitejs/plugin-react';
|
|||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react()]
|
||||||
});
|
});
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import SiteNav from '../siteNav/SiteNav';
|
|
||||||
|
|
||||||
const Logbook: React.FC<unknown> = () => {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<SiteNav />
|
|
||||||
<div>
|
|
||||||
Logbook goes here
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Logbook;
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Accordion, AccordionItem, Button, DatePicker, Input } from '@nextui-org/react';
|
|
||||||
import { useForm, Controller, SubmitHandler } from 'react-hook-form';
|
|
||||||
|
|
||||||
const LogbookEntryForm: React.FC<unknown> = () => {
|
|
||||||
const { control, handleSubmit } = useForm();
|
|
||||||
|
|
||||||
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>Date</label>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Controller
|
|
||||||
name='date'
|
|
||||||
control={control}
|
|
||||||
render={({ field }) => (
|
|
||||||
<DatePicker
|
|
||||||
labelPlacement='outside-left'
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className='self-center'>
|
|
||||||
<label>Aircraft Type</label>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Controller
|
|
||||||
name='aircraftType'
|
|
||||||
control={control}
|
|
||||||
render={({ field }) => (
|
|
||||||
<Input
|
|
||||||
fullWidth={true}
|
|
||||||
labelPlacement='outside-left'
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className='self-center'>
|
|
||||||
<label>Aircraft Identity</label>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Controller
|
|
||||||
name='aircraftIdent'
|
|
||||||
control={control}
|
|
||||||
render={({ field }) => (
|
|
||||||
<Input
|
|
||||||
fullWidth={true}
|
|
||||||
labelPlacement='outside-left'
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className='self-center'>
|
|
||||||
<label>Route To</label>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Controller
|
|
||||||
name='routeTo'
|
|
||||||
control={control}
|
|
||||||
render={({ field }) => (
|
|
||||||
<Input />
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className='self-center'>
|
|
||||||
<label>Route From</label>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Controller
|
|
||||||
name='routeFrom'
|
|
||||||
control={control}
|
|
||||||
render={({ field }) => (
|
|
||||||
<Input />
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className='self-center'>
|
|
||||||
<label>Duration of Flight</label>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Controller
|
|
||||||
name='flightDuration'
|
|
||||||
control={control}
|
|
||||||
render={({ field }) => (
|
|
||||||
<Input />
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className='self-center'>
|
|
||||||
<label>Single Engine Land</label>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Controller
|
|
||||||
name='aircraftSEL'
|
|
||||||
control={control}
|
|
||||||
render={({ field }) => (
|
|
||||||
<Input />
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className='col-span-2'>
|
|
||||||
<Accordion>
|
|
||||||
<AccordionItem title='Aircraft Category and Class'>
|
|
||||||
<div className='self-center'>
|
|
||||||
<label>Single Engine Land</label>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Controller
|
|
||||||
name='aircraftSEL'
|
|
||||||
control={control}
|
|
||||||
render={({ field }) => (
|
|
||||||
<Input />
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</AccordionItem>
|
|
||||||
</Accordion>
|
|
||||||
</div>
|
|
||||||
<div className='col-span-2 justify-self-end'>
|
|
||||||
<Button color='default'>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button className='ml-10' color='primary'>
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default LogbookEntryForm;
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
import { Accordion, AccordionItem, Button, DatePicker, Input, Select, SelectItem } from '@nextui-org/react';
|
|
||||||
import { useForm, Controller, SubmitHandler } from 'react-hook-form';
|
|
||||||
|
|
||||||
const PilotForm: React.FC<unknown> = () => {
|
|
||||||
const { control, handleSubmit } = useForm();
|
|
||||||
|
|
||||||
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>
|
|
||||||
</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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PilotForm;
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import SiteNav from '../siteNav/SiteNav';
|
|
||||||
import PilotForm from '../pilotForm/PilotForm';
|
|
||||||
|
|
||||||
const Pilots: React.FC<unknown> = () => {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<SiteNav />
|
|
||||||
<div>
|
|
||||||
<PilotForm />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Pilots;
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
import { Avatar, Link, Navbar, NavbarBrand, NavbarContent, NavbarItem, Button } from '@nextui-org/react';
|
|
||||||
import { Link as ReactRouterLink } from 'react-router-dom';
|
|
||||||
import { useIsAuthenticated, useMsal } from '@azure/msal-react';
|
|
||||||
|
|
||||||
const SiteNav: React.FC<unknown> = () => {
|
|
||||||
const isAuthenticated = useIsAuthenticated();
|
|
||||||
const { accounts, instance } = useMsal();
|
|
||||||
const initializeLogin = () => {
|
|
||||||
instance.loginRedirect();
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(accounts)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Navbar isBordered>
|
|
||||||
<NavbarBrand>
|
|
||||||
Site Logo Goes Here
|
|
||||||
</NavbarBrand>
|
|
||||||
<NavbarContent justify='center'>
|
|
||||||
<NavbarItem>
|
|
||||||
<Link>
|
|
||||||
<ReactRouterLink to='/'>Flights</ReactRouterLink>
|
|
||||||
</Link>
|
|
||||||
</NavbarItem>
|
|
||||||
<NavbarItem>
|
|
||||||
<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>
|
|
||||||
</NavbarContent>
|
|
||||||
<NavbarContent justify='end'>
|
|
||||||
{!isAuthenticated &&
|
|
||||||
<Button as={Link} color='primary' href='#' onClick={initializeLogin}>Login</Button>
|
|
||||||
}
|
|
||||||
{isAuthenticated &&
|
|
||||||
<Avatar name={accounts[0]?.username} />
|
|
||||||
}
|
|
||||||
</NavbarContent>
|
|
||||||
</Navbar>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default SiteNav;
|
|
||||||
845
package-lock.json
generated
845
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -3,12 +3,13 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"api",
|
"api",
|
||||||
"client"
|
"app",
|
||||||
|
"tests"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"format": "prettier --write \"**/src/**/*.ts\" \"**/test/**/*.ts\"",
|
"format": "prettier --write \"**/src/**/*.ts\" \"**/test/**/*.ts\"",
|
||||||
"lint": "npm run lint -w api && npm run lint -w client",
|
"lint": "npm run lint -w api && npm run lint -w app",
|
||||||
"prepare": "husky"
|
"prepare": "husky || true"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@typescript-eslint/eslint-plugin": "^7.2.0",
|
"@typescript-eslint/eslint-plugin": "^7.2.0",
|
||||||
|
|||||||
0
tests/cucumber.mjs
Normal file
0
tests/cucumber.mjs
Normal file
30
tests/e2e/support/config.ts
Normal file
30
tests/e2e/support/config.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { LaunchOptions, devices } from '@playwright/test';
|
||||||
|
import dotenv from 'dotenv';
|
||||||
|
|
||||||
|
dotenv.config({ path: '.env' });
|
||||||
|
|
||||||
|
const browserOptions: LaunchOptions = {
|
||||||
|
headless: Boolean(process.env.HEADLESS) || false,
|
||||||
|
timeout: Number(process.env.TIMEOUT) || 60000,
|
||||||
|
args: [
|
||||||
|
'--use-fake-ui-for-media-stream',
|
||||||
|
'--use-fake-device-for-media-stream'
|
||||||
|
],
|
||||||
|
firefoxUserPrefs: {
|
||||||
|
'media.navigator.streams.fake': true,
|
||||||
|
'media.navigator.permission.disable': true
|
||||||
|
},
|
||||||
|
slowMo: Number(process.env.SLO_MO) || 0
|
||||||
|
};
|
||||||
|
const desktopChrome = devices['Desktop Chrome'];
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
browser: process.env.BROWSER || 'chromium',
|
||||||
|
browserOptions,
|
||||||
|
baseUrl: process.env.BASE_URL || 'http://localhost:3000',
|
||||||
|
username: process.env.TEST_USERNAME,
|
||||||
|
password: process.env.TEST_PASSWORD,
|
||||||
|
viewportHeight: process.env.VIEWPORT_HEIGHT || desktopChrome.viewport.height,
|
||||||
|
viewportWidth: process.env.VIEWPORT_WIDTH || desktopChrome.viewport.width,
|
||||||
|
userAgent: process.env.USER_AGENT || desktopChrome.userAgent
|
||||||
|
};
|
||||||
121
tests/e2e/support/hooks.ts
Normal file
121
tests/e2e/support/hooks.ts
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
import { ICustomWorld } from './world';
|
||||||
|
import { config } from './config';
|
||||||
|
import {
|
||||||
|
After,
|
||||||
|
AfterAll,
|
||||||
|
Before,
|
||||||
|
BeforeAll,
|
||||||
|
Status,
|
||||||
|
setDefaultTimeout
|
||||||
|
} from '@cucumber/cucumber';
|
||||||
|
import {
|
||||||
|
chromium,
|
||||||
|
ChromiumBrowser,
|
||||||
|
firefox,
|
||||||
|
FirefoxBrowser,
|
||||||
|
webkit,
|
||||||
|
WebKitBrowser,
|
||||||
|
ConsoleMessage,
|
||||||
|
ViewportSize
|
||||||
|
} from '@playwright/test';
|
||||||
|
import { ITestCaseHookParameter } from '@cucumber/cucumber/lib/support_code_library_builder/types';
|
||||||
|
import { ensureDir } from 'fs-extra';
|
||||||
|
|
||||||
|
const tracesDir = './e2e/results/traces';
|
||||||
|
let browser: ChromiumBrowser | FirefoxBrowser | WebKitBrowser;
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
let browser: ChromiumBrowser | FirefoxBrowser | WebKitBrowser;
|
||||||
|
}
|
||||||
|
|
||||||
|
setDefaultTimeout(process.env.PWDEBUG ? -1 : 60 * 100);
|
||||||
|
|
||||||
|
BeforeAll(async function () {
|
||||||
|
switch (config.browser) {
|
||||||
|
case 'firefox': {
|
||||||
|
browser = await firefox.launch(config.browserOptions);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'webkit': {
|
||||||
|
browser = await webkit.launch(config.browserOptions);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
browser = await chromium.launch(config.browserOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await ensureDir(tracesDir);
|
||||||
|
});
|
||||||
|
|
||||||
|
Before({ tags: '@ignore' }, async function () {
|
||||||
|
return 'skipped' as any;
|
||||||
|
});
|
||||||
|
|
||||||
|
Before({ tags: '@debug' }, async function (this: ICustomWorld) {
|
||||||
|
this.debug = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
Before(async function (this: ICustomWorld, { pickle }: ITestCaseHookParameter) {
|
||||||
|
const viewportSize: ViewportSize = {
|
||||||
|
height: Number(1080),
|
||||||
|
width: Number(1920)
|
||||||
|
};
|
||||||
|
|
||||||
|
this.startTime = new Date();
|
||||||
|
this.testName = pickle.name.replace(/\W/g, '-');
|
||||||
|
this.context = await browser.newContext({
|
||||||
|
acceptDownloads: true,
|
||||||
|
viewport: viewportSize,
|
||||||
|
userAgent: config.userAgent
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.context.tracing.start({
|
||||||
|
screenshots: true,
|
||||||
|
snapshots: true
|
||||||
|
});
|
||||||
|
|
||||||
|
this.page = await this.context.newPage();
|
||||||
|
this.page.on('console', async (msg: ConsoleMessage) => {
|
||||||
|
if (msg.type() === 'log') {
|
||||||
|
await this.attach(msg.text());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.feature = pickle;
|
||||||
|
});
|
||||||
|
|
||||||
|
After(async function (this: ICustomWorld, { result }: ITestCaseHookParameter) {
|
||||||
|
if (result) {
|
||||||
|
await this.attach(
|
||||||
|
`Status: ${result?.status}. Duration:${result.duration?.seconds}s`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result.status !== Status.PASSED) {
|
||||||
|
const image = await this.page?.screenshot({
|
||||||
|
path: `./e2e/results/screenshots/${this.testName}.png`,
|
||||||
|
type: 'png'
|
||||||
|
});
|
||||||
|
const timePart = this.startTime
|
||||||
|
?.toISOString()
|
||||||
|
.split('.')[0]
|
||||||
|
.replace(/:/g, '_');
|
||||||
|
|
||||||
|
if (image) {
|
||||||
|
await this.attach(image, 'image/png');
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.context?.tracing.stop({
|
||||||
|
path: `${tracesDir}/${this.testName}-${timePart}trace.zip`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.page?.close();
|
||||||
|
await this.context?.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
AfterAll(async function () {
|
||||||
|
await browser.close();
|
||||||
|
});
|
||||||
0
tests/e2e/support/report.ts
Normal file
0
tests/e2e/support/report.ts
Normal file
33
tests/e2e/support/world.ts
Normal file
33
tests/e2e/support/world.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { setWorldConstructor, World, IWorldOptions } from '@cucumber/cucumber';
|
||||||
|
import * as messages from '@cucumber/messages';
|
||||||
|
import {
|
||||||
|
BrowserContext,
|
||||||
|
Page,
|
||||||
|
PlaywrightTestOptions,
|
||||||
|
APIRequestContext
|
||||||
|
} from '@playwright/test';
|
||||||
|
|
||||||
|
export interface CucumberWorldConstructorParams {
|
||||||
|
parameters: { [key: string]: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICustomWorld extends World {
|
||||||
|
debug: boolean;
|
||||||
|
feature?: messages.Pickle;
|
||||||
|
context?: BrowserContext;
|
||||||
|
page?: Page;
|
||||||
|
testName?: string;
|
||||||
|
startTime?: Date;
|
||||||
|
server?: APIRequestContext;
|
||||||
|
playwrightOptions?: PlaywrightTestOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CustomWorld extends World implements ICustomWorld {
|
||||||
|
constructor(options: IWorldOptions) {
|
||||||
|
super(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
debug = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
setWorldConstructor(CustomWorld);
|
||||||
1665
tests/package-lock.json
generated
Normal file
1665
tests/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
17
tests/package.json
Normal file
17
tests/package.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "tests",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@cucumber/cucumber": "^10.7.0",
|
||||||
|
"@playwright/test": "^1.44.0",
|
||||||
|
"@types/fs-extra": "^11.0.4",
|
||||||
|
"fs-extra": "^11.2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
20
tests/tsconfig.json
Normal file
20
tests/tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es6",
|
||||||
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "commonjs",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"typeRoots": ["../node_modules/@types"]
|
||||||
|
},
|
||||||
|
"include": ["**/*.ts", "**/*.tsx"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user