Compare commits

..

No commits in common. "ai-dev" and "master" have entirely different histories.

2 changed files with 948 additions and 315 deletions

View File

@ -7,189 +7,146 @@ import LayoutAuthenticated from '../layouts/Authenticated'
import SectionMain from '../components/SectionMain' import SectionMain from '../components/SectionMain'
import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton' import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton'
import BaseIcon from "../components/BaseIcon"; import BaseIcon from "../components/BaseIcon";
import BaseButton from "../components/BaseButton";
import { getPageTitle } from '../config' import { getPageTitle } from '../config'
import Link from "next/link"; import Link from "next/link";
import { hasPermission } from "../helpers/userPermissions"; import { hasPermission } from "../helpers/userPermissions";
import { fetchWidgets } from '../stores/roles/rolesSlice'; import { fetchWidgets } from '../stores/roles/rolesSlice';
import { WidgetCreator } from '../components/WidgetCreator/WidgetCreator'; import { WidgetCreator } from '../components/WidgetCreator/WidgetCreator';
import { SmartWidget } from '../components/SmartWidget/SmartWidget'; import { SmartWidget } from '../components/SmartWidget/SmartWidget';
import { useAppDispatch, useAppSelector } from '../stores/hooks'; import { useAppDispatch, useAppSelector } from '../stores/hooks';
const EntityCard = ({ entity, label, count, iconPath, iconsColor, corners, cardsStyle }: any) => (
<Link href={`/${entity}/${entity}-list`}>
<div className={`${corners !== 'rounded-full' ? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6 hover:shadow-lg transition-all duration-200 transform hover:-translate-y-1`}>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400 font-medium">
{label}
</div>
<div className="text-3xl leading-tight font-bold mt-1">
{count}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-12"
h="h-12"
size={40}
path={iconPath || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>
)
const Dashboard = () => { const Dashboard = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const iconsColor = useAppSelector((state) => state.style.iconsColor); const iconsColor = useAppSelector((state) => state.style.iconsColor);
const corners = useAppSelector((state) => state.style.corners); const corners = useAppSelector((state) => state.style.corners);
const cardsStyle = useAppSelector((state) => state.style.cardsStyle); const cardsStyle = useAppSelector((state) => state.style.cardsStyle);
const loadingMessage = '...'; const loadingMessage = 'Loading...';
const [counts, setCounts] = React.useState<Record<string, any>>({});
const [users, setUsers] = React.useState(loadingMessage);
const [roles, setRoles] = React.useState(loadingMessage);
const [permissions, setPermissions] = React.useState(loadingMessage);
const [tenants, setTenants] = React.useState(loadingMessage);
const [user_roles, setUser_roles] = React.useState(loadingMessage);
const [modules, setModules] = React.useState(loadingMessage);
const [tenant_modules, setTenant_modules] = React.useState(loadingMessage);
const [plans, setPlans] = React.useState(loadingMessage);
const [subscriptions, setSubscriptions] = React.useState(loadingMessage);
const [activation_codes, setActivation_codes] = React.useState(loadingMessage);
const [licenses, setLicenses] = React.useState(loadingMessage);
const [audit_logs, setAudit_logs] = React.useState(loadingMessage);
const [products, setProducts] = React.useState(loadingMessage);
const [stock_movements, setStock_movements] = React.useState(loadingMessage);
const [warehouses, setWarehouses] = React.useState(loadingMessage);
const [warehouse_locations, setWarehouse_locations] = React.useState(loadingMessage);
const [customers, setCustomers] = React.useState(loadingMessage);
const [suppliers, setSuppliers] = React.useState(loadingMessage);
const [invoices, setInvoices] = React.useState(loadingMessage);
const [invoice_items, setInvoice_items] = React.useState(loadingMessage);
const [payments, setPayments] = React.useState(loadingMessage);
const [expenses, setExpenses] = React.useState(loadingMessage);
const [equipment_assets, setEquipment_assets] = React.useState(loadingMessage);
const [orders, setOrders] = React.useState(loadingMessage);
const [order_items, setOrder_items] = React.useState(loadingMessage);
const [widgetsRole, setWidgetsRole] = React.useState({
role: { value: '', label: '' },
});
const { currentUser } = useAppSelector((state) => state.auth); const { currentUser } = useAppSelector((state) => state.auth);
const { isFetchingQuery } = useAppSelector((state) => state.openAi); const { isFetchingQuery } = useAppSelector((state) => state.openAi);
const { rolesWidgets, loading } = useAppSelector((state) => state.roles); const { rolesWidgets, loading } = useAppSelector((state) => state.roles);
const [widgetsRole, setWidgetsRole] = React.useState({ role: { value: '', label: '' } });
const entities = [
{ key: 'products', label: 'Products', icon: icon.mdiPackageVariant, module: 'Inventory & Storage' },
{ key: 'stock_movements', label: 'Stock Movements', icon: icon.mdiSwapHorizontal, module: 'Inventory & Storage' },
{ key: 'warehouses', label: 'Warehouses', icon: icon.mdiWarehouse, module: 'Inventory & Storage' },
{ key: 'warehouse_locations', label: 'Warehouse Locations', icon: icon.mdiMapMarkerRadius, module: 'Inventory & Storage' },
{ key: 'suppliers', label: 'Suppliers', icon: icon.mdiTruckDelivery, module: 'Inventory & Storage' },
{ key: 'invoices', label: 'Invoices', icon: icon.mdiInvoice, module: 'Accounting' }, const organizationId = currentUser?.tenants?.id;
{ key: 'payments', label: 'Payments', icon: icon.mdiCreditCard, module: 'Accounting' },
{ key: 'expenses', label: 'Expenses', icon: icon.mdiClipboardList, module: 'Accounting' },
{ key: 'customers', label: 'Customers', icon: icon.mdiAccountCircle, module: 'Accounting' },
{ key: 'equipment_assets', label: 'Equipment Assets', icon: icon.mdiWrench, module: 'Assets & Orders' },
{ key: 'orders', label: 'Orders', icon: icon.mdiTruck, module: 'Assets & Orders' },
{ key: 'users', label: 'Users', icon: icon.mdiAccountGroup, module: 'Administration' },
{ key: 'subscriptions', label: 'Subscriptions', icon: icon.mdiCalendarClock, module: 'Administration' },
{ key: 'activation_codes', label: 'Activation Codes', icon: icon.mdiKey, module: 'Administration' },
];
async function loadData() { async function loadData() {
const requests = entities.map((entity) => { const entities = ['users','roles','permissions','tenants','user_roles','modules','tenant_modules','plans','subscriptions','activation_codes','licenses','audit_logs','products','stock_movements','warehouses','warehouse_locations','customers','suppliers','invoices','invoice_items','payments','expenses','equipment_assets','orders','order_items',];
if(hasPermission(currentUser, `READ_${entity.key.toUpperCase()}`)) { const fns = [setUsers,setRoles,setPermissions,setTenants,setUser_roles,setModules,setTenant_modules,setPlans,setSubscriptions,setActivation_codes,setLicenses,setAudit_logs,setProducts,setStock_movements,setWarehouses,setWarehouse_locations,setCustomers,setSuppliers,setInvoices,setInvoice_items,setPayments,setExpenses,setEquipment_assets,setOrders,setOrder_items,];
return axios.get(`/${entity.key.toLowerCase()}/count`);
} const requests = entities.map((entity, index) => {
if(hasPermission(currentUser, `READ_${entity.toUpperCase()}`)) {
return axios.get(`/${entity.toLowerCase()}/count`);
} else {
fns[index](null);
return Promise.resolve({data: {count: null}}); return Promise.resolve({data: {count: null}});
}
}); });
const results = await Promise.allSettled(requests); Promise.allSettled(requests).then((results) => {
const newCounts: Record<string, any> = {};
results.forEach((result, i) => { results.forEach((result, i) => {
if (result.status === 'fulfilled') { if (result.status === 'fulfilled') {
newCounts[entities[i].key] = (result.value as any).data.count; fns[i](result.value.data.count);
} else { } else {
newCounts[entities[i].key] = 'Error'; fns[i](result.reason.message);
} }
}); });
setCounts(newCounts); });
} }
async function getWidgets(roleId) {
await dispatch(fetchWidgets(roleId));
}
React.useEffect(() => { React.useEffect(() => {
if (!currentUser) return; if (!currentUser) return;
loadData(); loadData().then();
setWidgetsRole({ role: { value: currentUser?.app_role?.id, label: currentUser?.app_role?.name } }); setWidgetsRole({ role: { value: currentUser?.app_role?.id, label: currentUser?.app_role?.name } });
}, [currentUser]); }, [currentUser]);
React.useEffect(() => { React.useEffect(() => {
if (!currentUser || !widgetsRole?.role?.value) return; if (!currentUser || !widgetsRole?.role?.value) return;
dispatch(fetchWidgets(widgetsRole?.role?.value)); getWidgets(widgetsRole?.role?.value || '').then();
}, [widgetsRole?.role?.value]); }, [widgetsRole?.role?.value]);
const renderModuleGroup = (moduleName: string) => {
const moduleEntities = entities.filter(e => e.module === moduleName && hasPermission(currentUser, `READ_${e.key.toUpperCase()}`));
if (moduleEntities.length === 0) return null;
return (
<div key={moduleName} className="mb-10">
<h3 className="text-xl font-bold mb-6 text-gray-700 dark:text-gray-300 flex items-center">
<span className="w-1.5 h-6 bg-indigo-600 mr-3 rounded-full"></span>
{moduleName}
</h3>
<div className="grid grid-cols-1 gap-6 lg:grid-cols-3 xl:grid-cols-4">
{moduleEntities.map(e => (
<EntityCard
key={e.key}
entity={e.key}
label={e.label}
count={counts[e.key] ?? loadingMessage}
iconPath={e.icon}
iconsColor={iconsColor}
corners={corners}
cardsStyle={cardsStyle}
/>
))}
</div>
</div>
)
}
return ( return (
<> <>
<Head> <Head>
<title>{getPageTitle('Overview')}</title> <title>
{getPageTitle('Overview')}
</title>
</Head> </Head>
<SectionMain> <SectionMain>
<SectionTitleLineWithButton <SectionTitleLineWithButton
icon={icon.mdiChartTimelineVariant} icon={icon.mdiChartTimelineVariant}
title='Enterprise Dashboard' title='Overview'
main main>
>
{''} {''}
</SectionTitleLineWithButton> </SectionTitleLineWithButton>
{/* Subscription Status Widget */} {hasPermission(currentUser, 'CREATE_ROLES') && <WidgetCreator
<div className={`mb-10 p-6 bg-indigo-50 border border-indigo-200 rounded-3xl flex flex-col md:flex-row items-center justify-between shadow-sm`}>
<div className="flex items-center mb-4 md:mb-0">
<div className="w-14 h-14 bg-indigo-100 text-indigo-600 rounded-2xl flex items-center justify-center mr-5 shadow-inner">
<BaseIcon path={icon.mdiCheckDecagram} size={32} />
</div>
<div>
<h4 className="text-indigo-900 font-bold text-lg leading-tight">DIGIHELP-IMS Premium Active</h4>
<p className="text-indigo-700 text-sm mt-0.5">Your enterprise subscription is valid until 2026-12-31</p>
</div>
</div>
<div className="flex space-x-3">
<BaseButton label="Renew" color="info" small className="rounded-xl px-6" />
<BaseButton label="View Plan" outline color="info" small className="rounded-xl px-6" />
</div>
</div>
{hasPermission(currentUser, 'CREATE_ROLES') && (
<WidgetCreator
currentUser={currentUser} currentUser={currentUser}
isFetchingQuery={isFetchingQuery} isFetchingQuery={isFetchingQuery}
setWidgetsRole={setWidgetsRole} setWidgetsRole={setWidgetsRole}
widgetsRole={widgetsRole} widgetsRole={widgetsRole}
/> />}
{!!rolesWidgets.length &&
hasPermission(currentUser, 'CREATE_ROLES') && (
<p className=' text-gray-500 dark:text-gray-400 mb-4'>
{`${widgetsRole?.role?.label || 'Users'}'s widgets`}
</p>
)} )}
{/* Dynamic AI Widgets */} <div className='grid grid-cols-1 gap-6 lg:grid-cols-4 mb-6 grid-flow-dense'>
{(rolesWidgets.length > 0 || isFetchingQuery || loading) && (
<div className="mb-10">
<h3 className="text-xl font-bold mb-6 text-gray-700 dark:text-gray-300 flex items-center">
<span className="w-1.5 h-6 bg-violet-600 mr-3 rounded-full"></span>
AI Insights & Reports
</h3>
<div className='grid grid-cols-1 gap-6 lg:grid-cols-4'>
{(isFetchingQuery || loading) && ( {(isFetchingQuery || loading) && (
<div className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 text-lg leading-tight text-gray-500 flex items-center ${cardsStyle} dark:border-dark-700 p-6 shadow-sm`}> <div className={` ${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 text-lg leading-tight text-gray-500 flex items-center ${cardsStyle} dark:border-dark-700 p-6`}>
<BaseIcon className={`${iconsColor} animate-spin mr-5`} w='w-12' h='h-12' size={32} path={icon.mdiLoading} /> <BaseIcon
Generating Insights... className={`${iconsColor} animate-spin mr-5`}
w='w-16'
h='h-16'
size={48}
path={icon.mdiLoading}
/>{' '}
Loading widgets...
</div> </div>
)} )}
{rolesWidgets.map((widget) => (
{ rolesWidgets &&
rolesWidgets.map((widget) => (
<SmartWidget <SmartWidget
key={widget.id} key={widget.id}
userId={currentUser?.id} userId={currentUser?.id}
@ -199,12 +156,714 @@ const Dashboard = () => {
/> />
))} ))}
</div> </div>
{!!rolesWidgets.length && <hr className='my-6 text-midnightBlueTheme-mainBG ' />}
<div id="dashboard" className='grid grid-cols-1 gap-6 lg:grid-cols-3 mb-6'>
{hasPermission(currentUser, 'READ_USERS') && <Link href={'/users/users-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Users
</div> </div>
)} <div className="text-3xl leading-tight font-semibold">
{users}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={icon.mdiAccountGroup || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{/* Grouped Entity Cards */} {hasPermission(currentUser, 'READ_ROLES') && <Link href={'/roles/roles-list'}>
{['Inventory & Storage', 'Accounting', 'Assets & Orders', 'Administration'].map(renderModuleGroup)} <div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Roles
</div>
<div className="text-3xl leading-tight font-semibold">
{roles}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={icon.mdiShieldAccountVariantOutline || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_PERMISSIONS') && <Link href={'/permissions/permissions-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Permissions
</div>
<div className="text-3xl leading-tight font-semibold">
{permissions}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={icon.mdiShieldAccountOutline || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_TENANTS') && <Link href={'/tenants/tenants-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Tenants
</div>
<div className="text-3xl leading-tight font-semibold">
{tenants}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={icon.mdiTable || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_USER_ROLES') && <Link href={'/user_roles/user_roles-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
User roles
</div>
<div className="text-3xl leading-tight font-semibold">
{user_roles}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={'mdiAccountMultipleCheck' in icon ? icon['mdiAccountMultipleCheck' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_MODULES') && <Link href={'/modules/modules-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Modules
</div>
<div className="text-3xl leading-tight font-semibold">
{modules}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={'mdiPuzzle' in icon ? icon['mdiPuzzle' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_TENANT_MODULES') && <Link href={'/tenant_modules/tenant_modules-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Tenant modules
</div>
<div className="text-3xl leading-tight font-semibold">
{tenant_modules}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={'mdiToggleSwitch' in icon ? icon['mdiToggleSwitch' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_PLANS') && <Link href={'/plans/plans-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Plans
</div>
<div className="text-3xl leading-tight font-semibold">
{plans}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={'mdiFinance' in icon ? icon['mdiFinance' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_SUBSCRIPTIONS') && <Link href={'/subscriptions/subscriptions-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Subscriptions
</div>
<div className="text-3xl leading-tight font-semibold">
{subscriptions}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={'mdiCalendarClock' in icon ? icon['mdiCalendarClock' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_ACTIVATION_CODES') && <Link href={'/activation_codes/activation_codes-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Activation codes
</div>
<div className="text-3xl leading-tight font-semibold">
{activation_codes}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={'mdiKey' in icon ? icon['mdiKey' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_LICENSES') && <Link href={'/licenses/licenses-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Licenses
</div>
<div className="text-3xl leading-tight font-semibold">
{licenses}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={'mdiCertificate' in icon ? icon['mdiCertificate' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_AUDIT_LOGS') && <Link href={'/audit_logs/audit_logs-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Audit logs
</div>
<div className="text-3xl leading-tight font-semibold">
{audit_logs}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={'mdiHistory' in icon ? icon['mdiHistory' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_PRODUCTS') && <Link href={'/products/products-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Products
</div>
<div className="text-3xl leading-tight font-semibold">
{products}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={'mdiPackageVariant' in icon ? icon['mdiPackageVariant' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_STOCK_MOVEMENTS') && <Link href={'/stock_movements/stock_movements-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Stock movements
</div>
<div className="text-3xl leading-tight font-semibold">
{stock_movements}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={'mdiSwapHorizontal' in icon ? icon['mdiSwapHorizontal' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_WAREHOUSES') && <Link href={'/warehouses/warehouses-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Warehouses
</div>
<div className="text-3xl leading-tight font-semibold">
{warehouses}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={'mdiWarehouse' in icon ? icon['mdiWarehouse' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_WAREHOUSE_LOCATIONS') && <Link href={'/warehouse_locations/warehouse_locations-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Warehouse locations
</div>
<div className="text-3xl leading-tight font-semibold">
{warehouse_locations}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={'mdiMapMarkerRadius' in icon ? icon['mdiMapMarkerRadius' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_CUSTOMERS') && <Link href={'/customers/customers-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Customers
</div>
<div className="text-3xl leading-tight font-semibold">
{customers}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={'mdiAccountCircle' in icon ? icon['mdiAccountCircle' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_SUPPLIERS') && <Link href={'/suppliers/suppliers-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Suppliers
</div>
<div className="text-3xl leading-tight font-semibold">
{suppliers}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={'mdiTruckDelivery' in icon ? icon['mdiTruckDelivery' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_INVOICES') && <Link href={'/invoices/invoices-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Invoices
</div>
<div className="text-3xl leading-tight font-semibold">
{invoices}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={'mdiFileInvoice' in icon ? icon['mdiFileInvoice' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_INVOICE_ITEMS') && <Link href={'/invoice_items/invoice_items-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Invoice items
</div>
<div className="text-3xl leading-tight font-semibold">
{invoice_items}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={'mdiReceipt' in icon ? icon['mdiReceipt' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_PAYMENTS') && <Link href={'/payments/payments-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Payments
</div>
<div className="text-3xl leading-tight font-semibold">
{payments}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={'mdiCreditCard' in icon ? icon['mdiCreditCard' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_EXPENSES') && <Link href={'/expenses/expenses-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Expenses
</div>
<div className="text-3xl leading-tight font-semibold">
{expenses}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={'mdiClipboardList' in icon ? icon['mdiClipboardList' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_EQUIPMENT_ASSETS') && <Link href={'/equipment_assets/equipment_assets-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Equipment assets
</div>
<div className="text-3xl leading-tight font-semibold">
{equipment_assets}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={'mdiScrewdriverWrench' in icon ? icon['mdiScrewdriverWrench' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_ORDERS') && <Link href={'/orders/orders-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Orders
</div>
<div className="text-3xl leading-tight font-semibold">
{orders}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={'mdiTruck' in icon ? icon['mdiTruck' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
{hasPermission(currentUser, 'READ_ORDER_ITEMS') && <Link href={'/order_items/order_items-list'}>
<div
className={`${corners !== 'rounded-full'? corners : 'rounded-3xl'} dark:bg-dark-900 ${cardsStyle} dark:border-dark-700 p-6`}
>
<div className="flex justify-between align-center">
<div>
<div className="text-lg leading-tight text-gray-500 dark:text-gray-400">
Order items
</div>
<div className="text-3xl leading-tight font-semibold">
{order_items}
</div>
</div>
<div>
<BaseIcon
className={`${iconsColor}`}
w="w-16"
h="h-16"
size={48}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
path={'mdiFormatListBulleted' in icon ? icon['mdiFormatListBulleted' as keyof typeof icon] : icon.mdiTable || icon.mdiTable}
/>
</div>
</div>
</div>
</Link>}
</div>
</SectionMain> </SectionMain>
</> </>
) )

View File

@ -1,192 +1,166 @@
import React from 'react';
import React, { useEffect, useState } from 'react';
import type { ReactElement } from 'react'; import type { ReactElement } from 'react';
import Head from 'next/head'; import Head from 'next/head';
import Link from 'next/link'; import Link from 'next/link';
import BaseButton from '../components/BaseButton'; import BaseButton from '../components/BaseButton';
import CardBox from '../components/CardBox';
import SectionFullScreen from '../components/SectionFullScreen';
import LayoutGuest from '../layouts/Guest'; import LayoutGuest from '../layouts/Guest';
import BaseDivider from '../components/BaseDivider';
import BaseButtons from '../components/BaseButtons';
import { getPageTitle } from '../config'; import { getPageTitle } from '../config';
import { mdiPackageVariant, mdiWarehouse, mdiInvoice, mdiTruck, mdiWrench } from '@mdi/js'; import { useAppSelector } from '../stores/hooks';
import BaseIcon from '../components/BaseIcon'; import CardBoxComponentTitle from "../components/CardBoxComponentTitle";
import { getPexelsImage, getPexelsVideo } from '../helpers/pexels';
const features = [
{
title: 'Inventory Management',
description: 'Track stock levels, products, and movements in real-time.',
icon: mdiPackageVariant,
},
{
title: 'Storage & Warehouses',
description: 'Manage multiple warehouses and locations with ease.',
icon: mdiWarehouse,
},
{
title: 'Accounting & Invoices',
description: 'Complete billing system with payments and expense tracking.',
icon: mdiInvoice,
},
{
title: 'Shipping & Orders',
description: 'Handle customer orders and shipping workflows efficiently.',
icon: mdiTruck,
},
{
title: 'Asset Management',
description: 'Track equipment and assets across your organization.',
icon: mdiWrench,
},
];
export default function LandingPage() { export default function Starter() {
const [illustrationImage, setIllustrationImage] = useState({
src: undefined,
photographer: undefined,
photographer_url: undefined,
})
const [illustrationVideo, setIllustrationVideo] = useState({video_files: []})
const [contentType, setContentType] = useState('image');
const [contentPosition, setContentPosition] = useState('right');
const textColor = useAppSelector((state) => state.style.linkColor);
const title = 'DIGIHELP-IMS'
// Fetch Pexels image/video
useEffect(() => {
async function fetchData() {
const image = await getPexelsImage();
const video = await getPexelsVideo();
setIllustrationImage(image);
setIllustrationVideo(video);
}
fetchData();
}, []);
const imageBlock = (image) => (
<div
className='hidden md:flex flex-col justify-end relative flex-grow-0 flex-shrink-0 w-1/3'
style={{
backgroundImage: `${
image
? `url(${image?.src?.original})`
: 'linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5))'
}`,
backgroundSize: 'cover',
backgroundPosition: 'left center',
backgroundRepeat: 'no-repeat',
}}
>
<div className='flex justify-center w-full bg-blue-300/20'>
<a
className='text-[8px]'
href={image?.photographer_url}
target='_blank'
rel='noreferrer'
>
Photo by {image?.photographer} on Pexels
</a>
</div>
</div>
);
const videoBlock = (video) => {
if (video?.video_files?.length > 0) {
return ( return (
<div className="bg-white text-gray-900 font-sans"> <div className='hidden md:flex flex-col justify-end relative flex-grow-0 flex-shrink-0 w-1/3'>
<video
className='absolute top-0 left-0 w-full h-full object-cover'
autoPlay
loop
muted
>
<source src={video?.video_files[0]?.link} type='video/mp4'/>
Your browser does not support the video tag.
</video>
<div className='flex justify-center w-full bg-blue-300/20 z-10'>
<a
className='text-[8px]'
href={video?.user?.url}
target='_blank'
rel='noreferrer'
>
Video by {video.user.name} on Pexels
</a>
</div>
</div>)
}
};
return (
<div
style={
contentPosition === 'background'
? {
backgroundImage: `${
illustrationImage
? `url(${illustrationImage.src?.original})`
: 'linear-gradient(rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0.5))'
}`,
backgroundSize: 'cover',
backgroundPosition: 'left center',
backgroundRepeat: 'no-repeat',
}
: {}
}
>
<Head> <Head>
<title>{getPageTitle('DIGIHELP-IMS - Integrated Management Suite')}</title> <title>{getPageTitle('Starter Page')}</title>
<meta name="description" content="Next-generation SaaS for Inventory, Storage, Accounting, and Enterprise Management." />
</Head> </Head>
{/* Hero Section */} <SectionFullScreen bg='violet'>
<section className="relative py-20 overflow-hidden bg-gradient-to-br from-indigo-900 via-violet-800 to-indigo-900 text-white"> <div
<div className="absolute top-0 left-0 w-full h-full opacity-10"> className={`flex ${
<div className="absolute top-10 left-10 w-64 h-64 bg-white rounded-full blur-3xl"></div> contentPosition === 'right' ? 'flex-row-reverse' : 'flex-row'
<div className="absolute bottom-10 right-10 w-96 h-96 bg-indigo-300 rounded-full blur-3xl"></div> } min-h-screen w-full`}
>
{contentType === 'image' && contentPosition !== 'background'
? imageBlock(illustrationImage)
: null}
{contentType === 'video' && contentPosition !== 'background'
? videoBlock(illustrationVideo)
: null}
<div className='flex items-center justify-center flex-col space-y-4 w-full lg:w-full'>
<CardBox className='w-full md:w-3/5 lg:w-2/3'>
<CardBoxComponentTitle title="Welcome to your DIGIHELP-IMS app!"/>
<div className="space-y-3">
<p className='text-center '>This is a React.js/Node.js app generated by the <a className={`${textColor}`} href="https://flatlogic.com/generator">Flatlogic Web App Generator</a></p>
<p className='text-center '>For guides and documentation please check
your local README.md and the <a className={`${textColor}`} href="https://flatlogic.com/documentation">Flatlogic documentation</a></p>
</div> </div>
<div className="container mx-auto px-6 relative z-10 text-center"> <BaseButtons>
<h1 className="text-5xl md:text-7xl font-extrabold mb-6 tracking-tight animate-fade-in">
DIGIHELP-IMS
</h1>
<p className="text-xl md:text-2xl mb-10 text-indigo-100 max-w-2xl mx-auto leading-relaxed">
The Integrated Management Suite for Modern Enterprise.
Streamline Inventory, Accounting, and Logistics in one unified platform.
</p>
<div className="flex flex-col md:flex-row justify-center space-y-4 md:space-y-0 md:space-x-6">
<BaseButton <BaseButton
href="/register" href='/login'
label="Start Free Trial" label='Login'
color="white" color='info'
className="px-10 py-4 text-lg font-bold rounded-xl shadow-2xl hover:scale-105 transition-all duration-300" className='w-full'
/> />
<BaseButton
href="/login"
label="Sign In"
outline
color="white"
className="px-10 py-4 text-lg font-bold rounded-xl border-2 hover:bg-white/10 transition-all duration-300"
/>
</div>
</div>
</section>
{/* Features Section */} </BaseButtons>
<section className="py-24 bg-gray-50"> </CardBox>
<div className="container mx-auto px-6"> </div>
<div className="text-center mb-20"> </div>
<h2 className="text-3xl md:text-4xl font-bold mb-4 text-gray-900">Comprehensive Enterprise Modules</h2> </SectionFullScreen>
<div className="w-20 h-1 bg-indigo-600 mx-auto mb-6"></div> <div className='bg-black text-white flex flex-col text-center justify-center md:flex-row'>
<p className="text-gray-600 max-w-2xl mx-auto text-lg"> <p className='py-6 text-sm'>© 2026 <span>{title}</span>. All rights reserved</p>
Everything you need to run your business operations smoothly and efficiently. <Link className='py-6 ml-4 text-sm' href='/privacy-policy/'>
</p> Privacy Policy
</Link>
</div> </div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-10">
{features.map((feature, index) => (
<div key={index} className="bg-white p-10 rounded-3xl shadow-sm border border-gray-100 hover:shadow-2xl hover:-translate-y-2 transition-all duration-300 group">
<div className="w-16 h-16 bg-indigo-50 text-indigo-600 rounded-2xl flex items-center justify-center mb-8 group-hover:bg-indigo-600 group-hover:text-white transition-colors duration-300">
<BaseIcon path={feature.icon} size={36} />
</div>
<h3 className="text-2xl font-bold mb-4">{feature.title}</h3>
<p className="text-gray-600 leading-relaxed text-lg">{feature.description}</p>
</div>
))}
</div>
</div>
</section>
{/* How It Works Section */}
<section className="py-24 bg-indigo-900 text-white">
<div className="container mx-auto px-6">
<div className="text-center mb-16">
<h2 className="text-3xl md:text-4xl font-bold mb-4">How It Works</h2>
<p className="text-indigo-200">Scale your business in three simple steps.</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-12 text-center">
{[
{ step: '01', title: 'Register Tenant', desc: 'Create your organization account and subdomain.' },
{ step: '02', title: 'Activate Modules', desc: 'Choose the modules your business needs to grow.' },
{ step: '03', title: 'Start Scaling', desc: 'Onboard your team and automate your workflows.' },
].map((item, idx) => (
<div key={idx} className="relative">
<div className="text-6xl font-black text-white/10 absolute -top-10 left-1/2 -translate-x-1/2 z-0">{item.step}</div>
<h3 className="text-2xl font-bold mb-4 relative z-10">{item.title}</h3>
<p className="text-indigo-200 relative z-10">{item.desc}</p>
</div>
))}
</div>
</div>
</section>
{/* Pricing Section */}
<section className="py-24 bg-white">
<div className="container mx-auto px-6">
<div className="text-center mb-16">
<h2 className="text-3xl md:text-4xl font-bold mb-4 text-gray-900 text-center">Flexible Subscription Plans</h2>
<p className="text-gray-600">Choose a plan that fits your business scale.</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
{['Weekly', 'Monthly', 'Yearly', 'Lifetime'].map((plan) => (
<div key={plan} className={`p-8 rounded-3xl border ${plan === 'Yearly' ? 'border-indigo-600 bg-indigo-50 ring-4 ring-indigo-600 ring-opacity-10' : 'border-gray-200'} hover:shadow-xl transition-all duration-300`}>
<h3 className="text-lg font-semibold text-gray-500 mb-2 uppercase tracking-widest">{plan}</h3>
<div className="text-4xl font-bold mb-6 text-indigo-600">
{plan === 'Lifetime' ? 'Contact' : '$XX'}
</div>
<ul className="mb-8 space-y-3 text-gray-600 text-sm">
<li> All Modules Included</li>
<li> Unlimited Users</li>
<li> Priority Support</li>
<li> 99.9% Uptime SLA</li>
</ul>
<BaseButton label="Get Started" color={plan === 'Yearly' ? 'info' : 'white'} outline={plan !== 'Yearly'} className="w-full rounded-xl py-3 font-bold" href="/register" />
</div>
))}
</div>
</div>
</section>
{/* Footer */}
<footer className="bg-gray-900 text-white py-16 border-t border-gray-800">
<div className="container mx-auto px-6">
<div className="grid grid-cols-1 md:grid-cols-3 gap-12 mb-12">
<div>
<h3 className="text-2xl font-bold mb-6 text-indigo-400">DIGIHELP-IMS</h3>
<p className="text-gray-400 leading-relaxed">
Next-generation enterprise resource planning and management suite.
</p>
</div>
<div>
<h4 className="font-bold mb-6 uppercase tracking-wider text-gray-500 text-sm">Product</h4>
<ul className="space-y-4 text-gray-400">
<li><Link href="/login" className="hover:text-white transition-colors">Dashboard</Link></li>
<li><Link href="/register" className="hover:text-white transition-colors">Pricing</Link></li>
<li><Link href="/modules" className="hover:text-white transition-colors">Modules</Link></li>
</ul>
</div>
<div>
<h4 className="font-bold mb-6 uppercase tracking-wider text-gray-500 text-sm">Legal</h4>
<ul className="space-y-4 text-gray-400">
<li><Link href="/privacy-policy" className="hover:text-white transition-colors">Privacy Policy</Link></li>
<li><Link href="/terms-of-use" className="hover:text-white transition-colors">Terms of Service</Link></li>
</ul>
</div>
</div>
<div className="pt-12 border-t border-gray-800 text-center text-gray-500 text-sm">
<p>© 2026 DIGIHELP-IMS. All rights reserved.</p>
</div>
</div>
</footer>
</div> </div>
); );
} }
LandingPage.getLayout = function getLayout(page: ReactElement) { Starter.getLayout = function getLayout(page: ReactElement) {
return <LayoutGuest>{page}</LayoutGuest>; return <LayoutGuest>{page}</LayoutGuest>;
}; };