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 },
|
||||
extends: [
|
||||
"eslint:recommended",
|
@ -1,4 +1,4 @@
|
||||
export default {
|
||||
module.exports = {
|
||||
printWidth: 80,
|
||||
trailingComma: "all",
|
||||
singleQuote: false,
|
24
README.md
24
README.md
@ -10,22 +10,20 @@
|
||||
|
||||
## Usage
|
||||
|
||||
1. Setup `.env.{mode}` files
|
||||
|
||||
```ini
|
||||
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_
|
||||
1. Setup `.env` files
|
||||
1. `.env` - for production and development
|
||||
2. `.env.development` - for development
|
||||
3. `.env.production` - for production
|
||||
2. Build or run dev server
|
||||
3. Enjoy 🎉
|
||||
|
||||
### 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
|
||||
|
||||
> UI inspiration: <https://demo.themesberg.com/windster-pro/#>
|
||||
> UI inspirations:
|
||||
>
|
||||
> - <https://demo.themesberg.com/windster-pro/#>
|
||||
> - <https://tamagui.dev/>
|
||||
|
@ -29,7 +29,7 @@
|
||||
"roundness": {
|
||||
"type": 3
|
||||
},
|
||||
"boundElements": null,
|
||||
"boundElements": [],
|
||||
"updated": 1705396978531,
|
||||
"link": null,
|
||||
"locked": false
|
||||
@ -94,7 +94,7 @@
|
||||
],
|
||||
"frameId": null,
|
||||
"roundness": null,
|
||||
"boundElements": null,
|
||||
"boundElements": [],
|
||||
"updated": 1705397026573,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
@ -106,7 +106,7 @@
|
||||
"containerId": "2zx0egnLluNOg2aagGFf5",
|
||||
"originalText": "Username",
|
||||
"lineHeight": 1.25,
|
||||
"baseline": 18
|
||||
"baseline": 19
|
||||
},
|
||||
{
|
||||
"type": "rectangle",
|
||||
@ -168,7 +168,7 @@
|
||||
],
|
||||
"frameId": null,
|
||||
"roundness": null,
|
||||
"boundElements": null,
|
||||
"boundElements": [],
|
||||
"updated": 1705397072409,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
@ -180,7 +180,7 @@
|
||||
"containerId": "I0Xg4QaaO7XRxm7w1P_0a",
|
||||
"originalText": "Password",
|
||||
"lineHeight": 1.25,
|
||||
"baseline": 18
|
||||
"baseline": 19
|
||||
},
|
||||
{
|
||||
"type": "rectangle",
|
||||
@ -242,7 +242,7 @@
|
||||
],
|
||||
"frameId": null,
|
||||
"roundness": null,
|
||||
"boundElements": null,
|
||||
"boundElements": [],
|
||||
"updated": 1705397026573,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
@ -254,12 +254,12 @@
|
||||
"containerId": "ZikkdqWQ7aOQyqTYogsSg",
|
||||
"originalText": "Login",
|
||||
"lineHeight": 1.25,
|
||||
"baseline": 18
|
||||
"baseline": 19
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"version": 768,
|
||||
"versionNonce": 851740953,
|
||||
"version": 770,
|
||||
"versionNonce": 402387348,
|
||||
"isDeleted": false,
|
||||
"id": "97fLf76gEdKS_GvQ6ALpU",
|
||||
"fillStyle": "solid",
|
||||
@ -280,8 +280,8 @@
|
||||
],
|
||||
"frameId": null,
|
||||
"roundness": null,
|
||||
"boundElements": null,
|
||||
"updated": 1705396978532,
|
||||
"boundElements": [],
|
||||
"updated": 1705762591927,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"fontSize": 36,
|
||||
@ -295,40 +295,40 @@
|
||||
"baseline": 32
|
||||
},
|
||||
{
|
||||
"id": "no67OXeP6ZJsJ51TOHxJy",
|
||||
"type": "text",
|
||||
"x": 719.7241633880242,
|
||||
"y": 107.53011605520807,
|
||||
"width": 70.11228942871094,
|
||||
"height": 35,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"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,
|
||||
"seed": 1759430041,
|
||||
"version": 34,
|
||||
"versionNonce": 764198007,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1705397088770,
|
||||
"boundElements": [],
|
||||
"updated": 1705762591928,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "/login",
|
||||
"fontSize": 28,
|
||||
"fontFamily": 1,
|
||||
"text": "/login",
|
||||
"textAlign": "left",
|
||||
"verticalAlign": "top",
|
||||
"baseline": 25,
|
||||
"containerId": null,
|
||||
"originalText": "/login",
|
||||
"lineHeight": 1.25
|
||||
"lineHeight": 1.25,
|
||||
"baseline": 25
|
||||
},
|
||||
{
|
||||
"type": "rectangle",
|
||||
@ -362,69 +362,69 @@
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"id": "wApCZR8vnuIdRuwfVeC5x",
|
||||
"type": "rectangle",
|
||||
"x": 953.3863135555557,
|
||||
"y": 244.88073477777772,
|
||||
"width": 468.8888888888887,
|
||||
"height": 264.44444444444457,
|
||||
"angle": 0,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"backgroundColor": "transparent",
|
||||
"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
|
||||
},
|
||||
"seed": 902925239,
|
||||
"version": 109,
|
||||
"versionNonce": 1334885687,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"boundElements": [],
|
||||
"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",
|
||||
"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,
|
||||
"seed": 858001817,
|
||||
"version": 132,
|
||||
"versionNonce": 1615445527,
|
||||
"isDeleted": false,
|
||||
"boundElements": null,
|
||||
"updated": 1705397104893,
|
||||
"boundElements": [],
|
||||
"updated": 1705762591928,
|
||||
"link": null,
|
||||
"locked": false,
|
||||
"text": "/; /home",
|
||||
"fontSize": 28,
|
||||
"fontFamily": 1,
|
||||
"text": "/; /home",
|
||||
"textAlign": "left",
|
||||
"verticalAlign": "top",
|
||||
"baseline": 25,
|
||||
"containerId": null,
|
||||
"originalText": "/; /home",
|
||||
"lineHeight": 1.25
|
||||
"lineHeight": 1.25,
|
||||
"baseline": 25
|
||||
}
|
||||
],
|
||||
"appState": {
|
||||
|
@ -17,9 +17,12 @@
|
||||
"axios": "^1.6.1",
|
||||
"daisyui": "latest",
|
||||
"echarts": "^5.4.3",
|
||||
"i18next": "^23.7.18",
|
||||
"i18next-browser-languagedetector": "^7.2.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hook-form": "^7.48.2",
|
||||
"react-i18next": "^14.0.1",
|
||||
"react-icons": "^5.0.1",
|
||||
"uuid": "^9.0.1",
|
||||
"wouter": "next",
|
||||
|
13
src/App.tsx
13
src/App.tsx
@ -1,25 +1,18 @@
|
||||
import { IconContext } from "react-icons/lib";
|
||||
import DevControlPanel from "./components/DevControlPanel";
|
||||
import FloatingMenu from "./components/FloatingMenu";
|
||||
import {
|
||||
floatingLanguageMenuStructure,
|
||||
floatingMenuStructure,
|
||||
} from "./configure";
|
||||
import { 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
|
||||
// TODO: - [x] Rewrite DevControlPanel to use custom routes object
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div>
|
||||
<IconContext.Provider value={{ className: "size-5" }}>
|
||||
<FloatingMenu menuStructure={floatingMenuStructure} />
|
||||
<FloatingMenu
|
||||
menuStructure={floatingLanguageMenuStructure}
|
||||
className="flex gap-2 absolute left-4 top-4 flex-col"
|
||||
tooltipClassName="tooltip tooltip-right"
|
||||
/>
|
||||
</IconContext.Provider>
|
||||
<SwitchRouteGenerator routesTree={routesTree} />
|
||||
<DevControlPanel routesTree={routesTree} />
|
||||
|
@ -1,9 +1,12 @@
|
||||
import { useLocation } from "wouter";
|
||||
import { main } from "../configure";
|
||||
import { floatingLanguageMenuStructure, main } from "../configure";
|
||||
import { DevControlPanelProps } from "../types/devControlPanelTypes";
|
||||
import { RoutingTree } from "../types/routesTypes";
|
||||
import inDebug from "../utils/inDebug";
|
||||
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
|
||||
@ -16,10 +19,11 @@ import ThemeButton from "./ThemeButton";
|
||||
*/
|
||||
const DevControlPanel = ({ routesTree }: DevControlPanelProps) => {
|
||||
const [, setLocation] = useLocation();
|
||||
const { t, i18n } = useTranslation();
|
||||
|
||||
// Function to update the current location, with debug logging
|
||||
const _setLocation = (targetLocation: string) => {
|
||||
inDebug(() => console.log("DevControlPanel_setLocation", targetLocation));
|
||||
inDev(() => console.log("DevControlPanel_setLocation", targetLocation));
|
||||
setLocation(targetLocation);
|
||||
};
|
||||
|
||||
@ -30,12 +34,12 @@ const DevControlPanel = ({ routesTree }: DevControlPanelProps) => {
|
||||
): JSX.Element[] => {
|
||||
return routesTree.map((route): JSX.Element => {
|
||||
// Constructing path for the route button
|
||||
const _path = (
|
||||
parentPath ? "/" + parentPath + "/" + route.path : "/" + route.path
|
||||
).replace(/\/\//g, "/");
|
||||
const _path = clearMultiplePathSlashes(
|
||||
parentPath ? "/" + parentPath + "/" + route.path : "/" + route.path,
|
||||
);
|
||||
|
||||
// Debug logging for route information
|
||||
inDebug(() =>
|
||||
inDev(() =>
|
||||
console.log(
|
||||
"%croutesCrawler_routes %s %s",
|
||||
"color: lightblue",
|
||||
@ -86,6 +90,15 @@ const DevControlPanel = ({ routesTree }: DevControlPanelProps) => {
|
||||
</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>
|
||||
);
|
||||
};
|
||||
|
@ -1,5 +1,5 @@
|
||||
import useTheme from "../hooks/useTheme";
|
||||
import inDebug from "../utils/inDebug";
|
||||
import inDev from "../utils/inDev";
|
||||
|
||||
const ThemeButton = () => {
|
||||
const [isDark, toggleTheme] = useTheme();
|
||||
@ -8,7 +8,7 @@ const ThemeButton = () => {
|
||||
<input
|
||||
checked={isDark}
|
||||
className="hidden"
|
||||
onChange={() => inDebug(() => console.log("Theme changed"))}
|
||||
onChange={() => inDev(() => console.log("Theme changed"))}
|
||||
onClick={toggleTheme}
|
||||
type="checkbox"
|
||||
/>
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { FiHome, FiLogIn } from "react-icons/fi";
|
||||
import { setLocation } from "./components/FloatingMenu";
|
||||
import ThemeButton from "./components/ThemeButton";
|
||||
import { HOME, LOGIN, THEME } from "./consts";
|
||||
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
|
||||
@ -27,6 +29,24 @@ export const main = {
|
||||
// sizes in pixels
|
||||
export const topbarSize = 64;
|
||||
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,
|
||||
@ -39,16 +59,34 @@ export const floatingMenuStructure: MenuStructure[] = [
|
||||
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"),
|
||||
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>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
@ -14,3 +14,4 @@ export const ROLES = "roles";
|
||||
export const USERS = "users";
|
||||
// -- BUTTONS --
|
||||
export const THEME = "theme";
|
||||
export const LANGUAGE = "language";
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { useState } from "react";
|
||||
/**
|
||||
* Custom hook for persisting state in local storage.
|
||||
*
|
||||
*
|
||||
* This hook works similarly to the standard `useState` hook but also stores the state in local storage,
|
||||
* 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.
|
||||
|
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 useLocalStorage from "./useLocalStorage";
|
||||
import inDebug from "../utils/inDebug";
|
||||
import inDev from "../utils/inDev";
|
||||
/**
|
||||
* Custom hook for managing theme state.
|
||||
*
|
||||
@ -20,14 +20,14 @@ const useTheme = (): [boolean, () => void] => {
|
||||
const isDark = theme === "dark";
|
||||
|
||||
const toggleTheme = () => {
|
||||
inDebug(() => console.log("toggleTheme called"));
|
||||
inDev(() => console.log("toggleTheme called"));
|
||||
const newTheme = theme === "light" ? "dark" : "light";
|
||||
setTheme(newTheme);
|
||||
document.documentElement.dataset.theme = newTheme;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
inDebug(() => console.log("useEffect called"));
|
||||
inDev(() => console.log("useEffect called"));
|
||||
document.documentElement.dataset.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 React from "react";
|
||||
import React, { useEffect } from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { Router } from "wouter";
|
||||
import App from "./App";
|
||||
import { setupAxiosInterceptors } from "./api/AxiosService";
|
||||
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 { Router } from "wouter";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
setupAxiosInterceptors();
|
||||
inDebug(() => console.log(viteEnv));
|
||||
//! Important, defining base as '/' isn't equal to not defining it at all. That way is easier 😁
|
||||
inDev(() => console.log(viteEnv));
|
||||
export let language: string, setLanguage: (value: string) => void;
|
||||
|
||||
const _App = () => {
|
||||
[language, setLanguage] = useLocalStorage("language", "eng");
|
||||
const { i18n } = useTranslation();
|
||||
useEffect(() => {
|
||||
i18n.changeLanguage(language);
|
||||
}, [language]);
|
||||
if (main.base_path !== "/") {
|
||||
return (
|
||||
<Router base={main.base_path}>
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { main } from "../configure";
|
||||
|
||||
const LoginPage = () => {
|
||||
const { t } = useTranslation();
|
||||
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">
|
||||
@ -15,21 +16,21 @@ const LoginPage = () => {
|
||||
<form>
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text">Login</span>
|
||||
<span className="label-text">{t("login_username")}</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="login"
|
||||
placeholder={t("login_username")}
|
||||
className="input input-bordered"
|
||||
/>
|
||||
</div>
|
||||
<div className="form-control">
|
||||
<label className="label">
|
||||
<span className="label-text">Password</span>
|
||||
<span className="label-text">{t("login_password")}</span>
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
placeholder="password"
|
||||
placeholder={t("login_password")}
|
||||
className="input input-bordered"
|
||||
/>
|
||||
</div>
|
||||
@ -37,12 +38,16 @@ const LoginPage = () => {
|
||||
<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
|
||||
{t("login_accept_terms")}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<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>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { Route, Switch } from "wouter";
|
||||
import inDebug from "../utils/inDebug";
|
||||
import inDev from "../utils/inDev";
|
||||
import { RoutingTree } from "../types/routesTypes";
|
||||
import { SwitchRouteGeneratorProps } from "../types/switchRouteGeneratorTypes";
|
||||
import { clearMultiplePathSlashes } from "../utils/StringTransformationUtils";
|
||||
|
||||
const routesCrawler = (
|
||||
routesTree: RoutingTree,
|
||||
@ -10,11 +11,11 @@ const routesCrawler = (
|
||||
return routesTree.map((route): JSX.Element => {
|
||||
// -----
|
||||
if (route.path) {
|
||||
const _path = (
|
||||
parentPath && parentPath !== "/" ? parentPath + route.path : route.path
|
||||
).replace("//", "/");
|
||||
const _path = clearMultiplePathSlashes(
|
||||
parentPath && parentPath !== "/" ? parentPath + route.path : route.path,
|
||||
);
|
||||
// Debug log shows generated routes
|
||||
inDebug(() => console.log("routesCrawler_routes", route.name, _path));
|
||||
inDev(() => console.log("routesCrawler_routes", route.name, _path));
|
||||
// -----
|
||||
if (route.nest) {
|
||||
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 = (
|
||||
obj: Readonly<Partial<{ state: string; id: string }>>,
|
||||
) => {
|
||||
): string => {
|
||||
// Initialize an empty result string.
|
||||
let result = "";
|
||||
|
||||
// Iterate over each entry in the object.
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
// Append the key-value pair to the result string in a formatted way.
|
||||
result += ` -${key}: ${value}`;
|
||||
}
|
||||
|
||||
// Return the trimmed result.
|
||||
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 {
|
||||
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"
|
||||
*/
|
||||
export const clearMultiplePathSlashes = (path: string): string => {
|
||||
return path.replace(/\/{2,}/g, "/");
|
||||
};
|
||||
|
||||
/**
|
||||
* Trims parameters and queries from a URL path that start with ':' or '?'.
|
||||
*
|
||||
@ -17,6 +28,6 @@ export const clearMultiplePathSlashes = (path: string): string => {
|
||||
* // returns "/path"
|
||||
* trimPathOfParameters("/path/:param1/:param2:param3/?param4")
|
||||
*/
|
||||
export const trimPathOfParameters = (path: string) => {
|
||||
export const trimPathOfParameters = (path: string): string => {
|
||||
return path.replace(/\/:[^/]*|\?[^/]*/g, "");
|
||||
};
|
||||
|
@ -1,11 +1,11 @@
|
||||
/**
|
||||
* 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") {
|
||||
return callback();
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export default inDebug;
|
||||
export default inDev;
|
@ -17,6 +17,7 @@
|
||||
"jsx": "react-jsx", // Transform JSX for React 17+ JSX Transform
|
||||
// Strengthening type-checking and ensuring consistency
|
||||
"strict": true, // Enable all strict type-checking options
|
||||
"strictNullChecks": true, // Enable strict null checks
|
||||
"noUnusedLocals": true, // Disallow unused local variables
|
||||
"noUnusedParameters": true, // Disallow unused function parameters
|
||||
"noFallthroughCasesInSwitch": true, // Prevent fallthrough cases in switch statements
|
||||
|
Loading…
x
Reference in New Issue
Block a user