Initial commit

This commit is contained in:
ZFS-Backup
2025-04-22 02:42:37 +02:00
commit 13220d2cab
20 changed files with 4306 additions and 0 deletions

27
.gitignore vendored Normal file
View File

@@ -0,0 +1,27 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
client/.vite
client/dist

145
README.md Normal file
View File

@@ -0,0 +1,145 @@
# React Template with Vite and Deno
This is a GitHub template project to set up a [React](https://react.dev/) app
with TypeScript running on [Deno](https://deno.com). It uses
[Vite](https://vite.dev) as the dev server and an [oak](https://jsr.io/@oak/oak)
http server on the backend to serve the built project.
## Features
- React with TypeScript on the frontend
- Vite for the development server
- Deno for server-side JavaScript/TypeScript
- Oak framework for building web applications
- Static file serving
- Router setup
## Getting Started
### Prerequisites
To run this app, you will need to have [Deno](https://docs.deno.com/runtime/)
installed.
### Installation
1. Create a new repository using this template. From the repository page, click
the "Use this template" button in the top right hand of the page:
<img src="https://docs.github.com/assets/cb-76823/images/help/repository/use-this-template-button.png" alt="Use this template button" width="400">
2. Use the Owner dropdown menu to select the account you want to own the
repository and set the repository name and visibility.
3. Clone the repository created to your local machine.
```sh
git clone https://github.com/your-username/your-repo-name.git
cd your-repo-name
```
> For a step by step guide to using a GitHub template
> [follow this walkthrough](https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-repository-from-a-template)
## Install the dependencies
To install the dependencies for the frontend and backend, run the following
command:
```sh
deno install
```
## Run the dev server with vite
The app uses a Vite dev server to run in development mode. To start the dev
server, run the following command:
```sh
deno run dev
```
## Build the app
To build the app for production, run the following command:
```sh
deno run build
```
## Run the backend server
The backend server uses Deno and the Oak framework to serve the built React app.
To start the backend server, run the following command:
```sh
deno run serve
```
## Running Tests
To run the tests, use the following command:
```sh
deno test -A
```
## Project Structure
```sh
.
├── client
│ ├── dist
│ ├── public
│ └── src
│ ├── App.tsx
│ └── main.tsx
└── server
├── main.ts
├── main_test.ts
└── util
└── routeStaticFilesFrom.ts
```
- `App.tsx`: The main React component
- `main.tsx`: The entry point for the React app
- `main.ts`: The entry point for the Deno server
- `main_test.ts`: The test file for the Deno server
- `routeStaticFilesFrom.ts`: A utility function to serve static files
- `dist`: The output directory for the built React app
- `public`: The public directory for the React app
## Points of note
The React app is contained in the `client` directory. This is also where Vite
will install its dependencies and build the app.
There is a `vite.config.ts` file in the root of the project that configures Vite
to build the app in the `client/dist` directory and serve the app on port 3000.
The `deno.json` file contains the tasks to run the dev server, build the app,
and serve the app, along with the dependencies and the compiler configuration
required to use JSX and React.
The Deno server is contained in the `server` directory. The server serves the
built React app from the `client/dist` directory and listens on port 8000. This
is what should be used in production.
## Deploying
You can deploy the app with [Deno Deploy](https://dash.deno.com/new_project).
1. Link your github account
2. Select the repository
3. Give the project a name
4. Set the "Build Step" to `deno task build`
5. Set the entry point to `./server/main.ts`
6. Click 'deploy project'
## Contributing
Contributions are welcome! Please open an issue or submit a pull request.
## License
This project is licensed under the MIT License.

13
client/index.html Normal file
View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

1
client/public/react.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

15
client/public/vite.svg Normal file
View File

@@ -0,0 +1,15 @@
<svg width="410" height="404" viewBox="0 0 410 404" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M399.641 59.5246L215.643 388.545C211.844 395.338 202.084 395.378 198.228 388.618L10.5817 59.5563C6.38087 52.1896 12.6802 43.2665 21.0281 44.7586L205.223 77.6824C206.398 77.8924 207.601 77.8904 208.776 77.6763L389.119 44.8058C397.439 43.2894 403.768 52.1434 399.641 59.5246Z" fill="url(#paint0_linear)"/>
<path d="M292.965 1.5744L156.801 28.2552C154.563 28.6937 152.906 30.5903 152.771 32.8664L144.395 174.33C144.198 177.662 147.258 180.248 150.51 179.498L188.42 170.749C191.967 169.931 195.172 173.055 194.443 176.622L183.18 231.775C182.422 235.487 185.907 238.661 189.532 237.56L212.947 230.446C216.577 229.344 220.065 232.527 219.297 236.242L201.398 322.875C200.278 328.294 207.486 331.249 210.492 326.603L212.5 323.5L323.454 102.072C325.312 98.3645 322.108 94.137 318.036 94.9228L279.014 102.454C275.347 103.161 272.227 99.746 273.262 96.1583L298.731 7.86689C299.767 4.27314 296.636 0.855181 292.965 1.5744Z" fill="url(#paint1_linear)"/>
<defs>
<linearGradient id="paint0_linear" x1="6.00017" y1="32.9999" x2="235" y2="344" gradientUnits="userSpaceOnUse">
<stop stop-color="#41D1FF"/>
<stop offset="1" stop-color="#BD34FE"/>
</linearGradient>
<linearGradient id="paint1_linear" x1="194.651" y1="8.81818" x2="236.076" y2="292.989" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFEA83"/>
<stop offset="0.0833333" stop-color="#FFDD35"/>
<stop offset="1" stop-color="#FFA800"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

46
client/src/App.css Normal file
View File

@@ -0,0 +1,46 @@
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}
@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}
.card {
padding: 2em;
}
.read-the-docs {
color: #888;
}
.dinosaur {
display: block;
}

17
client/src/App.tsx Normal file
View File

@@ -0,0 +1,17 @@
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Index from "./pages/Index.tsx";
import Dinosaur from "./pages/Dinosaur.tsx";
import "./App.css";
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Index />} />
<Route path="/:selectedDinosaur" element={<Dinosaur />} />
</Routes>
</BrowserRouter>
);
}
export default App;

68
client/src/index.css Normal file
View File

@@ -0,0 +1,68 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}

10
client/src/main.tsx Normal file
View File

@@ -0,0 +1,10 @@
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "./index.css";
import App from "./App.tsx";
createRoot(document.getElementById("root")!).render(
<StrictMode>
<App />
</StrictMode>,
);

View File

@@ -0,0 +1,24 @@
import { useEffect, useState } from "react";
import { Link, useParams } from "react-router-dom";
import { Dino } from "../types";
export default function Dinosaur() {
const { selectedDinosaur } = useParams();
const [dinosaur, setDino] = useState<Dino>({ name: "", description: "" });
useEffect(() => {
(async () => {
const resp = await fetch(`/api/dinosaurs/${selectedDinosaur}`);
const dino = await resp.json() as Dino;
setDino(dino);
})();
}, [selectedDinosaur]);
return (
<div>
<h1>{dinosaur.name}</h1>
<p>{dinosaur.description}</p>
<Link to="/">🠠 Back to all dinosaurs</Link>
</div>
);
}

View File

@@ -0,0 +1,33 @@
import { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import { Dino } from "../types.ts";
export default function Index() {
const [dinosaurs, setDinosaurs] = useState<Dino[]>([]);
useEffect(() => {
(async () => {
const response = await fetch(`/api/dinosaurs/`);
const allDinosaurs = await response.json() as Dino[];
setDinosaurs(allDinosaurs);
})();
}, []);
return (
<main>
<h1>Welcome to the Dinosaur app</h1>
<p>Click on a dinosaur below to learn more.</p>
{dinosaurs.map((dinosaur: Dino) => {
return (
<Link
to={`/${dinosaur.name.toLowerCase()}`}
key={dinosaur.name}
className="dinosaur"
>
{dinosaur.name}
</Link>
);
})}
</main>
);
}

1
client/src/types.ts Normal file
View File

@@ -0,0 +1 @@
export type Dino = { name: string; description: string };

1
client/src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="vite/client" />

34
deno.json Normal file
View File

@@ -0,0 +1,34 @@
{
"tasks": {
"dev": "deno run -A --node-modules-dir=auto npm:vite & deno run server:start",
"build": "deno run -A --node-modules-dir=auto npm:vite build",
"server:start": "deno run -A --node-modules-dir --watch ./server/main.ts",
"serve": "deno run build && deno run server:start"
},
"imports": {
"@deno/vite-plugin": "npm:@deno/vite-plugin@^1.0.0",
"@oak/oak": "jsr:@oak/oak@^17.1.3",
"@std/assert": "jsr:@std/assert@1",
"@tajpouria/cors": "jsr:@tajpouria/cors@^1.2.1",
"@types/react": "npm:@types/react@^18.3.12",
"@vitejs/plugin-react": "npm:@vitejs/plugin-react@^4.3.3",
"react": "npm:react@^18.3.1",
"react-dom": "npm:react-dom@^18.3.1",
"react-router-dom": "npm:react-router-dom@^7.5.1",
"vite": "npm:vite@^5.4.11"
},
"compilerOptions": {
"types": [
"react",
"react-dom",
"@types/react"
],
"lib": [
"dom",
"dom.iterable",
"deno.ns"
],
"jsx": "react-jsx",
"jsxImportSource": "react"
}
}

698
deno.lock generated Normal file
View File

@@ -0,0 +1,698 @@
{
"version": "4",
"specifiers": {
"jsr:@oak/commons@1": "1.0.0",
"jsr:@oak/oak@*": "17.1.3",
"jsr:@oak/oak@^17.1.3": "17.1.3",
"jsr:@std/assert@*": "1.0.8",
"jsr:@std/assert@1": "1.0.8",
"jsr:@std/assert@^1.0.8": "1.0.8",
"jsr:@std/bytes@1": "1.0.4",
"jsr:@std/bytes@^1.0.2": "1.0.4",
"jsr:@std/crypto@1": "1.0.3",
"jsr:@std/encoding@1": "1.0.5",
"jsr:@std/encoding@^1.0.5": "1.0.5",
"jsr:@std/expect@*": "1.0.8",
"jsr:@std/http@1": "1.0.10",
"jsr:@std/internal@^1.0.5": "1.0.5",
"jsr:@std/io@0.224": "0.224.9",
"jsr:@std/media-types@1": "1.1.0",
"jsr:@std/path@1": "1.0.8",
"jsr:@std/testing@*": "1.0.5",
"jsr:@tajpouria/cors@^1.2.1": "1.2.1",
"npm:@deno/vite-plugin@1": "1.0.0_vite@5.4.11__@types+node@22.5.4_@types+node@22.5.4",
"npm:@types/node@*": "22.5.4",
"npm:@types/react@^18.3.12": "18.3.12",
"npm:@vitejs/plugin-react@^4.3.3": "4.3.3_vite@5.4.11__@types+node@22.5.4_@babel+core@7.26.0_@types+node@22.5.4",
"npm:path-to-regexp@6.2.1": "6.2.1",
"npm:react-dom@^18.3.1": "18.3.1_react@18.3.1",
"npm:react-router-dom@^7.5.1": "7.5.1_react@18.3.1_react-dom@18.3.1__react@18.3.1",
"npm:react@^18.3.1": "18.3.1",
"npm:vite@*": "5.4.11_@types+node@22.5.4",
"npm:vite@^5.4.11": "5.4.11_@types+node@22.5.4"
},
"jsr": {
"@oak/commons@1.0.0": {
"integrity": "49805b55603c3627a9d6235c0655aa2b6222d3036b3a13ff0380c16368f607ac",
"dependencies": [
"jsr:@std/assert@1",
"jsr:@std/bytes@1",
"jsr:@std/crypto",
"jsr:@std/encoding@1",
"jsr:@std/http",
"jsr:@std/media-types"
]
},
"@oak/oak@17.1.3": {
"integrity": "d89296c22db91681dd3a2a1e1fd14e258d0d5a9654de55637aee5b661c159f33",
"dependencies": [
"jsr:@oak/commons",
"jsr:@std/assert@1",
"jsr:@std/bytes@1",
"jsr:@std/crypto",
"jsr:@std/http",
"jsr:@std/io",
"jsr:@std/media-types",
"jsr:@std/path",
"npm:path-to-regexp"
]
},
"@std/assert@1.0.8": {
"integrity": "ebe0bd7eb488ee39686f77003992f389a06c3da1bbd8022184804852b2fa641b",
"dependencies": [
"jsr:@std/internal"
]
},
"@std/bytes@1.0.4": {
"integrity": "11a0debe522707c95c7b7ef89b478c13fb1583a7cfb9a85674cd2cc2e3a28abc"
},
"@std/crypto@1.0.3": {
"integrity": "a2a32f51ddef632d299e3879cd027c630dcd4d1d9a5285d6e6788072f4e51e7f"
},
"@std/encoding@1.0.5": {
"integrity": "ecf363d4fc25bd85bd915ff6733a7e79b67e0e7806334af15f4645c569fefc04"
},
"@std/expect@1.0.8": {
"integrity": "27e40d8f3aefb372fc6a703fb0b69e34560e72a2f78705178babdffa00119a5f",
"dependencies": [
"jsr:@std/assert@^1.0.8",
"jsr:@std/internal"
]
},
"@std/http@1.0.10": {
"integrity": "4e32d11493ab04e3ef09f104f0cb9beb4228b1d4b47c5469573c2c294c0d3692",
"dependencies": [
"jsr:@std/encoding@^1.0.5"
]
},
"@std/internal@1.0.5": {
"integrity": "54a546004f769c1ac9e025abd15a76b6671ddc9687e2313b67376125650dc7ba"
},
"@std/io@0.224.9": {
"integrity": "4414664b6926f665102e73c969cfda06d2c4c59bd5d0c603fd4f1b1c840d6ee3",
"dependencies": [
"jsr:@std/bytes@^1.0.2"
]
},
"@std/media-types@1.1.0": {
"integrity": "c9d093f0c05c3512932b330e3cc1fe1d627b301db33a4c2c2185c02471d6eaa4"
},
"@std/path@1.0.8": {
"integrity": "548fa456bb6a04d3c1a1e7477986b6cffbce95102d0bb447c67c4ee70e0364be"
},
"@std/testing@1.0.5": {
"integrity": "6e693cbec94c81a1ad3df668685c7ba8e20742bb10305bc7137faa5cf16d2ec4",
"dependencies": [
"jsr:@std/assert@^1.0.8",
"jsr:@std/internal"
]
},
"@tajpouria/cors@1.2.1": {
"integrity": "eca42e4fb7cb3906ef0ee3d1e565dd6bb4632ccd8e70a95cf4279759743328f0"
}
},
"npm": {
"@ampproject/remapping@2.3.0": {
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
"dependencies": [
"@jridgewell/gen-mapping",
"@jridgewell/trace-mapping"
]
},
"@babel/code-frame@7.26.2": {
"integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
"dependencies": [
"@babel/helper-validator-identifier",
"js-tokens",
"picocolors"
]
},
"@babel/compat-data@7.26.2": {
"integrity": "sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg=="
},
"@babel/core@7.26.0": {
"integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==",
"dependencies": [
"@ampproject/remapping",
"@babel/code-frame",
"@babel/generator",
"@babel/helper-compilation-targets",
"@babel/helper-module-transforms",
"@babel/helpers",
"@babel/parser",
"@babel/template",
"@babel/traverse",
"@babel/types",
"convert-source-map",
"debug",
"gensync",
"json5",
"semver"
]
},
"@babel/generator@7.26.2": {
"integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==",
"dependencies": [
"@babel/parser",
"@babel/types",
"@jridgewell/gen-mapping",
"@jridgewell/trace-mapping",
"jsesc"
]
},
"@babel/helper-compilation-targets@7.25.9": {
"integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==",
"dependencies": [
"@babel/compat-data",
"@babel/helper-validator-option",
"browserslist",
"lru-cache",
"semver"
]
},
"@babel/helper-module-imports@7.25.9": {
"integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==",
"dependencies": [
"@babel/traverse",
"@babel/types"
]
},
"@babel/helper-module-transforms@7.26.0_@babel+core@7.26.0": {
"integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==",
"dependencies": [
"@babel/core",
"@babel/helper-module-imports",
"@babel/helper-validator-identifier",
"@babel/traverse"
]
},
"@babel/helper-plugin-utils@7.25.9": {
"integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw=="
},
"@babel/helper-string-parser@7.25.9": {
"integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA=="
},
"@babel/helper-validator-identifier@7.25.9": {
"integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="
},
"@babel/helper-validator-option@7.25.9": {
"integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw=="
},
"@babel/helpers@7.26.0": {
"integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==",
"dependencies": [
"@babel/template",
"@babel/types"
]
},
"@babel/parser@7.26.2": {
"integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==",
"dependencies": [
"@babel/types"
]
},
"@babel/plugin-transform-react-jsx-self@7.25.9_@babel+core@7.26.0": {
"integrity": "sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==",
"dependencies": [
"@babel/core",
"@babel/helper-plugin-utils"
]
},
"@babel/plugin-transform-react-jsx-source@7.25.9_@babel+core@7.26.0": {
"integrity": "sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==",
"dependencies": [
"@babel/core",
"@babel/helper-plugin-utils"
]
},
"@babel/template@7.25.9": {
"integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==",
"dependencies": [
"@babel/code-frame",
"@babel/parser",
"@babel/types"
]
},
"@babel/traverse@7.25.9": {
"integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==",
"dependencies": [
"@babel/code-frame",
"@babel/generator",
"@babel/parser",
"@babel/template",
"@babel/types",
"debug",
"globals"
]
},
"@babel/types@7.26.0": {
"integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==",
"dependencies": [
"@babel/helper-string-parser",
"@babel/helper-validator-identifier"
]
},
"@deno/vite-plugin@1.0.0_vite@5.4.11__@types+node@22.5.4_@types+node@22.5.4": {
"integrity": "sha512-Q9UeWqs3s7B5lqzu1Z5QrzYAzqTj3+F9YW17tWobGRbT2G40ihwis6zK/+QgMgcG4fm3IqdIfXmpQYhkZpdMfw==",
"dependencies": [
"vite"
]
},
"@esbuild/aix-ppc64@0.21.5": {
"integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="
},
"@esbuild/android-arm64@0.21.5": {
"integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A=="
},
"@esbuild/android-arm@0.21.5": {
"integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg=="
},
"@esbuild/android-x64@0.21.5": {
"integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA=="
},
"@esbuild/darwin-arm64@0.21.5": {
"integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ=="
},
"@esbuild/darwin-x64@0.21.5": {
"integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw=="
},
"@esbuild/freebsd-arm64@0.21.5": {
"integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g=="
},
"@esbuild/freebsd-x64@0.21.5": {
"integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ=="
},
"@esbuild/linux-arm64@0.21.5": {
"integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q=="
},
"@esbuild/linux-arm@0.21.5": {
"integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA=="
},
"@esbuild/linux-ia32@0.21.5": {
"integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg=="
},
"@esbuild/linux-loong64@0.21.5": {
"integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg=="
},
"@esbuild/linux-mips64el@0.21.5": {
"integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg=="
},
"@esbuild/linux-ppc64@0.21.5": {
"integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w=="
},
"@esbuild/linux-riscv64@0.21.5": {
"integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA=="
},
"@esbuild/linux-s390x@0.21.5": {
"integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A=="
},
"@esbuild/linux-x64@0.21.5": {
"integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ=="
},
"@esbuild/netbsd-x64@0.21.5": {
"integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg=="
},
"@esbuild/openbsd-x64@0.21.5": {
"integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow=="
},
"@esbuild/sunos-x64@0.21.5": {
"integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg=="
},
"@esbuild/win32-arm64@0.21.5": {
"integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A=="
},
"@esbuild/win32-ia32@0.21.5": {
"integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA=="
},
"@esbuild/win32-x64@0.21.5": {
"integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw=="
},
"@jridgewell/gen-mapping@0.3.5": {
"integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
"dependencies": [
"@jridgewell/set-array",
"@jridgewell/sourcemap-codec",
"@jridgewell/trace-mapping"
]
},
"@jridgewell/resolve-uri@3.1.2": {
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="
},
"@jridgewell/set-array@1.2.1": {
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A=="
},
"@jridgewell/sourcemap-codec@1.5.0": {
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="
},
"@jridgewell/trace-mapping@0.3.25": {
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
"dependencies": [
"@jridgewell/resolve-uri",
"@jridgewell/sourcemap-codec"
]
},
"@rollup/rollup-android-arm-eabi@4.27.3": {
"integrity": "sha512-EzxVSkIvCFxUd4Mgm4xR9YXrcp976qVaHnqom/Tgm+vU79k4vV4eYTjmRvGfeoW8m9LVcsAy/lGjcgVegKEhLQ=="
},
"@rollup/rollup-android-arm64@4.27.3": {
"integrity": "sha512-LJc5pDf1wjlt9o/Giaw9Ofl+k/vLUaYsE2zeQGH85giX2F+wn/Cg8b3c5CDP3qmVmeO5NzwVUzQQxwZvC2eQKw=="
},
"@rollup/rollup-darwin-arm64@4.27.3": {
"integrity": "sha512-OuRysZ1Mt7wpWJ+aYKblVbJWtVn3Cy52h8nLuNSzTqSesYw1EuN6wKp5NW/4eSre3mp12gqFRXOKTcN3AI3LqA=="
},
"@rollup/rollup-darwin-x64@4.27.3": {
"integrity": "sha512-xW//zjJMlJs2sOrCmXdB4d0uiilZsOdlGQIC/jjmMWT47lkLLoB1nsNhPUcnoqyi5YR6I4h+FjBpILxbEy8JRg=="
},
"@rollup/rollup-freebsd-arm64@4.27.3": {
"integrity": "sha512-58E0tIcwZ+12nK1WiLzHOD8I0d0kdrY/+o7yFVPRHuVGY3twBwzwDdTIBGRxLmyjciMYl1B/U515GJy+yn46qw=="
},
"@rollup/rollup-freebsd-x64@4.27.3": {
"integrity": "sha512-78fohrpcVwTLxg1ZzBMlwEimoAJmY6B+5TsyAZ3Vok7YabRBUvjYTsRXPTjGEvv/mfgVBepbW28OlMEz4w8wGA=="
},
"@rollup/rollup-linux-arm-gnueabihf@4.27.3": {
"integrity": "sha512-h2Ay79YFXyQi+QZKo3ISZDyKaVD7uUvukEHTOft7kh00WF9mxAaxZsNs3o/eukbeKuH35jBvQqrT61fzKfAB/Q=="
},
"@rollup/rollup-linux-arm-musleabihf@4.27.3": {
"integrity": "sha512-Sv2GWmrJfRY57urktVLQ0VKZjNZGogVtASAgosDZ1aUB+ykPxSi3X1nWORL5Jk0sTIIwQiPH7iE3BMi9zGWfkg=="
},
"@rollup/rollup-linux-arm64-gnu@4.27.3": {
"integrity": "sha512-FPoJBLsPW2bDNWjSrwNuTPUt30VnfM8GPGRoLCYKZpPx0xiIEdFip3dH6CqgoT0RnoGXptaNziM0WlKgBc+OWQ=="
},
"@rollup/rollup-linux-arm64-musl@4.27.3": {
"integrity": "sha512-TKxiOvBorYq4sUpA0JT+Fkh+l+G9DScnG5Dqx7wiiqVMiRSkzTclP35pE6eQQYjP4Gc8yEkJGea6rz4qyWhp3g=="
},
"@rollup/rollup-linux-powerpc64le-gnu@4.27.3": {
"integrity": "sha512-v2M/mPvVUKVOKITa0oCFksnQQ/TqGrT+yD0184/cWHIu0LoIuYHwox0Pm3ccXEz8cEQDLk6FPKd1CCm+PlsISw=="
},
"@rollup/rollup-linux-riscv64-gnu@4.27.3": {
"integrity": "sha512-LdrI4Yocb1a/tFVkzmOE5WyYRgEBOyEhWYJe4gsDWDiwnjYKjNs7PS6SGlTDB7maOHF4kxevsuNBl2iOcj3b4A=="
},
"@rollup/rollup-linux-s390x-gnu@4.27.3": {
"integrity": "sha512-d4wVu6SXij/jyiwPvI6C4KxdGzuZOvJ6y9VfrcleHTwo68fl8vZC5ZYHsCVPUi4tndCfMlFniWgwonQ5CUpQcA=="
},
"@rollup/rollup-linux-x64-gnu@4.27.3": {
"integrity": "sha512-/6bn6pp1fsCGEY5n3yajmzZQAh+mW4QPItbiWxs69zskBzJuheb3tNynEjL+mKOsUSFK11X4LYF2BwwXnzWleA=="
},
"@rollup/rollup-linux-x64-musl@4.27.3": {
"integrity": "sha512-nBXOfJds8OzUT1qUreT/en3eyOXd2EH5b0wr2bVB5999qHdGKkzGzIyKYaKj02lXk6wpN71ltLIaQpu58YFBoQ=="
},
"@rollup/rollup-win32-arm64-msvc@4.27.3": {
"integrity": "sha512-ogfbEVQgIZOz5WPWXF2HVb6En+kWzScuxJo/WdQTqEgeyGkaa2ui5sQav9Zkr7bnNCLK48uxmmK0TySm22eiuw=="
},
"@rollup/rollup-win32-ia32-msvc@4.27.3": {
"integrity": "sha512-ecE36ZBMLINqiTtSNQ1vzWc5pXLQHlf/oqGp/bSbi7iedcjcNb6QbCBNG73Euyy2C+l/fn8qKWEwxr+0SSfs3w=="
},
"@rollup/rollup-win32-x64-msvc@4.27.3": {
"integrity": "sha512-vliZLrDmYKyaUoMzEbMTg2JkerfBjn03KmAw9CykO0Zzkzoyd7o3iZNam/TpyWNjNT+Cz2iO3P9Smv2wgrR+Eg=="
},
"@types/babel__core@7.20.5": {
"integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
"dependencies": [
"@babel/parser",
"@babel/types",
"@types/babel__generator",
"@types/babel__template",
"@types/babel__traverse"
]
},
"@types/babel__generator@7.6.8": {
"integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==",
"dependencies": [
"@babel/types"
]
},
"@types/babel__template@7.4.4": {
"integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
"dependencies": [
"@babel/parser",
"@babel/types"
]
},
"@types/babel__traverse@7.20.6": {
"integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==",
"dependencies": [
"@babel/types"
]
},
"@types/estree@1.0.6": {
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="
},
"@types/node@22.5.4": {
"integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==",
"dependencies": [
"undici-types"
]
},
"@types/prop-types@15.7.13": {
"integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA=="
},
"@types/react@18.3.12": {
"integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==",
"dependencies": [
"@types/prop-types",
"csstype"
]
},
"@vitejs/plugin-react@4.3.3_vite@5.4.11__@types+node@22.5.4_@babel+core@7.26.0_@types+node@22.5.4": {
"integrity": "sha512-NooDe9GpHGqNns1i8XDERg0Vsg5SSYRhRxxyTGogUdkdNt47jal+fbuYi+Yfq6pzRCKXyoPcWisfxE6RIM3GKA==",
"dependencies": [
"@babel/core",
"@babel/plugin-transform-react-jsx-self",
"@babel/plugin-transform-react-jsx-source",
"@types/babel__core",
"react-refresh",
"vite"
]
},
"browserslist@4.24.2": {
"integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==",
"dependencies": [
"caniuse-lite",
"electron-to-chromium",
"node-releases",
"update-browserslist-db"
]
},
"caniuse-lite@1.0.30001680": {
"integrity": "sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA=="
},
"convert-source-map@2.0.0": {
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="
},
"cookie@1.0.2": {
"integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="
},
"csstype@3.1.3": {
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
},
"debug@4.3.7": {
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"dependencies": [
"ms"
]
},
"electron-to-chromium@1.5.63": {
"integrity": "sha512-ddeXKuY9BHo/mw145axlyWjlJ1UBt4WK3AlvkT7W2AbqfRQoacVoRUCF6wL3uIx/8wT9oLKXzI+rFqHHscByaA=="
},
"esbuild@0.21.5": {
"integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
"dependencies": [
"@esbuild/aix-ppc64",
"@esbuild/android-arm",
"@esbuild/android-arm64",
"@esbuild/android-x64",
"@esbuild/darwin-arm64",
"@esbuild/darwin-x64",
"@esbuild/freebsd-arm64",
"@esbuild/freebsd-x64",
"@esbuild/linux-arm",
"@esbuild/linux-arm64",
"@esbuild/linux-ia32",
"@esbuild/linux-loong64",
"@esbuild/linux-mips64el",
"@esbuild/linux-ppc64",
"@esbuild/linux-riscv64",
"@esbuild/linux-s390x",
"@esbuild/linux-x64",
"@esbuild/netbsd-x64",
"@esbuild/openbsd-x64",
"@esbuild/sunos-x64",
"@esbuild/win32-arm64",
"@esbuild/win32-ia32",
"@esbuild/win32-x64"
]
},
"escalade@3.2.0": {
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="
},
"fsevents@2.3.3": {
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="
},
"gensync@1.0.0-beta.2": {
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="
},
"globals@11.12.0": {
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="
},
"js-tokens@4.0.0": {
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"jsesc@3.0.2": {
"integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g=="
},
"json5@2.2.3": {
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="
},
"loose-envify@1.4.0": {
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"dependencies": [
"js-tokens"
]
},
"lru-cache@5.1.1": {
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
"dependencies": [
"yallist"
]
},
"ms@2.1.3": {
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"nanoid@3.3.7": {
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g=="
},
"node-releases@2.0.18": {
"integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g=="
},
"path-to-regexp@6.2.1": {
"integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw=="
},
"picocolors@1.1.1": {
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
},
"postcss@8.4.49": {
"integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
"dependencies": [
"nanoid",
"picocolors",
"source-map-js"
]
},
"react-dom@18.3.1_react@18.3.1": {
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"dependencies": [
"loose-envify",
"react",
"scheduler"
]
},
"react-refresh@0.14.2": {
"integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA=="
},
"react-router-dom@7.5.1_react@18.3.1_react-dom@18.3.1__react@18.3.1": {
"integrity": "sha512-5DPSPc7ENrt2tlKPq0FtpG80ZbqA9aIKEyqX6hSNJDlol/tr6iqCK4crqdsusmOSSotq6zDsn0y3urX9TuTNmA==",
"dependencies": [
"react",
"react-dom",
"react-router"
]
},
"react-router@7.5.1_react@18.3.1_react-dom@18.3.1__react@18.3.1": {
"integrity": "sha512-/jjU3fcYNd2bwz9Q0xt5TwyiyoO8XjSEFXJY4O/lMAlkGTHWuHRAbR9Etik+lSDqMC7A7mz3UlXzgYT6Vl58sA==",
"dependencies": [
"cookie",
"react",
"react-dom",
"set-cookie-parser",
"turbo-stream"
]
},
"react@18.3.1": {
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"dependencies": [
"loose-envify"
]
},
"rollup@4.27.3": {
"integrity": "sha512-SLsCOnlmGt9VoZ9Ek8yBK8tAdmPHeppkw+Xa7yDlCEhDTvwYei03JlWo1fdc7YTfLZ4tD8riJCUyAgTbszk1fQ==",
"dependencies": [
"@rollup/rollup-android-arm-eabi",
"@rollup/rollup-android-arm64",
"@rollup/rollup-darwin-arm64",
"@rollup/rollup-darwin-x64",
"@rollup/rollup-freebsd-arm64",
"@rollup/rollup-freebsd-x64",
"@rollup/rollup-linux-arm-gnueabihf",
"@rollup/rollup-linux-arm-musleabihf",
"@rollup/rollup-linux-arm64-gnu",
"@rollup/rollup-linux-arm64-musl",
"@rollup/rollup-linux-powerpc64le-gnu",
"@rollup/rollup-linux-riscv64-gnu",
"@rollup/rollup-linux-s390x-gnu",
"@rollup/rollup-linux-x64-gnu",
"@rollup/rollup-linux-x64-musl",
"@rollup/rollup-win32-arm64-msvc",
"@rollup/rollup-win32-ia32-msvc",
"@rollup/rollup-win32-x64-msvc",
"@types/estree",
"fsevents"
]
},
"scheduler@0.23.2": {
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
"dependencies": [
"loose-envify"
]
},
"semver@6.3.1": {
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="
},
"set-cookie-parser@2.7.1": {
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="
},
"source-map-js@1.2.1": {
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="
},
"turbo-stream@2.4.0": {
"integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g=="
},
"undici-types@6.19.8": {
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="
},
"update-browserslist-db@1.1.1_browserslist@4.24.2": {
"integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==",
"dependencies": [
"browserslist",
"escalade",
"picocolors"
]
},
"vite@5.4.11_@types+node@22.5.4": {
"integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==",
"dependencies": [
"@types/node",
"esbuild",
"fsevents",
"postcss",
"rollup"
]
},
"yallist@3.1.1": {
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
}
},
"workspace": {
"dependencies": [
"jsr:@oak/oak@^17.1.3",
"jsr:@std/assert@1",
"jsr:@tajpouria/cors@^1.2.1",
"npm:@deno/vite-plugin@1",
"npm:@types/react@^18.3.12",
"npm:@vitejs/plugin-react@^4.3.3",
"npm:react-dom@^18.3.1",
"npm:react-router-dom@^7.5.1",
"npm:react@^18.3.1",
"npm:vite@^5.4.11"
]
}
}

3022
server/api/data.json Normal file

File diff suppressed because it is too large Load Diff

38
server/main.ts Normal file
View File

@@ -0,0 +1,38 @@
import { Application } from "jsr:@oak/oak/application";
import { Router } from "jsr:@oak/oak/router";
import { oakCors } from "@tajpouria/cors";
import routeStaticFilesFrom from "./util/routeStaticFilesFrom.ts";
import data from "./api/data.json" with { type: "json" };
export const app = new Application();
const router = new Router();
router.get("/api/dinosaurs", (context) => {
context.response.body = data;
});
router.get("/api/dinosaurs/:dinosaur", (context) => {
if (!context?.params?.dinosaur) {
context.response.body = "No dinosaur name provided.";
}
const dinosaur = data.find((item) =>
item.name.toLowerCase() === context.params.dinosaur.toLowerCase()
);
context.response.body = dinosaur ?? "No dinosaur found.";
});
app.use(oakCors());
app.use(router.routes());
app.use(router.allowedMethods());
app.use(routeStaticFilesFrom([
`${Deno.cwd()}/client/dist`,
`${Deno.cwd()}/client/public`,
]));
if (import.meta.main) {
console.log("Server listening on port http://localhost:8000");
await app.listen({ port: 8000 });
}

68
server/main_test.ts Normal file
View File

@@ -0,0 +1,68 @@
import { assertEquals, assertExists } from "jsr:@std/assert";
import { afterAll, beforeAll, describe, it } from "jsr:@std/testing/bdd";
import { expect } from "jsr:@std/expect";
import { Application } from "jsr:@oak/oak/application";
import { Router } from "jsr:@oak/oak/router";
import { app } from "./main.ts";
import routeStaticFilesFrom from "./util/routeStaticFilesFrom.ts";
describe("Application", () => {
let serverInfo: { baseUrl: string; abortController: AbortController };
beforeAll(async () => {
console.log("Starting server");
serverInfo = await serve();
});
afterAll(() => {
console.log("Shutting down server");
serverInfo.abortController.abort();
});
it("can be created", () => {
assertExists(app);
assertEquals(app instanceof Application, true);
});
it("router accepts routes without throwing errors", () => {
const router = new Router();
app.use(router.routes());
assertExists(router);
});
it("can configure static routes", () => {
const staticFileMiddleware = routeStaticFilesFrom([
`${Deno.cwd()}/client/dist`,
`${Deno.cwd()}/client/public`,
]);
app.use(staticFileMiddleware);
assertExists(staticFileMiddleware);
});
it("can request home page from running server", async () => {
const response = await fetch(serverInfo.baseUrl);
const body = await response.text();
assertEquals(response.status, 200);
expect(body).toContain("<title>Vite + React + TS</title>");
});
});
async function serve(abortController = new AbortController()) {
let randomPort = 0;
app.listen({ port: randomPort, signal: abortController.signal });
await new Promise<void>((resolve) => {
app.addEventListener("listen", (ev) => {
randomPort = ev.port;
console.log(`Server running on http://localhost:${ev.port}`);
resolve();
});
});
return {
baseUrl: `http://localhost:${randomPort}`,
abortController: abortController,
};
}

View File

@@ -0,0 +1,19 @@
import { Next } from "jsr:@oak/oak/middleware";
import { Context } from "jsr:@oak/oak/context";
// Configure static site routes so that we can serve
// the Vite build output and the public folder
export default function routeStaticFilesFrom(staticPaths: string[]) {
return async (context: Context<Record<string, object>>, next: Next) => {
for (const path of staticPaths) {
try {
await context.send({ root: path, index: "index.html" });
return;
} catch {
continue;
}
}
await next();
};
}

26
vite.config.ts Normal file
View File

@@ -0,0 +1,26 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import deno from "@deno/vite-plugin";
import "react";
import "react-dom";
export default defineConfig({
root: "./client",
server: {
port: 3000,
proxy: {
"/api": {
target: "http://localhost:8000",
changeOrigin: true,
},
},
},
plugins: [
react(),
deno(),
],
optimizeDeps: {
include: ["react/jsx-runtime"],
},
});