Updated based on my other project
This commit is contained in:
parent
ea2629b7eb
commit
acebe04456
@ -1,4 +1,4 @@
|
|||||||
export default {
|
module.exports = {
|
||||||
env: { browser: true, es2020: true, node: true },
|
env: { browser: true, es2020: true, node: true },
|
||||||
extends: [
|
extends: [
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
@ -1,4 +1,4 @@
|
|||||||
export default {
|
module.exports = {
|
||||||
printWidth: 80,
|
printWidth: 80,
|
||||||
trailingComma: "all",
|
trailingComma: "all",
|
||||||
singleQuote: false,
|
singleQuote: false,
|
24
README.md
24
README.md
@ -10,22 +10,20 @@
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
1. Setup `.env.{mode}` files
|
1. Setup `.env` files
|
||||||
|
1. `.env` - for production and development
|
||||||
```ini
|
2. `.env.development` - for development
|
||||||
VITE_APP_NAME=Universal React Starter
|
3. `.env.production` - for production
|
||||||
VITE_BASE_PATH=/
|
2. Build or run dev server
|
||||||
```
|
3. Enjoy 🎉
|
||||||
|
|
||||||
2. run: `bun run dev`
|
|
||||||
3. To build for production, run: `bun run build`
|
|
||||||
|
|
||||||
_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 inspiration: <https://demo.themesberg.com/windster-pro/#>
|
> UI inspirations:
|
||||||
|
>
|
||||||
|
> - <https://demo.themesberg.com/windster-pro/#>
|
||||||
|
> - <https://tamagui.dev/>
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
"roundness": {
|
"roundness": {
|
||||||
"type": 3
|
"type": 3
|
||||||
},
|
},
|
||||||
"boundElements": null,
|
"boundElements": [],
|
||||||
"updated": 1705396978531,
|
"updated": 1705396978531,
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false
|
"locked": false
|
||||||
@ -94,7 +94,7 @@
|
|||||||
],
|
],
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"boundElements": null,
|
"boundElements": [],
|
||||||
"updated": 1705397026573,
|
"updated": 1705397026573,
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false,
|
"locked": false,
|
||||||
@ -106,7 +106,7 @@
|
|||||||
"containerId": "2zx0egnLluNOg2aagGFf5",
|
"containerId": "2zx0egnLluNOg2aagGFf5",
|
||||||
"originalText": "Username",
|
"originalText": "Username",
|
||||||
"lineHeight": 1.25,
|
"lineHeight": 1.25,
|
||||||
"baseline": 18
|
"baseline": 19
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
@ -168,7 +168,7 @@
|
|||||||
],
|
],
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"boundElements": null,
|
"boundElements": [],
|
||||||
"updated": 1705397072409,
|
"updated": 1705397072409,
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false,
|
"locked": false,
|
||||||
@ -180,7 +180,7 @@
|
|||||||
"containerId": "I0Xg4QaaO7XRxm7w1P_0a",
|
"containerId": "I0Xg4QaaO7XRxm7w1P_0a",
|
||||||
"originalText": "Password",
|
"originalText": "Password",
|
||||||
"lineHeight": 1.25,
|
"lineHeight": 1.25,
|
||||||
"baseline": 18
|
"baseline": 19
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
@ -242,7 +242,7 @@
|
|||||||
],
|
],
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"boundElements": null,
|
"boundElements": [],
|
||||||
"updated": 1705397026573,
|
"updated": 1705397026573,
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false,
|
"locked": false,
|
||||||
@ -254,12 +254,12 @@
|
|||||||
"containerId": "ZikkdqWQ7aOQyqTYogsSg",
|
"containerId": "ZikkdqWQ7aOQyqTYogsSg",
|
||||||
"originalText": "Login",
|
"originalText": "Login",
|
||||||
"lineHeight": 1.25,
|
"lineHeight": 1.25,
|
||||||
"baseline": 18
|
"baseline": 19
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"version": 768,
|
"version": 770,
|
||||||
"versionNonce": 851740953,
|
"versionNonce": 402387348,
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"id": "97fLf76gEdKS_GvQ6ALpU",
|
"id": "97fLf76gEdKS_GvQ6ALpU",
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
@ -280,8 +280,8 @@
|
|||||||
],
|
],
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"boundElements": null,
|
"boundElements": [],
|
||||||
"updated": 1705396978532,
|
"updated": 1705762591927,
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false,
|
"locked": false,
|
||||||
"fontSize": 36,
|
"fontSize": 36,
|
||||||
@ -295,40 +295,40 @@
|
|||||||
"baseline": 32
|
"baseline": 32
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "no67OXeP6ZJsJ51TOHxJy",
|
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"x": 719.7241633880242,
|
"version": 36,
|
||||||
"y": 107.53011605520807,
|
"versionNonce": 1797562796,
|
||||||
"width": 70.11228942871094,
|
"isDeleted": false,
|
||||||
"height": 35,
|
"id": "no67OXeP6ZJsJ51TOHxJy",
|
||||||
"angle": 0,
|
|
||||||
"strokeColor": "#1e1e1e",
|
|
||||||
"backgroundColor": "transparent",
|
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
|
"angle": 0,
|
||||||
|
"x": 719.7241633880242,
|
||||||
|
"y": 107.53011605520807,
|
||||||
|
"strokeColor": "#1e1e1e",
|
||||||
|
"backgroundColor": "transparent",
|
||||||
|
"width": 70.11228942871094,
|
||||||
|
"height": 35,
|
||||||
|
"seed": 1759430041,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"seed": 1759430041,
|
"boundElements": [],
|
||||||
"version": 34,
|
"updated": 1705762591928,
|
||||||
"versionNonce": 764198007,
|
|
||||||
"isDeleted": false,
|
|
||||||
"boundElements": null,
|
|
||||||
"updated": 1705397088770,
|
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false,
|
"locked": false,
|
||||||
"text": "/login",
|
|
||||||
"fontSize": 28,
|
"fontSize": 28,
|
||||||
"fontFamily": 1,
|
"fontFamily": 1,
|
||||||
|
"text": "/login",
|
||||||
"textAlign": "left",
|
"textAlign": "left",
|
||||||
"verticalAlign": "top",
|
"verticalAlign": "top",
|
||||||
"baseline": 25,
|
|
||||||
"containerId": null,
|
"containerId": null,
|
||||||
"originalText": "/login",
|
"originalText": "/login",
|
||||||
"lineHeight": 1.25
|
"lineHeight": 1.25,
|
||||||
|
"baseline": 25
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
@ -362,69 +362,69 @@
|
|||||||
"locked": false
|
"locked": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "wApCZR8vnuIdRuwfVeC5x",
|
|
||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
"x": 953.3863135555557,
|
"version": 109,
|
||||||
"y": 244.88073477777772,
|
"versionNonce": 1334885687,
|
||||||
"width": 468.8888888888887,
|
"isDeleted": false,
|
||||||
"height": 264.44444444444457,
|
"id": "wApCZR8vnuIdRuwfVeC5x",
|
||||||
"angle": 0,
|
|
||||||
"strokeColor": "#1e1e1e",
|
|
||||||
"backgroundColor": "transparent",
|
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
|
"angle": 0,
|
||||||
|
"x": 953.3863135555557,
|
||||||
|
"y": 244.88073477777772,
|
||||||
|
"strokeColor": "#1e1e1e",
|
||||||
|
"backgroundColor": "transparent",
|
||||||
|
"width": 468.8888888888887,
|
||||||
|
"height": 264.44444444444457,
|
||||||
|
"seed": 902925239,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"roundness": {
|
"roundness": {
|
||||||
"type": 3
|
"type": 3
|
||||||
},
|
},
|
||||||
"seed": 902925239,
|
"boundElements": [],
|
||||||
"version": 109,
|
|
||||||
"versionNonce": 1334885687,
|
|
||||||
"isDeleted": false,
|
|
||||||
"boundElements": null,
|
|
||||||
"updated": 1705396987119,
|
"updated": 1705396987119,
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false
|
"locked": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "Gmr1u54szmiUkN9YuVUAY",
|
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"x": 1686.3908303880241,
|
"version": 134,
|
||||||
"y": 107.53011605520807,
|
"versionNonce": 1076928276,
|
||||||
"width": 111.60848999023438,
|
"isDeleted": false,
|
||||||
"height": 35,
|
"id": "Gmr1u54szmiUkN9YuVUAY",
|
||||||
"angle": 0,
|
|
||||||
"strokeColor": "#1e1e1e",
|
|
||||||
"backgroundColor": "transparent",
|
|
||||||
"fillStyle": "solid",
|
"fillStyle": "solid",
|
||||||
"strokeWidth": 2,
|
"strokeWidth": 2,
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
|
"angle": 0,
|
||||||
|
"x": 1686.3908303880241,
|
||||||
|
"y": 107.53011605520807,
|
||||||
|
"strokeColor": "#1e1e1e",
|
||||||
|
"backgroundColor": "transparent",
|
||||||
|
"width": 111.60848999023438,
|
||||||
|
"height": 35,
|
||||||
|
"seed": 858001817,
|
||||||
"groupIds": [],
|
"groupIds": [],
|
||||||
"frameId": null,
|
"frameId": null,
|
||||||
"roundness": null,
|
"roundness": null,
|
||||||
"seed": 858001817,
|
"boundElements": [],
|
||||||
"version": 132,
|
"updated": 1705762591928,
|
||||||
"versionNonce": 1615445527,
|
|
||||||
"isDeleted": false,
|
|
||||||
"boundElements": null,
|
|
||||||
"updated": 1705397104893,
|
|
||||||
"link": null,
|
"link": null,
|
||||||
"locked": false,
|
"locked": false,
|
||||||
"text": "/; /home",
|
|
||||||
"fontSize": 28,
|
"fontSize": 28,
|
||||||
"fontFamily": 1,
|
"fontFamily": 1,
|
||||||
|
"text": "/; /home",
|
||||||
"textAlign": "left",
|
"textAlign": "left",
|
||||||
"verticalAlign": "top",
|
"verticalAlign": "top",
|
||||||
"baseline": 25,
|
|
||||||
"containerId": null,
|
"containerId": null,
|
||||||
"originalText": "/; /home",
|
"originalText": "/; /home",
|
||||||
"lineHeight": 1.25
|
"lineHeight": 1.25,
|
||||||
|
"baseline": 25
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"appState": {
|
"appState": {
|
||||||
|
@ -17,9 +17,12 @@
|
|||||||
"axios": "^1.6.1",
|
"axios": "^1.6.1",
|
||||||
"daisyui": "latest",
|
"daisyui": "latest",
|
||||||
"echarts": "^5.4.3",
|
"echarts": "^5.4.3",
|
||||||
|
"i18next": "^23.7.18",
|
||||||
|
"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": "^5.0.1",
|
"react-icons": "^5.0.1",
|
||||||
"uuid": "^9.0.1",
|
"uuid": "^9.0.1",
|
||||||
"wouter": "next",
|
"wouter": "next",
|
||||||
|
13
src/App.tsx
13
src/App.tsx
@ -1,25 +1,18 @@
|
|||||||
import { IconContext } from "react-icons/lib";
|
import { IconContext } from "react-icons/lib";
|
||||||
import DevControlPanel from "./components/DevControlPanel";
|
import DevControlPanel from "./components/DevControlPanel";
|
||||||
import FloatingMenu from "./components/FloatingMenu";
|
import FloatingMenu from "./components/FloatingMenu";
|
||||||
import {
|
import { floatingMenuStructure } from "./configure";
|
||||||
floatingLanguageMenuStructure,
|
|
||||||
floatingMenuStructure,
|
|
||||||
} from "./configure";
|
|
||||||
import SwitchRouteGenerator from "./routes/SwitchRouteGenerator";
|
import SwitchRouteGenerator from "./routes/SwitchRouteGenerator";
|
||||||
import routesTree from "./routes/routes";
|
import routesTree from "./routes/routes";
|
||||||
// TODO: - [x] Rewrite new switch generating function based on custom routes object
|
// 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] 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
|
// TODO: - [x] Rewrite DevControlPanel to use custom routes object
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<IconContext.Provider value={{ className: "size-5" }}>
|
<IconContext.Provider value={{ className: "size-5" }}>
|
||||||
<FloatingMenu menuStructure={floatingMenuStructure} />
|
<FloatingMenu menuStructure={floatingMenuStructure} />
|
||||||
<FloatingMenu
|
|
||||||
menuStructure={floatingLanguageMenuStructure}
|
|
||||||
className="flex gap-2 absolute left-4 top-4 flex-col"
|
|
||||||
tooltipClassName="tooltip tooltip-right"
|
|
||||||
/>
|
|
||||||
</IconContext.Provider>
|
</IconContext.Provider>
|
||||||
<SwitchRouteGenerator routesTree={routesTree} />
|
<SwitchRouteGenerator routesTree={routesTree} />
|
||||||
<DevControlPanel routesTree={routesTree} />
|
<DevControlPanel routesTree={routesTree} />
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
import { useLocation } from "wouter";
|
import { useLocation } from "wouter";
|
||||||
import { main } from "../configure";
|
import { floatingLanguageMenuStructure, main } from "../configure";
|
||||||
import { DevControlPanelProps } from "../types/devControlPanelTypes";
|
import { DevControlPanelProps } from "../types/devControlPanelTypes";
|
||||||
import { RoutingTree } from "../types/routesTypes";
|
import { RoutingTree } from "../types/routesTypes";
|
||||||
import inDebug from "../utils/inDebug";
|
import inDev from "../utils/inDev";
|
||||||
import ThemeButton from "./ThemeButton";
|
import ThemeButton from "./ThemeButton";
|
||||||
|
import { clearMultiplePathSlashes } from "../utils/StringTransformationUtils";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import FloatingMenu from "./FloatingMenu";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Development Control Panel Component
|
* Development Control Panel Component
|
||||||
@ -16,10 +19,11 @@ import ThemeButton from "./ThemeButton";
|
|||||||
*/
|
*/
|
||||||
const DevControlPanel = ({ routesTree }: DevControlPanelProps) => {
|
const DevControlPanel = ({ routesTree }: DevControlPanelProps) => {
|
||||||
const [, setLocation] = useLocation();
|
const [, setLocation] = useLocation();
|
||||||
|
const { t, i18n } = useTranslation();
|
||||||
|
|
||||||
// Function to update the current location, with debug logging
|
// Function to update the current location, with debug logging
|
||||||
const _setLocation = (targetLocation: string) => {
|
const _setLocation = (targetLocation: string) => {
|
||||||
inDebug(() => console.log("DevControlPanel_setLocation", targetLocation));
|
inDev(() => console.log("DevControlPanel_setLocation", targetLocation));
|
||||||
setLocation(targetLocation);
|
setLocation(targetLocation);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -30,12 +34,12 @@ const DevControlPanel = ({ routesTree }: DevControlPanelProps) => {
|
|||||||
): JSX.Element[] => {
|
): JSX.Element[] => {
|
||||||
return routesTree.map((route): JSX.Element => {
|
return routesTree.map((route): JSX.Element => {
|
||||||
// Constructing path for the route button
|
// Constructing path for the route button
|
||||||
const _path = (
|
const _path = clearMultiplePathSlashes(
|
||||||
parentPath ? "/" + parentPath + "/" + route.path : "/" + route.path
|
parentPath ? "/" + parentPath + "/" + route.path : "/" + route.path,
|
||||||
).replace(/\/\//g, "/");
|
);
|
||||||
|
|
||||||
// Debug logging for route information
|
// Debug logging for route information
|
||||||
inDebug(() =>
|
inDev(() =>
|
||||||
console.log(
|
console.log(
|
||||||
"%croutesCrawler_routes %s %s",
|
"%croutesCrawler_routes %s %s",
|
||||||
"color: lightblue",
|
"color: lightblue",
|
||||||
@ -86,6 +90,15 @@ const DevControlPanel = ({ routesTree }: DevControlPanelProps) => {
|
|||||||
</div>
|
</div>
|
||||||
{/* Rendering the dynamically generated route navigation buttons */}
|
{/* Rendering the dynamically generated route navigation buttons */}
|
||||||
<div className="space-y-2 space-x-2 mb-6">{routesCrawler(routesTree)}</div>
|
<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>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import useTheme from "../hooks/useTheme";
|
import useTheme from "../hooks/useTheme";
|
||||||
import inDebug from "../utils/inDebug";
|
import inDev from "../utils/inDev";
|
||||||
|
|
||||||
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={() => inDebug(() => console.log("Theme changed"))}
|
onChange={() => inDev(() => console.log("Theme changed"))}
|
||||||
onClick={toggleTheme}
|
onClick={toggleTheme}
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
/>
|
/>
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import { FiHome, FiLogIn } from "react-icons/fi";
|
import { FiHome, FiLogIn } from "react-icons/fi";
|
||||||
import { setLocation } from "./components/FloatingMenu";
|
import { setLocation } from "./components/FloatingMenu";
|
||||||
import ThemeButton from "./components/ThemeButton";
|
import ThemeButton from "./components/ThemeButton";
|
||||||
import { HOME, LOGIN, THEME } from "./consts";
|
import { HOME, LANGUAGE, LOGIN, THEME } from "./consts";
|
||||||
import { MenuStructure } from "./types/floatingMenuTypes";
|
import { MenuStructure } from "./types/floatingMenuTypes";
|
||||||
|
import { setLanguage } from "./main";
|
||||||
|
import { HiMiniLanguage } from "react-icons/hi2";
|
||||||
/* INSTRUCTIONS:
|
/* INSTRUCTIONS:
|
||||||
* Here you can configure:
|
* Here you can configure:
|
||||||
* - program version
|
* - program version
|
||||||
@ -27,6 +29,24 @@ export const main = {
|
|||||||
// 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[] = [
|
export const floatingMenuStructure: MenuStructure[] = [
|
||||||
{
|
{
|
||||||
label: HOME,
|
label: HOME,
|
||||||
@ -39,16 +59,34 @@ export const floatingMenuStructure: MenuStructure[] = [
|
|||||||
action: () => setLocation(`/${LOGIN}`),
|
action: () => setLocation(`/${LOGIN}`),
|
||||||
},
|
},
|
||||||
{ label: THEME, element: <ThemeButton /> },
|
{ label: THEME, element: <ThemeButton /> },
|
||||||
];
|
|
||||||
export const floatingLanguageMenuStructure: MenuStructure[] = [
|
|
||||||
{
|
{
|
||||||
label: "English",
|
label: LANGUAGE,
|
||||||
icon: <p>🇬🇧</p>,
|
element: (
|
||||||
action: () => console.log("English"),
|
<div className="dropdown dropdown-left">
|
||||||
},
|
<div tabIndex={0} role="button" className="btn btn-circle btn-outline">
|
||||||
{
|
<HiMiniLanguage />
|
||||||
label: "Polski",
|
</div>
|
||||||
icon: <p>🇵🇱</p>,
|
<ul
|
||||||
action: () => console.log("Polski"),
|
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>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -14,3 +14,4 @@ export const ROLES = "roles";
|
|||||||
export const USERS = "users";
|
export const USERS = "users";
|
||||||
// -- BUTTONS --
|
// -- BUTTONS --
|
||||||
export const THEME = "theme";
|
export const THEME = "theme";
|
||||||
|
export const LANGUAGE = "language";
|
||||||
|
35
src/hooks/useSessionStorage.ts
Normal file
35
src/hooks/useSessionStorage.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
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,6 +1,6 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import useLocalStorage from "./useLocalStorage";
|
import useLocalStorage from "./useLocalStorage";
|
||||||
import inDebug from "../utils/inDebug";
|
import inDev from "../utils/inDev";
|
||||||
/**
|
/**
|
||||||
* Custom hook for managing theme state.
|
* Custom hook for managing theme state.
|
||||||
*
|
*
|
||||||
@ -20,14 +20,14 @@ const useTheme = (): [boolean, () => void] => {
|
|||||||
const isDark = theme === "dark";
|
const isDark = theme === "dark";
|
||||||
|
|
||||||
const toggleTheme = () => {
|
const toggleTheme = () => {
|
||||||
inDebug(() => console.log("toggleTheme called"));
|
inDev(() => 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(() => {
|
||||||
inDebug(() => console.log("useEffect called"));
|
inDev(() => console.log("useEffect called"));
|
||||||
document.documentElement.dataset.theme = theme;
|
document.documentElement.dataset.theme = theme;
|
||||||
}, [theme]);
|
}, [theme]);
|
||||||
|
|
||||||
|
7
src/locales/eng/general.json
Normal file
7
src/locales/eng/general.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"welcome": "Welcome!",
|
||||||
|
"login_button": "Login",
|
||||||
|
"login_username": "Username",
|
||||||
|
"login_password": "Password",
|
||||||
|
"login_accept_terms": "I accept the terms and conditions"
|
||||||
|
}
|
24
src/locales/localesConfig.ts
Normal file
24
src/locales/localesConfig.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
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",
|
||||||
|
});
|
7
src/locales/pol/general.json
Normal file
7
src/locales/pol/general.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"welcome": "Witaj!",
|
||||||
|
"login_button": "Zaloguj",
|
||||||
|
"login_username": "Nazwa użytkownika",
|
||||||
|
"login_password": "Hasło",
|
||||||
|
"login_accept_terms": "Akceptuję regulamin"
|
||||||
|
}
|
19
src/main.tsx
19
src/main.tsx
@ -1,17 +1,26 @@
|
|||||||
import { Global, css } from "@emotion/react";
|
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 { Router } from "wouter";
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
import { setupAxiosInterceptors } from "./api/AxiosService";
|
import { setupAxiosInterceptors } from "./api/AxiosService";
|
||||||
import { main, viteEnv } from "./configure";
|
import { main, viteEnv } from "./configure";
|
||||||
import inDebug from "./utils/inDebug";
|
import useLocalStorage from "./hooks/useLocalStorage";
|
||||||
|
import "./locales/localesConfig";
|
||||||
|
import inDev from "./utils/inDev";
|
||||||
import "/style.css"; // Global tailwind styles
|
import "/style.css"; // Global tailwind styles
|
||||||
import { Router } from "wouter";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
setupAxiosInterceptors();
|
setupAxiosInterceptors();
|
||||||
inDebug(() => console.log(viteEnv));
|
inDev(() => console.log(viteEnv));
|
||||||
//! Important, defining base as '/' isn't equal to not defining it at all. That way is easier 😁
|
export let language: string, setLanguage: (value: string) => void;
|
||||||
|
|
||||||
const _App = () => {
|
const _App = () => {
|
||||||
|
[language, setLanguage] = useLocalStorage("language", "eng");
|
||||||
|
const { i18n } = useTranslation();
|
||||||
|
useEffect(() => {
|
||||||
|
i18n.changeLanguage(language);
|
||||||
|
}, [language]);
|
||||||
if (main.base_path !== "/") {
|
if (main.base_path !== "/") {
|
||||||
return (
|
return (
|
||||||
<Router base={main.base_path}>
|
<Router base={main.base_path}>
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import { main } from "../configure";
|
import { main } from "../configure";
|
||||||
|
|
||||||
const LoginPage = () => {
|
const LoginPage = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
// hero daisyui login page
|
|
||||||
<div className="hero min-h-screen bg-base-200">
|
<div className="hero min-h-screen bg-base-200">
|
||||||
<div className="flex-col justify-center hero-content lg:flex-row-reverse">
|
<div className="flex-col justify-center hero-content lg:flex-row-reverse">
|
||||||
<div className="text-center lg:text-left">
|
<div className="text-center lg:text-left">
|
||||||
@ -15,21 +16,21 @@ const LoginPage = () => {
|
|||||||
<form>
|
<form>
|
||||||
<div className="form-control">
|
<div className="form-control">
|
||||||
<label className="label">
|
<label className="label">
|
||||||
<span className="label-text">Login</span>
|
<span className="label-text">{t("login_username")}</span>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="login"
|
placeholder={t("login_username")}
|
||||||
className="input input-bordered"
|
className="input input-bordered"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-control">
|
<div className="form-control">
|
||||||
<label className="label">
|
<label className="label">
|
||||||
<span className="label-text">Password</span>
|
<span className="label-text">{t("login_password")}</span>
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="password"
|
placeholder={t("login_password")}
|
||||||
className="input input-bordered"
|
className="input input-bordered"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -37,12 +38,16 @@ const LoginPage = () => {
|
|||||||
<label className="cursor-pointer label justify-start gap-4">
|
<label className="cursor-pointer label justify-start gap-4">
|
||||||
<input type="checkbox" className="checkbox checkbox-success" />
|
<input type="checkbox" className="checkbox checkbox-success" />
|
||||||
<span className="label-text justify-start">
|
<span className="label-text justify-start">
|
||||||
Agree to the terms and policy
|
{t("login_accept_terms")}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-control mt-6">
|
<div className="form-control mt-6">
|
||||||
<input type="submit" value="Login" className="btn btn-primary" />
|
<input
|
||||||
|
type="submit"
|
||||||
|
value={t("login_button")}
|
||||||
|
className="btn btn-primary"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { Route, Switch } from "wouter";
|
import { Route, Switch } from "wouter";
|
||||||
import inDebug from "../utils/inDebug";
|
import inDev from "../utils/inDev";
|
||||||
import { RoutingTree } from "../types/routesTypes";
|
import { RoutingTree } from "../types/routesTypes";
|
||||||
import { SwitchRouteGeneratorProps } from "../types/switchRouteGeneratorTypes";
|
import { SwitchRouteGeneratorProps } from "../types/switchRouteGeneratorTypes";
|
||||||
|
import { clearMultiplePathSlashes } from "../utils/StringTransformationUtils";
|
||||||
|
|
||||||
const routesCrawler = (
|
const routesCrawler = (
|
||||||
routesTree: RoutingTree,
|
routesTree: RoutingTree,
|
||||||
@ -10,11 +11,11 @@ const routesCrawler = (
|
|||||||
return routesTree.map((route): JSX.Element => {
|
return routesTree.map((route): JSX.Element => {
|
||||||
// -----
|
// -----
|
||||||
if (route.path) {
|
if (route.path) {
|
||||||
const _path = (
|
const _path = clearMultiplePathSlashes(
|
||||||
parentPath && parentPath !== "/" ? parentPath + route.path : route.path
|
parentPath && parentPath !== "/" ? parentPath + route.path : route.path,
|
||||||
).replace("//", "/");
|
);
|
||||||
// Debug log shows generated routes
|
// Debug log shows generated routes
|
||||||
inDebug(() => console.log("routesCrawler_routes", route.name, _path));
|
inDev(() => console.log("routesCrawler_routes", route.name, _path));
|
||||||
// -----
|
// -----
|
||||||
if (route.nest) {
|
if (route.nest) {
|
||||||
return (
|
return (
|
||||||
|
@ -1,9 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* 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,13 +1,24 @@
|
|||||||
|
/**
|
||||||
|
* 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Function to clear multiple path slashes
|
* Clears multiple consecutive slashes in a path string.
|
||||||
|
*
|
||||||
|
* @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 '?'.
|
||||||
*
|
*
|
||||||
@ -17,6 +28,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) => {
|
export const trimPathOfParameters = (path: string): string => {
|
||||||
return path.replace(/\/:[^/]*|\?[^/]*/g, "");
|
return path.replace(/\/:[^/]*|\?[^/]*/g, "");
|
||||||
};
|
};
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
/**
|
/**
|
||||||
* Execute whatever you pass only in development (not production)
|
* Execute whatever you pass only in development (not production)
|
||||||
*/
|
*/
|
||||||
const inDebug = <T>(callback: () => T): T | null => {
|
const inDev = <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 inDebug;
|
export default inDev;
|
@ -17,6 +17,7 @@
|
|||||||
"jsx": "react-jsx", // Transform JSX for React 17+ JSX Transform
|
"jsx": "react-jsx", // Transform JSX for React 17+ JSX Transform
|
||||||
// Strengthening type-checking and ensuring consistency
|
// Strengthening type-checking and ensuring consistency
|
||||||
"strict": true, // Enable all strict type-checking options
|
"strict": true, // Enable all strict type-checking options
|
||||||
|
"strictNullChecks": true, // Enable strict null checks
|
||||||
"noUnusedLocals": true, // Disallow unused local variables
|
"noUnusedLocals": true, // Disallow unused local variables
|
||||||
"noUnusedParameters": true, // Disallow unused function parameters
|
"noUnusedParameters": true, // Disallow unused function parameters
|
||||||
"noFallthroughCasesInSwitch": true, // Prevent fallthrough cases in switch statements
|
"noFallthroughCasesInSwitch": true, // Prevent fallthrough cases in switch statements
|
||||||
|
Loading…
x
Reference in New Issue
Block a user