Bunch of imprevments copied from other project based on this starter repo

This commit is contained in:
Igor Barcik 2024-01-20 15:01:35 +01:00
parent cfe5a813f7
commit ea2629b7eb
44 changed files with 1129 additions and 3348 deletions

View 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"
}

View File

@ -1,2 +1,2 @@
# Config avaliable on development environment # Config avaliable on development environment
VITE_API_URL=http://localhost:7082 VITE_BASE_PATH=/

View File

@ -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=/

View File

@ -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
.nvmrc Normal file
View File

@ -0,0 +1 @@
latest

View File

@ -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
View File

@ -0,0 +1,3 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
}

13
Dockerfile Normal file
View 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"]

View File

@ -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_

BIN
bun.lockb

Binary file not shown.

View 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
View 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": {}
}

View File

@ -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>

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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 <div>
<Router base={main.basePath}> <IconContext.Provider value={{ className: "size-5" }}>
<div className="space-x-4 space-y-6"> <FloatingMenu menuStructure={floatingMenuStructure} />
<div className="px-5 mt-6"> <FloatingMenu
{/* Displaying program name and version */} menuStructure={floatingLanguageMenuStructure}
<h1 className="text-center "> className="flex gap-2 absolute left-4 top-4 flex-col"
{main.program_name} v{main.program_version} tooltipClassName="tooltip tooltip-right"
<p>Base path is: {main.basePath}</p> />
</h1> </IconContext.Provider>
<div> <SwitchRouteGenerator routesTree={routesTree} />
{/* ThemeButton component */} <DevControlPanel routesTree={routesTree} />
<ThemeButton /> </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>
); );
} }

View 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;

View 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;

View File

@ -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"
/> />

View File

@ -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"),
},
];

View File

@ -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";

View File

@ -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,

View File

@ -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]);

View File

@ -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>,
); );

View File

@ -1,5 +0,0 @@
const AdministrationPage = () => {
return <div>🚀AdministrationPage🚀</div>;
};
export default AdministrationPage;

View File

@ -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>

View File

@ -1,5 +0,0 @@
const InboxPage = () => {
return <div>InboxPage</div>;
};
export default InboxPage;

55
src/pages/LoginPage.tsx Normal file
View 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;

View File

@ -1,8 +0,0 @@
import { useParams } from "wouter";
const SettingsAdvancedPage = () => {
const params = useParams();
return <div>SettingsAdvancedPage {params.id}</div>;
};
export default SettingsAdvancedPage;

View File

@ -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;

View 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;

View 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;

View File

@ -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;

View File

@ -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",
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", name: capitalizeFirstLetter(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: "UserName",
path: ":username",
component: (username) => <>User: {username}</>,
},
{
name: "UserNotFound",
component: () => <>User not found</>,
},
],
}, },
{ {
name: SETTINGS, name: capitalizeFirstLetter(SETTINGS),
path: "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;

View File

@ -0,0 +1,3 @@
import { SwitchRouteGeneratorProps } from "./switchRouteGeneratorTypes";
export type DevControlPanelProps = SwitchRouteGeneratorProps;

View 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
View 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[];

View File

@ -0,0 +1,5 @@
import { RoutingTree } from "./routesTypes";
export interface SwitchRouteGeneratorProps {
routesTree: RoutingTree;
}

View File

@ -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;

View File

@ -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
View 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;

View File

@ -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"
], ]
} }

View File

@ -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,
},
},
}); });
}; };