From acebe04456d5701c593f5c156950423996cb3eee Mon Sep 17 00:00:00 2001 From: Igor Barcik Date: Wed, 24 Jan 2024 09:54:32 +0100 Subject: [PATCH] Updated based on my other project --- .eslintrc.js => .eslintrc.cjs | 2 +- .prettierrc.js => .prettierrc.cjs | 2 +- README.md | 24 +++-- bun.lockb | Bin 170512 -> 172573 bytes docs/servimainUI.excalidraw | 116 ++++++++++++------------- package.json | 3 + src/App.tsx | 13 +-- src/components/DevControlPanel.tsx | 27 ++++-- src/components/ThemeButton.tsx | 4 +- src/configure.tsx | 60 ++++++++++--- src/consts.ts | 1 + src/hooks/useLocalStorage.ts | 4 +- src/hooks/useSessionStorage.ts | 35 ++++++++ src/hooks/useTheme.ts | 6 +- src/locales/eng/general.json | 7 ++ src/locales/localesConfig.ts | 24 +++++ src/locales/pol/general.json | 7 ++ src/main.tsx | 19 ++-- src/pages/LoginPage.tsx | 19 ++-- src/routes/SwitchRouteGenerator.tsx | 11 +-- src/utils/ObjectUtils.ts | 15 +++- src/utils/StringTransformationUtils.ts | 15 +++- src/utils/{inDebug.ts => inDev.ts} | 4 +- tsconfig.json | 1 + 24 files changed, 289 insertions(+), 130 deletions(-) rename .eslintrc.js => .eslintrc.cjs (95%) rename .prettierrc.js => .prettierrc.cjs (91%) create mode 100644 src/hooks/useSessionStorage.ts create mode 100644 src/locales/eng/general.json create mode 100644 src/locales/localesConfig.ts create mode 100644 src/locales/pol/general.json rename src/utils/{inDebug.ts => inDev.ts} (67%) diff --git a/.eslintrc.js b/.eslintrc.cjs similarity index 95% rename from .eslintrc.js rename to .eslintrc.cjs index f7472b4..d29b926 100644 --- a/.eslintrc.js +++ b/.eslintrc.cjs @@ -1,4 +1,4 @@ -export default { +module.exports = { env: { browser: true, es2020: true, node: true }, extends: [ "eslint:recommended", diff --git a/.prettierrc.js b/.prettierrc.cjs similarity index 91% rename from .prettierrc.js rename to .prettierrc.cjs index 5223e66..eaf4b83 100644 --- a/.prettierrc.js +++ b/.prettierrc.cjs @@ -1,4 +1,4 @@ -export default { +module.exports = { printWidth: 80, trailingComma: "all", singleQuote: false, diff --git a/README.md b/README.md index 68f56dd..feb6fce 100644 --- a/README.md +++ b/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: +> UI inspirations: +> +> - +> - diff --git a/bun.lockb b/bun.lockb index cdb38c832768c6e5f579e436af0d7728e9c76815..c71a4fe18e147f752e78472a54e0b36c6ee7119c 100755 GIT binary patch delta 33532 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 P4ApCGydJGrO8I{P_ww<= delta 32375 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 - diff --git a/src/components/DevControlPanel.tsx b/src/components/DevControlPanel.tsx index 12e3b69..eb23ea1 100644 --- a/src/components/DevControlPanel.tsx +++ b/src/components/DevControlPanel.tsx @@ -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) => { {/* Rendering the dynamically generated route navigation buttons */}
{routesCrawler(routesTree)}
+
+

{t("welcome")}

+

Lang: {i18n.language}

+
+ ); }; diff --git a/src/components/ThemeButton.tsx b/src/components/ThemeButton.tsx index 2289c9c..bddad40 100644 --- a/src/components/ThemeButton.tsx +++ b/src/components/ThemeButton.tsx @@ -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 = () => { inDebug(() => console.log("Theme changed"))} + onChange={() => inDev(() => console.log("Theme changed"))} onClick={toggleTheme} type="checkbox" /> diff --git a/src/configure.tsx b/src/configure.tsx index 846c144..79f64b4 100644 --- a/src/configure.tsx +++ b/src/configure.tsx @@ -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:

🇬🇧

, + action: () => { + console.log("English"); + setLanguage("eng"); + }, + }, + { + label: "Polski", + icon:

🇵🇱

, + 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: }, -]; -export const floatingLanguageMenuStructure: MenuStructure[] = [ { - label: "English", - icon:

🇬🇧

, - action: () => console.log("English"), - }, - { - label: "Polski", - icon:

🇵🇱

, - action: () => console.log("Polski"), + label: LANGUAGE, + element: ( + + ), }, ]; diff --git a/src/consts.ts b/src/consts.ts index 2ab1074..47eb5ed 100644 --- a/src/consts.ts +++ b/src/consts.ts @@ -14,3 +14,4 @@ export const ROLES = "roles"; export const USERS = "users"; // -- BUTTONS -- export const THEME = "theme"; +export const LANGUAGE = "language"; diff --git a/src/hooks/useLocalStorage.ts b/src/hooks/useLocalStorage.ts index 629d1c3..e3a6e72 100644 --- a/src/hooks/useLocalStorage.ts +++ b/src/hooks/useLocalStorage.ts @@ -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. diff --git a/src/hooks/useSessionStorage.ts b/src/hooks/useSessionStorage.ts new file mode 100644 index 0000000..7fc5aeb --- /dev/null +++ b/src/hooks/useSessionStorage.ts @@ -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 = ( + key: string, + initialValue: T, +): [T, (value: T) => void] => { + const [storedValue, setStoredValue] = useState(() => { + 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; diff --git a/src/hooks/useTheme.ts b/src/hooks/useTheme.ts index dceb815..b75726f 100644 --- a/src/hooks/useTheme.ts +++ b/src/hooks/useTheme.ts @@ -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]); diff --git a/src/locales/eng/general.json b/src/locales/eng/general.json new file mode 100644 index 0000000..e7a35f9 --- /dev/null +++ b/src/locales/eng/general.json @@ -0,0 +1,7 @@ +{ + "welcome": "Welcome!", + "login_button": "Login", + "login_username": "Username", + "login_password": "Password", + "login_accept_terms": "I accept the terms and conditions" +} diff --git a/src/locales/localesConfig.ts b/src/locales/localesConfig.ts new file mode 100644 index 0000000..79575e8 --- /dev/null +++ b/src/locales/localesConfig.ts @@ -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", + }); diff --git a/src/locales/pol/general.json b/src/locales/pol/general.json new file mode 100644 index 0000000..a2e514e --- /dev/null +++ b/src/locales/pol/general.json @@ -0,0 +1,7 @@ +{ + "welcome": "Witaj!", + "login_button": "Zaloguj", + "login_username": "Nazwa użytkownika", + "login_password": "Hasło", + "login_accept_terms": "Akceptuję regulamin" +} diff --git a/src/main.tsx b/src/main.tsx index a4276ef..06efb39 100644 --- a/src/main.tsx +++ b/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 ( diff --git a/src/pages/LoginPage.tsx b/src/pages/LoginPage.tsx index 86a4cda..18783c9 100644 --- a/src/pages/LoginPage.tsx +++ b/src/pages/LoginPage.tsx @@ -1,8 +1,9 @@ +import { useTranslation } from "react-i18next"; import { main } from "../configure"; const LoginPage = () => { + const { t } = useTranslation(); return ( - // hero daisyui login page
@@ -15,21 +16,21 @@ const LoginPage = () => {
@@ -37,12 +38,16 @@ const LoginPage = () => {
- +
diff --git a/src/routes/SwitchRouteGenerator.tsx b/src/routes/SwitchRouteGenerator.tsx index f0d0321..bca3208 100644 --- a/src/routes/SwitchRouteGenerator.tsx +++ b/src/routes/SwitchRouteGenerator.tsx @@ -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 ( diff --git a/src/utils/ObjectUtils.ts b/src/utils/ObjectUtils.ts index 47b3177..b19cfd2 100644 --- a/src/utils/ObjectUtils.ts +++ b/src/utils/ObjectUtils.ts @@ -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>, -) => { +): 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(); }; diff --git a/src/utils/StringTransformationUtils.ts b/src/utils/StringTransformationUtils.ts index b96728d..e389c68 100644 --- a/src/utils/StringTransformationUtils.ts +++ b/src/utils/StringTransformationUtils.ts @@ -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, ""); }; diff --git a/src/utils/inDebug.ts b/src/utils/inDev.ts similarity index 67% rename from src/utils/inDebug.ts rename to src/utils/inDev.ts index 744805a..064adb5 100644 --- a/src/utils/inDebug.ts +++ b/src/utils/inDev.ts @@ -1,11 +1,11 @@ /** * Execute whatever you pass only in development (not production) */ -const inDebug = (callback: () => T): T | null => { +const inDev = (callback: () => T): T | null => { if (process.env.NODE_ENV === "development") { return callback(); } return null; }; -export default inDebug; +export default inDev; diff --git a/tsconfig.json b/tsconfig.json index 14d2753..d710eeb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -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