diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 7c90784..cc58a0d 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -4,18 +4,18 @@ # https://docs.microsoft.com/azure/devops/pipelines/languages/javascript trigger: -- develop + - develop pool: vmImage: ubuntu-latest steps: -- task: NodeTool@0 - inputs: - versionSpec: '20.x' - displayName: 'Install Node.js' + - task: NodeTool@0 + inputs: + versionSpec: '20.x' + displayName: 'Install Node.js' -- script: | - npm install - npm run build - displayName: 'npm install and build' + - script: | + npm install + npm run build + displayName: 'npm install and build' diff --git a/package-lock.json b/package-lock.json index 44936b8..0c87afa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,9 +16,11 @@ "i18next": "^25.3.0", "i18next-browser-languagedetector": "^8.2.0", "i18next-http-backend": "^3.0.2", + "iconsax-react": "^0.0.8", "react": "^19.1.0", "react-dom": "^19.1.0", "react-i18next": "^15.6.0", + "react-router-dom": "^7.8.0", "stylis": "^4.3.6", "stylis-plugin-rtl": "^2.1.1" }, @@ -3033,7 +3035,6 @@ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -3995,7 +3996,6 @@ "resolved": "https://registry.npmjs.org/iconsax-react/-/iconsax-react-0.0.8.tgz", "integrity": "sha512-l3dVk4zGtkkJHgvNYqAf0wDKqnKxXykee5/DoESGo2JvSYwaxajJUHSX2YrPRXSov8Hd8ClGFwJxCEaEjrFD1Q==", "license": "MIT", - "peer": true, "dependencies": { "prop-types": "^15.7.2" }, @@ -5033,7 +5033,6 @@ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.8.0.tgz", "integrity": "sha512-r15M3+LHKgM4SOapNmsH3smAizWds1vJ0Z9C4mWaKnT9/wD7+d/0jYcj6LmOvonkrO4Rgdyp4KQ/29gWN2i1eg==", "license": "MIT", - "peer": true, "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" @@ -5056,7 +5055,6 @@ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.8.0.tgz", "integrity": "sha512-ntInsnDVnVRdtSu6ODmTQ41cbluak/ENeTif7GBce0L6eztFg6/e1hXAysFQI8X25C8ipKmT9cClbJwxx3Kaqw==", "license": "MIT", - "peer": true, "dependencies": { "react-router": "7.8.0" }, @@ -5321,8 +5319,7 @@ "version": "2.7.1", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/setimmediate": { "version": "1.0.5", diff --git a/package.json b/package.json index 00ed48f..da6fe2c 100644 --- a/package.json +++ b/package.json @@ -19,9 +19,11 @@ "i18next": "^25.3.0", "i18next-browser-languagedetector": "^8.2.0", "i18next-http-backend": "^3.0.2", + "iconsax-react": "^0.0.8", "react": "^19.1.0", "react-dom": "^19.1.0", "react-i18next": "^15.6.0", + "react-router-dom": "^7.8.0", "stylis": "^4.3.6", "stylis-plugin-rtl": "^2.1.1" }, diff --git a/public/locales/fa/common.json b/public/locales/fa/common.json index 3f4cd0d..71d6ae5 100644 --- a/public/locales/fa/common.json +++ b/public/locales/fa/common.json @@ -1,3 +1,7 @@ { - "helloWorld": "سلام دنیا" + "side": { + "account": "حساب کاربری", + "personalInfo": "اطلاعات شخصی", + "contactInfo": "شماره تماس" + } } diff --git a/src/App.tsx b/src/App.tsx index f1bd30e..ec7f7b6 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,68 +1,17 @@ -import { - Alert, - Box, - CssBaseline, - TextField, - Typography, - useColorScheme, -} from '@mui/material'; +import { CssBaseline } from '@mui/material'; import './App.css'; -import { useTranslation } from 'react-i18next'; -import { LanguageManager } from './components/LanguageManager'; +import { LanguageManager } from '@/components/LanguageManager'; +import { RouterProvider } from 'react-router-dom'; +import { router } from '@/routes'; function App() { - const { t } = useTranslation(); - const showToast = useToast(); - return ( <> -
- {t('helloWorld')} - - - - - - success - - - warning - - - info - - - error - - -
- + ); } export default App; - -import { Button } from '@mui/material'; -import { useToast } from '@rkheftan/harmony-ui'; - -export const ThemeToggleButton = () => { - const { mode, setMode } = useColorScheme(); - - return ( - - ); -}; diff --git a/src/components/Layout/Layout.tsx b/src/components/Layout/Layout.tsx new file mode 100644 index 0000000..504dd3e --- /dev/null +++ b/src/components/Layout/Layout.tsx @@ -0,0 +1,46 @@ +import { SideNav } from '@rkheftan/harmony-ui'; +import { buildNavItems } from './navItems'; +import { appRoutes } from '@/routes/config'; +import { Outlet, useLocation } from 'react-router-dom'; +import { Box } from '@mui/material'; +import { grey } from '@mui/material/colors'; + +export const Layout = () => { + const navItemConfigs = buildNavItems(appRoutes); + const location = useLocation(); + + return ( + + + + + + + + + + ); +}; diff --git a/src/components/Layout/navItems.tsx b/src/components/Layout/navItems.tsx new file mode 100644 index 0000000..7160ea9 --- /dev/null +++ b/src/components/Layout/navItems.tsx @@ -0,0 +1,27 @@ +// src/components/SideNav.tsx (Conceptual Example) + +import { useTranslation } from 'react-i18next'; +import { type RouteConfig } from '@/routes/config'; +import { Icon, type NavItemConfig } from '@rkheftan/harmony-ui'; +import type { Icon as Iconsax } from 'iconsax-react'; + +const getIcon = (icon?: Iconsax) => (isSelected: boolean) => + icon ? ( + + ) : undefined; + +export function buildNavItems(routes: RouteConfig[]): NavItemConfig[] { + const { t } = useTranslation(); + + return routes + .filter((route) => route.navConfig) + .map((route) => { + const { title, icon } = route.navConfig!; + return { + text: t(title), + getIcon: getIcon(icon), + path: route.path, + children: route.children ? buildNavItems(route.children) : undefined, + }; + }); +} diff --git a/src/routes/config.tsx b/src/routes/config.tsx new file mode 100644 index 0000000..b5ed42c --- /dev/null +++ b/src/routes/config.tsx @@ -0,0 +1,48 @@ +import { Layout } from '@/components/Layout/Layout'; +import { Mobile, Personalcard, ProfileCircle, type Icon } from 'iconsax-react'; +import { type ReactNode } from 'react'; +import { Navigate } from 'react-router-dom'; + +export interface RouteConfig { + path: string; + element?: ReactNode; + navConfig?: { + title: string; // Translation key + icon?: Icon; + }; + children?: RouteConfig[]; +} + +export const appRoutes: RouteConfig[] = [ + { + path: '/', + element: , + }, + { + path: '/profile', + // can lazy load component if needed (ex. lazy(() => import('@/features/home/routes/HomePage'));) + element: , + navConfig: { + title: 'side.account', + icon: ProfileCircle, + }, + children: [ + { + path: '/profile/info', + element:
Personal Info Section
, + navConfig: { + title: 'side.personalInfo', + icon: Personalcard, + }, + }, + { + path: '/profile/contact-info', + element:
Personal Info Section
, + navConfig: { + title: 'side.contactInfo', + icon: Mobile, + }, + }, + ], + }, +]; diff --git a/src/routes/index.tsx b/src/routes/index.tsx new file mode 100644 index 0000000..541a2fb --- /dev/null +++ b/src/routes/index.tsx @@ -0,0 +1,34 @@ +import { Suspense, type ReactNode } from 'react'; +import { createBrowserRouter, type RouteObject } from 'react-router-dom'; +import { appRoutes, type RouteConfig } from './config'; + +/** + * A recursive function to map our custom route config to the format + * that react-router-dom expects, applying layouts and guards. + */ +function mapRoutes(routes: RouteConfig[]): RouteObject[] { + return routes.map((route) => { + // Start with the base element, wrapped in Suspense for lazy loading + let element: ReactNode = ( + Loading...}>{route.element} + ); + + // Conditionally wrap the element in the specified layout + // if (route.layout) { + // element = {element}; + // } + + // Conditionally wrap the element in the authentication guard + // if (route.authRequired) { + // element = {element}; + // } + + return { + path: route.path, + element: element, + ...(route.children && { children: mapRoutes(route.children) }), + }; + }); +} + +export const router = createBrowserRouter(mapRoutes(appRoutes));