This commit is contained in:
David Arranz 2025-05-28 16:21:09 +02:00
parent eabd63ec09
commit 2c89ebb002
58 changed files with 267 additions and 481 deletions

1
.gitignore vendored
View File

@ -12,3 +12,4 @@ public/dist
apps/**/false/*
false

View File

@ -40,9 +40,8 @@
"typescript": "^5.8.3"
},
"dependencies": {
"@erp/auth": "workspace:*",
"@erp/core": "workspace:*",
"@erp/invoices": "workspace:*",
"@erp/auth": "workspace:*",
"bcrypt": "^5.1.1",
"cls-rtracer": "^2.6.3",
"cors": "^2.8.5",

View File

@ -1,4 +1,3 @@
import { globalErrorHandler } from "@erp/core";
//import { initPackages } from "@/core/package-loader";
import dotenv from "dotenv";
import express, { Application } from "express";
@ -52,7 +51,7 @@ export function createApp(): Application {
// Gestión global de errores.
// Siempre al final de la cadena de middlewares
// y después de las rutas.
app.use(globalErrorHandler);
//app.use(globalErrorHandler);
return app;
}

View File

@ -1 +0,0 @@
export * from "../../../lib/httpServer";

View File

@ -1,4 +1,4 @@
import { ModuleParams } from "@erp/core";
import { ModuleParams } from "@erp/core/api";
import { logger } from "../logger";
const allModelInitializers: any[] = [];

View File

@ -1,4 +1,4 @@
import { IModuleServer, ModuleParams } from "@erp/core";
import { IModuleServer, ModuleParams } from "@erp/core/api";
import { logger } from "../logger";
import { initModels, registerModels } from "./model-loader";
import { registerService } from "./service-registry";

View File

@ -1,8 +1,8 @@
//import { authAPIModule } from "@erp/auth";
import { invoicesAPIModule } from "@erp/invoices";
import { registerModule } from "./lib/modules";
import { authAPIModule } from "@erp/auth/api";
import { registerModule } from "./lib";
//import { invoicesAPIModule } from "@erp/invoices/api";
export const registerModules = () => {
//registerModule(authAPIModule);
registerModule(invoicesAPIModule);
registerModule(authAPIModule);
//registerModule(invoicesAPIModule);
};

View File

@ -8,7 +8,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link href="https://fonts.upset.dev/css2?family=Poppins&display=swap" rel="stylesheet" />
<title>FactuGES 2025</title>
<!-- FAVICONS -->
<link rel="icon" type="image/png" href="/favicon.png" />
</head>
<body>

View File

@ -15,7 +15,6 @@
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@hookform/devtools": "^4.4.0",
"@peterek/vite-plugin-favicons": "^2.1.0",
"@repo/typescript-config": "workspace:*",
"@tailwindcss/postcss": "^4.1.5",
"@tailwindcss/vite": "^4.1.6",
@ -30,8 +29,8 @@
"vite": "^6.3.5"
},
"dependencies": {
"@erp/core": "workspace:*",
"@erp/auth": "workspace:*",
"@erp/core": "workspace:*",
"@erp/invoices": "workspace:*",
"@repo/rdx-criteria": "workspace:*",
"@repo/rdx-ui": "workspace:*",
@ -46,6 +45,7 @@
"react-hook-form-persist": "^3.0.0",
"react-i18next": "^15.0.1",
"react-router-dom": "^6.26.0",
"react-secure-storage": "^1.3.2",
"sequelize": "^6.37.5",
"tailwind-merge": "^3.2.0",
"tailwindcss": "^4.1.6",

BIN
apps/web/public/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1,64 +0,0 @@
import { AppLayout, LoadingOverlay, ScrollToTop } from "@repo/rdx-ui/components";
import { JSX, Suspense } from "react";
import { Route, BrowserRouter as Router, Routes } from "react-router-dom";
import { ErrorPage } from "./pages";
import { modules } from "./register-modules"; // Aquí ca
// Lazy load components
//const ErrorPage = lazy(() => import("./pages").then((m) => ({ default: m.ErrorPage })));
//const LogoutPage = lazy(() => import("./app").then((m) => ({ default: m.LogoutPage })));
/*const DealerLayout = lazy(() => import("./app").then((m) => ({ default: m.DealerLayout })));
const DealersList = lazy(() => import("./app").then((m) => ({ default: m.DealersList })));
const LoginPageWithLanguageSelector = lazy(() =>
import("./app").then((m) => ({ default: m.LoginPageWithLanguageSelector }))
);
const QuoteCreate = lazy(() => import("./app").then((m) => ({ default: m.QuoteCreate })));
const QuoteEdit = lazy(() => import("./app").then((m) => ({ default: m.QuoteEdit })));
const SettingsEditor = lazy(() => import("./app").then((m) => ({ default: m.SettingsEditor })));
const SettingsLayout = lazy(() => import("./app").then((m) => ({ default: m.SettingsLayout })));
const CatalogLayout = lazy(() => import("./app").then((m) => ({ default: m.CatalogLayout })));
const CatalogList = lazy(() => import("./app").then((m) => ({ default: m.CatalogList })));
const DashboardPage = lazy(() => import("./app").then((m) => ({ default: m.DashboardPage })));
const QuotesLayout = lazy(() => import("./app").then((m) => ({ default: m.QuotesLayout })));
const QuotesList = lazy(() => import("./app").then((m) => ({ default: m.QuotesList })));*/
export const AppRoutes = (): JSX.Element => {
return (
<Router>
<ScrollToTop />
<Suspense fallback={<LoadingOverlay />}>
<Routes>
<Route element={<AppLayout />}>
{/* Main Layout */}
<Route index element={<ErrorPage />} />
<Route path='/dashboard' element={<ErrorPage />} />
<Route path='/settings' element={<ErrorPage />} />
<Route path='/catalog' element={<ErrorPage />} />
<Route path='/quotes' element={<ErrorPage />} />
{/* Dynamic Module Routes */}
{modules.map((module) => {
if (module.routes) {
return module.routes();
}
return null;
})}
</Route>
{/* Auth Layout */}
{/*<Route path="/signin" element={<SignIn />} />
<Route path="/signup" element={<SignUp />} />*/}
{/* Fallback Route */}
<Route path='*' element={<ErrorPage />} />
</Routes>
</Suspense>
</Router>
);
};

View File

@ -8,8 +8,8 @@ import { i18n } from "@/locales";
import { clearAccessToken, getAccessToken } from "@erp/auth/client";
import { DataSourceProvider, createAxiosDataSource, createAxiosInstance } from "@erp/core/client";
import { AppRoutes } from "./app-routes";
import "./app.css";
import { AppRoutes } from "./routes/app-routes";
/**
* Clave utilizada en el almacenamiento local para el token JWT.
@ -26,14 +26,16 @@ export const App = () => {
},
});
const dataSource = createAxiosDataSource(createAxiosInstance({
baseURL: import.meta.env.VITE_API_URL,
getAccessToken: () => getAccessToken(TOKEN_STORAGE_KEY),
onAuthError: () => {
clearAccessToken(TOKEN_STORAGE_KEY);
window.location.href = "/login"; // o usar navegación programática
},
}););
const dataSource = createAxiosDataSource(
createAxiosInstance({
baseURL: import.meta.env.VITE_API_URL,
getAccessToken: () => getAccessToken(TOKEN_STORAGE_KEY),
onAuthError: () => {
clearAccessToken(TOKEN_STORAGE_KEY);
window.location.href = "/login"; // o usar navegación programática
},
})
);
return (
<I18nextProvider i18n={i18n}>

View File

@ -0,0 +1,56 @@
import { IModuleClient, ModuleClientParams } from "@erp/core/client";
import { JSX } from "react";
import { RouteObject, useRoutes } from "react-router-dom";
interface ModuleRoutesProps {
modules: IModuleClient[];
params: ModuleClientParams;
}
interface WarpIfProtectedProps {
component: JSX.Element;
isProtected: boolean;
}
const WarpIfProtected = ({ component, isProtected }: WarpIfProtectedProps) => {
return isProtected ? <>{component}</> : component;
};
export const ModuleRoutes = ({ modules, params }: ModuleRoutesProps) => {
const routes: RouteObject[] = [];
if (modules) {
for (const module of modules) {
if (typeof module.routes !== "function") {
console.warn(`[ModuleRoutes] El módulo "${module.name}" no define una función 'routes()'`);
continue;
}
const moduleRoutes = module.routes(params);
if (!Array.isArray(moduleRoutes)) {
console.error(
`[ModuleRoutes] El módulo "${module.name}" debe devolver un RouteObject[], pero devolvió:`,
moduleRoutes
);
continue;
}
const allAreRouteObjects = moduleRoutes.every(
(r) => typeof r === "object" && r.element !== undefined
);
if (!allAreRouteObjects) {
console.error(
`[ModuleRoutes] El módulo "${module.name}" contiene elementos inválidos en su RouteObject[]`,
moduleRoutes
);
continue;
}
routes.push(...moduleRoutes);
}
}
return useRoutes(routes);
};

View File

@ -1,4 +1,5 @@
import { IModuleClient } from "@erp/core";
import InvoicesModule from "@erp/invoices/manifest";
import { AuthModuleManifiest } from "@erp/auth/client";
import { IModuleClient } from "@erp/core/client";
//import InvoicesModule from "@erp/invoices/client";
export const modules: IModuleClient[] = [InvoicesModule];
export const modules: IModuleClient[] = [AuthModuleManifiest];

View File

@ -0,0 +1,66 @@
import { ModuleRoutes } from "@/components/module-routes";
import { IModuleClient } from "@erp/core/client";
import { AppLayout, LoadingOverlay, ScrollToTop } from "@repo/rdx-ui/components";
import { JSX, Suspense } from "react";
import { Navigate, Route, BrowserRouter as Router, Routes } from "react-router-dom";
import { ErrorPage } from "../pages";
import { modules } from "../register-modules"; // Aquí ca
function groupModulesByLayout(modules: IModuleClient[]) {
const groups: Record<string, IModuleClient[]> = {
auth: [],
app: [],
};
if (modules) {
for (const module of modules) {
if (typeof module.layout !== "string") continue;
const layout = module.layout || "app";
groups[layout] = groups[layout] || [];
groups[layout].push(module);
}
}
return groups;
}
export const AppRoutes = (): JSX.Element => {
const params = {
...import.meta.env,
};
const grouped = groupModulesByLayout(modules);
return (
<Router>
<ScrollToTop />
<Suspense fallback={<LoadingOverlay />}>
<Routes>
{/* Auth Layout */}
<Route path='/auth'>
<Route index element={<Navigate to='login' />} />
<Route path='*' element={<ModuleRoutes modules={grouped.auth} params={params} />} />
</Route>
{/* App Layout */}
<Route element={<AppLayout />}>
{/* Dynamic Module Routes */}
<Route path='*' element={<ModuleRoutes modules={grouped.app} params={params} />} />
{/* Main Layout */}
<Route path='/dashboard' element={<ErrorPage />} />
<Route path='/settings' element={<ErrorPage />} />
<Route path='/catalog' element={<ErrorPage />} />
<Route path='/quotes' element={<ErrorPage />} />
<Route path='*' element={<ErrorPage />} />
</Route>
{/* Fallback Route */}
<Route path='*' element={<ErrorPage />} />
</Routes>
</Suspense>
</Router>
);
};

View File

@ -0,0 +1 @@
export * from "./app-routes";

View File

@ -1,4 +1,3 @@
import favicons from "@peterek/vite-plugin-favicons";
import tailwindcss from "@tailwindcss/vite";
import react from "@vitejs/plugin-react";
import path from "path";
@ -6,7 +5,7 @@ import { defineConfig } from "vite";
// https://vite.dev/config/
export default defineConfig({
plugins: [react(), tailwindcss(), favicons("public/logo.svg")],
plugins: [react(), tailwindcss()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),

View File

@ -1,27 +1,23 @@
{
"name": "@erp/auth",
"version": "0.0.1",
"main": "src/index.ts",
"types": "src/index.ts",
"exports": {
".": "./src/common/index.ts",
"./api": "./src/api/index.ts",
"./client": "./src/web/index.ts"
},
"peerDependencies": {
"react": "^18 || ^19",
"react-dom": "^18 || ^19"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@types/react": "^19.1.2",
"@types/react-dom": "^19.1.3",
"@types/react-i18next": "^8.1.0",
"typescript": "^5.8.3"
},
"dependencies": {
"@repo/shadcn-ui": "workspace:*",
"@repo/rdx-ui": "workspace:*",
"@erp/core": "workspace:*",
"i18next": "^25.1.1",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-router-dom": "^6.26.0",

View File

@ -0,0 +1,3 @@
{
"auth": {}
}

View File

@ -0,0 +1,3 @@
{
"auth": {}
}

View File

@ -0,0 +1,16 @@
import { ModuleClientParams } from "@erp/core/client";
import { RouteObject } from "react-router-dom";
import { LoginPage } from "./pages";
export const AuthRoutes = (params: ModuleClientParams): RouteObject[] => {
return [
{
path: "login",
element: <LoginPage />,
},
{
path: "register",
element: <div>Register</div>, // o tu componente real
},
];
};

View File

@ -1,11 +1,12 @@
import { JSX } from "react";
import { Navigate } from "react-router-dom";
import { useIsAuthenticated } from "../hooks";
/**
* Protege una ruta para usuarios autenticados.
*/
export const AuthGuard = ({ children }: { children: JSX.Element }) => {
const { isAuthenticated } = useAuth();
const isAuthenticated = useIsAuthenticated();
if (!isAuthenticated) {
return <Navigate to='/login' replace />;

View File

@ -1 +1 @@
export * from "./protected-route";
export * from "./auth-guard";

View File

@ -1,54 +0,0 @@
import { ErrorOverlay, LoadingOverlay } from "@repo/rdx-ui/components";
import React, { useId } from "react";
import { Navigate, useLocation } from "react-router-dom";
import { useGetProfile, useIsLoggedIn } from "../hooks.old";
type ProctectRouteProps = {
children?: React.ReactNode;
};
export const ProtectedRoute = ({ children }: ProctectRouteProps) => {
const location = useLocation();
//const { i18n } = useTranslation();
const id = useId();
const {
isSuccess: isLoggedInSuccess,
isLoading: isLoggedInLoading,
data: { authenticated = false, redirectTo = "/login" } = {},
} = useIsLoggedIn();
const {
isSuccess: isProfileSuccess,
isLoading: isProfileLoading,
error: profileError,
data: profile,
} = useGetProfile({
enabled: isLoggedInSuccess && authenticated,
});
// Cambiamos el idioma de la aplicación si es necesario
/*React.useEffect(() => {
if (isProfileSuccess && profile?.lang_code && i18n.language !== profile.lang_code) {
i18n.changeLanguage(profile.lang_code);
}
}, [isProfileSuccess, profile?.lang_code, i18n]);*/
if (isLoggedInLoading || isProfileLoading) {
return <LoadingOverlay />;
}
if (profileError) {
console.error("Error loading profile:", profileError);
return <ErrorOverlay subtitle={(profileError as Error).message} />;
}
// Redirección si el usuario no está autenticado
if ((isLoggedInSuccess && !authenticated) || (isProfileSuccess && !profile?.id)) {
console.debug("Not authenticated, redirecting to:", redirectTo);
return <Navigate to={redirectTo} state={{ from: location }} replace />;
}
// Renderizamos los hijos si el usuario está autenticado
return <div id={`${id}-${profile?.lang_code}`}>{children ?? null}</div>;
};

View File

@ -0,0 +1,3 @@
export * from "./useAuth";
export * from "./useCurrentUser";
export * from "./useIsAuthenticated";

View File

@ -1,2 +1,2 @@
export * from "./components";
export * from "./lib";
export * from "./manifest";

View File

@ -0,0 +1,24 @@
import { IModuleClient, ModuleClientParams } from "@erp/core/client";
import i18next from "i18next";
import enResources from "../common/locales/en.json";
import esResources from "../common/locales/es.json";
import { AuthRoutes } from "./auth-routes";
const MODULE_NAME = "auth";
const MODULE_VERSION = "1.0.0";
export const AuthModuleManifiest: IModuleClient = {
name: MODULE_NAME,
version: MODULE_VERSION,
dependencies: ["core"],
protected: false,
layout: "auth",
routes: (params: ModuleClientParams) => {
i18next.addResourceBundle("en", MODULE_NAME, enResources, true, true);
i18next.addResourceBundle("es", MODULE_NAME, esResources, true, true);
return AuthRoutes(params);
},
};
export default AuthModuleManifiest;

View File

@ -0,0 +1,7 @@
export const LoginPage = () => {
return (
<div>
<h2>Iniciar Sesión</h2>
</div>
);
};

View File

@ -0,0 +1 @@
export * from "./LoginPage";

View File

@ -1,8 +1,6 @@
{
"name": "@erp/core",
"version": "0.0.1",
"main": "src/index.ts",
"types": "src/index.ts",
"exports": {
".": "./src/common/index.ts",
"./api": "./src/api/index.ts",

View File

@ -1,4 +1,3 @@
export * from "../common/dto";
export * from "./infrastructure";
export * from "./logger";
export * from "./modules";

View File

@ -1,4 +1,3 @@
import { logger } from "@/lib/logger";
import { NextFunction, Request, Response } from "express";
import { ApiError } from "../api-error";
@ -13,7 +12,7 @@ export const globalErrorHandler = async (
return next(error);
}
logger.error(`❌ Unhandled API error: ${error.message}`);
//logger.error(`❌ Unhandled API error: ${error.message}`);
// Verifica si el error es una instancia de ApiError
if (error instanceof ApiError) {

View File

@ -1,3 +1,2 @@
export * from "./module-client.interface";
export * from "./module-server.interface";
export * from "./types";

View File

@ -1,14 +0,0 @@
import React, { JSX, ReactNode } from "react";
import { ModuleMetadata } from "./types";
export interface ModuleClientMetadata extends ModuleMetadata {
route: string;
icon?: ReactNode;
}
export interface IModuleClient {
metadata: ModuleClientMetadata;
routes?: () => JSX.Element;
component?: React.FC;
setup?(): void;
}

View File

@ -1,9 +1,9 @@
//Contrato para los Modules backend (Node.js)
import { ModuleDependencies, ModuleMetadata, ModuleParams } from "./types";
import { ModuleMetadata } from "../../common";
import { ModuleDependencies, ModuleParams } from "./types";
export interface IModuleServer {
metadata: ModuleMetadata;
export interface IModuleServer extends ModuleMetadata {
init(params: ModuleParams): void;
registerDependencies?(params: ModuleParams): ModuleDependencies;
}

View File

@ -1,5 +1,3 @@
// Contiene tipos comunes entre cliente y servidor
import { Model, ModelStatic, Sequelize } from "sequelize";
export interface SequelizeModel extends Model {
@ -13,13 +11,6 @@ export type ModuleParams = {
//export type ModelInitializer = typeof SequelizeModel;
export interface ModuleMetadata {
name: string;
version: string;
description?: string;
dependencies?: string[];
}
export interface ModuleDependencies {
models?: any[];
services?: { [key: string]: any };

View File

@ -0,0 +1 @@
export * from "./types";

View File

@ -0,0 +1,6 @@
export interface ModuleMetadata {
name: string;
version: string;
description?: string;
dependencies?: string[];
}

View File

@ -0,0 +1 @@
//

View File

@ -1,4 +1,3 @@
export * from "./use-datasource";
export * from "./use-pagination";
export * from "./use-query-key";
export * from "./use-toggle";

View File

@ -1,3 +1,2 @@
export * from "./api";
export * from "./components";
export * from "./hooks";
export * from "./lib";

View File

@ -0,0 +1,2 @@
export * from "./data-source";
export * from "./modules";

View File

@ -0,0 +1 @@
export * from "./module-client.interface";

View File

@ -0,0 +1,12 @@
import { ReactNode } from "react";
import { RouteObject } from "react-router-dom";
import { ModuleMetadata } from "../../../common";
export type ModuleClientParams = { [key: string]: any };
export interface IModuleClient extends ModuleMetadata {
protected?: boolean; // determina si las rutas deben ser protegidas
icon?: ReactNode;
routes?: (params: ModuleClientParams) => RouteObject[];
layout?: "app" | "auth";
}

View File

@ -4,14 +4,9 @@
"main": "src/index.ts",
"types": "src/index.ts",
"exports": {
".": "./src/index.ts",
".": "./src/common/index.ts",
"./api": "./src/api/index.ts",
"./dto": "./src/common/dto/index.ts",
"./manifest": "./src/web/manifest.ts",
"./hooks/*": ["./src/web/hooks/*.tsx", "./src/hooks/*.ts"],
"./components": "./src/web/components/index.tsx",
"./components/*": "./src/web/components/*.tsx",
"./locales": "./src/common/locales/index.tsx"
"./client": "./src/web/manifest.ts"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",

View File

@ -1 +0,0 @@
export * from "./api";

View File

@ -5,7 +5,7 @@ import {
useDataSource,
useList,
useQueryKey,
} from "@erp/core/hooks";
} from "@erp/core/client";
import { IListInvoicesResponseDTO } from "@erp/invoices/common/dto";
export type UseInvoicesListParams = Omit<IGetListDataProviderParams, "filters" | "resource"> & {

View File

@ -1,4 +1,4 @@
import { IModuleClient } from "@erp/core";
import { IModuleClient } from "@erp/core/client";
import i18next from "i18next";
import enResources from "../common/locales/en.json";
import esResources from "../common/locales/es.json";
@ -7,13 +7,13 @@ import { InvoiceRoutes } from "./invoice-routes";
const MODULE_NAME = "invoices";
const MODULE_VERSION = "1.0.0";
const InvoicesModule: IModuleClient = {
metadata: {
name: MODULE_NAME,
version: MODULE_VERSION,
route: "/invoices",
dependencies: ["contacts"],
},
export const InvoicesModuleManifest: IModuleClient = {
name: MODULE_NAME,
version: MODULE_VERSION,
dependencies: ["auth"],
protected: true,
layout: "app",
routes: () => {
i18next.addResourceBundle("en", MODULE_NAME, enResources, true, true);
i18next.addResourceBundle("es", MODULE_NAME, esResources, true, true);
@ -21,4 +21,4 @@ const InvoicesModule: IModuleClient = {
},
};
export default InvoicesModule;
export default InvoicesModuleManifest;

View File

@ -5,7 +5,7 @@
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Node",
"moduleResolution": "Bundler",
"importsNotUsedAsValues": "remove",
"esModuleInterop": true,
"skipLibCheck": true,

View File

@ -41,9 +41,6 @@ importers:
'@erp/core':
specifier: workspace:*
version: link:../../modules/core
'@erp/invoices':
specifier: workspace:*
version: link:../../modules/invoices
bcrypt:
specifier: ^5.1.1
version: 5.1.1
@ -231,6 +228,9 @@ importers:
react-router-dom:
specifier: ^6.26.0
version: 6.30.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
react-secure-storage:
specifier: ^1.3.2
version: 1.3.2
sequelize:
specifier: ^6.37.5
version: 6.37.7(mysql2@3.14.1)
@ -253,9 +253,6 @@ importers:
'@hookform/devtools':
specifier: ^4.4.0
version: 4.4.0(@types/react@19.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@peterek/vite-plugin-favicons':
specifier: ^2.1.0
version: 2.1.0(vite@6.3.5(@types/node@22.15.12)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(sass@1.87.0)(stylus@0.62.0)(terser@5.39.0)(tsx@4.19.4)(yaml@2.7.1))
'@repo/typescript-config':
specifier: workspace:*
version: link:../../packages/typescript-config
@ -304,6 +301,9 @@ importers:
'@repo/shadcn-ui':
specifier: workspace:*
version: link:../../packages/shadcn-ui
i18next:
specifier: ^25.1.1
version: 25.1.1(typescript@5.8.3)
react:
specifier: ^19.1.0
version: 19.1.0
@ -326,6 +326,9 @@ importers:
'@types/react-dom':
specifier: ^19.1.3
version: 19.1.3(@types/react@19.1.3)
'@types/react-i18next':
specifier: ^8.1.0
version: 8.1.0(i18next@25.1.1(typescript@5.8.3))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(typescript@5.8.3)
typescript:
specifier: ^5.8.3
version: 5.8.3
@ -1105,9 +1108,6 @@ packages:
peerDependencies:
react: '>=16.8.0'
'@emnapi/runtime@1.4.3':
resolution: {integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==}
'@emotion/babel-plugin@11.13.5':
resolution: {integrity: sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==}
@ -1344,111 +1344,6 @@ packages:
peerDependencies:
react-hook-form: ^7.55.0
'@img/sharp-darwin-arm64@0.33.5':
resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [darwin]
'@img/sharp-darwin-x64@0.33.5':
resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [darwin]
'@img/sharp-libvips-darwin-arm64@1.0.4':
resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==}
cpu: [arm64]
os: [darwin]
'@img/sharp-libvips-darwin-x64@1.0.4':
resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==}
cpu: [x64]
os: [darwin]
'@img/sharp-libvips-linux-arm64@1.0.4':
resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==}
cpu: [arm64]
os: [linux]
'@img/sharp-libvips-linux-arm@1.0.5':
resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==}
cpu: [arm]
os: [linux]
'@img/sharp-libvips-linux-s390x@1.0.4':
resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==}
cpu: [s390x]
os: [linux]
'@img/sharp-libvips-linux-x64@1.0.4':
resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==}
cpu: [x64]
os: [linux]
'@img/sharp-libvips-linuxmusl-arm64@1.0.4':
resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==}
cpu: [arm64]
os: [linux]
'@img/sharp-libvips-linuxmusl-x64@1.0.4':
resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==}
cpu: [x64]
os: [linux]
'@img/sharp-linux-arm64@0.33.5':
resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [linux]
'@img/sharp-linux-arm@0.33.5':
resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm]
os: [linux]
'@img/sharp-linux-s390x@0.33.5':
resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [s390x]
os: [linux]
'@img/sharp-linux-x64@0.33.5':
resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [linux]
'@img/sharp-linuxmusl-arm64@0.33.5':
resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [arm64]
os: [linux]
'@img/sharp-linuxmusl-x64@0.33.5':
resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [linux]
'@img/sharp-wasm32@0.33.5':
resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [wasm32]
'@img/sharp-win32-ia32@0.33.5':
resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [ia32]
os: [win32]
'@img/sharp-win32-x64@0.33.5':
resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
cpu: [x64]
os: [win32]
'@inquirer/checkbox@4.1.5':
resolution: {integrity: sha512-swPczVU+at65xa5uPfNP9u3qx/alNwiaykiI/ExpsmMSQW55trmZcwhYWzw/7fj+n6Q8z1eENvR7vFfq9oPSAQ==}
engines: {node: '>=18'}
@ -1774,12 +1669,6 @@ packages:
resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==}
engines: {node: '>= 10.0.0'}
'@peterek/vite-plugin-favicons@2.1.0':
resolution: {integrity: sha512-jx2eg6xXTXB+zezx6tPoS3WVlO7Kfep+brnmt4P5Jf4sdbNul1HqhyzlsRHORbXgs/MxF3fmRiaRU3dcgs49wQ==}
engines: {node: '>=18.0.0'}
peerDependencies:
vite: '>=5.0.0'
'@pkgjs/parseargs@0.11.0':
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
@ -3341,10 +3230,6 @@ packages:
color@3.2.1:
resolution: {integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==}
color@4.2.3:
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
engines: {node: '>=12.5.0'}
colorette@2.0.20:
resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
@ -3820,10 +3705,6 @@ packages:
fastq@1.19.1:
resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
favicons@7.2.0:
resolution: {integrity: sha512-k/2rVBRIRzOeom3wI9jBPaSEvoTSQEW4iM0EveBmBBKFxO8mSyyRWtDlfC3VnEfu0avmjrMzy8/ZFPSe6F71Hw==}
engines: {node: '>=14.0.0'}
fb-watchman@2.0.2:
resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==}
@ -5642,10 +5523,6 @@ packages:
shallow-equal-object@1.1.1:
resolution: {integrity: sha512-9DDzYRlzCwF2CemeF0aOFk5T5KMrjG7HldcW7utwYhA/limuGHn3No8KhpDE8BrO7GLaSRJumNKReipZBybd7A==}
sharp@0.33.5:
resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
engines: {node: '>=8'}
@ -6312,14 +6189,6 @@ packages:
resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==}
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
xml2js@0.6.2:
resolution: {integrity: sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==}
engines: {node: '>=4.0.0'}
xmlbuilder@11.0.1:
resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==}
engines: {node: '>=4.0'}
y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
@ -6661,11 +6530,6 @@ snapshots:
react: 19.1.0
tslib: 2.8.1
'@emnapi/runtime@1.4.3':
dependencies:
tslib: 2.8.1
optional: true
'@emotion/babel-plugin@11.13.5':
dependencies:
'@babel/helper-module-imports': 7.27.1
@ -6868,81 +6732,6 @@ snapshots:
'@standard-schema/utils': 0.3.0
react-hook-form: 7.56.2(react@19.1.0)
'@img/sharp-darwin-arm64@0.33.5':
optionalDependencies:
'@img/sharp-libvips-darwin-arm64': 1.0.4
optional: true
'@img/sharp-darwin-x64@0.33.5':
optionalDependencies:
'@img/sharp-libvips-darwin-x64': 1.0.4
optional: true
'@img/sharp-libvips-darwin-arm64@1.0.4':
optional: true
'@img/sharp-libvips-darwin-x64@1.0.4':
optional: true
'@img/sharp-libvips-linux-arm64@1.0.4':
optional: true
'@img/sharp-libvips-linux-arm@1.0.5':
optional: true
'@img/sharp-libvips-linux-s390x@1.0.4':
optional: true
'@img/sharp-libvips-linux-x64@1.0.4':
optional: true
'@img/sharp-libvips-linuxmusl-arm64@1.0.4':
optional: true
'@img/sharp-libvips-linuxmusl-x64@1.0.4':
optional: true
'@img/sharp-linux-arm64@0.33.5':
optionalDependencies:
'@img/sharp-libvips-linux-arm64': 1.0.4
optional: true
'@img/sharp-linux-arm@0.33.5':
optionalDependencies:
'@img/sharp-libvips-linux-arm': 1.0.5
optional: true
'@img/sharp-linux-s390x@0.33.5':
optionalDependencies:
'@img/sharp-libvips-linux-s390x': 1.0.4
optional: true
'@img/sharp-linux-x64@0.33.5':
optionalDependencies:
'@img/sharp-libvips-linux-x64': 1.0.4
optional: true
'@img/sharp-linuxmusl-arm64@0.33.5':
optionalDependencies:
'@img/sharp-libvips-linuxmusl-arm64': 1.0.4
optional: true
'@img/sharp-linuxmusl-x64@0.33.5':
optionalDependencies:
'@img/sharp-libvips-linuxmusl-x64': 1.0.4
optional: true
'@img/sharp-wasm32@0.33.5':
dependencies:
'@emnapi/runtime': 1.4.3
optional: true
'@img/sharp-win32-ia32@0.33.5':
optional: true
'@img/sharp-win32-x64@0.33.5':
optional: true
'@inquirer/checkbox@4.1.5(@types/node@22.15.12)':
dependencies:
'@inquirer/core': 10.1.10(@types/node@22.15.12)
@ -7358,11 +7147,6 @@ snapshots:
'@parcel/watcher-win32-ia32': 2.5.1
'@parcel/watcher-win32-x64': 2.5.1
'@peterek/vite-plugin-favicons@2.1.0(vite@6.3.5(@types/node@22.15.12)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(sass@1.87.0)(stylus@0.62.0)(terser@5.39.0)(tsx@4.19.4)(yaml@2.7.1))':
dependencies:
favicons: 7.2.0
vite: 6.3.5(@types/node@22.15.12)(jiti@2.4.2)(less@4.3.0)(lightningcss@1.30.1)(sass@1.87.0)(stylus@0.62.0)(terser@5.39.0)(tsx@4.19.4)(yaml@2.7.1)
'@pkgjs/parseargs@0.11.0':
optional: true
@ -8987,11 +8771,6 @@ snapshots:
color-convert: 1.9.3
color-string: 1.9.1
color@4.2.3:
dependencies:
color-convert: 2.0.1
color-string: 1.9.1
colorette@2.0.20: {}
colorspace@1.1.4:
@ -9471,12 +9250,6 @@ snapshots:
dependencies:
reusify: 1.1.0
favicons@7.2.0:
dependencies:
escape-html: 1.0.3
sharp: 0.33.5
xml2js: 0.6.2
fb-watchman@2.0.2:
dependencies:
bser: 2.1.1
@ -11352,7 +11125,8 @@ snapshots:
sax@1.3.0: {}
sax@1.4.1: {}
sax@1.4.1:
optional: true
scheduler@0.26.0: {}
@ -11430,32 +11204,6 @@ snapshots:
shallow-equal-object@1.1.1: {}
sharp@0.33.5:
dependencies:
color: 4.2.3
detect-libc: 2.0.4
semver: 7.7.1
optionalDependencies:
'@img/sharp-darwin-arm64': 0.33.5
'@img/sharp-darwin-x64': 0.33.5
'@img/sharp-libvips-darwin-arm64': 1.0.4
'@img/sharp-libvips-darwin-x64': 1.0.4
'@img/sharp-libvips-linux-arm': 1.0.5
'@img/sharp-libvips-linux-arm64': 1.0.4
'@img/sharp-libvips-linux-s390x': 1.0.4
'@img/sharp-libvips-linux-x64': 1.0.4
'@img/sharp-libvips-linuxmusl-arm64': 1.0.4
'@img/sharp-libvips-linuxmusl-x64': 1.0.4
'@img/sharp-linux-arm': 0.33.5
'@img/sharp-linux-arm64': 0.33.5
'@img/sharp-linux-s390x': 0.33.5
'@img/sharp-linux-x64': 0.33.5
'@img/sharp-linuxmusl-arm64': 0.33.5
'@img/sharp-linuxmusl-x64': 0.33.5
'@img/sharp-wasm32': 0.33.5
'@img/sharp-win32-ia32': 0.33.5
'@img/sharp-win32-x64': 0.33.5
shebang-command@2.0.0:
dependencies:
shebang-regex: 3.0.0
@ -12140,13 +11888,6 @@ snapshots:
imurmurhash: 0.1.4
signal-exit: 3.0.7
xml2js@0.6.2:
dependencies:
sax: 1.4.1
xmlbuilder: 11.0.1
xmlbuilder@11.0.1: {}
y18n@5.0.8: {}
yallist@3.1.1: {}