Bunch of imprevments copied from other project based on this starter repo
This commit is contained in:
parent
cfe5a813f7
commit
ea2629b7eb
24
.devcontainer/devcontainer.json
Normal file
24
.devcontainer/devcontainer.json
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
||||||
|
// README at: https://github.com/devcontainers/templates/tree/main/src/docker-existing-dockerfile
|
||||||
|
{
|
||||||
|
"name": "ServiMain Dev Container",
|
||||||
|
"build": {
|
||||||
|
// Sets the run context to one level up instead of the .devcontainer folder.
|
||||||
|
"context": "..",
|
||||||
|
// Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename.
|
||||||
|
"dockerfile": "../Dockerfile"
|
||||||
|
},
|
||||||
|
// Features to add to the dev container. More info: https://containers.dev/features.
|
||||||
|
// "features": {},
|
||||||
|
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||||
|
"forwardPorts": [
|
||||||
|
3000,
|
||||||
|
3000
|
||||||
|
]
|
||||||
|
// Uncomment the next line to run commands after the container is created.
|
||||||
|
// "postCreateCommand": "cat /etc/os-release",
|
||||||
|
// Configure tool-specific properties.
|
||||||
|
// "customizations": {},
|
||||||
|
// Uncomment to connect as an existing user other than the container default. More info: https://aka.ms/dev-containers-non-root.
|
||||||
|
// "remoteUser": "devcontainer"
|
||||||
|
}
|
@ -1,2 +1,2 @@
|
|||||||
# Config avaliable on development environment
|
# Config avaliable on development environment
|
||||||
VITE_API_URL=http://localhost:7082
|
VITE_BASE_PATH=/
|
@ -1,2 +1,2 @@
|
|||||||
# Config avaliable on production
|
# Config avaliable on production environment
|
||||||
VITE_API_URL=http://192.168.179.36:7082
|
VITE_BASE_PATH=/
|
@ -1,4 +1,4 @@
|
|||||||
module.exports = {
|
export default {
|
||||||
env: { browser: true, es2020: true, node: true },
|
env: { browser: true, es2020: true, node: true },
|
||||||
extends: [
|
extends: [
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
@ -1,4 +1,4 @@
|
|||||||
module.exports = {
|
export default {
|
||||||
printWidth: 80,
|
printWidth: 80,
|
||||||
trailingComma: "all",
|
trailingComma: "all",
|
||||||
singleQuote: false,
|
singleQuote: false,
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"typescript.tsdk": "node_modules/typescript/lib"
|
||||||
|
}
|
13
Dockerfile
Normal file
13
Dockerfile
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
FROM node:lts
|
||||||
|
LABEL maintainer="Igor Barcik <"
|
||||||
|
RUN mkdir /opt/app
|
||||||
|
WORKDIR /opt/app
|
||||||
|
COPY package.json /opt/app
|
||||||
|
RUN npm install -g pnpm
|
||||||
|
RUN pnpm install
|
||||||
|
COPY . /opt/app
|
||||||
|
RUN pnpm run build
|
||||||
|
#change port if needed
|
||||||
|
EXPOSE 3000
|
||||||
|
#adjust command if needed
|
||||||
|
CMD ["pnpm", "start"]
|
13
README.md
13
README.md
@ -10,10 +10,15 @@
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
1. Setup `VITE_APP_NAME` in `.env.{mode}` files
|
1. Setup `.env.{mode}` files
|
||||||
2. Setup `VITE_BASE_PATH` in `.env.{mode}` files
|
|
||||||
3. run: `bun run dev`
|
```ini
|
||||||
4. To build for production, run: `bun run build`
|
VITE_APP_NAME=Universal React Starter
|
||||||
|
VITE_BASE_PATH=/
|
||||||
|
```
|
||||||
|
|
||||||
|
2. run: `bun run dev`
|
||||||
|
3. To build for production, run: `bun run build`
|
||||||
|
|
||||||
_bun can be replaced by packet manager of your choice_
|
_bun can be replaced by packet manager of your choice_
|
||||||
|
|
||||||
|
39
docs/routes_wouter_example.md
Normal file
39
docs/routes_wouter_example.md
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# Wouter Example
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { Link, Route, Router, Switch } from "wouter";
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Router base="/app">
|
||||||
|
<Switch>
|
||||||
|
<Route path="/">Home</Route>
|
||||||
|
<Route path="/user" nest>
|
||||||
|
<Route path="/">User</Route>
|
||||||
|
<Route path="/:username">
|
||||||
|
{(params) => {
|
||||||
|
console.log(params);
|
||||||
|
return <div>User Txt: {JSON.stringify(params)}</div>;
|
||||||
|
}}
|
||||||
|
</Route>
|
||||||
|
</Route>
|
||||||
|
</Switch>
|
||||||
|
<br />
|
||||||
|
{/* ----- */}
|
||||||
|
<Link className="btn btn-neutral" href="/">
|
||||||
|
Home
|
||||||
|
</Link>
|
||||||
|
<Link className="btn btn-neutral" href="/user">
|
||||||
|
User
|
||||||
|
</Link>
|
||||||
|
<Link className="btn btn-neutral" href="/user/USERNAME">
|
||||||
|
User Txt
|
||||||
|
</Link>
|
||||||
|
</Router>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
|
```
|
435
docs/servimainUI.excalidraw
Normal file
435
docs/servimainUI.excalidraw
Normal file
@ -0,0 +1,435 @@
|
|||||||
|
{
|
||||||
|
"type": "excalidraw",
|
||||||
|
"version": 2,
|
||||||
|
"source": "https://marketplace.visualstudio.com/items?itemName=pomdtr.excalidraw-editor",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"type": "rectangle",
|
||||||
|
"version": 1123,
|
||||||
|
"versionNonce": 1691917721,
|
||||||
|
"isDeleted": false,
|
||||||
|
"id": "S0A9g07WpP2BzYKcwg7_M",
|
||||||
|
"fillStyle": "solid",
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"strokeStyle": "solid",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"angle": 0,
|
||||||
|
"x": 719.724163,
|
||||||
|
"y": 143.81704726508062,
|
||||||
|
"strokeColor": "#1e1e1e",
|
||||||
|
"backgroundColor": "transparent",
|
||||||
|
"width": 936.2131903354187,
|
||||||
|
"height": 466.57181944584806,
|
||||||
|
"seed": 166536857,
|
||||||
|
"groupIds": [
|
||||||
|
"vs-w3LFUkaakMp9EAkrXR"
|
||||||
|
],
|
||||||
|
"frameId": null,
|
||||||
|
"roundness": {
|
||||||
|
"type": 3
|
||||||
|
},
|
||||||
|
"boundElements": null,
|
||||||
|
"updated": 1705396978531,
|
||||||
|
"link": null,
|
||||||
|
"locked": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "rectangle",
|
||||||
|
"version": 881,
|
||||||
|
"versionNonce": 27719639,
|
||||||
|
"isDeleted": false,
|
||||||
|
"id": "2zx0egnLluNOg2aagGFf5",
|
||||||
|
"fillStyle": "solid",
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"strokeStyle": "solid",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"angle": 0,
|
||||||
|
"x": 1053.8463976564144,
|
||||||
|
"y": 282.54197840360115,
|
||||||
|
"strokeColor": "#1e1e1e",
|
||||||
|
"backgroundColor": "transparent",
|
||||||
|
"width": 267.9687206871713,
|
||||||
|
"height": 55,
|
||||||
|
"seed": 1587473273,
|
||||||
|
"groupIds": [
|
||||||
|
"vs-w3LFUkaakMp9EAkrXR"
|
||||||
|
],
|
||||||
|
"frameId": null,
|
||||||
|
"roundness": {
|
||||||
|
"type": 3
|
||||||
|
},
|
||||||
|
"boundElements": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"id": "rb6uePhVtcsn3pyWiAATT"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"updated": 1705397026573,
|
||||||
|
"link": null,
|
||||||
|
"locked": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"version": 936,
|
||||||
|
"versionNonce": 1537819705,
|
||||||
|
"isDeleted": false,
|
||||||
|
"id": "rb6uePhVtcsn3pyWiAATT",
|
||||||
|
"fillStyle": "solid",
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"strokeStyle": "solid",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"angle": 0,
|
||||||
|
"x": 1142.3508080488282,
|
||||||
|
"y": 297.54197840360115,
|
||||||
|
"strokeColor": "#1e1e1e",
|
||||||
|
"backgroundColor": "#a5d8ff",
|
||||||
|
"width": 90.95989990234375,
|
||||||
|
"height": 25,
|
||||||
|
"seed": 1183126617,
|
||||||
|
"groupIds": [
|
||||||
|
"vs-w3LFUkaakMp9EAkrXR"
|
||||||
|
],
|
||||||
|
"frameId": null,
|
||||||
|
"roundness": null,
|
||||||
|
"boundElements": null,
|
||||||
|
"updated": 1705397026573,
|
||||||
|
"link": null,
|
||||||
|
"locked": false,
|
||||||
|
"fontSize": 20,
|
||||||
|
"fontFamily": 1,
|
||||||
|
"text": "Username",
|
||||||
|
"textAlign": "center",
|
||||||
|
"verticalAlign": "middle",
|
||||||
|
"containerId": "2zx0egnLluNOg2aagGFf5",
|
||||||
|
"originalText": "Username",
|
||||||
|
"lineHeight": 1.25,
|
||||||
|
"baseline": 18
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "rectangle",
|
||||||
|
"version": 937,
|
||||||
|
"versionNonce": 342071031,
|
||||||
|
"isDeleted": false,
|
||||||
|
"id": "I0Xg4QaaO7XRxm7w1P_0a",
|
||||||
|
"fillStyle": "solid",
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"strokeStyle": "solid",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"angle": 0,
|
||||||
|
"x": 1053.8463976564144,
|
||||||
|
"y": 352.73966244936236,
|
||||||
|
"strokeColor": "#1e1e1e",
|
||||||
|
"backgroundColor": "transparent",
|
||||||
|
"width": 267.9687206871713,
|
||||||
|
"height": 50.72658910127527,
|
||||||
|
"seed": 628302137,
|
||||||
|
"groupIds": [
|
||||||
|
"vs-w3LFUkaakMp9EAkrXR"
|
||||||
|
],
|
||||||
|
"frameId": null,
|
||||||
|
"roundness": {
|
||||||
|
"type": 3
|
||||||
|
},
|
||||||
|
"boundElements": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"id": "jHHO9SMQy-Scl-_j7OuG1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"updated": 1705397072409,
|
||||||
|
"link": null,
|
||||||
|
"locked": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"version": 933,
|
||||||
|
"versionNonce": 803446807,
|
||||||
|
"isDeleted": false,
|
||||||
|
"id": "jHHO9SMQy-Scl-_j7OuG1",
|
||||||
|
"fillStyle": "solid",
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"strokeStyle": "solid",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"angle": 0,
|
||||||
|
"x": 1142.0708092695313,
|
||||||
|
"y": 365.602957,
|
||||||
|
"strokeColor": "#1e1e1e",
|
||||||
|
"backgroundColor": "#a5d8ff",
|
||||||
|
"width": 91.5198974609375,
|
||||||
|
"height": 25,
|
||||||
|
"seed": 416059929,
|
||||||
|
"groupIds": [
|
||||||
|
"vs-w3LFUkaakMp9EAkrXR"
|
||||||
|
],
|
||||||
|
"frameId": null,
|
||||||
|
"roundness": null,
|
||||||
|
"boundElements": null,
|
||||||
|
"updated": 1705397072409,
|
||||||
|
"link": null,
|
||||||
|
"locked": false,
|
||||||
|
"fontSize": 20,
|
||||||
|
"fontFamily": 1,
|
||||||
|
"text": "Password",
|
||||||
|
"textAlign": "center",
|
||||||
|
"verticalAlign": "middle",
|
||||||
|
"containerId": "I0Xg4QaaO7XRxm7w1P_0a",
|
||||||
|
"originalText": "Password",
|
||||||
|
"lineHeight": 1.25,
|
||||||
|
"baseline": 18
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "rectangle",
|
||||||
|
"version": 995,
|
||||||
|
"versionNonce": 1770925591,
|
||||||
|
"isDeleted": false,
|
||||||
|
"id": "ZikkdqWQ7aOQyqTYogsSg",
|
||||||
|
"fillStyle": "solid",
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"strokeStyle": "solid",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"angle": 0,
|
||||||
|
"x": 1053.8463976564144,
|
||||||
|
"y": 420.93734649512356,
|
||||||
|
"strokeColor": "#1e1e1e",
|
||||||
|
"backgroundColor": "#a5d8ff",
|
||||||
|
"width": 267.9687206871713,
|
||||||
|
"height": 50.72658910127527,
|
||||||
|
"seed": 1166337785,
|
||||||
|
"groupIds": [
|
||||||
|
"vs-w3LFUkaakMp9EAkrXR"
|
||||||
|
],
|
||||||
|
"frameId": null,
|
||||||
|
"roundness": {
|
||||||
|
"type": 3
|
||||||
|
},
|
||||||
|
"boundElements": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"id": "T3YVOW1Ns_aODcZTbHigc"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"updated": 1705397026573,
|
||||||
|
"link": null,
|
||||||
|
"locked": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"version": 925,
|
||||||
|
"versionNonce": 147737081,
|
||||||
|
"isDeleted": false,
|
||||||
|
"id": "T3YVOW1Ns_aODcZTbHigc",
|
||||||
|
"fillStyle": "solid",
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"strokeStyle": "solid",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"angle": 0,
|
||||||
|
"x": 1164.4307793623047,
|
||||||
|
"y": 433.8006410457612,
|
||||||
|
"strokeColor": "#1e1e1e",
|
||||||
|
"backgroundColor": "#a5d8ff",
|
||||||
|
"width": 46.799957275390625,
|
||||||
|
"height": 25,
|
||||||
|
"seed": 1713348569,
|
||||||
|
"groupIds": [
|
||||||
|
"vs-w3LFUkaakMp9EAkrXR"
|
||||||
|
],
|
||||||
|
"frameId": null,
|
||||||
|
"roundness": null,
|
||||||
|
"boundElements": null,
|
||||||
|
"updated": 1705397026573,
|
||||||
|
"link": null,
|
||||||
|
"locked": false,
|
||||||
|
"fontSize": 20,
|
||||||
|
"fontFamily": 1,
|
||||||
|
"text": "Login",
|
||||||
|
"textAlign": "center",
|
||||||
|
"verticalAlign": "middle",
|
||||||
|
"containerId": "ZikkdqWQ7aOQyqTYogsSg",
|
||||||
|
"originalText": "Login",
|
||||||
|
"lineHeight": 1.25,
|
||||||
|
"baseline": 18
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"version": 768,
|
||||||
|
"versionNonce": 851740953,
|
||||||
|
"isDeleted": false,
|
||||||
|
"id": "97fLf76gEdKS_GvQ6ALpU",
|
||||||
|
"fillStyle": "solid",
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"strokeStyle": "solid",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"angle": 0,
|
||||||
|
"x": 1107.8027277397107,
|
||||||
|
"y": 190.00681289938947,
|
||||||
|
"strokeColor": "#1e1e1e",
|
||||||
|
"backgroundColor": "#a5d8ff",
|
||||||
|
"width": 160.05606079101562,
|
||||||
|
"height": 45,
|
||||||
|
"seed": 1826241945,
|
||||||
|
"groupIds": [
|
||||||
|
"vs-w3LFUkaakMp9EAkrXR"
|
||||||
|
],
|
||||||
|
"frameId": null,
|
||||||
|
"roundness": null,
|
||||||
|
"boundElements": null,
|
||||||
|
"updated": 1705396978532,
|
||||||
|
"link": null,
|
||||||
|
"locked": false,
|
||||||
|
"fontSize": 36,
|
||||||
|
"fontFamily": 1,
|
||||||
|
"text": "ServiMain",
|
||||||
|
"textAlign": "left",
|
||||||
|
"verticalAlign": "top",
|
||||||
|
"containerId": null,
|
||||||
|
"originalText": "ServiMain",
|
||||||
|
"lineHeight": 1.25,
|
||||||
|
"baseline": 32
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "no67OXeP6ZJsJ51TOHxJy",
|
||||||
|
"type": "text",
|
||||||
|
"x": 719.7241633880242,
|
||||||
|
"y": 107.53011605520807,
|
||||||
|
"width": 70.11228942871094,
|
||||||
|
"height": 35,
|
||||||
|
"angle": 0,
|
||||||
|
"strokeColor": "#1e1e1e",
|
||||||
|
"backgroundColor": "transparent",
|
||||||
|
"fillStyle": "solid",
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"strokeStyle": "solid",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"groupIds": [],
|
||||||
|
"frameId": null,
|
||||||
|
"roundness": null,
|
||||||
|
"seed": 1759430041,
|
||||||
|
"version": 34,
|
||||||
|
"versionNonce": 764198007,
|
||||||
|
"isDeleted": false,
|
||||||
|
"boundElements": null,
|
||||||
|
"updated": 1705397088770,
|
||||||
|
"link": null,
|
||||||
|
"locked": false,
|
||||||
|
"text": "/login",
|
||||||
|
"fontSize": 28,
|
||||||
|
"fontFamily": 1,
|
||||||
|
"textAlign": "left",
|
||||||
|
"verticalAlign": "top",
|
||||||
|
"baseline": 25,
|
||||||
|
"containerId": null,
|
||||||
|
"originalText": "/login",
|
||||||
|
"lineHeight": 1.25
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "rectangle",
|
||||||
|
"version": 1200,
|
||||||
|
"versionNonce": 1801846265,
|
||||||
|
"isDeleted": false,
|
||||||
|
"id": "0nggjHEz4VcO5hVagQVcv",
|
||||||
|
"fillStyle": "solid",
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"strokeStyle": "solid",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"angle": 0,
|
||||||
|
"x": 1686.3908299999998,
|
||||||
|
"y": 143.81704726508062,
|
||||||
|
"strokeColor": "#1e1e1e",
|
||||||
|
"backgroundColor": "transparent",
|
||||||
|
"width": 936.2131903354187,
|
||||||
|
"height": 466.57181944584806,
|
||||||
|
"seed": 1590948695,
|
||||||
|
"groupIds": [
|
||||||
|
"o2JEyUESBRRq2XUNVAWkr"
|
||||||
|
],
|
||||||
|
"frameId": null,
|
||||||
|
"roundness": {
|
||||||
|
"type": 3
|
||||||
|
},
|
||||||
|
"boundElements": [],
|
||||||
|
"updated": 1705396978532,
|
||||||
|
"link": null,
|
||||||
|
"locked": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "wApCZR8vnuIdRuwfVeC5x",
|
||||||
|
"type": "rectangle",
|
||||||
|
"x": 953.3863135555557,
|
||||||
|
"y": 244.88073477777772,
|
||||||
|
"width": 468.8888888888887,
|
||||||
|
"height": 264.44444444444457,
|
||||||
|
"angle": 0,
|
||||||
|
"strokeColor": "#1e1e1e",
|
||||||
|
"backgroundColor": "transparent",
|
||||||
|
"fillStyle": "solid",
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"strokeStyle": "solid",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"groupIds": [],
|
||||||
|
"frameId": null,
|
||||||
|
"roundness": {
|
||||||
|
"type": 3
|
||||||
|
},
|
||||||
|
"seed": 902925239,
|
||||||
|
"version": 109,
|
||||||
|
"versionNonce": 1334885687,
|
||||||
|
"isDeleted": false,
|
||||||
|
"boundElements": null,
|
||||||
|
"updated": 1705396987119,
|
||||||
|
"link": null,
|
||||||
|
"locked": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Gmr1u54szmiUkN9YuVUAY",
|
||||||
|
"type": "text",
|
||||||
|
"x": 1686.3908303880241,
|
||||||
|
"y": 107.53011605520807,
|
||||||
|
"width": 111.60848999023438,
|
||||||
|
"height": 35,
|
||||||
|
"angle": 0,
|
||||||
|
"strokeColor": "#1e1e1e",
|
||||||
|
"backgroundColor": "transparent",
|
||||||
|
"fillStyle": "solid",
|
||||||
|
"strokeWidth": 2,
|
||||||
|
"strokeStyle": "solid",
|
||||||
|
"roughness": 1,
|
||||||
|
"opacity": 100,
|
||||||
|
"groupIds": [],
|
||||||
|
"frameId": null,
|
||||||
|
"roundness": null,
|
||||||
|
"seed": 858001817,
|
||||||
|
"version": 132,
|
||||||
|
"versionNonce": 1615445527,
|
||||||
|
"isDeleted": false,
|
||||||
|
"boundElements": null,
|
||||||
|
"updated": 1705397104893,
|
||||||
|
"link": null,
|
||||||
|
"locked": false,
|
||||||
|
"text": "/; /home",
|
||||||
|
"fontSize": 28,
|
||||||
|
"fontFamily": 1,
|
||||||
|
"textAlign": "left",
|
||||||
|
"verticalAlign": "top",
|
||||||
|
"baseline": 25,
|
||||||
|
"containerId": null,
|
||||||
|
"originalText": "/; /home",
|
||||||
|
"lineHeight": 1.25
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"appState": {
|
||||||
|
"gridSize": null,
|
||||||
|
"viewBackgroundColor": "#ffffff"
|
||||||
|
},
|
||||||
|
"files": {}
|
||||||
|
}
|
@ -29,6 +29,6 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script type="module" src="src/main.tsx"></script>
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
18
package.json
18
package.json
@ -13,24 +13,24 @@
|
|||||||
"@emotion/react": "^11.11.1",
|
"@emotion/react": "^11.11.1",
|
||||||
"@emotion/styled": "^11.11.0",
|
"@emotion/styled": "^11.11.0",
|
||||||
"@hookform/resolvers": "^3.3.2",
|
"@hookform/resolvers": "^3.3.2",
|
||||||
"@tailwindcss/forms": "^0.5.7",
|
|
||||||
"@tanstack/react-table": "^8.10.7",
|
"@tanstack/react-table": "^8.10.7",
|
||||||
"axios": "^1.6.1",
|
"axios": "^1.6.1",
|
||||||
"daisyui": "latest",
|
"daisyui": "latest",
|
||||||
"echarts": "^5.4.3",
|
"echarts": "^5.4.3",
|
||||||
"jotai": "^2.6.0",
|
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-hook-form": "^7.48.2",
|
"react-hook-form": "^7.48.2",
|
||||||
"react-icons": "^4.12.0",
|
"react-icons": "^5.0.1",
|
||||||
"recharts": "^2.10.1",
|
|
||||||
"uuid": "^9.0.1",
|
"uuid": "^9.0.1",
|
||||||
"wouter": "^2.12.1",
|
"wouter": "next",
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.22.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/typography": "^0.5.10",
|
"@tailwindcss/forms": "^0.5.7",
|
||||||
|
"@tailwindcss/nesting": "^0.0.0-insiders.565cd3e",
|
||||||
|
"@tailwindcss/typography": "latest",
|
||||||
"@types/node": "^20.9.0",
|
"@types/node": "^20.9.0",
|
||||||
|
"@types/postcss-import": "^14.0.3",
|
||||||
"@types/react": "^18.2.37",
|
"@types/react": "^18.2.37",
|
||||||
"@types/react-dom": "^18.2.15",
|
"@types/react-dom": "^18.2.15",
|
||||||
"@types/uuid": "^9.0.7",
|
"@types/uuid": "^9.0.7",
|
||||||
@ -43,10 +43,12 @@
|
|||||||
"eslint-plugin-prettier": "^5.0.1",
|
"eslint-plugin-prettier": "^5.0.1",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.4",
|
"eslint-plugin-react-refresh": "^0.4.4",
|
||||||
"postcss": "^8.4.31",
|
"postcss": "^8.4.33",
|
||||||
|
"postcss-import": "^16.0.0",
|
||||||
"postcss-simple-vars": "^7.0.1",
|
"postcss-simple-vars": "^7.0.1",
|
||||||
"prettier": "^3.1.0",
|
"prettier": "^3.1.0",
|
||||||
"tailwindcss": "^3.3.5",
|
"tailwindcss": "^3.4.1",
|
||||||
|
"ts-node": "latest",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
"vite": "^4.5.0"
|
"vite": "^4.5.0"
|
||||||
}
|
}
|
||||||
|
3000
pnpm-lock.yaml
generated
3000
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,8 +1,10 @@
|
|||||||
export default {
|
/** @type {import('postcss-load-config').Config} */
|
||||||
plugins: {
|
const config = {
|
||||||
"postcss-import": {},
|
plugins: [
|
||||||
"tailwindcss/nesting": {},
|
require('autoprefixer'),
|
||||||
tailwindcss: {},
|
require('tailwindcss'),
|
||||||
autoprefixer: {},
|
require('postcss-import'),
|
||||||
},
|
require('tailwindcss/nesting'),
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
module.exports = config
|
51
src/App.tsx
51
src/App.tsx
@ -1,36 +1,29 @@
|
|||||||
import ThemeButton from "./components/ThemeButton";
|
import { IconContext } from "react-icons/lib";
|
||||||
import { main } from "./configure";
|
import DevControlPanel from "./components/DevControlPanel";
|
||||||
import { Router } from "wouter";
|
import FloatingMenu from "./components/FloatingMenu";
|
||||||
import SwitchRoutesGenerator from "./routes/SwitchRoutesGenerator";
|
import {
|
||||||
import GenerateNavigationButtonsDebug from "./routes/GenerateNavigationButtonsDebug";
|
floatingLanguageMenuStructure,
|
||||||
|
floatingMenuStructure,
|
||||||
|
} from "./configure";
|
||||||
|
import SwitchRouteGenerator from "./routes/SwitchRouteGenerator";
|
||||||
|
import routesTree from "./routes/routes";
|
||||||
|
// TODO: - [x] Rewrite new switch generating function based on custom routes object
|
||||||
|
// TODO: - [x] Make condition for base path in Router, for "/" in main.base_path don't add to Router
|
||||||
|
// TODO: - [ ] Rewrite DevControlPanel to use custom routes object
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
// Router component with base path
|
|
||||||
<Router base={main.basePath}>
|
|
||||||
<div className="space-x-4 space-y-6">
|
|
||||||
<div className="px-5 mt-6">
|
|
||||||
{/* Displaying program name and version */}
|
|
||||||
<h1 className="text-center ">
|
|
||||||
{main.program_name} v{main.program_version}
|
|
||||||
<p>Base path is: {main.basePath}</p>
|
|
||||||
</h1>
|
|
||||||
<div>
|
<div>
|
||||||
{/* ThemeButton component */}
|
<IconContext.Provider value={{ className: "size-5" }}>
|
||||||
<ThemeButton />
|
<FloatingMenu menuStructure={floatingMenuStructure} />
|
||||||
|
<FloatingMenu
|
||||||
|
menuStructure={floatingLanguageMenuStructure}
|
||||||
|
className="flex gap-2 absolute left-4 top-4 flex-col"
|
||||||
|
tooltipClassName="tooltip tooltip-right"
|
||||||
|
/>
|
||||||
|
</IconContext.Provider>
|
||||||
|
<SwitchRouteGenerator routesTree={routesTree} />
|
||||||
|
<DevControlPanel routesTree={routesTree} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{/* Separator */}
|
|
||||||
<hr className="my-12 h-[2px] border-t-0 bg-transparent bg-gradient-to-r from-transparent via-current to-transparent opacity-25 dark:opacity-100" />
|
|
||||||
<div>
|
|
||||||
{/* GenerateNavigationButtonsDebug component */}
|
|
||||||
<GenerateNavigationButtonsDebug />
|
|
||||||
{/* Paths in Route should be without base path (even without '/'). If want Route for base path than pass base path */}
|
|
||||||
{/* SwitchRoutesGenerator component */}
|
|
||||||
<SwitchRoutesGenerator />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Router>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
93
src/components/DevControlPanel.tsx
Normal file
93
src/components/DevControlPanel.tsx
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import { useLocation } from "wouter";
|
||||||
|
import { main } from "../configure";
|
||||||
|
import { DevControlPanelProps } from "../types/devControlPanelTypes";
|
||||||
|
import { RoutingTree } from "../types/routesTypes";
|
||||||
|
import inDebug from "../utils/inDebug";
|
||||||
|
import ThemeButton from "./ThemeButton";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Development Control Panel Component
|
||||||
|
*
|
||||||
|
* This component renders a development control panel for navigation and debugging.
|
||||||
|
* It includes a dynamic list of routes and a theme toggle button.
|
||||||
|
*
|
||||||
|
* @param {DevControlPanelProps} props - Properties passed to the DevControlPanel component.
|
||||||
|
* @returns {JSX.Element} A React component that renders the development control panel.
|
||||||
|
*/
|
||||||
|
const DevControlPanel = ({ routesTree }: DevControlPanelProps) => {
|
||||||
|
const [, setLocation] = useLocation();
|
||||||
|
|
||||||
|
// Function to update the current location, with debug logging
|
||||||
|
const _setLocation = (targetLocation: string) => {
|
||||||
|
inDebug(() => console.log("DevControlPanel_setLocation", targetLocation));
|
||||||
|
setLocation(targetLocation);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to generate a list of buttons for navigation based on the routing tree
|
||||||
|
const routesCrawler = (
|
||||||
|
routesTree: RoutingTree,
|
||||||
|
parentPath?: string,
|
||||||
|
): JSX.Element[] => {
|
||||||
|
return routesTree.map((route): JSX.Element => {
|
||||||
|
// Constructing path for the route button
|
||||||
|
const _path = (
|
||||||
|
parentPath ? "/" + parentPath + "/" + route.path : "/" + route.path
|
||||||
|
).replace(/\/\//g, "/");
|
||||||
|
|
||||||
|
// Debug logging for route information
|
||||||
|
inDebug(() =>
|
||||||
|
console.log(
|
||||||
|
"%croutesCrawler_routes %s %s",
|
||||||
|
"color: lightblue",
|
||||||
|
route.name,
|
||||||
|
_path,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Generating nested routes or single route buttons
|
||||||
|
if (route.nest) {
|
||||||
|
return (
|
||||||
|
<div className="indicator">
|
||||||
|
<span className="indicator-item indicator-center badge badge-primary">
|
||||||
|
{route.name}
|
||||||
|
</span>
|
||||||
|
<div key={_path} className="join">
|
||||||
|
{routesCrawler(route.nest, _path)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={_path}
|
||||||
|
className="btn btn-neutral join-item"
|
||||||
|
onClick={() => _setLocation(_path)}
|
||||||
|
>
|
||||||
|
{route.name}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Render the development control panel with navigation buttons and theme toggle
|
||||||
|
return (
|
||||||
|
<div className="border border-base-content flex gap-4 p-4 absolute w-full bottom-0 bg-base-300 z-50">
|
||||||
|
<div className="px-5 mt-6">
|
||||||
|
{/* Displaying the program name and version */}
|
||||||
|
<div className="text-center">
|
||||||
|
{main.program_name} v{main.program_version}
|
||||||
|
<p>Base path is: {main.base_path}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{/* Embedding the ThemeButton component for theme toggling */}
|
||||||
|
<ThemeButton />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* Rendering the dynamically generated route navigation buttons */}
|
||||||
|
<div className="space-y-2 space-x-2 mb-6">{routesCrawler(routesTree)}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DevControlPanel;
|
73
src/components/FloatingMenu.tsx
Normal file
73
src/components/FloatingMenu.tsx
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import { Path, useLocation } from "wouter";
|
||||||
|
import { navigate } from "wouter/use-browser-location";
|
||||||
|
import { FloatingMenuProps } from "../types/floatingMenuTypes";
|
||||||
|
import { capitalizeFirstLetter } from "../utils/StringTransformationUtils";
|
||||||
|
|
||||||
|
// -----VARIABLES-----
|
||||||
|
// Exported variables for shared location state management
|
||||||
|
export let location: Path;
|
||||||
|
export let setLocation: typeof navigate;
|
||||||
|
|
||||||
|
// -----COMPONENT-----
|
||||||
|
/**
|
||||||
|
* FloatingMenu Component
|
||||||
|
*
|
||||||
|
* This component renders a customizable floating menu with interactive elements.
|
||||||
|
* It uses the 'wouter' library for location management and provides a dynamic way
|
||||||
|
* to render menu items based on the 'menuStructure' prop passed to it.
|
||||||
|
*
|
||||||
|
* @param {FloatingMenuProps} props - The properties passed to the FloatingMenu component.
|
||||||
|
* @returns {JSX.Element} A React component that renders a floating menu.
|
||||||
|
*/
|
||||||
|
const FloatingMenu = (props: FloatingMenuProps) => {
|
||||||
|
// Setting up location hooks for navigation
|
||||||
|
[location, setLocation] = useLocation();
|
||||||
|
|
||||||
|
// Function to generate interactive buttons based on provided menu structure
|
||||||
|
const generateButtons = () => {
|
||||||
|
return props.menuStructure.map((item, index) => {
|
||||||
|
// Checking if the menu item includes label, icon, and action for button generation
|
||||||
|
if ("label" in item && "icon" in item && "action" in item) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={`floating_menu_element_container_${index}`}
|
||||||
|
className={props.tooltipClassName || `tooltip tooltip-left`}
|
||||||
|
data-tip={capitalizeFirstLetter(item.label)}
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
key={`floating_menu_element_${index}`}
|
||||||
|
className="btn btn-circle btn-outline"
|
||||||
|
onClick={item.action}
|
||||||
|
>
|
||||||
|
{item.icon}
|
||||||
|
</li>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Rendering custom JSX elements when provided
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={`floating_menu_element_container_${index}`}
|
||||||
|
className={props.tooltipClassName || `tooltip tooltip-left`}
|
||||||
|
data-tip={capitalizeFirstLetter(item.label)}
|
||||||
|
>
|
||||||
|
{item.element}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Render the floating menu with dynamically generated buttons
|
||||||
|
return (
|
||||||
|
<ul
|
||||||
|
className={
|
||||||
|
props.className || `flex gap-2 absolute right-4 top-4 flex-col` + " z-[999]"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{generateButtons()}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FloatingMenu;
|
@ -1,5 +1,5 @@
|
|||||||
import useTheme from "../hooks/useTheme";
|
import useTheme from "../hooks/useTheme";
|
||||||
import inDev from "../utils/inDebug";
|
import inDebug from "../utils/inDebug";
|
||||||
|
|
||||||
const ThemeButton = () => {
|
const ThemeButton = () => {
|
||||||
const [isDark, toggleTheme] = useTheme();
|
const [isDark, toggleTheme] = useTheme();
|
||||||
@ -8,7 +8,7 @@ const ThemeButton = () => {
|
|||||||
<input
|
<input
|
||||||
checked={isDark}
|
checked={isDark}
|
||||||
className="hidden"
|
className="hidden"
|
||||||
onChange={() => inDev(() => console.log("Theme changed"))}
|
onChange={() => inDebug(() => console.log("Theme changed"))}
|
||||||
onClick={toggleTheme}
|
onClick={toggleTheme}
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
/>
|
/>
|
||||||
|
@ -1,19 +1,54 @@
|
|||||||
|
import { FiHome, FiLogIn } from "react-icons/fi";
|
||||||
|
import { setLocation } from "./components/FloatingMenu";
|
||||||
|
import ThemeButton from "./components/ThemeButton";
|
||||||
|
import { HOME, LOGIN, THEME } from "./consts";
|
||||||
|
import { MenuStructure } from "./types/floatingMenuTypes";
|
||||||
|
/* INSTRUCTIONS:
|
||||||
|
* Here you can configure:
|
||||||
|
* - program version
|
||||||
|
* - program description
|
||||||
|
* - program authors
|
||||||
|
* - floating menu structure (icons, labels, actions)
|
||||||
|
*/
|
||||||
// -------- ENVIRONMENT VARIABLES --------
|
// -------- ENVIRONMENT VARIABLES --------
|
||||||
export const viteEnv = import.meta.env;
|
export const viteEnv = import.meta.env;
|
||||||
//Main configuration, static data, mostly used here
|
//Main configuration, static data, mostly used here
|
||||||
export const main = {
|
export const main = {
|
||||||
program_name: `${viteEnv.VITE_APP_NAME}${
|
api_url: viteEnv.VITE_API_URL || `setup in .env.${viteEnv.MODE}`,
|
||||||
viteEnv.MODE ? " [development]" : ""
|
base_path: viteEnv.VITE_BASE_PATH || "/",
|
||||||
}`,
|
|
||||||
program_version: "1.0.0",
|
|
||||||
basePath: viteEnv.VITE_BASE_PATH,
|
|
||||||
};
|
|
||||||
//About page configuration
|
|
||||||
export const about = {
|
|
||||||
program_description: `This is a ${main.program_name} for <purpose>.`,
|
|
||||||
program_authors: [{ name: "Author Name", email: "email@example.com" }],
|
program_authors: [{ name: "Author Name", email: "email@example.com" }],
|
||||||
|
program_description: `It is a software application that helps organizations manage maintenance activities. With a ${viteEnv.VITE_APP_NAME}, companies can track preventative maintenance schedules, record equipment repairs, and monitor inventory levels. It also allows for easy reporting and analysis, providing valuable insights into maintenance operations. By automating these processes, a ${viteEnv.VITE_APP_NAME} can help reduce costs, improve efficiency, and ensure compliance with regulatory standards.`,
|
||||||
|
program_name:
|
||||||
|
`${viteEnv.VITE_APP_NAME}${viteEnv.MODE ? " [development]" : ""}` ||
|
||||||
|
"setup in .env",
|
||||||
|
program_version: "1.0.0",
|
||||||
};
|
};
|
||||||
// -------- FRONTEND CONFIGURATION --------
|
// -------- FRONTEND CONFIGURATION --------
|
||||||
// sizes in pixels
|
// sizes in pixels
|
||||||
export const topbarSize = 64;
|
export const topbarSize = 64;
|
||||||
export const sidebarSize = 256;
|
export const sidebarSize = 256;
|
||||||
|
export const floatingMenuStructure: MenuStructure[] = [
|
||||||
|
{
|
||||||
|
label: HOME,
|
||||||
|
icon: <FiHome />,
|
||||||
|
action: () => setLocation("/"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: LOGIN,
|
||||||
|
icon: <FiLogIn />,
|
||||||
|
action: () => setLocation(`/${LOGIN}`),
|
||||||
|
},
|
||||||
|
{ label: THEME, element: <ThemeButton /> },
|
||||||
|
];
|
||||||
|
export const floatingLanguageMenuStructure: MenuStructure[] = [
|
||||||
|
{
|
||||||
|
label: "English",
|
||||||
|
icon: <p>🇬🇧</p>,
|
||||||
|
action: () => console.log("English"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Polski",
|
||||||
|
icon: <p>🇵🇱</p>,
|
||||||
|
action: () => console.log("Polski"),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
@ -1,2 +1,16 @@
|
|||||||
|
// ---- MAIN PAGE TITLES ----
|
||||||
export const ADMINISTRATION = "administration";
|
export const ADMINISTRATION = "administration";
|
||||||
|
export const HOME = "home";
|
||||||
|
export const LOGIN = "login";
|
||||||
|
export const PAGE_NOT_FOUND = "page_not_found";
|
||||||
export const SETTINGS = "settings";
|
export const SETTINGS = "settings";
|
||||||
|
export const ABOUT = "about";
|
||||||
|
export const CONTACT = "contact";
|
||||||
|
// ---- SUB-PAGE TITLES ----
|
||||||
|
// -- ADMINISTRATION --
|
||||||
|
export const GROUPS = "groups";
|
||||||
|
export const PERMISSIONS = "permissions";
|
||||||
|
export const ROLES = "roles";
|
||||||
|
export const USERS = "users";
|
||||||
|
// -- BUTTONS --
|
||||||
|
export const THEME = "theme";
|
||||||
|
@ -1,9 +1,21 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
/**
|
/**
|
||||||
* Works like useState but stores the value in local storage
|
* Custom hook for persisting state in local storage.
|
||||||
* @param key Create a key to store the value in local storage
|
*
|
||||||
* @param initialValue Assign an initial value to the key
|
* This hook works similarly to the standard `useState` hook but also stores the state in local storage,
|
||||||
* @returns [storedValue, setValue] Returns the stored value and a function to set the value
|
* allowing the state to persist across browser sessions. The state is initialized from local storage
|
||||||
|
* if it exists; otherwise, it falls back to the provided initial value.
|
||||||
|
*
|
||||||
|
* @template T The type of the value to be stored.
|
||||||
|
* @param {string} key The key under which the value is stored in local storage.
|
||||||
|
* @param {T} initialValue The initial value to be used if there is no item in local storage with the given key.
|
||||||
|
* @returns {[T, (value: T | ((val: T) => T)) => void]} A tuple containing:
|
||||||
|
* - `storedValue`: the current value stored in local storage.
|
||||||
|
* - `setValue`: a function to set the value, which updates both the local state and local storage.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const [name, setName] = useLocalStorage<string>("name", "Initial Name");
|
||||||
|
* // Use setName to update the name and it will be stored in local storage.
|
||||||
*/
|
*/
|
||||||
const useLocalStorage = <T>(
|
const useLocalStorage = <T>(
|
||||||
key: string,
|
key: string,
|
||||||
|
@ -1,19 +1,33 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import useLocalStorage from "./useLocalStorage";
|
import useLocalStorage from "./useLocalStorage";
|
||||||
|
import inDebug from "../utils/inDebug";
|
||||||
|
/**
|
||||||
|
* Custom hook for managing theme state.
|
||||||
|
*
|
||||||
|
* This hook utilizes local storage to persist the theme state across sessions. It provides functionality
|
||||||
|
* to toggle between light and dark themes and automatically applies the selected theme to the document.
|
||||||
|
*
|
||||||
|
* @returns A tuple containing:
|
||||||
|
* - `isDark`: a boolean indicating whether the current theme is dark.
|
||||||
|
* - `toggleTheme`: a function to toggle the theme between light and dark.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const [isDark, toggleTheme] = useTheme();
|
||||||
|
* // Use isDark to determine the theme state and toggleTheme to change the theme.
|
||||||
|
*/
|
||||||
const useTheme = (): [boolean, () => void] => {
|
const useTheme = (): [boolean, () => void] => {
|
||||||
const [theme, setTheme] = useLocalStorage<string>("theme", "light");
|
const [theme, setTheme] = useLocalStorage<string>("theme", "light");
|
||||||
const isDark = theme === "dark";
|
const isDark = theme === "dark";
|
||||||
|
|
||||||
const toggleTheme = () => {
|
const toggleTheme = () => {
|
||||||
console.log("toggleTheme called");
|
inDebug(() => console.log("toggleTheme called"));
|
||||||
const newTheme = theme === "light" ? "dark" : "light";
|
const newTheme = theme === "light" ? "dark" : "light";
|
||||||
setTheme(newTheme);
|
setTheme(newTheme);
|
||||||
document.documentElement.dataset.theme = newTheme;
|
document.documentElement.dataset.theme = newTheme;
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log("useEffect called");
|
inDebug(() => console.log("useEffect called"));
|
||||||
document.documentElement.dataset.theme = theme;
|
document.documentElement.dataset.theme = theme;
|
||||||
}, [theme]);
|
}, [theme]);
|
||||||
|
|
||||||
|
42
src/main.tsx
42
src/main.tsx
@ -1,26 +1,46 @@
|
|||||||
|
import { Global, css } from "@emotion/react";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import ReactDOM from "react-dom/client";
|
import ReactDOM from "react-dom/client";
|
||||||
import { setupAxiosInterceptors } from "./api/AxiosService";
|
|
||||||
import { viteEnv } from "./configure";
|
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
import inDev from "./utils/inDebug";
|
import { setupAxiosInterceptors } from "./api/AxiosService";
|
||||||
import { Global, css } from "@emotion/react";
|
import { main, viteEnv } from "./configure";
|
||||||
import "/style.css";
|
import inDebug from "./utils/inDebug";
|
||||||
import { Provider } from "jotai";
|
import "/style.css"; // Global tailwind styles
|
||||||
|
import { Router } from "wouter";
|
||||||
|
|
||||||
setupAxiosInterceptors();
|
setupAxiosInterceptors();
|
||||||
inDev(() => console.log(viteEnv));
|
inDebug(() => console.log(viteEnv));
|
||||||
|
//! Important, defining base as '/' isn't equal to not defining it at all. That way is easier 😁
|
||||||
|
const _App = () => {
|
||||||
|
if (main.base_path !== "/") {
|
||||||
|
return (
|
||||||
|
<Router base={main.base_path}>
|
||||||
|
<App />
|
||||||
|
</Router>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Router>
|
||||||
|
<App />
|
||||||
|
</Router>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Hook react to the HTML element with id="root"
|
// Hook react to the HTML element with id="root"
|
||||||
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<Global
|
<Global
|
||||||
styles={css`
|
styles={css`
|
||||||
// You can add more global styles here
|
html,
|
||||||
|
body,
|
||||||
|
#root {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
`}
|
`}
|
||||||
/>
|
/>
|
||||||
<Provider>
|
<_App />
|
||||||
<App />
|
|
||||||
</Provider>
|
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
);
|
);
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
const AdministrationPage = () => {
|
|
||||||
return <div>🚀AdministrationPage🚀</div>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AdministrationPage;
|
|
@ -1,8 +1,7 @@
|
|||||||
import { atom, useAtom } from "jotai";
|
import { useState } from "react";
|
||||||
const counterAtom = atom(0);
|
|
||||||
|
|
||||||
const HomePage = () => {
|
const HomePage = () => {
|
||||||
const [counter, setCounter] = useAtom(counterAtom);
|
const [counter, setCounter] = useState(0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
const InboxPage = () => {
|
|
||||||
return <div>InboxPage</div>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default InboxPage;
|
|
55
src/pages/LoginPage.tsx
Normal file
55
src/pages/LoginPage.tsx
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import { main } from "../configure";
|
||||||
|
|
||||||
|
const LoginPage = () => {
|
||||||
|
return (
|
||||||
|
// hero daisyui login page
|
||||||
|
<div className="hero min-h-screen bg-base-200">
|
||||||
|
<div className="flex-col justify-center hero-content lg:flex-row-reverse">
|
||||||
|
<div className="text-center lg:text-left">
|
||||||
|
<h1 className="mb-5 text-5xl font-bold">{main.program_name}</h1>
|
||||||
|
<p className="mb-5">{main.program_description}</p>
|
||||||
|
<button className="btn btn-outline">Get Started</button>
|
||||||
|
</div>
|
||||||
|
<div className="card flex-shrink-0 w-full max-w-sm shadow-2xl bg-base-100">
|
||||||
|
<div className="card-body">
|
||||||
|
<form>
|
||||||
|
<div className="form-control">
|
||||||
|
<label className="label">
|
||||||
|
<span className="label-text">Login</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="login"
|
||||||
|
className="input input-bordered"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="form-control">
|
||||||
|
<label className="label">
|
||||||
|
<span className="label-text">Password</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
placeholder="password"
|
||||||
|
className="input input-bordered"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="form-control mt-6">
|
||||||
|
<label className="cursor-pointer label justify-start gap-4">
|
||||||
|
<input type="checkbox" className="checkbox checkbox-success" />
|
||||||
|
<span className="label-text justify-start">
|
||||||
|
Agree to the terms and policy
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div className="form-control mt-6">
|
||||||
|
<input type="submit" value="Login" className="btn btn-primary" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LoginPage;
|
@ -1,8 +0,0 @@
|
|||||||
import { useParams } from "wouter";
|
|
||||||
|
|
||||||
const SettingsAdvancedPage = () => {
|
|
||||||
const params = useParams();
|
|
||||||
return <div>SettingsAdvancedPage {params.id}</div>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SettingsAdvancedPage;
|
|
@ -1,50 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { v4 } from "uuid";
|
|
||||||
import { Link } from "wouter";
|
|
||||||
import * as R from "./routes";
|
|
||||||
import { Routes } from "./routes";
|
|
||||||
import inDev from "../utils/inDebug";
|
|
||||||
|
|
||||||
const routesCrawler = (
|
|
||||||
parentPath?: string,
|
|
||||||
routesPack: Routes[] = R.routesRoot,
|
|
||||||
): JSX.Element[] => {
|
|
||||||
// Roll through routes and generate Route for each
|
|
||||||
return routesPack.map((route): JSX.Element => {
|
|
||||||
// Debug log shows generated routes
|
|
||||||
inDev(() =>
|
|
||||||
console.log(
|
|
||||||
"routesCrawler buttons",
|
|
||||||
parentPath ? parentPath + route.path : route.path,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<div key={v4()} className="join">
|
|
||||||
{/* Check does route have children and perform routing generation for children */}
|
|
||||||
<Link
|
|
||||||
className={`btn join-item hover:btn-primary duration-75 `}
|
|
||||||
href={
|
|
||||||
parentPath ? parentPath + route.path : route.path != "/" ? route.path : ""
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{/* Render icon */}
|
|
||||||
{route.icon && React.createElement(route.icon, { className: "h-6 w-6" })}
|
|
||||||
{/* Makes first letter uppercase */}
|
|
||||||
{route.name[0].toUpperCase() + route.name.slice(1)}
|
|
||||||
</Link>
|
|
||||||
{route.children
|
|
||||||
? routesCrawler(parentPath ?? "" + route.path, route.children)
|
|
||||||
: null}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This component is used to generate buttons for debug navigation
|
|
||||||
*/
|
|
||||||
const GenerateNavigationButtonsDebug = () => {
|
|
||||||
return <div className="space-y-2 space-x-2 mb-6">{routesCrawler()}</div>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default GenerateNavigationButtonsDebug;
|
|
22
src/routes/ManuallyDefinedRoutesTest.tsx
Normal file
22
src/routes/ManuallyDefinedRoutesTest.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { Switch, Route } from "wouter";
|
||||||
|
import HomePage from "../pages/HomePage";
|
||||||
|
|
||||||
|
const ManuallyDefinedRoutesTest = () => {
|
||||||
|
return (
|
||||||
|
<Switch>
|
||||||
|
<Route path="/" component={HomePage}>
|
||||||
|
<Route path="/users" nest>
|
||||||
|
<Switch>
|
||||||
|
<Route component={() => <>User HP</>} />
|
||||||
|
<Route
|
||||||
|
path="/users/:username"
|
||||||
|
component={(username) => <>User: {username}</>}
|
||||||
|
/>
|
||||||
|
</Switch>
|
||||||
|
</Route>
|
||||||
|
</Route>
|
||||||
|
</Switch>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ManuallyDefinedRoutesTest;
|
38
src/routes/SwitchRouteGenerator.tsx
Normal file
38
src/routes/SwitchRouteGenerator.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { Route, Switch } from "wouter";
|
||||||
|
import inDebug from "../utils/inDebug";
|
||||||
|
import { RoutingTree } from "../types/routesTypes";
|
||||||
|
import { SwitchRouteGeneratorProps } from "../types/switchRouteGeneratorTypes";
|
||||||
|
|
||||||
|
const routesCrawler = (
|
||||||
|
routesTree: RoutingTree,
|
||||||
|
parentPath?: string,
|
||||||
|
): JSX.Element[] => {
|
||||||
|
return routesTree.map((route): JSX.Element => {
|
||||||
|
// -----
|
||||||
|
if (route.path) {
|
||||||
|
const _path = (
|
||||||
|
parentPath && parentPath !== "/" ? parentPath + route.path : route.path
|
||||||
|
).replace("//", "/");
|
||||||
|
// Debug log shows generated routes
|
||||||
|
inDebug(() => console.log("routesCrawler_routes", route.name, _path));
|
||||||
|
// -----
|
||||||
|
if (route.nest) {
|
||||||
|
return (
|
||||||
|
<Route key={_path} path={_path}>
|
||||||
|
<Switch key={`${route.name}_switch_route`}>
|
||||||
|
{routesCrawler(route.nest, _path)}
|
||||||
|
</Switch>
|
||||||
|
</Route>
|
||||||
|
);
|
||||||
|
} else return <Route key={_path} path={_path} component={route.component} />;
|
||||||
|
} else return <Route key={route.name} component={route.component} />;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component is used to generate Switch with Routes for given routes object
|
||||||
|
*/
|
||||||
|
const SwitchRouteGenerator = ({ routesTree }: SwitchRouteGeneratorProps) => {
|
||||||
|
return <Switch key={"main_switch_route"}>{routesCrawler(routesTree)}</Switch>;
|
||||||
|
};
|
||||||
|
export default SwitchRouteGenerator;
|
@ -1,52 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import { v4 } from "uuid";
|
|
||||||
import { Route, Switch } from "wouter";
|
|
||||||
import inDev from "../utils/inDebug";
|
|
||||||
import * as R from "./routes";
|
|
||||||
import { Routes } from "./routes";
|
|
||||||
|
|
||||||
const routesCrawler = (
|
|
||||||
parentPath?: string,
|
|
||||||
routesPack: Routes[] = R.routesRoot,
|
|
||||||
): JSX.Element[] => {
|
|
||||||
// Roll through routes and generate Route for each
|
|
||||||
return routesPack.map((route): JSX.Element => {
|
|
||||||
// Debug log shows generated routes
|
|
||||||
inDev(() =>
|
|
||||||
console.log(
|
|
||||||
"routesCrawler routes",
|
|
||||||
parentPath ? parentPath + route.path : route.path,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return route.children ? (
|
|
||||||
<React.Fragment key={v4()}>
|
|
||||||
{routesCrawler(parentPath ?? "" + route.path, route.children)}
|
|
||||||
<Route
|
|
||||||
key={v4()}
|
|
||||||
path={parentPath ? parentPath + route.path : route.path}
|
|
||||||
component={route.component}
|
|
||||||
/>
|
|
||||||
</React.Fragment>
|
|
||||||
) : (
|
|
||||||
<Route
|
|
||||||
key={v4()}
|
|
||||||
path={parentPath ? parentPath + route.path : route.path}
|
|
||||||
component={route.component}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This component is used to generate Switch with Routes for given routes object
|
|
||||||
*/
|
|
||||||
const SwitchRoutesGenerator = () => {
|
|
||||||
return (
|
|
||||||
<Switch key={v4()}>
|
|
||||||
{routesCrawler()}
|
|
||||||
<Route component={() => <>404</>} />
|
|
||||||
</Switch>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SwitchRoutesGenerator;
|
|
@ -1,90 +1,67 @@
|
|||||||
// ----- IMPORT ICONS -----
|
// ----- IMPORT ICONS -----
|
||||||
|
// Importing necessary icons from 'react-icons' library
|
||||||
|
import { IoMdLogIn, IoMdOptions } from "react-icons/io";
|
||||||
import { RiHome3Fill } from "react-icons/ri";
|
import { RiHome3Fill } from "react-icons/ri";
|
||||||
import { IoMdOptions } from "react-icons/io";
|
|
||||||
import { FaUsers } from "react-icons/fa";
|
|
||||||
// ----- IMPORT PAGES -----
|
// ----- IMPORT PAGES -----
|
||||||
import AdministrationPage from "../pages/AdministrationPage";
|
// Importing page components for route configuration
|
||||||
import HomePage from "../pages/HomePage";
|
import HomePage from "../pages/HomePage";
|
||||||
// ----- IMPORT CONSTS -----
|
import LoginPage from "../pages/LoginPage";
|
||||||
import { ADMINISTRATION, SETTINGS } from "../consts";
|
|
||||||
import SettingsAdvancedPage from "../pages/SettingsAdvancedPage";
|
|
||||||
|
|
||||||
// ----- TYPE DEFINITIONS -----
|
// ----- IMPORT CONSTANTS -----
|
||||||
export type Routes = {
|
// Importing route-related constants for route naming
|
||||||
name: string;
|
import { HOME, LOGIN, PAGE_NOT_FOUND, SETTINGS, USERS } from "../consts";
|
||||||
path: string;
|
|
||||||
component: () => JSX.Element;
|
|
||||||
icon?: () => JSX.Element;
|
|
||||||
children?: Routes[];
|
|
||||||
};
|
|
||||||
type RoutesObject = {
|
|
||||||
[key: string]: Routes[];
|
|
||||||
};
|
|
||||||
|
|
||||||
// ----- ROUTES DEFINITIONS -----
|
// ----- IMPORT TYPES AND UTILITIES -----
|
||||||
//! Don't leave trailor '/' in paths
|
// Importing types for route configuration and utility functions
|
||||||
//! Paths in Route should be without base path (even without '/'). If want Route for base path than pass base path
|
import { RoutingTree } from "../types/routesTypes";
|
||||||
//* Wouter is basically join paths with each other, so if base path is "/" every sub path souldn't have "/" at the beginning
|
import { capitalizeFirstLetter } from "../utils/StringTransformationUtils";
|
||||||
|
|
||||||
// const routesSecondLevel = {};
|
// ----- ROUTES CONFIGURATION -----
|
||||||
|
// Configuration of the application's route structure
|
||||||
const routesFirstLevel: RoutesObject = {
|
// This includes both parent routes and nested routes within them
|
||||||
[ADMINISTRATION]: [
|
export const routesTree: RoutingTree = [
|
||||||
{
|
{
|
||||||
name: "Users",
|
name: capitalizeFirstLetter(HOME),
|
||||||
path: "/users",
|
|
||||||
component: () => <>UserPage</>,
|
|
||||||
icon: FaUsers,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Roles",
|
|
||||||
path: "/roles",
|
|
||||||
component: () => <>Roles</>,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Permissions",
|
|
||||||
path: "/permissions",
|
|
||||||
component: () => <>Permissions</>,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[SETTINGS]: [
|
|
||||||
{
|
|
||||||
name: "General",
|
|
||||||
path: "/general",
|
|
||||||
component: () => <>General</>,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Appearance",
|
|
||||||
path: "/appearance",
|
|
||||||
component: () => <>Appearance</>,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Advanced",
|
|
||||||
path: "/:id",
|
|
||||||
component: SettingsAdvancedPage,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
export const routesRoot: Routes[] = [
|
|
||||||
{
|
|
||||||
name: "Home",
|
|
||||||
path: "/",
|
path: "/",
|
||||||
component: HomePage,
|
component: HomePage,
|
||||||
icon: RiHome3Fill,
|
icon: RiHome3Fill,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: ADMINISTRATION,
|
name: capitalizeFirstLetter(USERS),
|
||||||
path: "administration",
|
path: "users",
|
||||||
component: AdministrationPage,
|
nest: [
|
||||||
icon: IoMdOptions,
|
{
|
||||||
children: routesFirstLevel[ADMINISTRATION],
|
name: "User",
|
||||||
|
path: "/",
|
||||||
|
component: () => <>User HP</>,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: SETTINGS,
|
name: "UserName",
|
||||||
path: "settings",
|
path: ":username",
|
||||||
|
component: (username) => <>User: {username}</>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "UserNotFound",
|
||||||
|
component: () => <>User not found</>,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: capitalizeFirstLetter(SETTINGS),
|
||||||
|
path: SETTINGS,
|
||||||
component: () => <>Settings</>,
|
component: () => <>Settings</>,
|
||||||
icon: IoMdOptions,
|
icon: IoMdOptions,
|
||||||
children: routesFirstLevel[SETTINGS],
|
},
|
||||||
|
{
|
||||||
|
name: capitalizeFirstLetter(LOGIN),
|
||||||
|
path: LOGIN,
|
||||||
|
component: LoginPage,
|
||||||
|
icon: IoMdLogIn,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: capitalizeFirstLetter(PAGE_NOT_FOUND),
|
||||||
|
component: () => <>💥404💥</>,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
export default routesTree;
|
||||||
|
3
src/types/devControlPanelTypes.ts
Normal file
3
src/types/devControlPanelTypes.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { SwitchRouteGeneratorProps } from "./switchRouteGeneratorTypes";
|
||||||
|
|
||||||
|
export type DevControlPanelProps = SwitchRouteGeneratorProps;
|
15
src/types/floatingMenuTypes.ts
Normal file
15
src/types/floatingMenuTypes.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
interface FloatingMenuElement {
|
||||||
|
label: string;
|
||||||
|
icon: JSX.Element;
|
||||||
|
action: () => void;
|
||||||
|
}
|
||||||
|
interface FloatingMenuJSXElement {
|
||||||
|
label: string;
|
||||||
|
element: JSX.Element;
|
||||||
|
}
|
||||||
|
export interface FloatingMenuProps {
|
||||||
|
menuStructure: MenuStructure[];
|
||||||
|
className?: string | undefined;
|
||||||
|
tooltipClassName?: string | undefined;
|
||||||
|
}
|
||||||
|
export type MenuStructure = FloatingMenuElement | FloatingMenuJSXElement;
|
28
src/types/routesTypes.ts
Normal file
28
src/types/routesTypes.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { ComponentType } from "react";
|
||||||
|
import { RouteComponentProps } from "wouter";
|
||||||
|
|
||||||
|
interface RouteWithPath {
|
||||||
|
path: string;
|
||||||
|
nest?: RoutingTree;
|
||||||
|
}
|
||||||
|
interface RouteWithoutPath {
|
||||||
|
path?: never;
|
||||||
|
nest?: never;
|
||||||
|
}
|
||||||
|
interface RouteWithComponent {
|
||||||
|
nest?: never;
|
||||||
|
component: ComponentType<RouteComponentProps<{}>> | undefined;
|
||||||
|
}
|
||||||
|
interface RouteWithoutComponent {
|
||||||
|
nest: RoutingTree;
|
||||||
|
component?: never;
|
||||||
|
}
|
||||||
|
interface RouteObjectBase {
|
||||||
|
name: string;
|
||||||
|
icon?: React.FC;
|
||||||
|
}
|
||||||
|
export type RouteObject = RouteObjectBase &
|
||||||
|
(RouteWithPath | RouteWithoutPath) &
|
||||||
|
(RouteWithComponent | RouteWithoutComponent);
|
||||||
|
|
||||||
|
export type RoutingTree = RouteObject[];
|
5
src/types/switchRouteGeneratorTypes.ts
Normal file
5
src/types/switchRouteGeneratorTypes.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { RoutingTree } from "./routesTypes";
|
||||||
|
|
||||||
|
export interface SwitchRouteGeneratorProps {
|
||||||
|
routesTree: RoutingTree;
|
||||||
|
}
|
@ -1,11 +1,11 @@
|
|||||||
/**
|
/**
|
||||||
* Execute whatever you pass only in development (not production)
|
* Execute whatever you pass only in development (not production)
|
||||||
*/
|
*/
|
||||||
const inDev = <T>(callback: () => T): T | null => {
|
const inDebug = <T>(callback: () => T): T | null => {
|
||||||
if (process.env.NODE_ENV === "development") {
|
if (process.env.NODE_ENV === "development") {
|
||||||
return callback();
|
return callback();
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default inDev;
|
export default inDebug;
|
||||||
|
@ -1,40 +0,0 @@
|
|||||||
export default {
|
|
||||||
content: ["./src/**/*.{js,ts,jsx,tsx}", "./index.html"],
|
|
||||||
// safelist is used to allow classes to not be purged by tailwind
|
|
||||||
safelist: ["alert-info", "alert-success", "alert-warning", "alert-error"],
|
|
||||||
theme: {
|
|
||||||
extend: {
|
|
||||||
spacing: {
|
|
||||||
128: "32rem",
|
|
||||||
144: "36rem",
|
|
||||||
},
|
|
||||||
borderRadius: {
|
|
||||||
"4xl": "2rem",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
darkMode: "class",
|
|
||||||
plugins: [
|
|
||||||
require("@tailwindcss/typography"),
|
|
||||||
require("@tailwindcss/forms"),
|
|
||||||
require("daisyui"),
|
|
||||||
],
|
|
||||||
daisyui: {
|
|
||||||
themes: [
|
|
||||||
{
|
|
||||||
light: {
|
|
||||||
primary: "#38bdf8",
|
|
||||||
secondary: "#10b981",
|
|
||||||
accent: "#f59e0b",
|
|
||||||
neutral: "#e5e7eb",
|
|
||||||
"base-100": "#f3f4f6",
|
|
||||||
info: "#67e8f9",
|
|
||||||
success: "#6ee7b7",
|
|
||||||
warning: "#fde047",
|
|
||||||
error: "#f87171",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"dark",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
26
tailwind.config.ts
Normal file
26
tailwind.config.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import forms from "@tailwindcss/forms";
|
||||||
|
import typography from "@tailwindcss/typography";
|
||||||
|
import daisyUI from "daisyui";
|
||||||
|
import type { Config } from "tailwindcss";
|
||||||
|
export default {
|
||||||
|
content: ["./src/**/*.{js,ts,jsx,tsx}", "./index.html"],
|
||||||
|
// safelist is used to allow classes to not be purged by tailwind;
|
||||||
|
// I made this to set this classes dyanmically in the code, somehow without this tailwind purges them;
|
||||||
|
safelist: ["alert-info", "alert-success", "alert-warning", "alert-error"],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
spacing: {
|
||||||
|
128: "32rem",
|
||||||
|
144: "36rem",
|
||||||
|
},
|
||||||
|
borderRadius: {
|
||||||
|
"4xl": "2rem",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
darkMode: "media",
|
||||||
|
plugins: [forms, daisyUI, typography],
|
||||||
|
daisyui: {
|
||||||
|
themes: ["light", "dark"],
|
||||||
|
},
|
||||||
|
} satisfies Config;
|
@ -1,41 +1,41 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
// Specifying ECMAScript targets for the compiler
|
||||||
"target": "ESNext",
|
"target": "ESNext",
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"lib": [
|
"lib": [
|
||||||
"DOM",
|
"DOM",
|
||||||
"DOM.Iterable",
|
"DOM.Iterable",
|
||||||
"ESNext"
|
"ESNext"
|
||||||
],
|
],
|
||||||
"allowJs": true,
|
// Enabling options for better interoperability and module handling
|
||||||
"skipLibCheck": true,
|
"moduleResolution": "bundler",
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"allowImportingTsExtensions": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
// Configuring behavior for JSX, specific to React
|
||||||
"noErrorTruncation": true,
|
"jsx": "react-jsx", // Transform JSX for React 17+ JSX Transform
|
||||||
/* "react-jsx" and "react-jsxdev": These are new options available from TypeScript 4.1 onwards. "react-jsx" transforms JSX into calls to a function that will be imported from react/jsx-runtime. "react-jsxdev" does the same, but for development builds. These options are useful if you're using React 17 or later, which introduced a new JSX Transform.
|
// Strengthening type-checking and ensuring consistency
|
||||||
Remember that to use these options, you also need to set the module compiler option to esnext or commonjs, because the output will contain import statements.*/
|
"strict": true, // Enable all strict type-checking options
|
||||||
"jsx": "react-jsx",
|
"noUnusedLocals": true, // Disallow unused local variables
|
||||||
/* Vite handle emiting */
|
"noUnusedParameters": true, // Disallow unused function parameters
|
||||||
"noEmit": true,
|
"noFallthroughCasesInSwitch": true, // Prevent fallthrough cases in switch statements
|
||||||
"useDefineForClassFields": true,
|
"forceConsistentCasingInFileNames": true, // Ensure consistent file naming
|
||||||
/*
|
// Improving project robustness
|
||||||
"strict": true in TypeScript's tsconfig.json is an overarching setting that turns on a number of strict type-checking options. Some of these could overlap with ESLint rules related to good practices in JavaScript and TypeScript coding. However, since ESLint and TypeScript serve different purposes (ESLint for style and syntax, TypeScript for type checking), there usually isn't a direct conflict.
|
"isolatedModules": true, // Ensure correct transpiling of files
|
||||||
"noUnusedLocals": true and "noUnusedParameters": true in TypeScript could overlap with ESLint's no-unused-vars rule, which flags declared variables or arguments that are not used anywhere in the code.
|
"noErrorTruncation": true, // Show full type error messages
|
||||||
"noFallthroughCasesInSwitch": true could overlap with ESLint's no-fallthrough rule, which disallows fallthrough behavior in switch statements, a common error in JavaScript. */
|
"useDefineForClassFields": true, // Align class field behavior with the standard
|
||||||
"strict": true,
|
// Configuring project build process
|
||||||
"noUnusedLocals": true,
|
"allowJs": true, // Allow JavaScript files to be imported
|
||||||
"noUnusedParameters": true,
|
"skipLibCheck": true, // Skip type checking of declaration files
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noEmit": true, // Vite handles the emitting of files
|
||||||
},
|
},
|
||||||
|
// Specifying folders and files to include in compilation
|
||||||
"include": [
|
"include": [
|
||||||
"src/**/*"
|
"src/**/*"
|
||||||
],
|
],
|
||||||
|
// Excluding certain directories from the compilation
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules"
|
"node_modules"
|
||||||
],
|
]
|
||||||
}
|
}
|
@ -6,10 +6,5 @@ export default ({ mode }) => {
|
|||||||
process.env = { ...process.env, ...loadEnv(mode, process.cwd()) };
|
process.env = { ...process.env, ...loadEnv(mode, process.cwd()) };
|
||||||
return defineConfig({
|
return defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
build: {
|
|
||||||
watch: {
|
|
||||||
clearScreen: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user