@zauru-sdk/components 6.3.2 → 6.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/dist/Containers/BodyContainer.d.ts +3 -0
- package/dist/Layouts/errorLayout/index.d.ts +3 -1
- package/dist/Routes/ZauruHomePage.d.ts +7 -0
- package/dist/Routes/ZauruIndexPage.d.ts +7 -0
- package/dist/Routes/ZauruLoginPage.d.ts +7 -0
- package/dist/Routes/ZauruLogoutPage.d.ts +7 -0
- package/dist/Routes/ZauruReloadCatalogsPage.d.ts +8 -0
- package/dist/Routes/index.d.ts +6 -0
- package/dist/Routes/zauruRouteErrorBoundary.d.ts +4 -0
- package/dist/esm/Containers/BodyContainer.js +6 -1
- package/dist/esm/Layouts/errorLayout/index.js +21 -2
- package/dist/esm/Routes/ZauruHomePage.js +67 -0
- package/dist/esm/Routes/ZauruIndexPage.js +11 -0
- package/dist/esm/Routes/ZauruLoginPage.js +56 -0
- package/dist/esm/Routes/ZauruLogoutPage.js +32 -0
- package/dist/esm/Routes/ZauruReloadCatalogsPage.js +22 -0
- package/dist/esm/Routes/index.js +6 -0
- package/dist/esm/Routes/zauruRouteErrorBoundary.js +9 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/observability/ZauruObservability.js +9 -0
- package/dist/esm/observability/index.js +3 -0
- package/dist/esm/observability/sendClientError.js +48 -0
- package/dist/esm/observability/useZauruClientErrorReporting.js +41 -0
- package/dist/index.d.ts +2 -0
- package/dist/observability/ZauruObservability.d.ts +6 -0
- package/dist/observability/index.d.ts +3 -0
- package/dist/observability/sendClientError.d.ts +24 -0
- package/dist/observability/useZauruClientErrorReporting.d.ts +9 -0
- package/package.json +3 -3
- package/src/Containers/BodyContainer.tsx +9 -1
- package/src/Layouts/errorLayout/index.tsx +22 -1
- package/src/Routes/ZauruHomePage.tsx +113 -0
- package/src/Routes/ZauruIndexPage.tsx +21 -0
- package/src/Routes/ZauruLoginPage.tsx +100 -0
- package/src/Routes/ZauruLogoutPage.tsx +52 -0
- package/src/Routes/ZauruReloadCatalogsPage.tsx +45 -0
- package/src/Routes/index.ts +6 -0
- package/src/Routes/zauruRouteErrorBoundary.tsx +9 -0
- package/src/index.ts +2 -0
- package/src/observability/ZauruObservability.tsx +11 -0
- package/src/observability/index.ts +3 -0
- package/src/observability/sendClientError.ts +64 -0
- package/src/observability/useZauruClientErrorReporting.ts +56 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,28 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [6.5.0](https://github.com/intuitiva/zauru-typescript-sdk/compare/@zauru-sdk/components@6.4.0...@zauru-sdk/components@6.5.0) (2026-09-16)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* extraer CloudWatch y observabilidad de Remix al SDK ([425af0d](https://github.com/intuitiva/zauru-typescript-sdk/commit/425af0dcd1955d2739204b3f69f9d77090553141))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# [6.4.0](https://github.com/intuitiva/zauru-typescript-sdk/compare/@zauru-sdk/components@6.3.2...@zauru-sdk/components@6.4.0) (2026-09-16)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
* extraer login, logout, home e index a factories del SDK ([83312db](https://github.com/intuitiva/zauru-typescript-sdk/commit/83312db6778188df9852aa2afbf7d45ae3782b47))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
6
28
|
## [6.3.2](https://github.com/intuitiva/zauru-typescript-sdk/compare/@zauru-sdk/components@6.3.1...@zauru-sdk/components@6.3.2) (2026-09-16)
|
|
7
29
|
|
|
8
30
|
**Note:** Version bump only for package @zauru-sdk/components
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
type Props = {
|
|
3
3
|
children: React.ReactNode;
|
|
4
|
+
appVersion?: string;
|
|
5
|
+
/** Default true. Captures window.onerror / unhandledrejection. */
|
|
6
|
+
reportClientErrors?: boolean;
|
|
4
7
|
};
|
|
5
8
|
export declare const BodyContainer: (props: Props) => import("react/jsx-runtime").JSX.Element;
|
|
6
9
|
export {};
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
export declare const ErrorLayout: ({ from, isRootLevel, error: parentError, onError, }: {
|
|
1
|
+
export declare const ErrorLayout: ({ from, isRootLevel, error: parentError, onError, reportErrors, }: {
|
|
2
2
|
from?: string;
|
|
3
3
|
isRootLevel?: boolean;
|
|
4
4
|
error?: Error;
|
|
5
5
|
onError?: (error: Error, meta: {
|
|
6
6
|
from?: string;
|
|
7
7
|
}) => void;
|
|
8
|
+
/** Default true. POSTs to `/api/client-errors` unless `onError` is passed. */
|
|
9
|
+
reportErrors?: boolean;
|
|
8
10
|
}) => import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
export type ZauruHomePageProps = {
|
|
3
|
+
title: string;
|
|
4
|
+
includeAgency?: boolean;
|
|
5
|
+
extra?: ReactNode;
|
|
6
|
+
};
|
|
7
|
+
export declare function ZauruHomePage({ title, includeAgency, extra, }: ZauruHomePageProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ZauruHomeLayoutColor } from "./ZauruLoginPage.js";
|
|
2
|
+
export type ZauruIndexPageProps = {
|
|
3
|
+
title: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
color?: ZauruHomeLayoutColor;
|
|
6
|
+
};
|
|
7
|
+
export declare function ZauruIndexPage({ title, description, color, }: ZauruIndexPageProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { cleanLocalStorage } from "@zauru-sdk/redux";
|
|
2
|
+
export type ZauruHomeLayoutColor = "green" | "blue" | "red" | "purple" | "yellow";
|
|
3
|
+
export type ZauruLoginPageProps = {
|
|
4
|
+
cleanLocalStorageArgs?: Parameters<typeof cleanLocalStorage>[0];
|
|
5
|
+
color?: ZauruHomeLayoutColor;
|
|
6
|
+
};
|
|
7
|
+
export declare function ZauruLoginPage({ cleanLocalStorageArgs, color, }: ZauruLoginPageProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ZauruHomeLayoutColor } from "./ZauruLoginPage.js";
|
|
2
|
+
export type ZauruLogoutPageProps = {
|
|
3
|
+
requireOnline?: boolean;
|
|
4
|
+
beforeLogout?: () => boolean | void;
|
|
5
|
+
color?: ZauruHomeLayoutColor;
|
|
6
|
+
};
|
|
7
|
+
export declare function ZauruLogoutPage({ requireOnline, beforeLogout, color, }: ZauruLogoutPageProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type ZauruReloadCatalogItem = {
|
|
2
|
+
name: string;
|
|
3
|
+
loading: boolean;
|
|
4
|
+
};
|
|
5
|
+
export type ZauruReloadCatalogsPageProps = {
|
|
6
|
+
items: ZauruReloadCatalogItem[];
|
|
7
|
+
};
|
|
8
|
+
export declare function ZauruReloadCatalogsPage({ items, }: ZauruReloadCatalogsPageProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useZauruClientErrorReporting } from "../observability/useZauruClientErrorReporting.js";
|
|
2
3
|
export const BodyContainer = (props) => {
|
|
3
|
-
const { children } = props;
|
|
4
|
+
const { children, appVersion, reportClientErrors = true } = props;
|
|
5
|
+
useZauruClientErrorReporting({
|
|
6
|
+
appVersion,
|
|
7
|
+
enabled: reportClientErrors,
|
|
8
|
+
});
|
|
4
9
|
return _jsx("body", { className: "flex flex-col min-h-screen m-0", children: children });
|
|
5
10
|
};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { isRouteErrorResponse, Links, Meta, Scripts, useRouteError, Link, } from "@remix-run/react";
|
|
3
3
|
import { useEffect, useState } from "react";
|
|
4
|
-
|
|
4
|
+
import { sendClientError } from "../../observability/sendClientError.js";
|
|
5
|
+
export const ErrorLayout = ({ from, isRootLevel = true, error: parentError, onError, reportErrors = true, }) => {
|
|
5
6
|
try {
|
|
6
7
|
const error = useRouteError();
|
|
7
8
|
const [showDetails, setShowDetails] = useState(!!parentError);
|
|
@@ -10,7 +11,25 @@ export const ErrorLayout = ({ from, isRootLevel = true, error: parentError, onEr
|
|
|
10
11
|
if (reportable instanceof Error) {
|
|
11
12
|
onError?.(reportable, { from });
|
|
12
13
|
}
|
|
13
|
-
|
|
14
|
+
if (!reportErrors || onError)
|
|
15
|
+
return;
|
|
16
|
+
if (reportable instanceof Error) {
|
|
17
|
+
sendClientError({
|
|
18
|
+
message: reportable.message,
|
|
19
|
+
stack: reportable.stack,
|
|
20
|
+
source: "errorBoundary",
|
|
21
|
+
url: from,
|
|
22
|
+
});
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (isRouteErrorResponse(error)) {
|
|
26
|
+
sendClientError({
|
|
27
|
+
message: `Error en request: ${error.status}: ${error.statusText}`,
|
|
28
|
+
source: "errorBoundary",
|
|
29
|
+
url: from,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}, [error, parentError, from, onError, reportErrors]);
|
|
14
33
|
const baseError = (_jsxs("div", { className: "min-h-screen flex flex-col items-center justify-center p-4", children: [_jsx("img", { src: "/logo.png", alt: "Zauru Logo", className: "mb-8 h-20" }), _jsx("h1", { className: "text-5xl font-extrabold text-red-500 mb-6", children: "\u00A1Ups!" }), _jsxs("div", { className: "w-full max-w-2xl flex flex-col items-center", children: [_jsx("p", { className: "text-2xl text-gray-300 mb-8 text-center", children: isRouteErrorResponse(error)
|
|
15
34
|
? `Error en request: ${error.status}: ${error.statusText} - PATH: ${error.data.path} - MESSAGE: ${error.data.message}`
|
|
16
35
|
: error instanceof Error
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { useGetAgencyProfile, useGetUserProfile } from "@zauru-sdk/hooks";
|
|
4
|
+
import { getInitialProfileInfo } from "@zauru-sdk/utils";
|
|
5
|
+
import { LabelArray } from "../Titles/LabelArray.js";
|
|
6
|
+
import { ZauruTable } from "../Table/ZauruTable.js";
|
|
7
|
+
import { Container } from "../Containers/Container.js";
|
|
8
|
+
import { DoubleContainer } from "../Containers/DoubleContainer.js";
|
|
9
|
+
import { MainContainer } from "../Containers/MainContainer.js";
|
|
10
|
+
import { SubContainer } from "../Containers/SubContainer.js";
|
|
11
|
+
import { ErrorLayout } from "../Layouts/errorLayout/index.js";
|
|
12
|
+
import { LoadingWindow } from "../Skeletons/LoadingWindow.js";
|
|
13
|
+
export function ZauruHomePage({ title, includeAgency = true, extra, }) {
|
|
14
|
+
try {
|
|
15
|
+
const [isClient, setIsClient] = useState(false);
|
|
16
|
+
const { data: userProfile } = useGetUserProfile();
|
|
17
|
+
const { data: agencyProfile, loading: loadingAgencyProfile } = useGetAgencyProfile();
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
setIsClient(true);
|
|
20
|
+
}, []);
|
|
21
|
+
const { profileLabelInfo, membershipsInfo } = getInitialProfileInfo(userProfile);
|
|
22
|
+
const conditionalRowStyles = [
|
|
23
|
+
{
|
|
24
|
+
when: (row) => row.odd,
|
|
25
|
+
style: {
|
|
26
|
+
backgroundColor: "white",
|
|
27
|
+
"&:hover": {
|
|
28
|
+
cursor: "pointer",
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
when: (row) => row.entityId === userProfile?.profile?.selected_entity_id,
|
|
34
|
+
style: () => ({ backgroundColor: "lightgreen" }),
|
|
35
|
+
},
|
|
36
|
+
];
|
|
37
|
+
const columns = [
|
|
38
|
+
{
|
|
39
|
+
name: "Entidad",
|
|
40
|
+
selector: (row) => row.entity,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: "Contrato Expira",
|
|
44
|
+
selector: (row) => row.contractExpiration,
|
|
45
|
+
width: "10rem",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: "Pagado hasta",
|
|
49
|
+
selector: (row) => row.nextPay,
|
|
50
|
+
width: "10rem",
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
if (!isClient) {
|
|
54
|
+
return _jsx(LoadingWindow, { description: "Cargando home..." });
|
|
55
|
+
}
|
|
56
|
+
const pageTitle = includeAgency
|
|
57
|
+
? loadingAgencyProfile || !agencyProfile
|
|
58
|
+
? "Cargando..."
|
|
59
|
+
: `${title} (${agencyProfile?.name ?? "Usuario sin agencia asignada"})`
|
|
60
|
+
: title;
|
|
61
|
+
const profileAndMemberships = (_jsxs(_Fragment, { children: [_jsx(SubContainer, { title: "Perfil", children: _jsx(LabelArray, { info: profileLabelInfo }) }), _jsx(SubContainer, { title: "Suscripciones", children: _jsx(ZauruTable, { columns: columns, conditionalRowStyles: conditionalRowStyles, data: membershipsInfo }) })] }));
|
|
62
|
+
return (_jsx(MainContainer, { children: extra ? (_jsxs(DoubleContainer, { title: pageTitle, children: [profileAndMemberships, extra] })) : (_jsx(Container, { title: pageTitle, children: profileAndMemberships })) }));
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
return (_jsx(ErrorLayout, { from: "/home/index.tsx", error: error, isRootLevel: false }));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { ErrorLayout } from "../Layouts/errorLayout/index.js";
|
|
3
|
+
import { HomeLayout } from "../Layouts/homeLayout/index.js";
|
|
4
|
+
export function ZauruIndexPage({ title, description = "Inicie sesión para poder continuar.", color, }) {
|
|
5
|
+
try {
|
|
6
|
+
return _jsx(HomeLayout, { title: title, description: description, color: color });
|
|
7
|
+
}
|
|
8
|
+
catch (error) {
|
|
9
|
+
return _jsx(ErrorLayout, { from: "/index.tsx", error: error, isRootLevel: false });
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useFetcher } from "@remix-run/react";
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { useValidateNotifications } from "@zauru-sdk/hooks";
|
|
5
|
+
import { cleanLocalStorage, profileFetchSuccess, useAppDispatch, } from "@zauru-sdk/redux";
|
|
6
|
+
import { ErrorLayout } from "../Layouts/errorLayout/index.js";
|
|
7
|
+
import { HomeLayout } from "../Layouts/homeLayout/index.js";
|
|
8
|
+
import { LoadingWindow } from "../Skeletons/LoadingWindow.js";
|
|
9
|
+
export function ZauruLoginPage({ cleanLocalStorageArgs, color, }) {
|
|
10
|
+
try {
|
|
11
|
+
const fetcher = useFetcher();
|
|
12
|
+
const dispatch = useAppDispatch();
|
|
13
|
+
const [isClient, setIsClient] = useState(false);
|
|
14
|
+
useValidateNotifications({ fetcher });
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (fetcher.data?.employeeProfile) {
|
|
17
|
+
dispatch(profileFetchSuccess({
|
|
18
|
+
name: "agencyProfile",
|
|
19
|
+
data: fetcher.data.agencyProfile,
|
|
20
|
+
}));
|
|
21
|
+
dispatch(profileFetchSuccess({
|
|
22
|
+
name: "employeeProfile",
|
|
23
|
+
data: fetcher.data.employeeProfile,
|
|
24
|
+
}));
|
|
25
|
+
dispatch(profileFetchSuccess({
|
|
26
|
+
name: "userProfile",
|
|
27
|
+
data: fetcher.data.userProfile,
|
|
28
|
+
}));
|
|
29
|
+
dispatch(profileFetchSuccess({
|
|
30
|
+
name: "oauthProfile",
|
|
31
|
+
data: fetcher.data.oauthProfile,
|
|
32
|
+
}));
|
|
33
|
+
fetcher.submit({
|
|
34
|
+
action: "redirect",
|
|
35
|
+
cookie: fetcher.data?.cookie,
|
|
36
|
+
}, { method: "post" });
|
|
37
|
+
}
|
|
38
|
+
}, [fetcher.data]);
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
setIsClient(true);
|
|
41
|
+
}, []);
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
cleanLocalStorage(cleanLocalStorageArgs);
|
|
44
|
+
fetcher.submit({ action: "login" }, { method: "post" });
|
|
45
|
+
}, []);
|
|
46
|
+
if (!isClient) {
|
|
47
|
+
return _jsx(LoadingWindow, {});
|
|
48
|
+
}
|
|
49
|
+
return (_jsx(HomeLayout, { loading: true, color: color, title: fetcher.data?.title ? "Error al inciar sesión!" : "Iniciando sesión....", description: fetcher.data?.description ??
|
|
50
|
+
"Cargando (Perfil zauru, perfil oauth, agencia, membresías, webapptables)..." }));
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
console.log(error);
|
|
54
|
+
return (_jsx(ErrorLayout, { from: "/login/index.tsx", error: error, isRootLevel: false }));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useFetcher, useNavigate } from "@remix-run/react";
|
|
3
|
+
import { useEffect } from "react";
|
|
4
|
+
import { showAlert, useIsOnline } from "@zauru-sdk/hooks";
|
|
5
|
+
import { ErrorLayout } from "../Layouts/errorLayout/index.js";
|
|
6
|
+
import { HomeLayout } from "../Layouts/homeLayout/index.js";
|
|
7
|
+
export function ZauruLogoutPage({ requireOnline = true, beforeLogout, color, }) {
|
|
8
|
+
try {
|
|
9
|
+
const fetcher = useFetcher();
|
|
10
|
+
const navigate = useNavigate();
|
|
11
|
+
const isOnline = useIsOnline();
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (beforeLogout?.() === false) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
if (requireOnline && !isOnline) {
|
|
17
|
+
showAlert({
|
|
18
|
+
title: "No es posible cerrar sesión",
|
|
19
|
+
description: "No hay conexión a internet",
|
|
20
|
+
type: "error",
|
|
21
|
+
});
|
|
22
|
+
navigate("/home");
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
fetcher.submit({ action: "logout" }, { method: "post" });
|
|
26
|
+
}, []);
|
|
27
|
+
return (_jsx(HomeLayout, { color: color, title: "Cerrando sesi\u00F3n", description: "Espere mientras se cierra su sesi\u00F3n..." }));
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
return (_jsx(ErrorLayout, { from: "/logout/index.tsx", error: error, isRootLevel: false }));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { ErrorLayout } from "../Layouts/errorLayout/index.js";
|
|
4
|
+
import { LoadingWindow } from "../Skeletons/LoadingWindow.js";
|
|
5
|
+
export function ZauruReloadCatalogsPage({ items, }) {
|
|
6
|
+
try {
|
|
7
|
+
const [isClient, setIsClient] = useState(false);
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
setIsClient(true);
|
|
10
|
+
}, []);
|
|
11
|
+
if (!isClient) {
|
|
12
|
+
return _jsx(LoadingWindow, {});
|
|
13
|
+
}
|
|
14
|
+
const loading = items.some((item) => item.loading);
|
|
15
|
+
return (_jsx(LoadingWindow, { description: loading
|
|
16
|
+
? "Cargando catálogos..."
|
|
17
|
+
: "Catálogos cargados correctamente ✅", loadingItems: items }));
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
return (_jsx(ErrorLayout, { from: "/reload-catalogs", error: error, isRootLevel: false }));
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { ErrorLayout } from "../Layouts/errorLayout/index.js";
|
|
3
|
+
export function zauruRouteErrorBoundary(from) {
|
|
4
|
+
function ZauruRouteErrorBoundary() {
|
|
5
|
+
return _jsx(ErrorLayout, { from: from });
|
|
6
|
+
}
|
|
7
|
+
ZauruRouteErrorBoundary.displayName = `ZauruRouteErrorBoundary(${from})`;
|
|
8
|
+
return ZauruRouteErrorBoundary;
|
|
9
|
+
}
|
package/dist/esm/index.js
CHANGED
|
@@ -24,4 +24,6 @@ export * from "./WithTooltip/index.js";
|
|
|
24
24
|
export * from "./Wizards/index.js";
|
|
25
25
|
export * from "./Zendesk/index.js";
|
|
26
26
|
export * from "./HOC/ValidateEmployeeAccess/index.js";
|
|
27
|
+
export * from "./Routes/index.js";
|
|
27
28
|
export * from "./SidePanel/index.js";
|
|
29
|
+
export * from "./observability/index.js";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { useZauruClientErrorReporting } from "./useZauruClientErrorReporting.js";
|
|
2
|
+
/**
|
|
3
|
+
* Optional drop-in for apps that do not use `BodyContainer`.
|
|
4
|
+
* `BodyContainer` already installs the same listeners.
|
|
5
|
+
*/
|
|
6
|
+
export function ZauruObservability(options) {
|
|
7
|
+
useZauruClientErrorReporting(options);
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client helper to report errors to `/api/client-errors`, which forwards
|
|
3
|
+
* them to CloudWatch Logs (with host logs as fallback).
|
|
4
|
+
*
|
|
5
|
+
* `keepalive: true` keeps the request alive if the page unloads right after.
|
|
6
|
+
*/
|
|
7
|
+
const ENDPOINT = "/api/client-errors";
|
|
8
|
+
let lastReportedKey = "";
|
|
9
|
+
let defaultAppVersion;
|
|
10
|
+
export function setClientErrorAppVersion(version) {
|
|
11
|
+
defaultAppVersion = version;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Sends a client error to the server. Best-effort: never throws.
|
|
15
|
+
* Deduplicates identical consecutive reports so re-renders of the same
|
|
16
|
+
* error do not flood CloudWatch.
|
|
17
|
+
*/
|
|
18
|
+
export function sendClientError(payload) {
|
|
19
|
+
if (typeof window === "undefined")
|
|
20
|
+
return;
|
|
21
|
+
const body = {
|
|
22
|
+
message: payload.message,
|
|
23
|
+
stack: payload.stack,
|
|
24
|
+
source: payload.source ?? "unknown",
|
|
25
|
+
url: payload.url ?? window.location.href,
|
|
26
|
+
userAgent: payload.userAgent ?? navigator.userAgent,
|
|
27
|
+
appVersion: payload.appVersion ?? defaultAppVersion,
|
|
28
|
+
timestamp: payload.timestamp ?? Date.now(),
|
|
29
|
+
};
|
|
30
|
+
const dedupKey = `${body.source}|${body.message}|${body.url}`;
|
|
31
|
+
if (dedupKey === lastReportedKey)
|
|
32
|
+
return;
|
|
33
|
+
lastReportedKey = dedupKey;
|
|
34
|
+
try {
|
|
35
|
+
fetch(ENDPOINT, {
|
|
36
|
+
method: "POST",
|
|
37
|
+
headers: { "Content-Type": "application/json" },
|
|
38
|
+
body: JSON.stringify(body),
|
|
39
|
+
keepalive: true,
|
|
40
|
+
credentials: "same-origin",
|
|
41
|
+
}).catch(() => {
|
|
42
|
+
// Silent: reporting must not break UX.
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// Silent.
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { sendClientError, setClientErrorAppVersion, } from "./sendClientError.js";
|
|
3
|
+
/**
|
|
4
|
+
* Captures `window.onerror` and `unhandledrejection` and POSTs them to
|
|
5
|
+
* `/api/client-errors`. Used by `BodyContainer` by default.
|
|
6
|
+
*/
|
|
7
|
+
export function useZauruClientErrorReporting(options) {
|
|
8
|
+
const enabled = options?.enabled !== false;
|
|
9
|
+
const appVersion = options?.appVersion;
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
if (!enabled || typeof window === "undefined")
|
|
12
|
+
return;
|
|
13
|
+
if (appVersion)
|
|
14
|
+
setClientErrorAppVersion(appVersion);
|
|
15
|
+
const onError = (event) => {
|
|
16
|
+
sendClientError({
|
|
17
|
+
message: event.message || "Error no capturado (window.onerror)",
|
|
18
|
+
stack: event.error instanceof Error ? event.error.stack : undefined,
|
|
19
|
+
source: "window.onerror",
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
const onRejection = (event) => {
|
|
23
|
+
const reason = event.reason;
|
|
24
|
+
sendClientError({
|
|
25
|
+
message: reason instanceof Error
|
|
26
|
+
? reason.message
|
|
27
|
+
: typeof reason === "string"
|
|
28
|
+
? reason
|
|
29
|
+
: "Promesa rechazada no manejada",
|
|
30
|
+
stack: reason instanceof Error ? reason.stack : undefined,
|
|
31
|
+
source: "unhandledrejection",
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
window.addEventListener("error", onError);
|
|
35
|
+
window.addEventListener("unhandledrejection", onRejection);
|
|
36
|
+
return () => {
|
|
37
|
+
window.removeEventListener("error", onError);
|
|
38
|
+
window.removeEventListener("unhandledrejection", onRejection);
|
|
39
|
+
};
|
|
40
|
+
}, [enabled, appVersion]);
|
|
41
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -24,4 +24,6 @@ export * from "./WithTooltip/index.js";
|
|
|
24
24
|
export * from "./Wizards/index.js";
|
|
25
25
|
export * from "./Zendesk/index.js";
|
|
26
26
|
export * from "./HOC/ValidateEmployeeAccess/index.js";
|
|
27
|
+
export * from "./Routes/index.js";
|
|
27
28
|
export * from "./SidePanel/index.js";
|
|
29
|
+
export * from "./observability/index.js";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ZauruClientErrorReportingOptions } from "./useZauruClientErrorReporting.js";
|
|
2
|
+
/**
|
|
3
|
+
* Optional drop-in for apps that do not use `BodyContainer`.
|
|
4
|
+
* `BodyContainer` already installs the same listeners.
|
|
5
|
+
*/
|
|
6
|
+
export declare function ZauruObservability(options: ZauruClientErrorReportingOptions): null;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client helper to report errors to `/api/client-errors`, which forwards
|
|
3
|
+
* them to CloudWatch Logs (with host logs as fallback).
|
|
4
|
+
*
|
|
5
|
+
* `keepalive: true` keeps the request alive if the page unloads right after.
|
|
6
|
+
*/
|
|
7
|
+
export interface ClientErrorPayload {
|
|
8
|
+
message: string;
|
|
9
|
+
stack?: string;
|
|
10
|
+
/** Sub-origin: errorBoundary, window.onerror, unhandledrejection. */
|
|
11
|
+
source?: string;
|
|
12
|
+
url?: string;
|
|
13
|
+
userAgent?: string;
|
|
14
|
+
appVersion?: string;
|
|
15
|
+
timestamp?: number;
|
|
16
|
+
[key: string]: unknown;
|
|
17
|
+
}
|
|
18
|
+
export declare function setClientErrorAppVersion(version?: string): void;
|
|
19
|
+
/**
|
|
20
|
+
* Sends a client error to the server. Best-effort: never throws.
|
|
21
|
+
* Deduplicates identical consecutive reports so re-renders of the same
|
|
22
|
+
* error do not flood CloudWatch.
|
|
23
|
+
*/
|
|
24
|
+
export declare function sendClientError(payload: ClientErrorPayload): void;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type ZauruClientErrorReportingOptions = {
|
|
2
|
+
appVersion?: string;
|
|
3
|
+
enabled?: boolean;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Captures `window.onerror` and `unhandledrejection` and POSTs them to
|
|
7
|
+
* `/api/client-errors`. Used by `BodyContainer` by default.
|
|
8
|
+
*/
|
|
9
|
+
export declare function useZauruClientErrorReporting(options?: ZauruClientErrorReportingOptions): void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zauru-sdk/components",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.5.0",
|
|
4
4
|
"description": "Componentes reutilizables en las WebApps de Zauru.",
|
|
5
5
|
"main": "./dist/esm/index.js",
|
|
6
6
|
"module": "./dist/esm/index.js",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"@zauru-sdk/icons": "^6.0.0",
|
|
50
50
|
"@zauru-sdk/redux": "^6.5.9",
|
|
51
51
|
"@zauru-sdk/types": "^6.16.0",
|
|
52
|
-
"@zauru-sdk/utils": "^6.
|
|
52
|
+
"@zauru-sdk/utils": "^6.16.0",
|
|
53
53
|
"browser-image-compression": "^2.0.2",
|
|
54
54
|
"framer-motion": "^12.0.0",
|
|
55
55
|
"jsonwebtoken": "^9.0.2",
|
|
@@ -62,5 +62,5 @@
|
|
|
62
62
|
"styled-components": "^5.3.5",
|
|
63
63
|
"zod": "^3.23.8"
|
|
64
64
|
},
|
|
65
|
-
"gitHead": "
|
|
65
|
+
"gitHead": "41f536143a03ddca17bbf467c027ef0179493479"
|
|
66
66
|
}
|
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
+
import { useZauruClientErrorReporting } from "../observability/useZauruClientErrorReporting.js";
|
|
2
3
|
|
|
3
4
|
type Props = {
|
|
4
5
|
children: React.ReactNode;
|
|
6
|
+
appVersion?: string;
|
|
7
|
+
/** Default true. Captures window.onerror / unhandledrejection. */
|
|
8
|
+
reportClientErrors?: boolean;
|
|
5
9
|
};
|
|
6
10
|
|
|
7
11
|
export const BodyContainer = (props: Props) => {
|
|
8
|
-
const { children } = props;
|
|
12
|
+
const { children, appVersion, reportClientErrors = true } = props;
|
|
13
|
+
useZauruClientErrorReporting({
|
|
14
|
+
appVersion,
|
|
15
|
+
enabled: reportClientErrors,
|
|
16
|
+
});
|
|
9
17
|
return <body className="flex flex-col min-h-screen m-0">{children}</body>;
|
|
10
18
|
};
|
|
@@ -7,17 +7,21 @@ import {
|
|
|
7
7
|
Link,
|
|
8
8
|
} from "@remix-run/react";
|
|
9
9
|
import { useEffect, useState } from "react";
|
|
10
|
+
import { sendClientError } from "../../observability/sendClientError.js";
|
|
10
11
|
|
|
11
12
|
export const ErrorLayout = ({
|
|
12
13
|
from,
|
|
13
14
|
isRootLevel = true,
|
|
14
15
|
error: parentError,
|
|
15
16
|
onError,
|
|
17
|
+
reportErrors = true,
|
|
16
18
|
}: {
|
|
17
19
|
from?: string;
|
|
18
20
|
isRootLevel?: boolean;
|
|
19
21
|
error?: Error;
|
|
20
22
|
onError?: (error: Error, meta: { from?: string }) => void;
|
|
23
|
+
/** Default true. POSTs to `/api/client-errors` unless `onError` is passed. */
|
|
24
|
+
reportErrors?: boolean;
|
|
21
25
|
}) => {
|
|
22
26
|
try {
|
|
23
27
|
const error = useRouteError();
|
|
@@ -28,7 +32,24 @@ export const ErrorLayout = ({
|
|
|
28
32
|
if (reportable instanceof Error) {
|
|
29
33
|
onError?.(reportable, { from });
|
|
30
34
|
}
|
|
31
|
-
|
|
35
|
+
if (!reportErrors || onError) return;
|
|
36
|
+
if (reportable instanceof Error) {
|
|
37
|
+
sendClientError({
|
|
38
|
+
message: reportable.message,
|
|
39
|
+
stack: reportable.stack,
|
|
40
|
+
source: "errorBoundary",
|
|
41
|
+
url: from,
|
|
42
|
+
});
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (isRouteErrorResponse(error)) {
|
|
46
|
+
sendClientError({
|
|
47
|
+
message: `Error en request: ${error.status}: ${error.statusText}`,
|
|
48
|
+
source: "errorBoundary",
|
|
49
|
+
url: from,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}, [error, parentError, from, onError, reportErrors]);
|
|
32
53
|
|
|
33
54
|
const baseError = (
|
|
34
55
|
<div className="min-h-screen flex flex-col items-center justify-center p-4">
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { useEffect, useState, type ReactNode } from "react";
|
|
2
|
+
import { useGetAgencyProfile, useGetUserProfile } from "@zauru-sdk/hooks";
|
|
3
|
+
import { getInitialProfileInfo } from "@zauru-sdk/utils";
|
|
4
|
+
import { LabelArray } from "../Titles/LabelArray.js";
|
|
5
|
+
import { ZauruTable } from "../Table/ZauruTable.js";
|
|
6
|
+
import { Container } from "../Containers/Container.js";
|
|
7
|
+
import { DoubleContainer } from "../Containers/DoubleContainer.js";
|
|
8
|
+
import { MainContainer } from "../Containers/MainContainer.js";
|
|
9
|
+
import { SubContainer } from "../Containers/SubContainer.js";
|
|
10
|
+
import { ErrorLayout } from "../Layouts/errorLayout/index.js";
|
|
11
|
+
import { LoadingWindow } from "../Skeletons/LoadingWindow.js";
|
|
12
|
+
|
|
13
|
+
export type ZauruHomePageProps = {
|
|
14
|
+
title: string;
|
|
15
|
+
includeAgency?: boolean;
|
|
16
|
+
extra?: ReactNode;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export function ZauruHomePage({
|
|
20
|
+
title,
|
|
21
|
+
includeAgency = true,
|
|
22
|
+
extra,
|
|
23
|
+
}: ZauruHomePageProps) {
|
|
24
|
+
try {
|
|
25
|
+
const [isClient, setIsClient] = useState(false);
|
|
26
|
+
const { data: userProfile } = useGetUserProfile();
|
|
27
|
+
const { data: agencyProfile, loading: loadingAgencyProfile } =
|
|
28
|
+
useGetAgencyProfile();
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
setIsClient(true);
|
|
32
|
+
}, []);
|
|
33
|
+
|
|
34
|
+
const { profileLabelInfo, membershipsInfo } =
|
|
35
|
+
getInitialProfileInfo(userProfile);
|
|
36
|
+
|
|
37
|
+
const conditionalRowStyles = [
|
|
38
|
+
{
|
|
39
|
+
when: (row: { odd: any; entityId: any }) => row.odd,
|
|
40
|
+
style: {
|
|
41
|
+
backgroundColor: "white",
|
|
42
|
+
"&:hover": {
|
|
43
|
+
cursor: "pointer",
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
when: (row: { entityId: any }) =>
|
|
49
|
+
row.entityId === userProfile?.profile?.selected_entity_id,
|
|
50
|
+
style: () => ({ backgroundColor: "lightgreen" }),
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
const columns: any = [
|
|
55
|
+
{
|
|
56
|
+
name: "Entidad",
|
|
57
|
+
selector: (row: any) => row.entity,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: "Contrato Expira",
|
|
61
|
+
selector: (row: any) => row.contractExpiration,
|
|
62
|
+
width: "10rem",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: "Pagado hasta",
|
|
66
|
+
selector: (row: any) => row.nextPay,
|
|
67
|
+
width: "10rem",
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
if (!isClient) {
|
|
72
|
+
return <LoadingWindow description="Cargando home..." />;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const pageTitle = includeAgency
|
|
76
|
+
? loadingAgencyProfile || !agencyProfile
|
|
77
|
+
? "Cargando..."
|
|
78
|
+
: `${title} (${agencyProfile?.name ?? "Usuario sin agencia asignada"})`
|
|
79
|
+
: title;
|
|
80
|
+
|
|
81
|
+
const profileAndMemberships = (
|
|
82
|
+
<>
|
|
83
|
+
<SubContainer title="Perfil">
|
|
84
|
+
<LabelArray info={profileLabelInfo} />
|
|
85
|
+
</SubContainer>
|
|
86
|
+
<SubContainer title="Suscripciones">
|
|
87
|
+
<ZauruTable
|
|
88
|
+
columns={columns}
|
|
89
|
+
conditionalRowStyles={conditionalRowStyles}
|
|
90
|
+
data={membershipsInfo}
|
|
91
|
+
/>
|
|
92
|
+
</SubContainer>
|
|
93
|
+
</>
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<MainContainer>
|
|
98
|
+
{extra ? (
|
|
99
|
+
<DoubleContainer title={pageTitle}>
|
|
100
|
+
{profileAndMemberships}
|
|
101
|
+
{extra}
|
|
102
|
+
</DoubleContainer>
|
|
103
|
+
) : (
|
|
104
|
+
<Container title={pageTitle}>{profileAndMemberships}</Container>
|
|
105
|
+
)}
|
|
106
|
+
</MainContainer>
|
|
107
|
+
);
|
|
108
|
+
} catch (error: any) {
|
|
109
|
+
return (
|
|
110
|
+
<ErrorLayout from="/home/index.tsx" error={error} isRootLevel={false} />
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ErrorLayout } from "../Layouts/errorLayout/index.js";
|
|
2
|
+
import { HomeLayout } from "../Layouts/homeLayout/index.js";
|
|
3
|
+
import type { ZauruHomeLayoutColor } from "./ZauruLoginPage.js";
|
|
4
|
+
|
|
5
|
+
export type ZauruIndexPageProps = {
|
|
6
|
+
title: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
color?: ZauruHomeLayoutColor;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function ZauruIndexPage({
|
|
12
|
+
title,
|
|
13
|
+
description = "Inicie sesión para poder continuar.",
|
|
14
|
+
color,
|
|
15
|
+
}: ZauruIndexPageProps) {
|
|
16
|
+
try {
|
|
17
|
+
return <HomeLayout title={title} description={description} color={color} />;
|
|
18
|
+
} catch (error: any) {
|
|
19
|
+
return <ErrorLayout from="/index.tsx" error={error} isRootLevel={false} />;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { useFetcher } from "@remix-run/react";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { useValidateNotifications } from "@zauru-sdk/hooks";
|
|
4
|
+
import {
|
|
5
|
+
cleanLocalStorage,
|
|
6
|
+
profileFetchSuccess,
|
|
7
|
+
useAppDispatch,
|
|
8
|
+
} from "@zauru-sdk/redux";
|
|
9
|
+
import { ErrorLayout } from "../Layouts/errorLayout/index.js";
|
|
10
|
+
import { HomeLayout } from "../Layouts/homeLayout/index.js";
|
|
11
|
+
import { LoadingWindow } from "../Skeletons/LoadingWindow.js";
|
|
12
|
+
|
|
13
|
+
export type ZauruHomeLayoutColor = "green" | "blue" | "red" | "purple" | "yellow";
|
|
14
|
+
|
|
15
|
+
export type ZauruLoginPageProps = {
|
|
16
|
+
cleanLocalStorageArgs?: Parameters<typeof cleanLocalStorage>[0];
|
|
17
|
+
color?: ZauruHomeLayoutColor;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function ZauruLoginPage({
|
|
21
|
+
cleanLocalStorageArgs,
|
|
22
|
+
color,
|
|
23
|
+
}: ZauruLoginPageProps) {
|
|
24
|
+
try {
|
|
25
|
+
const fetcher = useFetcher<any>();
|
|
26
|
+
const dispatch = useAppDispatch();
|
|
27
|
+
const [isClient, setIsClient] = useState(false);
|
|
28
|
+
|
|
29
|
+
useValidateNotifications({ fetcher });
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (fetcher.data?.employeeProfile) {
|
|
33
|
+
dispatch(
|
|
34
|
+
profileFetchSuccess({
|
|
35
|
+
name: "agencyProfile",
|
|
36
|
+
data: fetcher.data.agencyProfile,
|
|
37
|
+
}),
|
|
38
|
+
);
|
|
39
|
+
dispatch(
|
|
40
|
+
profileFetchSuccess({
|
|
41
|
+
name: "employeeProfile",
|
|
42
|
+
data: fetcher.data.employeeProfile,
|
|
43
|
+
}),
|
|
44
|
+
);
|
|
45
|
+
dispatch(
|
|
46
|
+
profileFetchSuccess({
|
|
47
|
+
name: "userProfile",
|
|
48
|
+
data: fetcher.data.userProfile,
|
|
49
|
+
}),
|
|
50
|
+
);
|
|
51
|
+
dispatch(
|
|
52
|
+
profileFetchSuccess({
|
|
53
|
+
name: "oauthProfile",
|
|
54
|
+
data: fetcher.data.oauthProfile,
|
|
55
|
+
}),
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
fetcher.submit(
|
|
59
|
+
{
|
|
60
|
+
action: "redirect",
|
|
61
|
+
cookie: fetcher.data?.cookie,
|
|
62
|
+
},
|
|
63
|
+
{ method: "post" },
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}, [fetcher.data]);
|
|
67
|
+
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
setIsClient(true);
|
|
70
|
+
}, []);
|
|
71
|
+
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
cleanLocalStorage(cleanLocalStorageArgs);
|
|
74
|
+
fetcher.submit({ action: "login" }, { method: "post" });
|
|
75
|
+
}, []);
|
|
76
|
+
|
|
77
|
+
if (!isClient) {
|
|
78
|
+
return <LoadingWindow />;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<HomeLayout
|
|
83
|
+
loading
|
|
84
|
+
color={color}
|
|
85
|
+
title={
|
|
86
|
+
fetcher.data?.title ? "Error al inciar sesión!" : "Iniciando sesión...."
|
|
87
|
+
}
|
|
88
|
+
description={
|
|
89
|
+
fetcher.data?.description ??
|
|
90
|
+
"Cargando (Perfil zauru, perfil oauth, agencia, membresías, webapptables)..."
|
|
91
|
+
}
|
|
92
|
+
/>
|
|
93
|
+
);
|
|
94
|
+
} catch (error: any) {
|
|
95
|
+
console.log(error);
|
|
96
|
+
return (
|
|
97
|
+
<ErrorLayout from="/login/index.tsx" error={error} isRootLevel={false} />
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { useFetcher, useNavigate } from "@remix-run/react";
|
|
2
|
+
import { useEffect } from "react";
|
|
3
|
+
import { showAlert, useIsOnline } from "@zauru-sdk/hooks";
|
|
4
|
+
import { ErrorLayout } from "../Layouts/errorLayout/index.js";
|
|
5
|
+
import { HomeLayout } from "../Layouts/homeLayout/index.js";
|
|
6
|
+
import type { ZauruHomeLayoutColor } from "./ZauruLoginPage.js";
|
|
7
|
+
|
|
8
|
+
export type ZauruLogoutPageProps = {
|
|
9
|
+
requireOnline?: boolean;
|
|
10
|
+
beforeLogout?: () => boolean | void;
|
|
11
|
+
color?: ZauruHomeLayoutColor;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function ZauruLogoutPage({
|
|
15
|
+
requireOnline = true,
|
|
16
|
+
beforeLogout,
|
|
17
|
+
color,
|
|
18
|
+
}: ZauruLogoutPageProps) {
|
|
19
|
+
try {
|
|
20
|
+
const fetcher = useFetcher();
|
|
21
|
+
const navigate = useNavigate();
|
|
22
|
+
const isOnline = useIsOnline();
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (beforeLogout?.() === false) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (requireOnline && !isOnline) {
|
|
29
|
+
showAlert({
|
|
30
|
+
title: "No es posible cerrar sesión",
|
|
31
|
+
description: "No hay conexión a internet",
|
|
32
|
+
type: "error",
|
|
33
|
+
});
|
|
34
|
+
navigate("/home");
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
fetcher.submit({ action: "logout" }, { method: "post" });
|
|
38
|
+
}, []);
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<HomeLayout
|
|
42
|
+
color={color}
|
|
43
|
+
title="Cerrando sesión"
|
|
44
|
+
description="Espere mientras se cierra su sesión..."
|
|
45
|
+
/>
|
|
46
|
+
);
|
|
47
|
+
} catch (error: any) {
|
|
48
|
+
return (
|
|
49
|
+
<ErrorLayout from="/logout/index.tsx" error={error} isRootLevel={false} />
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { ErrorLayout } from "../Layouts/errorLayout/index.js";
|
|
3
|
+
import { LoadingWindow } from "../Skeletons/LoadingWindow.js";
|
|
4
|
+
|
|
5
|
+
export type ZauruReloadCatalogItem = {
|
|
6
|
+
name: string;
|
|
7
|
+
loading: boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type ZauruReloadCatalogsPageProps = {
|
|
11
|
+
items: ZauruReloadCatalogItem[];
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function ZauruReloadCatalogsPage({
|
|
15
|
+
items,
|
|
16
|
+
}: ZauruReloadCatalogsPageProps) {
|
|
17
|
+
try {
|
|
18
|
+
const [isClient, setIsClient] = useState(false);
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
setIsClient(true);
|
|
22
|
+
}, []);
|
|
23
|
+
|
|
24
|
+
if (!isClient) {
|
|
25
|
+
return <LoadingWindow />;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const loading = items.some((item) => item.loading);
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<LoadingWindow
|
|
32
|
+
description={
|
|
33
|
+
loading
|
|
34
|
+
? "Cargando catálogos..."
|
|
35
|
+
: "Catálogos cargados correctamente ✅"
|
|
36
|
+
}
|
|
37
|
+
loadingItems={items}
|
|
38
|
+
/>
|
|
39
|
+
);
|
|
40
|
+
} catch (error: any) {
|
|
41
|
+
return (
|
|
42
|
+
<ErrorLayout from="/reload-catalogs" error={error} isRootLevel={false} />
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ErrorLayout } from "../Layouts/errorLayout/index.js";
|
|
2
|
+
|
|
3
|
+
export function zauruRouteErrorBoundary(from: string) {
|
|
4
|
+
function ZauruRouteErrorBoundary() {
|
|
5
|
+
return <ErrorLayout from={from} />;
|
|
6
|
+
}
|
|
7
|
+
ZauruRouteErrorBoundary.displayName = `ZauruRouteErrorBoundary(${from})`;
|
|
8
|
+
return ZauruRouteErrorBoundary;
|
|
9
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -24,4 +24,6 @@ export * from "./WithTooltip/index.js";
|
|
|
24
24
|
export * from "./Wizards/index.js";
|
|
25
25
|
export * from "./Zendesk/index.js";
|
|
26
26
|
export * from "./HOC/ValidateEmployeeAccess/index.js";
|
|
27
|
+
export * from "./Routes/index.js";
|
|
27
28
|
export * from "./SidePanel/index.js";
|
|
29
|
+
export * from "./observability/index.js";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { useZauruClientErrorReporting } from "./useZauruClientErrorReporting.js";
|
|
2
|
+
import type { ZauruClientErrorReportingOptions } from "./useZauruClientErrorReporting.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Optional drop-in for apps that do not use `BodyContainer`.
|
|
6
|
+
* `BodyContainer` already installs the same listeners.
|
|
7
|
+
*/
|
|
8
|
+
export function ZauruObservability(options: ZauruClientErrorReportingOptions) {
|
|
9
|
+
useZauruClientErrorReporting(options);
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client helper to report errors to `/api/client-errors`, which forwards
|
|
3
|
+
* them to CloudWatch Logs (with host logs as fallback).
|
|
4
|
+
*
|
|
5
|
+
* `keepalive: true` keeps the request alive if the page unloads right after.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface ClientErrorPayload {
|
|
9
|
+
message: string;
|
|
10
|
+
stack?: string;
|
|
11
|
+
/** Sub-origin: errorBoundary, window.onerror, unhandledrejection. */
|
|
12
|
+
source?: string;
|
|
13
|
+
url?: string;
|
|
14
|
+
userAgent?: string;
|
|
15
|
+
appVersion?: string;
|
|
16
|
+
timestamp?: number;
|
|
17
|
+
[key: string]: unknown;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const ENDPOINT = "/api/client-errors";
|
|
21
|
+
|
|
22
|
+
let lastReportedKey = "";
|
|
23
|
+
let defaultAppVersion: string | undefined;
|
|
24
|
+
|
|
25
|
+
export function setClientErrorAppVersion(version?: string): void {
|
|
26
|
+
defaultAppVersion = version;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Sends a client error to the server. Best-effort: never throws.
|
|
31
|
+
* Deduplicates identical consecutive reports so re-renders of the same
|
|
32
|
+
* error do not flood CloudWatch.
|
|
33
|
+
*/
|
|
34
|
+
export function sendClientError(payload: ClientErrorPayload): void {
|
|
35
|
+
if (typeof window === "undefined") return;
|
|
36
|
+
|
|
37
|
+
const body = {
|
|
38
|
+
message: payload.message,
|
|
39
|
+
stack: payload.stack,
|
|
40
|
+
source: payload.source ?? "unknown",
|
|
41
|
+
url: payload.url ?? window.location.href,
|
|
42
|
+
userAgent: payload.userAgent ?? navigator.userAgent,
|
|
43
|
+
appVersion: payload.appVersion ?? defaultAppVersion,
|
|
44
|
+
timestamp: payload.timestamp ?? Date.now(),
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const dedupKey = `${body.source}|${body.message}|${body.url}`;
|
|
48
|
+
if (dedupKey === lastReportedKey) return;
|
|
49
|
+
lastReportedKey = dedupKey;
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
fetch(ENDPOINT, {
|
|
53
|
+
method: "POST",
|
|
54
|
+
headers: { "Content-Type": "application/json" },
|
|
55
|
+
body: JSON.stringify(body),
|
|
56
|
+
keepalive: true,
|
|
57
|
+
credentials: "same-origin",
|
|
58
|
+
}).catch(() => {
|
|
59
|
+
// Silent: reporting must not break UX.
|
|
60
|
+
});
|
|
61
|
+
} catch {
|
|
62
|
+
// Silent.
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import {
|
|
3
|
+
sendClientError,
|
|
4
|
+
setClientErrorAppVersion,
|
|
5
|
+
} from "./sendClientError.js";
|
|
6
|
+
|
|
7
|
+
export type ZauruClientErrorReportingOptions = {
|
|
8
|
+
appVersion?: string;
|
|
9
|
+
enabled?: boolean;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Captures `window.onerror` and `unhandledrejection` and POSTs them to
|
|
14
|
+
* `/api/client-errors`. Used by `BodyContainer` by default.
|
|
15
|
+
*/
|
|
16
|
+
export function useZauruClientErrorReporting(
|
|
17
|
+
options?: ZauruClientErrorReportingOptions,
|
|
18
|
+
): void {
|
|
19
|
+
const enabled = options?.enabled !== false;
|
|
20
|
+
const appVersion = options?.appVersion;
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (!enabled || typeof window === "undefined") return;
|
|
24
|
+
|
|
25
|
+
if (appVersion) setClientErrorAppVersion(appVersion);
|
|
26
|
+
|
|
27
|
+
const onError = (event: ErrorEvent) => {
|
|
28
|
+
sendClientError({
|
|
29
|
+
message: event.message || "Error no capturado (window.onerror)",
|
|
30
|
+
stack: event.error instanceof Error ? event.error.stack : undefined,
|
|
31
|
+
source: "window.onerror",
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const onRejection = (event: PromiseRejectionEvent) => {
|
|
36
|
+
const reason = event.reason;
|
|
37
|
+
sendClientError({
|
|
38
|
+
message:
|
|
39
|
+
reason instanceof Error
|
|
40
|
+
? reason.message
|
|
41
|
+
: typeof reason === "string"
|
|
42
|
+
? reason
|
|
43
|
+
: "Promesa rechazada no manejada",
|
|
44
|
+
stack: reason instanceof Error ? reason.stack : undefined,
|
|
45
|
+
source: "unhandledrejection",
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
window.addEventListener("error", onError);
|
|
50
|
+
window.addEventListener("unhandledrejection", onRejection);
|
|
51
|
+
return () => {
|
|
52
|
+
window.removeEventListener("error", onError);
|
|
53
|
+
window.removeEventListener("unhandledrejection", onRejection);
|
|
54
|
+
};
|
|
55
|
+
}, [enabled, appVersion]);
|
|
56
|
+
}
|