Compare commits
No commits in common. "04a42f073ba121b3ae4b03b499f1018ad221223c" and "cfe5a813f7b260a6b568eede6121283ab85dc445" have entirely different histories.
04a42f073b
...
cfe5a813f7
@ -1,24 +0,0 @@
|
|||||||
// 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_BASE_PATH=/
|
VITE_API_URL=http://localhost:7082
|
@ -1,2 +1,2 @@
|
|||||||
# Config avaliable on production environment
|
# Config avaliable on production
|
||||||
VITE_BASE_PATH=/
|
VITE_API_URL=http://192.168.179.36:7082
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"typescript.tsdk": "node_modules/typescript/lib"
|
|
||||||
}
|
|
13
Dockerfile
13
Dockerfile
@ -1,13 +0,0 @@
|
|||||||
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"]
|
|
19
README.md
19
README.md
@ -10,20 +10,17 @@
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
1. Setup `.env` files
|
1. Setup `VITE_APP_NAME` in `.env.{mode}` files
|
||||||
1. `.env` - for production and development
|
2. Setup `VITE_BASE_PATH` in `.env.{mode}` files
|
||||||
2. `.env.development` - for development
|
3. run: `bun run dev`
|
||||||
3. `.env.production` - for production
|
4. To build for production, run: `bun run build`
|
||||||
2. Build or run dev server
|
|
||||||
3. Enjoy 🎉
|
_bun can be replaced by packet manager of your choice_
|
||||||
|
|
||||||
### Contact
|
### Contact
|
||||||
|
|
||||||
If you have any suggestions/opinions, please let me know in issues.
|
If you have any suggestions/opinions, please let me know in issues
|
||||||
|
|
||||||
#### Dev notes
|
#### Dev notes
|
||||||
|
|
||||||
> UI inspirations:
|
> UI inspiration: <https://demo.themesberg.com/windster-pro/#>
|
||||||
>
|
|
||||||
> - <https://demo.themesberg.com/windster-pro/#>
|
|
||||||
> - <https://tamagui.dev/>
|
|
||||||
|
@ -1,39 +0,0 @@
|
|||||||
# 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;
|
|
||||||
```
|
|
@ -1,435 +0,0 @@
|
|||||||
{
|
|
||||||
"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": [],
|
|
||||||
"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": [],
|
|
||||||
"updated": 1705397026573,
|
|
||||||
"link": null,
|
|
||||||
"locked": false,
|
|
||||||
"fontSize": 20,
|
|
||||||
"fontFamily": 1,
|
|
||||||
"text": "Username",
|
|
||||||
"textAlign": "center",
|
|
||||||
"verticalAlign": "middle",
|
|
||||||
"containerId": "2zx0egnLluNOg2aagGFf5",
|
|
||||||
"originalText": "Username",
|
|
||||||
"lineHeight": 1.25,
|
|
||||||
"baseline": 19
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"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": [],
|
|
||||||
"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": 19
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"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": [],
|
|
||||||
"updated": 1705397026573,
|
|
||||||
"link": null,
|
|
||||||
"locked": false,
|
|
||||||
"fontSize": 20,
|
|
||||||
"fontFamily": 1,
|
|
||||||
"text": "Login",
|
|
||||||
"textAlign": "center",
|
|
||||||
"verticalAlign": "middle",
|
|
||||||
"containerId": "ZikkdqWQ7aOQyqTYogsSg",
|
|
||||||
"originalText": "Login",
|
|
||||||
"lineHeight": 1.25,
|
|
||||||
"baseline": 19
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"version": 770,
|
|
||||||
"versionNonce": 402387348,
|
|
||||||
"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": [],
|
|
||||||
"updated": 1705762591927,
|
|
||||||
"link": null,
|
|
||||||
"locked": false,
|
|
||||||
"fontSize": 36,
|
|
||||||
"fontFamily": 1,
|
|
||||||
"text": "ServiMain",
|
|
||||||
"textAlign": "left",
|
|
||||||
"verticalAlign": "top",
|
|
||||||
"containerId": null,
|
|
||||||
"originalText": "ServiMain",
|
|
||||||
"lineHeight": 1.25,
|
|
||||||
"baseline": 32
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"version": 36,
|
|
||||||
"versionNonce": 1797562796,
|
|
||||||
"isDeleted": false,
|
|
||||||
"id": "no67OXeP6ZJsJ51TOHxJy",
|
|
||||||
"fillStyle": "solid",
|
|
||||||
"strokeWidth": 2,
|
|
||||||
"strokeStyle": "solid",
|
|
||||||
"roughness": 1,
|
|
||||||
"opacity": 100,
|
|
||||||
"angle": 0,
|
|
||||||
"x": 719.7241633880242,
|
|
||||||
"y": 107.53011605520807,
|
|
||||||
"strokeColor": "#1e1e1e",
|
|
||||||
"backgroundColor": "transparent",
|
|
||||||
"width": 70.11228942871094,
|
|
||||||
"height": 35,
|
|
||||||
"seed": 1759430041,
|
|
||||||
"groupIds": [],
|
|
||||||
"frameId": null,
|
|
||||||
"roundness": null,
|
|
||||||
"boundElements": [],
|
|
||||||
"updated": 1705762591928,
|
|
||||||
"link": null,
|
|
||||||
"locked": false,
|
|
||||||
"fontSize": 28,
|
|
||||||
"fontFamily": 1,
|
|
||||||
"text": "/login",
|
|
||||||
"textAlign": "left",
|
|
||||||
"verticalAlign": "top",
|
|
||||||
"containerId": null,
|
|
||||||
"originalText": "/login",
|
|
||||||
"lineHeight": 1.25,
|
|
||||||
"baseline": 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
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "rectangle",
|
|
||||||
"version": 109,
|
|
||||||
"versionNonce": 1334885687,
|
|
||||||
"isDeleted": false,
|
|
||||||
"id": "wApCZR8vnuIdRuwfVeC5x",
|
|
||||||
"fillStyle": "solid",
|
|
||||||
"strokeWidth": 2,
|
|
||||||
"strokeStyle": "solid",
|
|
||||||
"roughness": 1,
|
|
||||||
"opacity": 100,
|
|
||||||
"angle": 0,
|
|
||||||
"x": 953.3863135555557,
|
|
||||||
"y": 244.88073477777772,
|
|
||||||
"strokeColor": "#1e1e1e",
|
|
||||||
"backgroundColor": "transparent",
|
|
||||||
"width": 468.8888888888887,
|
|
||||||
"height": 264.44444444444457,
|
|
||||||
"seed": 902925239,
|
|
||||||
"groupIds": [],
|
|
||||||
"frameId": null,
|
|
||||||
"roundness": {
|
|
||||||
"type": 3
|
|
||||||
},
|
|
||||||
"boundElements": [],
|
|
||||||
"updated": 1705396987119,
|
|
||||||
"link": null,
|
|
||||||
"locked": false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "text",
|
|
||||||
"version": 134,
|
|
||||||
"versionNonce": 1076928276,
|
|
||||||
"isDeleted": false,
|
|
||||||
"id": "Gmr1u54szmiUkN9YuVUAY",
|
|
||||||
"fillStyle": "solid",
|
|
||||||
"strokeWidth": 2,
|
|
||||||
"strokeStyle": "solid",
|
|
||||||
"roughness": 1,
|
|
||||||
"opacity": 100,
|
|
||||||
"angle": 0,
|
|
||||||
"x": 1686.3908303880241,
|
|
||||||
"y": 107.53011605520807,
|
|
||||||
"strokeColor": "#1e1e1e",
|
|
||||||
"backgroundColor": "transparent",
|
|
||||||
"width": 111.60848999023438,
|
|
||||||
"height": 35,
|
|
||||||
"seed": 858001817,
|
|
||||||
"groupIds": [],
|
|
||||||
"frameId": null,
|
|
||||||
"roundness": null,
|
|
||||||
"boundElements": [],
|
|
||||||
"updated": 1705762591928,
|
|
||||||
"link": null,
|
|
||||||
"locked": false,
|
|
||||||
"fontSize": 28,
|
|
||||||
"fontFamily": 1,
|
|
||||||
"text": "/; /home",
|
|
||||||
"textAlign": "left",
|
|
||||||
"verticalAlign": "top",
|
|
||||||
"containerId": null,
|
|
||||||
"originalText": "/; /home",
|
|
||||||
"lineHeight": 1.25,
|
|
||||||
"baseline": 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>
|
||||||
|
21
package.json
21
package.json
@ -13,27 +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",
|
||||||
"i18next": "^23.7.18",
|
"jotai": "^2.6.0",
|
||||||
"i18next-browser-languagedetector": "^7.2.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-i18next": "^14.0.1",
|
"react-icons": "^4.12.0",
|
||||||
"react-icons": "^5.0.1",
|
"recharts": "^2.10.1",
|
||||||
"uuid": "^9.0.1",
|
"uuid": "^9.0.1",
|
||||||
"wouter": "next",
|
"wouter": "^2.12.1",
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.22.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/forms": "^0.5.7",
|
"@tailwindcss/typography": "^0.5.10",
|
||||||
"@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",
|
||||||
@ -46,12 +43,10 @@
|
|||||||
"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.33",
|
"postcss": "^8.4.31",
|
||||||
"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.4.1",
|
"tailwindcss": "^3.3.5",
|
||||||
"ts-node": "latest",
|
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
"vite": "^4.5.0"
|
"vite": "^4.5.0"
|
||||||
}
|
}
|
||||||
|
3000
pnpm-lock.yaml
generated
Normal file
3000
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,10 +1,8 @@
|
|||||||
/** @type {import('postcss-load-config').Config} */
|
export default {
|
||||||
const config = {
|
plugins: {
|
||||||
plugins: [
|
"postcss-import": {},
|
||||||
require('autoprefixer'),
|
"tailwindcss/nesting": {},
|
||||||
require('tailwindcss'),
|
tailwindcss: {},
|
||||||
require('postcss-import'),
|
autoprefixer: {},
|
||||||
require('tailwindcss/nesting'),
|
},
|
||||||
],
|
|
||||||
};
|
};
|
||||||
module.exports = config
|
|
42
src/App.tsx
42
src/App.tsx
@ -1,22 +1,36 @@
|
|||||||
import { IconContext } from "react-icons/lib";
|
import ThemeButton from "./components/ThemeButton";
|
||||||
import DevControlPanel from "./components/DevControlPanel";
|
import { main } from "./configure";
|
||||||
import FloatingMenu from "./components/FloatingMenu";
|
import { Router } from "wouter";
|
||||||
import { floatingMenuStructure } from "./configure";
|
import SwitchRoutesGenerator from "./routes/SwitchRoutesGenerator";
|
||||||
import SwitchRouteGenerator from "./routes/SwitchRouteGenerator";
|
import GenerateNavigationButtonsDebug from "./routes/GenerateNavigationButtonsDebug";
|
||||||
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: - [x] 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>
|
||||||
<IconContext.Provider value={{ className: "size-5" }}>
|
{/* ThemeButton component */}
|
||||||
<FloatingMenu menuStructure={floatingMenuStructure} />
|
<ThemeButton />
|
||||||
</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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,106 +0,0 @@
|
|||||||
import { useLocation } from "wouter";
|
|
||||||
import { floatingLanguageMenuStructure, main } from "../configure";
|
|
||||||
import { DevControlPanelProps } from "../types/devControlPanelTypes";
|
|
||||||
import { RoutingTree } from "../types/routesTypes";
|
|
||||||
import inDev from "../utils/inDev";
|
|
||||||
import ThemeButton from "./ThemeButton";
|
|
||||||
import { clearMultiplePathSlashes } from "../utils/StringTransformationUtils";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import FloatingMenu from "./FloatingMenu";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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();
|
|
||||||
const { t, i18n } = useTranslation();
|
|
||||||
|
|
||||||
// Function to update the current location, with debug logging
|
|
||||||
const _setLocation = (targetLocation: string) => {
|
|
||||||
inDev(() => 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 = clearMultiplePathSlashes(
|
|
||||||
parentPath ? "/" + parentPath + "/" + route.path : "/" + route.path,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Debug logging for route information
|
|
||||||
inDev(() =>
|
|
||||||
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 className="px-5 mt-6">
|
|
||||||
<p>{t("welcome")}</p>
|
|
||||||
<p>Lang: {i18n.language}</p>
|
|
||||||
</div>
|
|
||||||
<FloatingMenu
|
|
||||||
menuStructure={floatingLanguageMenuStructure}
|
|
||||||
className="flex gap-2 flex-col"
|
|
||||||
tooltipClassName="tooltip tooltip-right"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DevControlPanel;
|
|
@ -1,73 +0,0 @@
|
|||||||
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/inDev";
|
import inDev from "../utils/inDebug";
|
||||||
|
|
||||||
const ThemeButton = () => {
|
const ThemeButton = () => {
|
||||||
const [isDark, toggleTheme] = useTheme();
|
const [isDark, toggleTheme] = useTheme();
|
||||||
|
@ -1,92 +1,19 @@
|
|||||||
import { FiHome, FiLogIn } from "react-icons/fi";
|
|
||||||
import { setLocation } from "./components/FloatingMenu";
|
|
||||||
import ThemeButton from "./components/ThemeButton";
|
|
||||||
import { HOME, LANGUAGE, LOGIN, THEME } from "./consts";
|
|
||||||
import { MenuStructure } from "./types/floatingMenuTypes";
|
|
||||||
import { setLanguage } from "./main";
|
|
||||||
import { HiMiniLanguage } from "react-icons/hi2";
|
|
||||||
/* 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 = {
|
||||||
api_url: viteEnv.VITE_API_URL || `setup in .env.${viteEnv.MODE}`,
|
program_name: `${viteEnv.VITE_APP_NAME}${
|
||||||
base_path: viteEnv.VITE_BASE_PATH || "/",
|
viteEnv.MODE ? " [development]" : ""
|
||||||
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",
|
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" }],
|
||||||
};
|
};
|
||||||
// -------- 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 floatingLanguageMenuStructure: MenuStructure[] = [
|
|
||||||
{
|
|
||||||
label: "English",
|
|
||||||
icon: <p>🇬🇧</p>,
|
|
||||||
action: () => {
|
|
||||||
console.log("English");
|
|
||||||
setLanguage("eng");
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Polski",
|
|
||||||
icon: <p>🇵🇱</p>,
|
|
||||||
action: () => {
|
|
||||||
console.log("Polski");
|
|
||||||
setLanguage("pol");
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
export const floatingMenuStructure: MenuStructure[] = [
|
|
||||||
{
|
|
||||||
label: HOME,
|
|
||||||
icon: <FiHome />,
|
|
||||||
action: () => setLocation("/"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: LOGIN,
|
|
||||||
icon: <FiLogIn />,
|
|
||||||
action: () => setLocation(`/${LOGIN}`),
|
|
||||||
},
|
|
||||||
{ label: THEME, element: <ThemeButton /> },
|
|
||||||
{
|
|
||||||
label: LANGUAGE,
|
|
||||||
element: (
|
|
||||||
<div className="dropdown dropdown-left">
|
|
||||||
<div tabIndex={0} role="button" className="btn btn-circle btn-outline">
|
|
||||||
<HiMiniLanguage />
|
|
||||||
</div>
|
|
||||||
<ul
|
|
||||||
tabIndex={0}
|
|
||||||
className="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-52 mr-1"
|
|
||||||
>
|
|
||||||
{floatingLanguageMenuStructure.map((element) => {
|
|
||||||
return (
|
|
||||||
<li>
|
|
||||||
<a
|
|
||||||
onClick={() => {
|
|
||||||
if ("action" in element && typeof element.action === "function")
|
|
||||||
element.action();
|
|
||||||
else console.log("No action");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{element.label} {"icon" in element ? element.icon : null}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
@ -1,17 +1,2 @@
|
|||||||
// ---- 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";
|
|
||||||
export const LANGUAGE = "language";
|
|
||||||
|
@ -1,21 +1,9 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
/**
|
/**
|
||||||
* Custom hook for persisting state in local storage.
|
* Works like useState but stores the value in local storage
|
||||||
*
|
* @param key Create a key to store the value in local storage
|
||||||
* This hook works similarly to the standard `useState` hook but also stores the state in local storage,
|
* @param initialValue Assign an initial value to the key
|
||||||
* allowing the state to persist across browser sessions. The state is initialized from local storage
|
* @returns [storedValue, setValue] Returns the stored value and a function to set the value
|
||||||
* 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,35 +0,0 @@
|
|||||||
import { useState } from "react";
|
|
||||||
/**
|
|
||||||
* Works like useState but stores the value in session storage
|
|
||||||
* @param key Create a key to store the value in session storage
|
|
||||||
* @param initialValue Assign an initial value to the key
|
|
||||||
* @returns [storedValue, setValue] Returns the stored value and a function to set the value
|
|
||||||
*/
|
|
||||||
const useSessionStorage = <T>(
|
|
||||||
key: string,
|
|
||||||
initialValue: T,
|
|
||||||
): [T, (value: T) => void] => {
|
|
||||||
const [storedValue, setStoredValue] = useState<T>(() => {
|
|
||||||
try {
|
|
||||||
const item = sessionStorage.getItem(key);
|
|
||||||
return item ? JSON.parse(item) : initialValue;
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
return initialValue;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const setValue = (value: T) => {
|
|
||||||
try {
|
|
||||||
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
|
||||||
setStoredValue(valueToStore);
|
|
||||||
sessionStorage.setItem(key, JSON.stringify(valueToStore));
|
|
||||||
} catch (error) {
|
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return [storedValue, setValue];
|
|
||||||
};
|
|
||||||
|
|
||||||
export default useSessionStorage;
|
|
@ -1,33 +1,19 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import useLocalStorage from "./useLocalStorage";
|
import useLocalStorage from "./useLocalStorage";
|
||||||
import inDev from "../utils/inDev";
|
|
||||||
/**
|
|
||||||
* 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 = () => {
|
||||||
inDev(() => console.log("toggleTheme called"));
|
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(() => {
|
||||||
inDev(() => console.log("useEffect called"));
|
console.log("useEffect called");
|
||||||
document.documentElement.dataset.theme = theme;
|
document.documentElement.dataset.theme = theme;
|
||||||
}, [theme]);
|
}, [theme]);
|
||||||
|
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"welcome": "Welcome!",
|
|
||||||
"login_button": "Login",
|
|
||||||
"login_username": "Username",
|
|
||||||
"login_password": "Password",
|
|
||||||
"login_accept_terms": "I accept the terms and conditions"
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
import i18n from "i18next";
|
|
||||||
import LanguageDetector from "i18next-browser-languagedetector";
|
|
||||||
import { initReactI18next } from "react-i18next";
|
|
||||||
import generalEng from "./eng/general.json";
|
|
||||||
import generalPol from "./pol/general.json";
|
|
||||||
|
|
||||||
i18n
|
|
||||||
.use(initReactI18next)
|
|
||||||
.use(LanguageDetector)
|
|
||||||
.init({
|
|
||||||
resources: {
|
|
||||||
eng: {
|
|
||||||
general: generalEng,
|
|
||||||
},
|
|
||||||
pol: {
|
|
||||||
general: generalPol,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
fallbackLng: "eng",
|
|
||||||
interpolation: {
|
|
||||||
escapeValue: false,
|
|
||||||
},
|
|
||||||
defaultNS: "general",
|
|
||||||
});
|
|
@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"welcome": "Witaj!",
|
|
||||||
"login_button": "Zaloguj",
|
|
||||||
"login_username": "Nazwa użytkownika",
|
|
||||||
"login_password": "Hasło",
|
|
||||||
"login_accept_terms": "Akceptuję regulamin"
|
|
||||||
}
|
|
75
src/main.tsx
75
src/main.tsx
@ -1,77 +1,26 @@
|
|||||||
import { Global, css } from "@emotion/react";
|
import React from "react";
|
||||||
import React, { useEffect } from "react";
|
|
||||||
import ReactDOM from "react-dom/client";
|
import ReactDOM from "react-dom/client";
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { Router } from "wouter";
|
|
||||||
import App from "./App";
|
|
||||||
import { setupAxiosInterceptors } from "./api/AxiosService";
|
import { setupAxiosInterceptors } from "./api/AxiosService";
|
||||||
import { main, viteEnv } from "./configure";
|
import { viteEnv } from "./configure";
|
||||||
import useLocalStorage from "./hooks/useLocalStorage";
|
import App from "./App";
|
||||||
import "./locales/localesConfig";
|
import inDev from "./utils/inDebug";
|
||||||
import inDev from "./utils/inDev";
|
import { Global, css } from "@emotion/react";
|
||||||
import "/style.css"; // Global tailwind styles
|
import "/style.css";
|
||||||
|
import { Provider } from "jotai";
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up Axios interceptors for HTTP requests.
|
|
||||||
*/
|
|
||||||
setupAxiosInterceptors();
|
setupAxiosInterceptors();
|
||||||
|
|
||||||
/**
|
|
||||||
* Logs environment variables if in development mode.
|
|
||||||
*/
|
|
||||||
inDev(() => console.log(viteEnv));
|
inDev(() => console.log(viteEnv));
|
||||||
|
|
||||||
/**
|
// Hook react to the HTML element with id="root"
|
||||||
* Global variables to manage the application's language state.
|
|
||||||
*/
|
|
||||||
export let language: string, setLanguage: (value: string) => void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* _App component which is the main entry point of the application.
|
|
||||||
* Manages routing and global state like language.
|
|
||||||
*
|
|
||||||
* @returns The Router component wrapping the main App component.
|
|
||||||
*/
|
|
||||||
const _App = () => {
|
|
||||||
// Hook to manage language state in local storage
|
|
||||||
[language, setLanguage] = useLocalStorage("language", "eng");
|
|
||||||
const { i18n } = useTranslation();
|
|
||||||
|
|
||||||
// Effect to change language based on state
|
|
||||||
useEffect(() => {
|
|
||||||
i18n.changeLanguage(language);
|
|
||||||
}, [language]);
|
|
||||||
|
|
||||||
// Conditional rendering based on base path
|
|
||||||
if (main.base_path !== "/") {
|
|
||||||
return (
|
|
||||||
<Router base={main.base_path}>
|
|
||||||
<App />
|
|
||||||
</Router>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<Router>
|
|
||||||
<App />
|
|
||||||
</Router>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Mounting the application to the DOM
|
|
||||||
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`
|
||||||
html,
|
// You can add more global styles here
|
||||||
body,
|
|
||||||
#root {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
`}
|
`}
|
||||||
/>
|
/>
|
||||||
<_App />
|
<Provider>
|
||||||
|
<App />
|
||||||
|
</Provider>
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
);
|
);
|
||||||
|
5
src/pages/AdministrationPage.tsx
Normal file
5
src/pages/AdministrationPage.tsx
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
const AdministrationPage = () => {
|
||||||
|
return <div>🚀AdministrationPage🚀</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AdministrationPage;
|
@ -1,7 +1,8 @@
|
|||||||
import { useState } from "react";
|
import { atom, useAtom } from "jotai";
|
||||||
|
const counterAtom = atom(0);
|
||||||
|
|
||||||
const HomePage = () => {
|
const HomePage = () => {
|
||||||
const [counter, setCounter] = useState(0);
|
const [counter, setCounter] = useAtom(counterAtom);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
5
src/pages/InboxPage.tsx
Normal file
5
src/pages/InboxPage.tsx
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
const InboxPage = () => {
|
||||||
|
return <div>InboxPage</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default InboxPage;
|
@ -1,60 +0,0 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { main } from "../configure";
|
|
||||||
|
|
||||||
const LoginPage = () => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
return (
|
|
||||||
<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">{t("login_username")}</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder={t("login_username")}
|
|
||||||
className="input input-bordered"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="form-control">
|
|
||||||
<label className="label">
|
|
||||||
<span className="label-text">{t("login_password")}</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
placeholder={t("login_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">
|
|
||||||
{t("login_accept_terms")}
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div className="form-control mt-6">
|
|
||||||
<input
|
|
||||||
type="submit"
|
|
||||||
value={t("login_button")}
|
|
||||||
className="btn btn-primary"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default LoginPage;
|
|
8
src/pages/SettingsAdvancedPage.tsx
Normal file
8
src/pages/SettingsAdvancedPage.tsx
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { useParams } from "wouter";
|
||||||
|
|
||||||
|
const SettingsAdvancedPage = () => {
|
||||||
|
const params = useParams();
|
||||||
|
return <div>SettingsAdvancedPage {params.id}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SettingsAdvancedPage;
|
50
src/routes/GenerateNavigationButtonsDebug.tsx
Normal file
50
src/routes/GenerateNavigationButtonsDebug.tsx
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
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;
|
@ -1,22 +0,0 @@
|
|||||||
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;
|
|
@ -1,39 +0,0 @@
|
|||||||
import { Route, Switch } from "wouter";
|
|
||||||
import inDev from "../utils/inDev";
|
|
||||||
import { RoutingTree } from "../types/routesTypes";
|
|
||||||
import { SwitchRouteGeneratorProps } from "../types/switchRouteGeneratorTypes";
|
|
||||||
import { clearMultiplePathSlashes } from "../utils/StringTransformationUtils";
|
|
||||||
|
|
||||||
const routesCrawler = (
|
|
||||||
routesTree: RoutingTree,
|
|
||||||
parentPath?: string,
|
|
||||||
): JSX.Element[] => {
|
|
||||||
return routesTree.map((route): JSX.Element => {
|
|
||||||
// -----
|
|
||||||
if (route.path) {
|
|
||||||
const _path = clearMultiplePathSlashes(
|
|
||||||
parentPath && parentPath !== "/" ? parentPath + route.path : route.path,
|
|
||||||
);
|
|
||||||
// Debug log shows generated routes
|
|
||||||
inDev(() => 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;
|
|
52
src/routes/SwitchRoutesGenerator.tsx
Normal file
52
src/routes/SwitchRoutesGenerator.tsx
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
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,67 +1,90 @@
|
|||||||
// ----- 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 -----
|
||||||
// Importing page components for route configuration
|
import AdministrationPage from "../pages/AdministrationPage";
|
||||||
import HomePage from "../pages/HomePage";
|
import HomePage from "../pages/HomePage";
|
||||||
import LoginPage from "../pages/LoginPage";
|
// ----- IMPORT CONSTS -----
|
||||||
|
import { ADMINISTRATION, SETTINGS } from "../consts";
|
||||||
|
import SettingsAdvancedPage from "../pages/SettingsAdvancedPage";
|
||||||
|
|
||||||
// ----- IMPORT CONSTANTS -----
|
// ----- TYPE DEFINITIONS -----
|
||||||
// Importing route-related constants for route naming
|
export type Routes = {
|
||||||
import { HOME, LOGIN, PAGE_NOT_FOUND, SETTINGS, USERS } from "../consts";
|
name: string;
|
||||||
|
path: string;
|
||||||
|
component: () => JSX.Element;
|
||||||
|
icon?: () => JSX.Element;
|
||||||
|
children?: Routes[];
|
||||||
|
};
|
||||||
|
type RoutesObject = {
|
||||||
|
[key: string]: Routes[];
|
||||||
|
};
|
||||||
|
|
||||||
// ----- IMPORT TYPES AND UTILITIES -----
|
// ----- ROUTES DEFINITIONS -----
|
||||||
// Importing types for route configuration and utility functions
|
//! Don't leave trailor '/' in paths
|
||||||
import { RoutingTree } from "../types/routesTypes";
|
//! Paths in Route should be without base path (even without '/'). If want Route for base path than pass base path
|
||||||
import { capitalizeFirstLetter } from "../utils/StringTransformationUtils";
|
//* Wouter is basically join paths with each other, so if base path is "/" every sub path souldn't have "/" at the beginning
|
||||||
|
|
||||||
// ----- ROUTES CONFIGURATION -----
|
// const routesSecondLevel = {};
|
||||||
// Configuration of the application's route structure
|
|
||||||
// This includes both parent routes and nested routes within them
|
const routesFirstLevel: RoutesObject = {
|
||||||
export const routesTree: RoutingTree = [
|
[ADMINISTRATION]: [
|
||||||
{
|
{
|
||||||
name: capitalizeFirstLetter(HOME),
|
name: "Users",
|
||||||
|
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: capitalizeFirstLetter(USERS),
|
name: ADMINISTRATION,
|
||||||
path: "users",
|
path: "administration",
|
||||||
nest: [
|
component: AdministrationPage,
|
||||||
{
|
icon: IoMdOptions,
|
||||||
name: "User",
|
children: routesFirstLevel[ADMINISTRATION],
|
||||||
path: "/",
|
|
||||||
component: () => <>User HP</>,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "UserName",
|
name: SETTINGS,
|
||||||
path: ":username",
|
path: "settings",
|
||||||
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;
|
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
import { SwitchRouteGeneratorProps } from "./switchRouteGeneratorTypes";
|
|
||||||
|
|
||||||
export type DevControlPanelProps = SwitchRouteGeneratorProps;
|
|
@ -1,15 +0,0 @@
|
|||||||
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;
|
|
@ -1,28 +0,0 @@
|
|||||||
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[];
|
|
@ -1,5 +0,0 @@
|
|||||||
import { RoutingTree } from "./routesTypes";
|
|
||||||
|
|
||||||
export interface SwitchRouteGeneratorProps {
|
|
||||||
routesTree: RoutingTree;
|
|
||||||
}
|
|
@ -1,22 +1,9 @@
|
|||||||
/**
|
|
||||||
* Rolls through each entry in a given object and creates a formatted string.
|
|
||||||
* The function expects an object with optional 'state' and 'id' properties.
|
|
||||||
*
|
|
||||||
* @param obj A readonly and partially optional object with 'state' and 'id' properties.
|
|
||||||
* @returns A string concatenating each key-value pair from the object.
|
|
||||||
*/
|
|
||||||
export const rollThroughObj = (
|
export const rollThroughObj = (
|
||||||
obj: Readonly<Partial<{ state: string; id: string }>>,
|
obj: Readonly<Partial<{ state: string; id: string }>>,
|
||||||
): string => {
|
) => {
|
||||||
// Initialize an empty result string.
|
|
||||||
let result = "";
|
let result = "";
|
||||||
|
|
||||||
// Iterate over each entry in the object.
|
|
||||||
for (const [key, value] of Object.entries(obj)) {
|
for (const [key, value] of Object.entries(obj)) {
|
||||||
// Append the key-value pair to the result string in a formatted way.
|
|
||||||
result += ` -${key}: ${value}`;
|
result += ` -${key}: ${value}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the trimmed result.
|
|
||||||
return result.trim();
|
return result.trim();
|
||||||
};
|
};
|
||||||
|
@ -1,24 +1,13 @@
|
|||||||
/**
|
|
||||||
* Capitalizes the first letter of a given string.
|
|
||||||
*
|
|
||||||
* @param {string} string - The string to capitalize.
|
|
||||||
* @returns {string} The string with the first letter capitalized.
|
|
||||||
*/
|
|
||||||
export function capitalizeFirstLetter(string: string): string {
|
export function capitalizeFirstLetter(string: string): string {
|
||||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears multiple consecutive slashes in a path string.
|
* @description Function to clear multiple path slashes
|
||||||
*
|
|
||||||
* @param {string} path - The path string to be formatted.
|
|
||||||
* @returns {string} The path with consecutive slashes reduced to a single slash.
|
|
||||||
* @example clearMultiplePathSlashes("/admin//MAINTENANCE") => "/admin/MAINTENANCE"
|
* @example clearMultiplePathSlashes("/admin//MAINTENANCE") => "/admin/MAINTENANCE"
|
||||||
*/
|
*/
|
||||||
export const clearMultiplePathSlashes = (path: string): string => {
|
export const clearMultiplePathSlashes = (path: string): string => {
|
||||||
return path.replace(/\/{2,}/g, "/");
|
return path.replace(/\/{2,}/g, "/");
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trims parameters and queries from a URL path that start with ':' or '?'.
|
* Trims parameters and queries from a URL path that start with ':' or '?'.
|
||||||
*
|
*
|
||||||
@ -28,6 +17,6 @@ export const clearMultiplePathSlashes = (path: string): string => {
|
|||||||
* // returns "/path"
|
* // returns "/path"
|
||||||
* trimPathOfParameters("/path/:param1/:param2:param3/?param4")
|
* trimPathOfParameters("/path/:param1/:param2:param3/?param4")
|
||||||
*/
|
*/
|
||||||
export const trimPathOfParameters = (path: string): string => {
|
export const trimPathOfParameters = (path: string) => {
|
||||||
return path.replace(/\/:[^/]*|\?[^/]*/g, "");
|
return path.replace(/\/:[^/]*|\?[^/]*/g, "");
|
||||||
};
|
};
|
||||||
|
40
tailwind.config.js
Normal file
40
tailwind.config.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
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",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
@ -1,26 +0,0 @@
|
|||||||
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,42 +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"
|
||||||
],
|
],
|
||||||
// Enabling options for better interoperability and module handling
|
"allowJs": true,
|
||||||
"moduleResolution": "bundler",
|
"skipLibCheck": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
// Configuring behavior for JSX, specific to React
|
"isolatedModules": true,
|
||||||
"jsx": "react-jsx", // Transform JSX for React 17+ JSX Transform
|
"noErrorTruncation": true,
|
||||||
// Strengthening type-checking and ensuring consistency
|
/* "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.
|
||||||
"strict": true, // Enable all strict type-checking options
|
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.*/
|
||||||
"strictNullChecks": true, // Enable strict null checks
|
"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,5 +6,10 @@ 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