Update template. This is good start code
@ -10,7 +10,7 @@ module.exports = {
|
|||||||
parserOptions: { ecmaVersion: "latest", sourceType: "module" },
|
parserOptions: { ecmaVersion: "latest", sourceType: "module" },
|
||||||
plugins: ["react-refresh", "prettier"],
|
plugins: ["react-refresh", "prettier"],
|
||||||
rules: {
|
rules: {
|
||||||
"react-refresh/only-export-components": "warn",
|
|
||||||
"prettier/prettier": "error",
|
"prettier/prettier": "error",
|
||||||
|
"react-refresh/only-export-components": "warn",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
5
docs/inspiration.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# Inspiration
|
||||||
|
|
||||||
|
* [Tasks shadcn](https://ui.shadcn.com/examples/tasks)
|
||||||
|
|
||||||
|

|
BIN
docs/inspiration1.png
Normal file
After Width: | Height: | Size: 121 KiB |
1587
docs/using_react_context_button_container.excalidraw
Normal file
34
index.html
@ -1,34 +1,20 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en" data-theme="light">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
||||||
|
<meta
|
||||||
<title>Fallback title</title>
|
name="description"
|
||||||
|
content="Detailed description of your app for better SEO."
|
||||||
<!-- SEO Meta Tags -->
|
/>
|
||||||
<meta name="description" content="Brief description of the page" />
|
<meta name="keywords" content="Specific, Keywords, Related, To, Your, App" />
|
||||||
<meta name="keywords" content="keyword1, keyword2, keyword3" />
|
<meta name="author" content="Igor Barcik - Biggy1606" />
|
||||||
<meta name="author" content="Author Name" />
|
<link rel="icon" href="/cmms_servimain_logo.png" type="image/x-icon" />
|
||||||
|
<title>Awesome App</title>
|
||||||
<!-- Social Media Meta Tags (Open Graph for Facebook, Twitter Card, etc.) -->
|
|
||||||
<meta property="og:title" content="Title for Social Media" />
|
|
||||||
<meta property="og:description" content="Description for Social Media" />
|
|
||||||
<meta property="og:image" content="URL to image for social media" />
|
|
||||||
<meta property="og:url" content="URL of the page" />
|
|
||||||
<meta name="twitter:card" content="summary_large_image" />
|
|
||||||
|
|
||||||
<!-- Favicon -->
|
|
||||||
<link rel="icon" href="/cmms_servimain_logo.png" />
|
|
||||||
|
|
||||||
<!-- Additional Tags (like canonical, robots, etc.) -->
|
|
||||||
<link rel="canonical" href="https://www.example.com/page-url" />
|
|
||||||
<meta name="robots" content="index, follow" />
|
|
||||||
<link rel="manifest" href="/manifest.json">
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
<script type="module" async src="/src/main.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
31
package.json
@ -1,31 +1,28 @@
|
|||||||
{
|
{
|
||||||
"name": "app",
|
|
||||||
"private": true,
|
|
||||||
"version": "1.0.0",
|
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
|
||||||
"dev": "vite serve",
|
|
||||||
"build": "tsc && vite build",
|
|
||||||
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
|
||||||
"preview": "vite preview --host"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@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",
|
||||||
|
"@radix-ui/react-popover": "^1.0.7",
|
||||||
|
"@radix-ui/react-tooltip": "^1.0.7",
|
||||||
"@tanstack/react-table": "^8.10.7",
|
"@tanstack/react-table": "^8.10.7",
|
||||||
"axios": "^1.6.1",
|
"axios": "^1.6.1",
|
||||||
|
"class-variance-authority": "^0.7.0",
|
||||||
|
"clsx": "^2.1.0",
|
||||||
"daisyui": "latest",
|
"daisyui": "latest",
|
||||||
"echarts": "^5.4.3",
|
"echarts": "^5.4.3",
|
||||||
"i18next": "^23.7.18",
|
"i18next": "^23.7.18",
|
||||||
"i18next-browser-languagedetector": "^7.2.0",
|
"i18next-browser-languagedetector": "^7.2.0",
|
||||||
|
"lucide-react": "^0.320.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-i18next": "^14.0.1",
|
||||||
"react-icons": "^5.0.1",
|
"react-icons": "^5.0.1",
|
||||||
|
"react-router-dom": "^6.21.3",
|
||||||
|
"tailwind-merge": "^2.2.1",
|
||||||
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"uuid": "^9.0.1",
|
"uuid": "^9.0.1",
|
||||||
"wouter": "next",
|
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.22.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -54,5 +51,15 @@
|
|||||||
"ts-node": "latest",
|
"ts-node": "latest",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.2.2",
|
||||||
"vite": "latest"
|
"vite": "latest"
|
||||||
}
|
},
|
||||||
|
"name": "app",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc && vite build",
|
||||||
|
"dev": "vite serve",
|
||||||
|
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||||
|
"preview": "vite preview --host"
|
||||||
|
},
|
||||||
|
"type": "module",
|
||||||
|
"version": "1.0.0"
|
||||||
}
|
}
|
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 13 MiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 344 KiB |
Before Width: | Height: | Size: 45 KiB |
@ -18,4 +18,3 @@
|
|||||||
"theme_color": "#000000",
|
"theme_color": "#000000",
|
||||||
"background_color": "#ffffff"
|
"background_color": "#ffffff"
|
||||||
}
|
}
|
||||||
|
|
40
src/App.tsx
@ -1,23 +1,33 @@
|
|||||||
import { IconContext } from "react-icons/lib";
|
|
||||||
import DevControlPanel from "./components/DevControlPanel";
|
|
||||||
import FloatingMenu from "./components/FloatingMenu";
|
|
||||||
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] 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: - [x] Rewrite DevControlPanel to use custom routes object
|
// TODO: - [x] Rewrite DevControlPanel to use custom routes object
|
||||||
|
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Outlet, useLocation, useNavigate } from "react-router-dom";
|
||||||
|
import useLocalStorage from "./hooks/useLocalStorage";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
// Hook to manage language state in local storage
|
||||||
<div>
|
const [language] = useLocalStorage("language", "eng");
|
||||||
<IconContext.Provider value={{ className: "size-5" }}>
|
const [isLoggedIn] = useLocalStorage("isLoggedIn", false);
|
||||||
<FloatingMenu menuStructure={floatingMenuStructure} />
|
const { i18n } = useTranslation();
|
||||||
</IconContext.Provider>
|
const navigate = useNavigate();
|
||||||
<SwitchRouteGenerator routesTree={routesTree} />
|
const location = useLocation();
|
||||||
<DevControlPanel routesTree={routesTree} />
|
|
||||||
</div>
|
// Effect to change language based on state
|
||||||
);
|
useEffect(() => {
|
||||||
|
i18n.changeLanguage(language);
|
||||||
|
}, [language]);
|
||||||
|
// Redirect to login page if not logged in
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isLoggedIn && location.pathname !== "/login") {
|
||||||
|
navigate("/login");
|
||||||
|
}
|
||||||
|
}, [isLoggedIn]);
|
||||||
|
|
||||||
|
// Conditional rendering based on base path
|
||||||
|
return <Outlet />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import axios from "axios";
|
import axios, { AxiosError } from "axios";
|
||||||
|
|
||||||
// TODO: Add calls for notifications on varius states of the request
|
// TODO: Add calls for notifications on varius states of the request
|
||||||
|
|
||||||
@ -34,7 +34,7 @@ export const setupAxiosInterceptors = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAxiosError = (error: any, callbackFunction = null) => {
|
const handleAxiosError = (error: AxiosError, callbackFunction = null) => {
|
||||||
if (error.message == "canceled") {
|
if (error.message == "canceled") {
|
||||||
callbackFunction && console.debug(callbackFunction);
|
callbackFunction && console.debug(callbackFunction);
|
||||||
console.debug("Request Aborted");
|
console.debug("Request Aborted");
|
||||||
@ -64,7 +64,7 @@ export const axiosGet = async (url: string) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const axiosPost = async (url: string, data: any) => {
|
export const axiosPost = async (url: string, data: unknown) => {
|
||||||
return axios
|
return axios
|
||||||
.post(url, data)
|
.post(url, data)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
@ -75,7 +75,7 @@ export const axiosPost = async (url: string, data: any) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const axiosPut = async (url: string, data: any) => {
|
export const axiosPut = async (url: string, data: unknown) => {
|
||||||
return axios
|
return axios
|
||||||
.put(url, data)
|
.put(url, data)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
|
@ -1,106 +0,0 @@
|
|||||||
import { useLocation } from "wouter";
|
|
||||||
import { floatingLanguageMenuStructure, main } from "../configure";
|
|
||||||
import { DevControlPanelProps } from "../types/devControlPanelTypes";
|
|
||||||
import { RoutingTree } from "../types/routesTypes";
|
|
||||||
import inDev from "../utils/inDev";
|
|
||||||
import ThemeButton from "./ThemeButton";
|
|
||||||
import { clearMultiplePathSlashes } from "../utils/StringTransformationUtils";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import FloatingMenu from "./FloatingMenu";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Development Control Panel Component
|
|
||||||
*
|
|
||||||
* This component renders a development control panel for navigation and debugging.
|
|
||||||
* It includes a dynamic list of routes and a theme toggle button.
|
|
||||||
*
|
|
||||||
* @param {DevControlPanelProps} props - Properties passed to the DevControlPanel component.
|
|
||||||
* @returns {JSX.Element} A React component that renders the development control panel.
|
|
||||||
*/
|
|
||||||
const DevControlPanel = ({ routesTree }: DevControlPanelProps) => {
|
|
||||||
const [, setLocation] = useLocation();
|
|
||||||
const { t, i18n } = useTranslation();
|
|
||||||
|
|
||||||
// Function to update the current location, with debug logging
|
|
||||||
const _setLocation = (targetLocation: string) => {
|
|
||||||
inDev(() => console.log("DevControlPanel_setLocation", targetLocation));
|
|
||||||
setLocation(targetLocation);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Function to generate a list of buttons for navigation based on the routing tree
|
|
||||||
const routesCrawler = (
|
|
||||||
routesTree: RoutingTree,
|
|
||||||
parentPath?: string,
|
|
||||||
): JSX.Element[] => {
|
|
||||||
return routesTree.map((route): JSX.Element => {
|
|
||||||
// Constructing path for the route button
|
|
||||||
const _path = clearMultiplePathSlashes(
|
|
||||||
parentPath ? "/" + parentPath + "/" + route.path : "/" + route.path,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Debug logging for route information
|
|
||||||
inDev(() =>
|
|
||||||
console.log(
|
|
||||||
"%croutesCrawler_routes %s %s",
|
|
||||||
"color: lightblue",
|
|
||||||
route.name,
|
|
||||||
_path,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Generating nested routes or single route buttons
|
|
||||||
if (route.nest) {
|
|
||||||
return (
|
|
||||||
<div className="indicator">
|
|
||||||
<span className="indicator-item indicator-center badge badge-primary">
|
|
||||||
{route.name}
|
|
||||||
</span>
|
|
||||||
<div key={_path} className="join">
|
|
||||||
{routesCrawler(route.nest, _path)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
key={_path}
|
|
||||||
className="btn btn-neutral join-item"
|
|
||||||
onClick={() => _setLocation(_path)}
|
|
||||||
>
|
|
||||||
{route.name}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Render the development control panel with navigation buttons and theme toggle
|
|
||||||
return (
|
|
||||||
<div className="border border-base-content flex gap-4 p-4 absolute w-full bottom-0 bg-base-300 z-50">
|
|
||||||
<div className="px-5 mt-6">
|
|
||||||
{/* Displaying the program name and version */}
|
|
||||||
<div className="text-center">
|
|
||||||
{main.program_name} v{main.program_version}
|
|
||||||
<p>Base path is: {main.base_path}</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
{/* Embedding the ThemeButton component for theme toggling */}
|
|
||||||
<ThemeButton />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/* Rendering the dynamically generated route navigation buttons */}
|
|
||||||
<div className="space-y-2 space-x-2 mb-6">{routesCrawler(routesTree)}</div>
|
|
||||||
<div className="px-5 mt-6">
|
|
||||||
<p>{t("welcome")}</p>
|
|
||||||
<p>Lang: {i18n.language}</p>
|
|
||||||
</div>
|
|
||||||
<FloatingMenu
|
|
||||||
menuStructure={floatingLanguageMenuStructure}
|
|
||||||
className="flex gap-2 flex-col"
|
|
||||||
tooltipClassName="tooltip tooltip-right"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DevControlPanel;
|
|
@ -1,73 +0,0 @@
|
|||||||
import { Path, useLocation } from "wouter";
|
|
||||||
import { navigate } from "wouter/use-browser-location";
|
|
||||||
import { FloatingMenuProps } from "../types/floatingMenuTypes";
|
|
||||||
import { capitalizeFirstLetter } from "../utils/StringTransformationUtils";
|
|
||||||
|
|
||||||
// -----VARIABLES-----
|
|
||||||
// Exported variables for shared location state management
|
|
||||||
export let location: Path;
|
|
||||||
export let setLocation: typeof navigate;
|
|
||||||
|
|
||||||
// -----COMPONENT-----
|
|
||||||
/**
|
|
||||||
* FloatingMenu Component
|
|
||||||
*
|
|
||||||
* This component renders a customizable floating menu with interactive elements.
|
|
||||||
* It uses the 'wouter' library for location management and provides a dynamic way
|
|
||||||
* to render menu items based on the 'menuStructure' prop passed to it.
|
|
||||||
*
|
|
||||||
* @param {FloatingMenuProps} props - The properties passed to the FloatingMenu component.
|
|
||||||
* @returns {JSX.Element} A React component that renders a floating menu.
|
|
||||||
*/
|
|
||||||
const FloatingMenu = (props: FloatingMenuProps) => {
|
|
||||||
// Setting up location hooks for navigation
|
|
||||||
[location, setLocation] = useLocation();
|
|
||||||
|
|
||||||
// Function to generate interactive buttons based on provided menu structure
|
|
||||||
const generateButtons = () => {
|
|
||||||
return props.menuStructure.map((item, index) => {
|
|
||||||
// Checking if the menu item includes label, icon, and action for button generation
|
|
||||||
if ("label" in item && "icon" in item && "action" in item) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={`floating_menu_element_container_${index}`}
|
|
||||||
className={props.tooltipClassName || `tooltip tooltip-left`}
|
|
||||||
data-tip={capitalizeFirstLetter(item.label)}
|
|
||||||
>
|
|
||||||
<li
|
|
||||||
key={`floating_menu_element_${index}`}
|
|
||||||
className="btn btn-circle btn-outline"
|
|
||||||
onClick={item.action}
|
|
||||||
>
|
|
||||||
{item.icon}
|
|
||||||
</li>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Rendering custom JSX elements when provided
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={`floating_menu_element_container_${index}`}
|
|
||||||
className={props.tooltipClassName || `tooltip tooltip-left`}
|
|
||||||
data-tip={capitalizeFirstLetter(item.label)}
|
|
||||||
>
|
|
||||||
{item.element}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Render the floating menu with dynamically generated buttons
|
|
||||||
return (
|
|
||||||
<ul
|
|
||||||
className={
|
|
||||||
props.className || `flex gap-2 absolute right-4 top-4 flex-col` + " z-[999]"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{generateButtons()}
|
|
||||||
</ul>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default FloatingMenu;
|
|
@ -1,33 +0,0 @@
|
|||||||
import useTheme from "../hooks/useTheme";
|
|
||||||
import inDev from "../utils/inDev";
|
|
||||||
|
|
||||||
const ThemeButton = () => {
|
|
||||||
const [isDark, toggleTheme] = useTheme();
|
|
||||||
return (
|
|
||||||
<label className="swap swap-rotate btn btn-outline btn-circle">
|
|
||||||
<input
|
|
||||||
checked={isDark}
|
|
||||||
className="hidden"
|
|
||||||
onChange={() => inDev(() => console.log("Theme changed"))}
|
|
||||||
onClick={toggleTheme}
|
|
||||||
type="checkbox"
|
|
||||||
/>
|
|
||||||
<svg
|
|
||||||
className="swap-on fill-current w-6 h-6"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path d="M5.64,17l-.71.71a1,1,0,0,0,0,1.41,1,1,0,0,0,1.41,0l.71-.71A1,1,0,0,0,5.64,17ZM5,12a1,1,0,0,0-1-1H3a1,1,0,0,0,0,2H4A1,1,0,0,0,5,12Zm7-7a1,1,0,0,0,1-1V3a1,1,0,0,0-2,0V4A1,1,0,0,0,12,5ZM5.64,7.05a1,1,0,0,0,.7.29,1,1,0,0,0,.71-.29,1,1,0,0,0,0-1.41l-.71-.71A1,1,0,0,0,4.93,6.34Zm12,.29a1,1,0,0,0,.7-.29l.71-.71a1,1,0,1,0-1.41-1.41L17,5.64a1,1,0,0,0,0,1.41A1,1,0,0,0,17.66,7.34ZM21,11H20a1,1,0,0,0,0,2h1a1,1,0,0,0,0-2Zm-9,8a1,1,0,0,0-1,1v1a1,1,0,0,0,2,0V20A1,1,0,0,0,12,19ZM18.36,17A1,1,0,0,0,17,18.36l.71.71a1,1,0,0,0,1.41,0,1,1,0,0,0,0-1.41ZM12,6.5A5.5,5.5,0,1,0,17.5,12,5.51,5.51,0,0,0,12,6.5Zm0,9A3.5,3.5,0,1,1,15.5,12,3.5,3.5,0,0,1,12,15.5Z" />
|
|
||||||
</svg>
|
|
||||||
<svg
|
|
||||||
className="swap-off fill-current w-6 h-6"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path d="M21.64,13a1,1,0,0,0-1.05-.14,8.05,8.05,0,0,1-3.37.73A8.15,8.15,0,0,1,9.08,5.49a8.59,8.59,0,0,1,.25-2A1,1,0,0,0,8,2.36,10.14,10.14,0,1,0,22,14.05,1,1,0,0,0,21.64,13Zm-9.5,6.69A8.14,8.14,0,0,1,7.08,5.22v.27A10.15,10.15,0,0,0,17.22,15.63a9.79,9.79,0,0,0,2.1-.22A8.11,8.11,0,0,1,12.14,19.73Z" />
|
|
||||||
</svg>
|
|
||||||
</label>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ThemeButton;
|
|
42
src/components/atoms/MenuContainer.tsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import {
|
||||||
|
TooltipPositionContext,
|
||||||
|
TooltipPositionContextType,
|
||||||
|
} from "@root/src/contexts/TooltipPositionContext";
|
||||||
|
import React, { useCallback } from "react";
|
||||||
|
// Local Interfaces
|
||||||
|
interface OverlayButtonContainerProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
position?: "top-left" | "top-right" | "bottom-left" | "bottom-right";
|
||||||
|
horizontal?: boolean;
|
||||||
|
tooltipPosition?: TooltipPositionContextType;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MenuContainer: React.FC<OverlayButtonContainerProps> = ({
|
||||||
|
children,
|
||||||
|
position = "top-right",
|
||||||
|
horizontal,
|
||||||
|
tooltipPosition = undefined,
|
||||||
|
}) => {
|
||||||
|
const chooseContainerPosition = useCallback(() => {
|
||||||
|
const commonClasses = `z-50 menu bg-base-300 rounded-box gap-4 absolute ${horizontal ? "menu-horizontal" : ""} `;
|
||||||
|
if (position === "top-left") return commonClasses + "top-4 left-4";
|
||||||
|
if (position === "top-right") return commonClasses + "top-4 right-4";
|
||||||
|
if (position === "bottom-left") return commonClasses + "bottom-4 left-4";
|
||||||
|
if (position === "bottom-right") return commonClasses + "bottom-4 right-4";
|
||||||
|
}, [horizontal, position]);
|
||||||
|
|
||||||
|
const overlayButtonContainer = () => {
|
||||||
|
const divProps = {
|
||||||
|
className: chooseContainerPosition(),
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<TooltipPositionContext.Provider value={tooltipPosition}>
|
||||||
|
<div {...divProps}>{children}</div>
|
||||||
|
</TooltipPositionContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return overlayButtonContainer();
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MenuContainer;
|
59
src/components/atoms/RoundButtonBase.tsx
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { TooltipPositionContext } from "@root/src/contexts/TooltipPositionContext";
|
||||||
|
import {
|
||||||
|
ButtonHTMLAttributes,
|
||||||
|
ReactNode,
|
||||||
|
useCallback,
|
||||||
|
useContext,
|
||||||
|
useMemo,
|
||||||
|
} from "react";
|
||||||
|
|
||||||
|
interface RoundButtonBaseProps {
|
||||||
|
children: ReactNode;
|
||||||
|
buttonProps?: ButtonHTMLAttributes<HTMLButtonElement>;
|
||||||
|
tooltipText?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RoundButtonBase: React.FC<RoundButtonBaseProps> = ({
|
||||||
|
children,
|
||||||
|
buttonProps,
|
||||||
|
tooltipText,
|
||||||
|
}) => {
|
||||||
|
const tooltipPosition = useContext(TooltipPositionContext);
|
||||||
|
const chooseTooltipPosition = useCallback(() => {
|
||||||
|
if (tooltipPosition === "top") return "tooltip-top";
|
||||||
|
if (tooltipPosition === "bottom") return "tooltip-bottom";
|
||||||
|
if (tooltipPosition === "left") return "tooltip-left";
|
||||||
|
if (tooltipPosition === "right") return "tooltip-right";
|
||||||
|
}, [tooltipPosition]);
|
||||||
|
|
||||||
|
const roundButtonBase = useMemo(() => {
|
||||||
|
const tooltipDivProps = {
|
||||||
|
className: "tooltip" + " " + chooseTooltipPosition(),
|
||||||
|
"data-tip": tooltipText,
|
||||||
|
};
|
||||||
|
if (!tooltipText || !tooltipPosition)
|
||||||
|
return (
|
||||||
|
<button className={"btn btn-outline btn-circle"} {...buttonProps}>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
else {
|
||||||
|
return (
|
||||||
|
<div {...tooltipDivProps}>
|
||||||
|
<button className={"btn btn-outline btn-circle"} {...buttonProps}>
|
||||||
|
{children}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
buttonProps,
|
||||||
|
children,
|
||||||
|
chooseTooltipPosition,
|
||||||
|
tooltipPosition,
|
||||||
|
tooltipText,
|
||||||
|
]);
|
||||||
|
return roundButtonBase;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RoundButtonBase;
|
30
src/components/molecules/IconBase.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { ReactNode } from "react";
|
||||||
|
import { IconContext } from "react-icons/lib";
|
||||||
|
|
||||||
|
interface IconBaseProps {
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* IconBase Component
|
||||||
|
*
|
||||||
|
* This component is a wrapper for the react-icons library.
|
||||||
|
* It provides a common interface for all icons and applies a consistent style to them.
|
||||||
|
* The style is defined by the IconContext.Provider, which is set to give all icons a height and width of 6.
|
||||||
|
*
|
||||||
|
* @component
|
||||||
|
* @param {ReactNode} children - The icons that are passed as children to the IconBase component.
|
||||||
|
* @returns {JSX.Element} A React component that renders the icons within the context of the IconContext.Provider.
|
||||||
|
*/
|
||||||
|
const IconBase: React.FC<IconBaseProps> = ({ children }) => {
|
||||||
|
return (
|
||||||
|
<IconContext.Provider
|
||||||
|
value={{
|
||||||
|
className: "h-6 w-6",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</IconContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default IconBase;
|
26
src/components/organisms/HomeButton.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { FiArrowLeft } from "react-icons/fi";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import RoundButtonBase from "../atoms/RoundButtonBase";
|
||||||
|
import IconBase from "../molecules/IconBase";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
const HomeButton = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const navigateToHome = () => {
|
||||||
|
navigate("/");
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RoundButtonBase
|
||||||
|
buttonProps={{ onClick: navigateToHome }}
|
||||||
|
tooltipText={t("button.navigation.home")}
|
||||||
|
>
|
||||||
|
<IconBase>
|
||||||
|
<FiArrowLeft />
|
||||||
|
</IconBase>
|
||||||
|
</RoundButtonBase>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default HomeButton;
|
40
src/components/organisms/LanguageButton.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { languageMenu } from "@root/src/configure";
|
||||||
|
import useLocalStorage from "@root/src/hooks/useLocalStorage";
|
||||||
|
import { changeLanguage } from "i18next";
|
||||||
|
import { FiFlag } from "react-icons/fi";
|
||||||
|
import RoundButtonBase from "../atoms/RoundButtonBase";
|
||||||
|
import IconBase from "../molecules/IconBase";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
const LanguageButton = () => {
|
||||||
|
const [, setLanguage] = useLocalStorage("language", "eng");
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<div className="dropdown dropdown-left">
|
||||||
|
<RoundButtonBase tooltipText={t("button.action.changeLanguage")}>
|
||||||
|
<IconBase>
|
||||||
|
<FiFlag />
|
||||||
|
</IconBase>
|
||||||
|
</RoundButtonBase>
|
||||||
|
<ul className="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-52">
|
||||||
|
{languageMenu
|
||||||
|
.sort((a, b) => a.label.localeCompare(b.label))
|
||||||
|
.map((language) => (
|
||||||
|
<li
|
||||||
|
key={language.label}
|
||||||
|
onClick={() => {
|
||||||
|
changeLanguage(language.lang);
|
||||||
|
setLanguage(language.lang);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button>
|
||||||
|
{language.icon} {language.label}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LanguageButton;
|
29
src/components/organisms/LogoutButton.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { FiLogOut } from "react-icons/fi";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import useLocalStorage from "../../hooks/useLocalStorage";
|
||||||
|
import RoundButtonBase from "../atoms/RoundButtonBase";
|
||||||
|
import IconBase from "../molecules/IconBase";
|
||||||
|
|
||||||
|
const LogoutButton = () => {
|
||||||
|
const [, setIsLoggedIn] = useLocalStorage("isLoggedIn", false);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<RoundButtonBase
|
||||||
|
buttonProps={{
|
||||||
|
onClick: () => {
|
||||||
|
setIsLoggedIn(false);
|
||||||
|
navigate("/login");
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
tooltipText={t("button.action.logout")}
|
||||||
|
>
|
||||||
|
<IconBase>
|
||||||
|
<FiLogOut />
|
||||||
|
</IconBase>
|
||||||
|
</RoundButtonBase>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LogoutButton;
|
31
src/components/organisms/ThemeButton.tsx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { FiMoon, FiSun } from "react-icons/fi";
|
||||||
|
import useTheme from "../../hooks/useTheme";
|
||||||
|
import RoundButtonBase from "../atoms/RoundButtonBase";
|
||||||
|
import IconBase from "../molecules/IconBase";
|
||||||
|
|
||||||
|
const ThemeButton = () => {
|
||||||
|
const [isDark, toggleTheme] = useTheme();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<RoundButtonBase
|
||||||
|
buttonProps={{
|
||||||
|
className: "swap swap-rotate btn btn-outline btn-circle",
|
||||||
|
onClick: toggleTheme,
|
||||||
|
}}
|
||||||
|
tooltipText={t("button.action.theme")}
|
||||||
|
>
|
||||||
|
<input checked={isDark} className="hidden" type="checkbox" readOnly />
|
||||||
|
<IconBase>
|
||||||
|
<div className="swap-on">
|
||||||
|
<FiSun />
|
||||||
|
</div>
|
||||||
|
<div className="swap-off">
|
||||||
|
<FiMoon />
|
||||||
|
</div>
|
||||||
|
</IconBase>
|
||||||
|
</RoundButtonBase>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ThemeButton;
|
@ -1,4 +1,8 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import MenuContainer from "../atoms/MenuContainer";
|
||||||
|
import LogoutButton from "../organisms/LogoutButton";
|
||||||
|
import ThemeButton from "../organisms/ThemeButton";
|
||||||
|
import LanguageButton from "../organisms/LanguageButton";
|
||||||
|
|
||||||
export interface HomePageTemplateProps {
|
export interface HomePageTemplateProps {
|
||||||
buttonLabel: string;
|
buttonLabel: string;
|
||||||
@ -10,6 +14,11 @@ const HomePageTemplate = (props: HomePageTemplateProps) => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<div className="hero">
|
<div className="hero">
|
||||||
|
<MenuContainer position="top-right" tooltipPosition="left">
|
||||||
|
<ThemeButton />
|
||||||
|
<LogoutButton />
|
||||||
|
<LanguageButton />
|
||||||
|
</MenuContainer>
|
||||||
<div className="hero-content prose flex-col">
|
<div className="hero-content prose flex-col">
|
||||||
<h1>HomePage</h1>
|
<h1>HomePage</h1>
|
||||||
<div className="card card-bordered shadow-2xl bg-base-100 text-base-content items-center">
|
<div className="card card-bordered shadow-2xl bg-base-100 text-base-content items-center">
|
68
src/components/templates/LoginPageTemplate.tsx
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import MenuContainer from "@root/src/components/atoms/MenuContainer";
|
||||||
|
import ThemeButton from "@root/src/components/organisms/ThemeButton";
|
||||||
|
import { main } from "@root/src/configure";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import LanguageButton from "../organisms/LanguageButton";
|
||||||
|
interface LoginPageTemplateProps {
|
||||||
|
handleLogin: () => void;
|
||||||
|
getStartedOnClick: () => void;
|
||||||
|
}
|
||||||
|
const LoginPageTemplate = ({
|
||||||
|
handleLogin,
|
||||||
|
getStartedOnClick,
|
||||||
|
}: LoginPageTemplateProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<div className="hero min-h-screen bg-base-200">
|
||||||
|
<MenuContainer position="top-right" tooltipPosition="left">
|
||||||
|
<ThemeButton />
|
||||||
|
<LanguageButton />
|
||||||
|
</MenuContainer>
|
||||||
|
<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" onClick={getStartedOnClick}>
|
||||||
|
{t("button.navigation.more")}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="card flex-shrink-0 w-full max-w-sm shadow-2xl bg-base-100">
|
||||||
|
<div className="card-body">
|
||||||
|
<form>
|
||||||
|
<div className="form-control">
|
||||||
|
<label className="label">
|
||||||
|
<span className="label-text">{t("input.username")}</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder={t("input.username")}
|
||||||
|
className="input input-bordered"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="form-control">
|
||||||
|
<label className="label">
|
||||||
|
<span className="label-text">{t("input.password")}</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
placeholder={t("input.password")}
|
||||||
|
className="input input-bordered"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="form-control mt-6">
|
||||||
|
<input
|
||||||
|
type="submit"
|
||||||
|
value={t("button.action.login")}
|
||||||
|
className="btn btn-primary"
|
||||||
|
onClick={handleLogin}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LoginPageTemplate;
|
@ -1,10 +1,3 @@
|
|||||||
import { FiHome, FiLogIn } from "react-icons/fi";
|
|
||||||
import { setLocation } from "./components/FloatingMenu";
|
|
||||||
import ThemeButton from "./components/ThemeButton";
|
|
||||||
import { HOME, LANGUAGE, LOGIN, THEME } from "./consts";
|
|
||||||
import { MenuStructure } from "./types/floatingMenuTypes";
|
|
||||||
import { setLanguage } from "./main";
|
|
||||||
import { HiMiniLanguage } from "react-icons/hi2";
|
|
||||||
/* INSTRUCTIONS:
|
/* INSTRUCTIONS:
|
||||||
* Here you can configure:
|
* Here you can configure:
|
||||||
* - program version
|
* - program version
|
||||||
@ -19,74 +12,23 @@ export const main = {
|
|||||||
api_url: viteEnv.VITE_API_URL || `setup in .env.${viteEnv.MODE}`,
|
api_url: viteEnv.VITE_API_URL || `setup in .env.${viteEnv.MODE}`,
|
||||||
base_path: viteEnv.VITE_BASE_PATH || "/",
|
base_path: viteEnv.VITE_BASE_PATH || "/",
|
||||||
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_description:
|
||||||
|
"Lorem ipsum dolor sit amet consectetur adipisicing elit. In eum ratione deserunt veritatis voluptatibus fugit harum? Commodi, eaque saepe facere maiores sit adipisci quis rerum doloribus quia excepturi in tenetur praesentium soluta asperiores nesciunt? Debitis nisi autem ipsa, laborum cumque minima officiis incidunt ullam quaerat similique officia beatae voluptatum maxime praesentium non laudantium rem asperiores amet at! Rem quis quidem ut aspernatur enim aperiam. Tenetur delectus nulla cupiditate corporis quisquam autem ab tempora. Vel voluptatum sit corrupti veniam tempore quos at vero hic alias necessitatibus quidem rerum deleniti enim adipisci nobis repellat, facere ullam modi error quaerat. A, tempora aliquam!",
|
||||||
program_name:
|
program_name:
|
||||||
`${viteEnv.VITE_APP_NAME}${viteEnv.MODE ? " [development]" : ""}` ||
|
`${viteEnv.VITE_APP_NAME}${viteEnv.MODE ? " [development]" : ""}` ||
|
||||||
"setup in .env",
|
"setup in .env",
|
||||||
program_version: "1.0.0",
|
program_version: "1.0.0",
|
||||||
};
|
};
|
||||||
// -------- FRONTEND CONFIGURATION --------
|
// -------- FRONTEND CONFIGURATION --------
|
||||||
// sizes in pixels
|
export const languageMenu = [
|
||||||
export const topbarSize = 64;
|
|
||||||
export const sidebarSize = 256;
|
|
||||||
export const floatingLanguageMenuStructure: MenuStructure[] = [
|
|
||||||
{
|
{
|
||||||
label: "English",
|
label: "English",
|
||||||
icon: <p>🇬🇧</p>,
|
icon: <p>🇬🇧</p>,
|
||||||
action: () => {
|
lang: "eng",
|
||||||
console.log("English");
|
|
||||||
setLanguage("eng");
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Polski",
|
label: "Polski",
|
||||||
icon: <p>🇵🇱</p>,
|
icon: <p>🇵🇱</p>,
|
||||||
action: () => {
|
lang: "pol",
|
||||||
console.log("Polski");
|
|
||||||
setLanguage("pol");
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
export const floatingMenuStructure: MenuStructure[] = [
|
|
||||||
{
|
|
||||||
label: HOME,
|
|
||||||
icon: <FiHome />,
|
|
||||||
action: () => setLocation("/"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: LOGIN,
|
|
||||||
icon: <FiLogIn />,
|
|
||||||
action: () => setLocation(`/${LOGIN}`),
|
|
||||||
},
|
|
||||||
{ label: THEME, element: <ThemeButton /> },
|
|
||||||
{
|
|
||||||
label: LANGUAGE,
|
|
||||||
element: (
|
|
||||||
<div className="dropdown dropdown-left">
|
|
||||||
<div tabIndex={0} role="button" className="btn btn-circle btn-outline">
|
|
||||||
<HiMiniLanguage />
|
|
||||||
</div>
|
|
||||||
<ul
|
|
||||||
tabIndex={0}
|
|
||||||
className="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-52 mr-1"
|
|
||||||
>
|
|
||||||
{floatingLanguageMenuStructure.map((element, index) => {
|
|
||||||
return (
|
|
||||||
<li key={element.label + index}>
|
|
||||||
<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>
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
10
src/contexts/TooltipPositionContext.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { createContext } from "react";
|
||||||
|
|
||||||
|
export type TooltipPositionContextType =
|
||||||
|
| "top"
|
||||||
|
| "bottom"
|
||||||
|
| "left"
|
||||||
|
| "right"
|
||||||
|
| undefined;
|
||||||
|
export const TooltipPositionContext =
|
||||||
|
createContext<TooltipPositionContextType>(undefined);
|
@ -1,6 +1,5 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import useLocalStorage from "./useLocalStorage";
|
import useLocalStorage from "./useLocalStorage";
|
||||||
import inDev from "../utils/inDev";
|
|
||||||
/**
|
/**
|
||||||
* Custom hook for managing theme state.
|
* Custom hook for managing theme state.
|
||||||
*
|
*
|
||||||
@ -20,14 +19,12 @@ const useTheme = (): [boolean, () => void] => {
|
|||||||
const isDark = theme === "dark";
|
const isDark = theme === "dark";
|
||||||
|
|
||||||
const toggleTheme = () => {
|
const toggleTheme = () => {
|
||||||
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(() => {
|
||||||
inDev(() => console.log("useEffect called"));
|
|
||||||
document.documentElement.dataset.theme = theme;
|
document.documentElement.dataset.theme = theme;
|
||||||
}, [theme]);
|
}, [theme]);
|
||||||
|
|
||||||
|
24
src/locales/eng/general.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
export const general = {
|
||||||
|
welcome: "Welcome!",
|
||||||
|
homePage: { click_me: "Click me!", counter: "Counter" },
|
||||||
|
Page404: {
|
||||||
|
notFoundTitle: "404 Not Found",
|
||||||
|
notFoundDescription: "The requested URL",
|
||||||
|
wasNotFound: "was not found on this server.",
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
navigation: { home: "Home", back: "Back", more: "More" },
|
||||||
|
action: {
|
||||||
|
accept: "Accept",
|
||||||
|
cancel: "Cancel",
|
||||||
|
theme: "Change theme",
|
||||||
|
login: "Login",
|
||||||
|
logout: "Logout",
|
||||||
|
changeLanguage: "Change language",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
username: "Username",
|
||||||
|
password: "Password",
|
||||||
|
},
|
||||||
|
};
|
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"welcome": "Welcome!",
|
|
||||||
"login_button": "Login",
|
|
||||||
"login_username": "Username",
|
|
||||||
"login_password": "Password",
|
|
||||||
"login_accept_terms": "I accept the terms and conditions",
|
|
||||||
"homePage": { "click_me": "Click me!", "counter": "Counter" }
|
|
||||||
}
|
|
@ -1,8 +1,8 @@
|
|||||||
import i18n from "i18next";
|
import i18n from "i18next";
|
||||||
import LanguageDetector from "i18next-browser-languagedetector";
|
import LanguageDetector from "i18next-browser-languagedetector";
|
||||||
import { initReactI18next } from "react-i18next";
|
import { initReactI18next } from "react-i18next";
|
||||||
import generalEng from "./eng/general.json";
|
import { general as generalEng } from "./eng/general";
|
||||||
import generalPol from "./pol/general.json";
|
import { general as generalPol } from "./pol/general";
|
||||||
|
|
||||||
i18n
|
i18n
|
||||||
.use(initReactI18next)
|
.use(initReactI18next)
|
||||||
|
28
src/locales/pol/general.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
export const general = {
|
||||||
|
welcome: "Witaj!",
|
||||||
|
homePage: { click_me: "Naciśnij tutaj!", counter: "Licznik" },
|
||||||
|
Page404: {
|
||||||
|
notFoundTitle: "404 Nie znaleziono",
|
||||||
|
notFoundDescription: "Rządany URL",
|
||||||
|
wasNotFound: "nie został znaleziony na tym serwerze.",
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
navigation: {
|
||||||
|
home: "Strona główna",
|
||||||
|
back: "Wstecz",
|
||||||
|
more: "Więcej",
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
accept: "Akceptuj",
|
||||||
|
cancel: "Anuluj",
|
||||||
|
theme: "Motyw",
|
||||||
|
login: "Zaloguj",
|
||||||
|
logout: "Wyloguj",
|
||||||
|
changeLanguage: "Zmień język",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
username: "Nazwa użytkownika",
|
||||||
|
password: "Hasło",
|
||||||
|
},
|
||||||
|
};
|
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"welcome": "Witaj!",
|
|
||||||
"login_button": "Zaloguj",
|
|
||||||
"login_username": "Nazwa użytkownika",
|
|
||||||
"login_password": "Hasło",
|
|
||||||
"login_accept_terms": "Akceptuję regulamin",
|
|
||||||
"homePage": { "click_me": "Naciśnij tutaj!", "counter": "Licznik" }
|
|
||||||
}
|
|
57
src/main.tsx
@ -1,13 +1,11 @@
|
|||||||
import { Global, css } from "@emotion/react";
|
import { Global, css } from "@emotion/react";
|
||||||
import React, { useEffect } from "react";
|
import React from "react";
|
||||||
import ReactDOM from "react-dom/client";
|
import ReactDOM from "react-dom/client";
|
||||||
import { useTranslation } from "react-i18next";
|
import { RouterProvider, createBrowserRouter } from "react-router-dom";
|
||||||
import { Router } from "wouter";
|
|
||||||
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 useLocalStorage from "./hooks/useLocalStorage";
|
|
||||||
import "./locales/localesConfig";
|
import "./locales/localesConfig";
|
||||||
|
import routes from "./routes/routes";
|
||||||
import inDev from "./utils/inDev";
|
import inDev from "./utils/inDev";
|
||||||
import "/style.css"; // Global tailwind styles
|
import "/style.css"; // Global tailwind styles
|
||||||
|
|
||||||
@ -20,44 +18,14 @@ setupAxiosInterceptors();
|
|||||||
* Logs environment variables if in development mode.
|
* Logs environment variables if in development mode.
|
||||||
*/
|
*/
|
||||||
inDev(() => console.log(viteEnv));
|
inDev(() => console.log(viteEnv));
|
||||||
|
// Routing
|
||||||
/**
|
const router = createBrowserRouter(routes, {
|
||||||
* Global variables to manage the application's language state.
|
basename: main.base_path,
|
||||||
*/
|
future: {
|
||||||
export let language: string, setLanguage: (value: string) => void;
|
// Normalize `useNavigation()`/`useFetcher()` `formMethod` to uppercase
|
||||||
|
v7_normalizeFormMethod: true,
|
||||||
/**
|
},
|
||||||
* _App component which is the main entry point of the application.
|
});
|
||||||
* Manages routing and global state like language.
|
|
||||||
*
|
|
||||||
* @returns The Router component wrapping the main App component.
|
|
||||||
*/
|
|
||||||
const _App = () => {
|
|
||||||
// Hook to manage language state in local storage
|
|
||||||
[language, setLanguage] = useLocalStorage("language", "eng");
|
|
||||||
const { i18n } = useTranslation();
|
|
||||||
|
|
||||||
// Effect to change language based on state
|
|
||||||
useEffect(() => {
|
|
||||||
i18n.changeLanguage(language);
|
|
||||||
}, [language]);
|
|
||||||
|
|
||||||
// Conditional rendering based on base path
|
|
||||||
if (main.base_path !== "/") {
|
|
||||||
return (
|
|
||||||
<Router base={main.base_path}>
|
|
||||||
<App />
|
|
||||||
</Router>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<Router>
|
|
||||||
<App />
|
|
||||||
</Router>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Mounting the application to the DOM
|
// Mounting the application to the DOM
|
||||||
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
@ -69,9 +37,10 @@ ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
background-color: oklch(var(--b2));
|
||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
/>
|
/>
|
||||||
<_App />
|
<RouterProvider router={router} />
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
);
|
);
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import HomePageTemplate from "../templates/HomePageTemplate";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Outlet } from "react-router-dom";
|
||||||
|
import HomePageTemplate from "../components/templates/HomePageTemplate";
|
||||||
|
|
||||||
const HomePage = () => {
|
const HomePage = () => {
|
||||||
const [counter, setCounter] = useState(0);
|
const [counter, setCounter] = useState(0);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<HomePageTemplate
|
<HomePageTemplate
|
||||||
buttonLabel={t("homePage.click_me", "Click me!!!")}
|
buttonLabel={t("homePage.click_me", "Click me!!!")}
|
||||||
buttonOnClick={() => {
|
buttonOnClick={() => {
|
||||||
@ -17,6 +19,8 @@ const HomePage = () => {
|
|||||||
}}
|
}}
|
||||||
value={counter}
|
value={counter}
|
||||||
/>
|
/>
|
||||||
|
<Outlet />
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,59 +1,22 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { main } from "../configure";
|
import LoginPageTemplate from "../components/templates/LoginPageTemplate";
|
||||||
|
import useLocalStorage from "../hooks/useLocalStorage";
|
||||||
|
|
||||||
const LoginPage = () => {
|
const LoginPage = () => {
|
||||||
const { t } = useTranslation();
|
const [, setIsLoggedIn] = useLocalStorage("isLoggedIn", false);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const handleLogin = () => {
|
||||||
|
setIsLoggedIn(true);
|
||||||
|
navigate("/");
|
||||||
|
};
|
||||||
|
const onClickGetStarted = () => {
|
||||||
|
navigate("/more");
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<div className="hero min-h-screen bg-base-200">
|
<LoginPageTemplate
|
||||||
<div className="flex-col justify-center hero-content lg:flex-row-reverse">
|
handleLogin={handleLogin}
|
||||||
<div className="text-center lg:text-left">
|
getStartedOnClick={onClickGetStarted}
|
||||||
<h1 className="mb-5 text-5xl font-bold">{main.program_name}</h1>
|
|
||||||
<p className="mb-5">{main.program_description}</p>
|
|
||||||
<button className="btn btn-outline">Get Started</button>
|
|
||||||
</div>
|
|
||||||
<div className="card flex-shrink-0 w-full max-w-sm shadow-2xl bg-base-100">
|
|
||||||
<div className="card-body">
|
|
||||||
<form>
|
|
||||||
<div className="form-control">
|
|
||||||
<label className="label">
|
|
||||||
<span className="label-text">{t("login_username")}</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder={t("login_username")}
|
|
||||||
className="input input-bordered"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
<div className="form-control">
|
|
||||||
<label className="label">
|
|
||||||
<span className="label-text">{t("login_password")}</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
placeholder={t("login_password")}
|
|
||||||
className="input input-bordered"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="form-control mt-6">
|
|
||||||
<label className="cursor-pointer label justify-start gap-4">
|
|
||||||
<input type="checkbox" className="checkbox checkbox-success" />
|
|
||||||
<span className="label-text justify-start">
|
|
||||||
{t("login_accept_terms")}
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div className="form-control mt-6">
|
|
||||||
<input
|
|
||||||
type="submit"
|
|
||||||
value={t("login_button")}
|
|
||||||
className="btn btn-primary"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
29
src/pages/NotFound_404.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
import ThemeButton from "../components/organisms/ThemeButton";
|
||||||
|
import HomeButton from "../components/organisms/HomeButton";
|
||||||
|
|
||||||
|
const NotFound_404 = () => {
|
||||||
|
const location = useLocation();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center h-screen bg-base-200">
|
||||||
|
<div className="absolute top-4 right-4">
|
||||||
|
<ThemeButton />
|
||||||
|
</div>
|
||||||
|
<div className="absolute top-4 left-4">
|
||||||
|
<HomeButton />
|
||||||
|
</div>
|
||||||
|
<div className="text-center">
|
||||||
|
<h1 className="text-5xl font-bold mb-4">{t("Page404.notFoundTitle")}</h1>
|
||||||
|
<p className="text-xl">
|
||||||
|
{t("Page404.notFoundDescription")}{" "}
|
||||||
|
<code className="text-info">{location.pathname}</code>{" "}
|
||||||
|
{t("Page404.wasNotFound")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NotFound_404;
|
@ -1,22 +0,0 @@
|
|||||||
import { Switch, Route } from "wouter";
|
|
||||||
import HomePage from "../pages/HomePage";
|
|
||||||
|
|
||||||
const ManuallyDefinedRoutesTest = () => {
|
|
||||||
return (
|
|
||||||
<Switch>
|
|
||||||
<Route path="/" component={HomePage}>
|
|
||||||
<Route path="/users" nest>
|
|
||||||
<Switch>
|
|
||||||
<Route component={() => <>User HP</>} />
|
|
||||||
<Route
|
|
||||||
path="/users/:username"
|
|
||||||
component={(username) => <>User: {username}</>}
|
|
||||||
/>
|
|
||||||
</Switch>
|
|
||||||
</Route>
|
|
||||||
</Route>
|
|
||||||
</Switch>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ManuallyDefinedRoutesTest;
|
|
@ -1,39 +0,0 @@
|
|||||||
import { Route, Switch } from "wouter";
|
|
||||||
import inDev from "../utils/inDev";
|
|
||||||
import { RoutingTree } from "../types/routesTypes";
|
|
||||||
import { SwitchRouteGeneratorProps } from "../types/switchRouteGeneratorTypes";
|
|
||||||
import { clearMultiplePathSlashes } from "../utils/StringTransformationUtils";
|
|
||||||
|
|
||||||
const routesCrawler = (
|
|
||||||
routesTree: RoutingTree,
|
|
||||||
parentPath?: string,
|
|
||||||
): JSX.Element[] => {
|
|
||||||
return routesTree.map((route): JSX.Element => {
|
|
||||||
// -----
|
|
||||||
if (route.path) {
|
|
||||||
const _path = clearMultiplePathSlashes(
|
|
||||||
parentPath && parentPath !== "/" ? parentPath + route.path : route.path,
|
|
||||||
);
|
|
||||||
// Debug log shows generated routes
|
|
||||||
inDev(() => console.log("routesCrawler_routes", route.name, _path));
|
|
||||||
// -----
|
|
||||||
if (route.nest) {
|
|
||||||
return (
|
|
||||||
<Route key={_path} path={_path}>
|
|
||||||
<Switch key={`${route.name}_switch_route`}>
|
|
||||||
{routesCrawler(route.nest, _path)}
|
|
||||||
</Switch>
|
|
||||||
</Route>
|
|
||||||
);
|
|
||||||
} else return <Route key={_path} path={_path} component={route.component} />;
|
|
||||||
} else return <Route key={route.name} component={route.component} />;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This component is used to generate Switch with Routes for given routes object
|
|
||||||
*/
|
|
||||||
const SwitchRouteGenerator = ({ routesTree }: SwitchRouteGeneratorProps) => {
|
|
||||||
return <Switch key={"main_switch_route"}>{routesCrawler(routesTree)}</Switch>;
|
|
||||||
};
|
|
||||||
export default SwitchRouteGenerator;
|
|
@ -1,56 +1,29 @@
|
|||||||
import { IoMdLogIn, IoMdOptions } from "react-icons/io";
|
|
||||||
import { RiHome3Fill } from "react-icons/ri";
|
|
||||||
import { HOME, LOGIN, PAGE_NOT_FOUND, SETTINGS, USERS } from "../consts";
|
|
||||||
import HomePage from "../pages/HomePage";
|
import HomePage from "../pages/HomePage";
|
||||||
import LoginPage from "../pages/LoginPage";
|
import LoginPage from "../pages/LoginPage";
|
||||||
import { RoutingTree } from "../types/routesTypes";
|
import { RouteObject } from "react-router-dom";
|
||||||
import { capitalizeFirstLetter } from "../utils/StringTransformationUtils";
|
import NotFound_404 from "../pages/NotFound_404";
|
||||||
|
import App from "../App";
|
||||||
|
|
||||||
// ----- ROUTES CONFIGURATION -----
|
// ----- ROUTES CONFIGURATION -----
|
||||||
// Configuration of the application's route structure
|
// Configuration of the application's route structure
|
||||||
// This includes both parent routes and nested routes within them
|
const routes: RouteObject[] = [
|
||||||
export const routesTree: RoutingTree = [
|
|
||||||
{
|
{
|
||||||
name: capitalizeFirstLetter(HOME),
|
|
||||||
path: "/",
|
path: "/",
|
||||||
component: HomePage,
|
element: <App />,
|
||||||
icon: RiHome3Fill,
|
children: [
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: capitalizeFirstLetter(USERS),
|
|
||||||
path: "users",
|
|
||||||
nest: [
|
|
||||||
{
|
|
||||||
name: "User",
|
|
||||||
path: "/",
|
path: "/",
|
||||||
component: () => <>User HP</>,
|
element: <HomePage />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "UserName",
|
path: "login",
|
||||||
path: ":username",
|
element: <LoginPage />,
|
||||||
component: (username) => <>User: {username}</>,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "UserNotFound",
|
|
||||||
component: () => <>User not found</>,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: capitalizeFirstLetter(SETTINGS),
|
path: "*",
|
||||||
path: SETTINGS,
|
element: <NotFound_404 />,
|
||||||
component: () => <>Settings</>,
|
|
||||||
icon: IoMdOptions,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: capitalizeFirstLetter(LOGIN),
|
|
||||||
path: LOGIN,
|
|
||||||
component: LoginPage,
|
|
||||||
icon: IoMdLogIn,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: capitalizeFirstLetter(PAGE_NOT_FOUND),
|
|
||||||
component: () => <>💥404💥</>,
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
export default routesTree;
|
export default routes;
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
import { SwitchRouteGeneratorProps } from "./switchRouteGeneratorTypes";
|
|
||||||
|
|
||||||
export type DevControlPanelProps = SwitchRouteGeneratorProps;
|
|
@ -1,15 +0,0 @@
|
|||||||
interface FloatingMenuElement {
|
|
||||||
label: string;
|
|
||||||
icon: JSX.Element;
|
|
||||||
action: () => void;
|
|
||||||
}
|
|
||||||
interface FloatingMenuJSXElement {
|
|
||||||
label: string;
|
|
||||||
element: JSX.Element;
|
|
||||||
}
|
|
||||||
export interface FloatingMenuProps {
|
|
||||||
menuStructure: MenuStructure[];
|
|
||||||
className?: string | undefined;
|
|
||||||
tooltipClassName?: string | undefined;
|
|
||||||
}
|
|
||||||
export type MenuStructure = FloatingMenuElement | FloatingMenuJSXElement;
|
|
@ -1,28 +0,0 @@
|
|||||||
import { ComponentType } from "react";
|
|
||||||
import { RouteComponentProps } from "wouter";
|
|
||||||
|
|
||||||
interface RouteWithPath {
|
|
||||||
path: string;
|
|
||||||
nest?: RoutingTree;
|
|
||||||
}
|
|
||||||
interface RouteWithoutPath {
|
|
||||||
path?: never;
|
|
||||||
nest?: never;
|
|
||||||
}
|
|
||||||
interface RouteWithComponent {
|
|
||||||
nest?: never;
|
|
||||||
component: ComponentType<RouteComponentProps<{}>> | undefined;
|
|
||||||
}
|
|
||||||
interface RouteWithoutComponent {
|
|
||||||
nest: RoutingTree;
|
|
||||||
component?: never;
|
|
||||||
}
|
|
||||||
interface RouteObjectBase {
|
|
||||||
name: string;
|
|
||||||
icon?: React.FC;
|
|
||||||
}
|
|
||||||
export type RouteObject = RouteObjectBase &
|
|
||||||
(RouteWithPath | RouteWithoutPath) &
|
|
||||||
(RouteWithComponent | RouteWithoutComponent);
|
|
||||||
|
|
||||||
export type RoutingTree = RouteObject[];
|
|
@ -1,5 +0,0 @@
|
|||||||
import { RoutingTree } from "./routesTypes";
|
|
||||||
|
|
||||||
export interface SwitchRouteGeneratorProps {
|
|
||||||
routesTree: RoutingTree;
|
|
||||||
}
|
|
@ -1,29 +1,23 @@
|
|||||||
import forms from "@tailwindcss/forms";
|
import forms from "@tailwindcss/forms";
|
||||||
import typography from "@tailwindcss/typography";
|
import typography from "@tailwindcss/typography";
|
||||||
import daisyUI from "daisyui";
|
import daisyUI from "daisyui";
|
||||||
import { Config } from "tailwindcss";
|
import type { Config } from "tailwindcss";
|
||||||
export default {
|
import twanimate from "tailwindcss-animate";
|
||||||
content: {
|
|
||||||
relative: true,
|
const config: Config = {
|
||||||
files: ["./src/**/*.{js,ts,jsx,tsx}", "./index.html"],
|
darkMode: "class",
|
||||||
},
|
content: [
|
||||||
// safelist is used to allow classes to not be purged by tailwind;
|
"./pages/**/*.{ts,tsx}",
|
||||||
// I made this to set this classes dyanmically in the code, somehow without this tailwind purges them;
|
"./components/**/*.{ts,tsx}",
|
||||||
// safelist: ["alert-info", "alert-success", "alert-warning", "alert-error"],
|
"./app/**/*.{ts,tsx}",
|
||||||
theme: {
|
"./src/**/*.{ts,tsx}",
|
||||||
extend: {
|
"./index.html",
|
||||||
spacing: {
|
],
|
||||||
128: "32rem",
|
prefix: "",
|
||||||
144: "36rem",
|
plugins: [twanimate, forms, typography, daisyUI],
|
||||||
},
|
|
||||||
borderRadius: {
|
|
||||||
"4xl": "2rem",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
darkMode: "media",
|
|
||||||
plugins: [forms, typography, daisyUI],
|
|
||||||
daisyui: {
|
daisyui: {
|
||||||
themes: ["light", "dark"],
|
themes: ["light", "dark"],
|
||||||
},
|
},
|
||||||
} as Config;
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
|
@ -30,6 +30,11 @@
|
|||||||
"allowJs": true, // Allow JavaScript files to be imported
|
"allowJs": true, // Allow JavaScript files to be imported
|
||||||
"skipLibCheck": true, // Skip type checking of declaration files
|
"skipLibCheck": true, // Skip type checking of declaration files
|
||||||
"noEmit": true, // Vite handles the emitting of files
|
"noEmit": true, // Vite handles the emitting of files
|
||||||
|
"paths": {
|
||||||
|
"@root/*": [
|
||||||
|
"./*"
|
||||||
|
]
|
||||||
|
}, // Allow absolute imports from src
|
||||||
},
|
},
|
||||||
// Specifying folders and files to include in compilation
|
// Specifying folders and files to include in compilation
|
||||||
"include": [
|
"include": [
|
||||||
|
@ -1,10 +1,17 @@
|
|||||||
import { defineConfig, loadEnv } from "vite";
|
import { defineConfig, loadEnv } from "vite";
|
||||||
import react from "@vitejs/plugin-react";
|
import react from "@vitejs/plugin-react";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default ({ mode }) => {
|
export default ({ mode }) => {
|
||||||
process.env = { ...process.env, ...loadEnv(mode, process.cwd()) };
|
process.env = { ...process.env, ...loadEnv(mode, process.cwd()) };
|
||||||
return defineConfig({
|
return defineConfig({
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"@root": path.resolve(__dirname, "./"),
|
||||||
|
"@": path.resolve(__dirname, "./src"),
|
||||||
|
},
|
||||||
|
},
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|