diff --git a/.gitignore b/.gitignore index 5b3eca4..2938463 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,9 @@ __pycache__ package-lock.json .parcel-cache *.old + +data/*.db* + +# macOS duplicate copies +* 2 +* 2.* diff --git a/.postcssrc b/.postcssrc deleted file mode 100644 index 39ef0bb..0000000 --- a/.postcssrc +++ /dev/null @@ -1,6 +0,0 @@ -{ - "plugins": { - "tailwindcss": {}, - "cssnano": {} - } -} diff --git a/Dockerfile b/Dockerfile index 988a57c..4ce9fa5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,7 +14,7 @@ RUN npm i --package-lock-only RUN npm ci COPY ./client ./client -COPY ./tailwind.config.js ./.postcssrc ./ +COPY ./tailwind.config.js ./postcss.config.js ./vite.config.ts ./tsconfig.json ./ RUN npm run build # RUNTIME diff --git a/client/App.tsx b/client/App.tsx index c9b05a7..37688fe 100644 --- a/client/App.tsx +++ b/client/App.tsx @@ -1,17 +1,26 @@ -import React from 'react'; -import { BrowserRouter, Routes, Route, Outlet } from 'react-router-dom'; +import { lazy, Suspense } from 'react' +import { BrowserRouter, Routes, Route } from 'react-router-dom' -import { BASE_URL } from './api'; +import { BASE_URL } from './api' +import { ToastProvider } from './components/Toast' -import Login from './pages/Login'; -import New from './pages/New'; +import Login from './pages/Login' import Home from './pages/Home' import AllFlights from './pages/AllFlights' -import Statistics from './pages/Statistics'; -import Settings from './pages/Settings'; +import { AppShell } from './components/shell/AppShell' +import { Spinner } from './components/ui/Spinner' -import Navbar from './components/Navbar'; -import { ToastProvider } from './components/Toast'; +const New = lazy(() => import('./pages/New')) +const Statistics = lazy(() => import('./pages/Statistics')) +const Settings = lazy(() => import('./pages/Settings')) + +function Loading() { + return ( +
+ +
+ ) +} export function App() { return ( @@ -19,21 +28,36 @@ export function App() { } /> - - -
- -
- }> + }> } /> - } /> + }> + + + } + /> } /> - } /> - } /> + }> + + + } + /> + }> + + + } + />
- ); + ) } diff --git a/client/components/Elements.tsx b/client/components/Elements.tsx deleted file mode 100644 index 675a00d..0000000 --- a/client/components/Elements.tsx +++ /dev/null @@ -1,235 +0,0 @@ -import React, {ChangeEvent, useState} from 'react'; - -interface HeadingProps { - text: string; -} -export function Heading({ text }: HeadingProps) { - return ( -

{text}

- ); -} - -interface SubheadingProps { - text: string; -} -export function Subheading({ text }: SubheadingProps) { - return ( -

{text}

- ); -} - -interface WhisperProps { - text: string; - negativeTopMargin?: boolean; -} -export function Whisper({ text, negativeTopMargin = false}: WhisperProps) { - return ( -

- {text} -

- ) -} - -interface LabelProps { - text: string; - required?: boolean; -} -export function Label({ text, required }: LabelProps) { - return ( - - ); -} - -interface ButtonProps { - text: string; - level?: "default"|"success"|"danger"; - right?: boolean; - submit?: boolean; - disabled?: boolean; - onClick?: React.MouseEventHandler|null; -} -export function Button({ text, - level = "default", - right = false, - submit = false, - disabled = false, - onClick = null }: ButtonProps) { - var colors = ""; - switch(level) { - case "success": - colors = "bg-green-500 text-white enabled:hover:bg-green-400"; - break; - case "danger": - colors = "bg-red-500 text-white enabled:hover:bg-red-400"; - break; - case "default": - default: - colors = "bg-white text-black border border-gray-300 enabled:hover:bg-gray-100"; - }; - - return ( - - ); -} - -interface InputProps { - type: "text"|"password"|"number"|"date"|"time"|"file"; - name?: string; - defaultValue?: string; - maxLength?: number; - onChange?: ((event: ChangeEvent) => any)|null; - required?: boolean; - placeholder?: string; -} -export function Input({ type, - name, - defaultValue, - maxLength, - onChange = null, - required = false, - placeholder}: InputProps) { - return ( - {}} - required={required} - placeholder={placeholder}/> - ); -} - -interface TextAreaProps { - name?: string; - defaultValue?: string; - placeholder?: string; - maxLength?: number; -} -export function TextArea ({ name, - defaultValue, - placeholder, - maxLength }: TextAreaProps) { - return ( - - ); -} - -interface CheckboxProps { - name?: string; - checked?: boolean; - onChange?: ((event: ChangeEvent) => any)|null; -} -export function Checkbox({ checked, name, onChange }: CheckboxProps) { - return ( - {}} - checked={checked} /> - ) -} - -interface OptionProps { - text: string; - value?: string; -} -function Option({text, value}: OptionProps) { - return ( - - ); -} - -interface SelectProps { - name?: string; - options: OptionProps[]; -} -export function Select({name, options}: SelectProps) { - return ( - - ); -} - -interface DialogProps { - title: string; - buttonLevel?: "default"|"success"|"danger"; - formBody: any; // ? - onSubmit: React.FormEventHandler; -} -export function Dialog({ title, buttonLevel = "default", formBody, onSubmit }: DialogProps) { - const modalId = Math.random().toString(36).slice(2, 10); // to support multiple modals in one page - - const openModal = () => { - const modalElement = document.getElementById(modalId) as HTMLDialogElement; - modalElement.showModal(); - } - - const closeModal = () => { - const modalElement = document.getElementById(modalId) as HTMLDialogElement; - modalElement.close(); - } - - const handleSubmit = (event) => { - closeModal(); - event.preventDefault(); - onSubmit(event); - } - - return ( - <> -