Update template. This is good start code

This commit is contained in:
Igor Barcik 2024-02-02 12:39:54 +01:00
parent 761039dcd0
commit 3ea8f1fd1b
Signed by: biggy
GPG Key ID: EA4CE0D1E2A6DC98
50 changed files with 4370 additions and 3852 deletions

View File

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

BIN
bun.lockb

Binary file not shown.

5
docs/inspiration.md Normal file
View File

@ -0,0 +1,5 @@
# Inspiration
* [Tasks shadcn](https://ui.shadcn.com/examples/tasks)
![Alt text](inspiration1.png)

BIN
docs/inspiration1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 13 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 344 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

View File

@ -1,21 +1,20 @@
{ {
"name": "My App", "name": "My App",
"short_name": "App", "short_name": "App",
"icons": [ "icons": [
{ {
"src": "cmms_servimain_logo_192.png", "src": "cmms_servimain_logo_192.png",
"sizes": "192x192", "sizes": "192x192",
"type": "image/png" "type": "image/png"
}, },
{ {
"src": "cmms_servimain_logo_512.png", "src": "cmms_servimain_logo_512.png",
"sizes": "512x512", "sizes": "512x512",
"type": "image/png" "type": "image/png"
} }
], ],
"start_url": "/", "start_url": "/",
"display": "standalone", "display": "standalone",
"theme_color": "#000000", "theme_color": "#000000",
"background_color": "#ffffff" "background_color": "#ffffff"
} }

View File

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

View File

@ -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) => {

View File

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

View File

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

View File

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

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

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

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

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

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

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

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

View File

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

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

View File

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

View File

@ -0,0 +1,10 @@
import { createContext } from "react";
export type TooltipPositionContextType =
| "top"
| "bottom"
| "left"
| "right"
| undefined;
export const TooltipPositionContext =
createContext<TooltipPositionContextType>(undefined);

View File

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

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

View File

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

View File

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

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

View File

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

View File

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

View File

@ -1,22 +1,26 @@
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 <>
buttonLabel={t("homePage.click_me", "Click me!!!")} <HomePageTemplate
buttonOnClick={() => { buttonLabel={t("homePage.click_me", "Click me!!!")}
if (counter >= 10) { buttonOnClick={() => {
setCounter(0); if (counter >= 10) {
return; setCounter(0);
} else setCounter(counter + 1); return;
}} } else setCounter(counter + 1);
value={counter} }}
/> value={counter}
/>
<Outlet />
</>
); );
}; };

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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": [

View File

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