@zauru-sdk/components 6.11.1 → 6.13.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/Buttons/Button.d.ts +5 -3
- package/dist/esm/Buttons/Button.js +79 -6
- package/dist/esm/Routes/ZauruLoginPage.js +11 -0
- package/package.json +5 -5
- package/src/Buttons/Button.tsx +158 -16
- package/src/Routes/ZauruLoginPage.tsx +16 -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.13.0](https://github.com/intuitiva/zauru-typescript-sdk/compare/@zauru-sdk/components@6.12.0...@zauru-sdk/components@6.13.0) (2026-10-09)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* reintentar login cuando el code OAuth de userinfo es 401 ([e32620b](https://github.com/intuitiva/zauru-typescript-sdk/commit/e32620be26ec19e1cbea1340f9a33ea7bbe48176))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# [6.12.0](https://github.com/intuitiva/zauru-typescript-sdk/compare/@zauru-sdk/components@6.11.1...@zauru-sdk/components@6.12.0) (2026-10-07)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
* agregar submenús anidados al dropdown de Button ([8bf8c5e](https://github.com/intuitiva/zauru-typescript-sdk/commit/8bf8c5ecb80d03cb10f806d912f148084576d655))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
6
28
|
## [6.11.1](https://github.com/intuitiva/zauru-typescript-sdk/compare/@zauru-sdk/components@6.11.0...@zauru-sdk/components@6.11.1) (2026-10-07)
|
|
7
29
|
|
|
8
30
|
**Note:** Version bump only for package @zauru-sdk/components
|
package/dist/Buttons/Button.d.ts
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
|
+
import { type MouseEvent as ReactMouseEvent, type ReactNode } from "react";
|
|
1
2
|
export type DropdownOption = {
|
|
2
3
|
label: string;
|
|
3
4
|
value: string;
|
|
4
|
-
onClick
|
|
5
|
+
onClick?: () => void;
|
|
6
|
+
children?: DropdownOption[];
|
|
5
7
|
};
|
|
6
8
|
type Props = {
|
|
7
9
|
type?: "reset" | "button" | "submit" | undefined;
|
|
8
10
|
title?: string;
|
|
9
11
|
name?: string;
|
|
10
|
-
onClickSave?: (e:
|
|
12
|
+
onClickSave?: (e: ReactMouseEvent<HTMLButtonElement>) => void;
|
|
11
13
|
loading?: boolean;
|
|
12
14
|
loadingText?: string;
|
|
13
15
|
selectedColor?: "indigo" | "green" | "red" | "yellow" | "gray";
|
|
14
|
-
children?:
|
|
16
|
+
children?: ReactNode;
|
|
15
17
|
className?: string;
|
|
16
18
|
disabled?: boolean;
|
|
17
19
|
enableFormErrorsValidation?: boolean;
|
|
@@ -1,6 +1,75 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useFormContext } from "react-hook-form";
|
|
3
|
-
import {
|
|
3
|
+
import { useEffect, useRef, useState, } from "react";
|
|
4
|
+
const SUBMENU_OPEN_DELAY_MS = 150;
|
|
5
|
+
const SUBMENU_CLOSE_DELAY_MS = 200;
|
|
6
|
+
const prefersReducedMotion = () => typeof window !== "undefined" &&
|
|
7
|
+
window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
8
|
+
const DropdownMenuItem = ({ option, onLeafClick }) => {
|
|
9
|
+
const hasChildren = Boolean(option.children?.length);
|
|
10
|
+
const [isSubmenuOpen, setIsSubmenuOpen] = useState(false);
|
|
11
|
+
const openTimerRef = useRef(null);
|
|
12
|
+
const closeTimerRef = useRef(null);
|
|
13
|
+
const clearTimers = () => {
|
|
14
|
+
if (openTimerRef.current != null) {
|
|
15
|
+
window.clearTimeout(openTimerRef.current);
|
|
16
|
+
openTimerRef.current = null;
|
|
17
|
+
}
|
|
18
|
+
if (closeTimerRef.current != null) {
|
|
19
|
+
window.clearTimeout(closeTimerRef.current);
|
|
20
|
+
closeTimerRef.current = null;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
return () => {
|
|
25
|
+
clearTimers();
|
|
26
|
+
};
|
|
27
|
+
}, []);
|
|
28
|
+
const openSubmenu = (immediate = false) => {
|
|
29
|
+
if (!hasChildren)
|
|
30
|
+
return;
|
|
31
|
+
clearTimers();
|
|
32
|
+
if (immediate || prefersReducedMotion()) {
|
|
33
|
+
setIsSubmenuOpen(true);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
openTimerRef.current = window.setTimeout(() => {
|
|
37
|
+
setIsSubmenuOpen(true);
|
|
38
|
+
}, SUBMENU_OPEN_DELAY_MS);
|
|
39
|
+
};
|
|
40
|
+
const closeSubmenu = (immediate = false) => {
|
|
41
|
+
clearTimers();
|
|
42
|
+
if (immediate || prefersReducedMotion()) {
|
|
43
|
+
setIsSubmenuOpen(false);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
closeTimerRef.current = window.setTimeout(() => {
|
|
47
|
+
setIsSubmenuOpen(false);
|
|
48
|
+
}, SUBMENU_CLOSE_DELAY_MS);
|
|
49
|
+
};
|
|
50
|
+
const handleClick = () => {
|
|
51
|
+
if (hasChildren) {
|
|
52
|
+
setIsSubmenuOpen((open) => !open);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
option.onClick?.();
|
|
56
|
+
onLeafClick();
|
|
57
|
+
};
|
|
58
|
+
const handleKeyDown = (event) => {
|
|
59
|
+
if (!hasChildren)
|
|
60
|
+
return;
|
|
61
|
+
if (event.key === "ArrowRight" || event.key === "Enter") {
|
|
62
|
+
event.preventDefault();
|
|
63
|
+
openSubmenu(true);
|
|
64
|
+
}
|
|
65
|
+
if (event.key === "ArrowLeft" || event.key === "Escape") {
|
|
66
|
+
event.preventDefault();
|
|
67
|
+
closeSubmenu(true);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
const itemClassName = "flex w-full cursor-pointer items-center justify-between gap-2 px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:ring-2 focus:ring-indigo-500 focus:ring-inset motion-reduce:transition-none";
|
|
71
|
+
return (_jsxs("div", { className: "relative", onMouseEnter: () => openSubmenu(), onMouseLeave: () => closeSubmenu(), children: [_jsxs("button", { type: "button", role: "menuitem", "aria-haspopup": hasChildren ? "menu" : undefined, "aria-expanded": hasChildren ? isSubmenuOpen : undefined, onClick: handleClick, onKeyDown: handleKeyDown, className: itemClassName, children: [_jsx("span", { children: option.label }), hasChildren ? (_jsx("svg", { "aria-hidden": "true", className: "h-4 w-4 shrink-0 text-gray-500", xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 20 20", fill: "currentColor", children: _jsx("path", { fillRule: "evenodd", d: "M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z", clipRule: "evenodd" }) })) : null] }), hasChildren && isSubmenuOpen ? (_jsx("div", { role: "menu", className: "absolute right-full top-0 z-20 mr-1 w-56 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none", children: option.children?.map((child) => (_jsx(DropdownMenuItem, { option: child, onLeafClick: onLeafClick }, child.value))) })) : null] }));
|
|
72
|
+
};
|
|
4
73
|
export const Button = (props) => {
|
|
5
74
|
const { type = "submit", loading = false, loadingText = "Guardando...", title = "Guardar", name = "save", onClickSave, selectedColor = "indigo", children, className = "", disabled = false, enableFormErrorsValidation = false, enableFormErrorsDescriptions = false, dropdownOptions = [], dropdownTitle, } = props;
|
|
6
75
|
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
|
@@ -69,9 +138,16 @@ export const Button = (props) => {
|
|
|
69
138
|
setIsDropdownOpen(false);
|
|
70
139
|
}
|
|
71
140
|
};
|
|
141
|
+
const handleEscape = (event) => {
|
|
142
|
+
if (event.key === "Escape") {
|
|
143
|
+
setIsDropdownOpen(false);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
72
146
|
document.addEventListener("mousedown", handleClickOutside);
|
|
147
|
+
document.addEventListener("keydown", handleEscape);
|
|
73
148
|
return () => {
|
|
74
149
|
document.removeEventListener("mousedown", handleClickOutside);
|
|
150
|
+
document.removeEventListener("keydown", handleEscape);
|
|
75
151
|
};
|
|
76
152
|
}, []);
|
|
77
153
|
const isButtonDisabled = loading || disabled || (enableFormErrorsValidation && formHasErrors);
|
|
@@ -89,10 +165,7 @@ export const Button = (props) => {
|
|
|
89
165
|
? " cursor-progress"
|
|
90
166
|
: `${isButtonDisabled ? " cursor-not-allowed" : `cursor-pointer hover:${color.bg700}`}`} inline-flex justify-center items-center rounded-md border border-transparent ${color.bg600} py-2 px-4 text-sm font-medium text-white shadow-sm focus:outline-none focus:ring-2 focus:${color.ring500} focus:ring-offset-2 ${className}`, children: [loading
|
|
91
167
|
? children ?? loadingText
|
|
92
|
-
: children ?? dropdownTitle ?? title, _jsx("svg", { className: `ml-2 -mr-1 h-4 w-4 transition-transform ${isDropdownOpen ? "rotate-180" : ""}`, xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 20 20", fill: "currentColor", children: _jsx("path", { fillRule: "evenodd", d: "M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z", clipRule: "evenodd" }) })] }) }), isDropdownOpen && !isButtonDisabled ? (_jsx("div", { role: "menu", className: "absolute right-0 z-10 mt-2 w-56 origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none", children: _jsx("div", { className: "py-1", children: dropdownOptions.map((option) => (_jsx(
|
|
93
|
-
option.onClick();
|
|
94
|
-
setIsDropdownOpen(false);
|
|
95
|
-
}, className: "block w-full cursor-pointer px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 text-left", children: option.label }, option.value))) }) })) : null] }));
|
|
168
|
+
: children ?? dropdownTitle ?? title, _jsx("svg", { className: `ml-2 -mr-1 h-4 w-4 transition-transform motion-reduce:transition-none ${isDropdownOpen ? "rotate-180" : ""}`, xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 20 20", fill: "currentColor", "aria-hidden": "true", children: _jsx("path", { fillRule: "evenodd", d: "M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z", clipRule: "evenodd" }) })] }) }), isDropdownOpen && !isButtonDisabled ? (_jsx("div", { role: "menu", className: "absolute right-0 z-10 mt-2 w-56 origin-top-right rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none", children: _jsx("div", { className: "py-1", children: dropdownOptions.map((option) => (_jsx(DropdownMenuItem, { option: option, onLeafClick: () => setIsDropdownOpen(false) }, option.value))) }) })) : null] }));
|
|
96
169
|
return (_jsxs(_Fragment, { children: [(enableFormErrorsValidation && formHasErrors && errorMessage) ||
|
|
97
170
|
(enableFormErrorsDescriptions && errorMessage) ? (_jsx("div", { className: "flex flex-col items-end mb-2", children: _jsx("div", { className: "p-2 bg-red-100 border border-red-400 text-red-700 rounded-md shadow-sm", children: _jsx("p", { className: "text-sm", children: errorMessage }) }) })) : null, dropdownContent] }));
|
|
98
171
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useFetcher } from "@remix-run/react";
|
|
3
3
|
import { useEffect, useState } from "react";
|
|
4
|
+
import { isOauthUserinfoUnauthorizedError } from "@zauru-sdk/common";
|
|
4
5
|
import { useValidateNotifications } from "@zauru-sdk/hooks";
|
|
5
6
|
import { cleanLocalStorage, profileFetchSuccess, useAppDispatch, } from "@zauru-sdk/redux";
|
|
6
7
|
import { ErrorLayout } from "../Layouts/errorLayout/index.js";
|
|
@@ -39,6 +40,16 @@ export function ZauruLoginPage({ cleanLocalStorageArgs, color, }) {
|
|
|
39
40
|
useEffect(() => {
|
|
40
41
|
setIsClient(true);
|
|
41
42
|
}, []);
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
const alreadyReauthed = document.cookie
|
|
45
|
+
.split(";")
|
|
46
|
+
.some((part) => part.trim() === "zauru_oauth_reauth=1");
|
|
47
|
+
if (fetcher.data?.error &&
|
|
48
|
+
!alreadyReauthed &&
|
|
49
|
+
isOauthUserinfoUnauthorizedError(String(fetcher.data?.description ?? ""))) {
|
|
50
|
+
window.location.replace("/logout?reauth=1");
|
|
51
|
+
}
|
|
52
|
+
}, [fetcher.data]);
|
|
42
53
|
useEffect(() => {
|
|
43
54
|
cleanLocalStorage(cleanLocalStorageArgs);
|
|
44
55
|
fetcher.submit({ action: "login" }, { method: "post" });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zauru-sdk/components",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.13.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",
|
|
@@ -44,12 +44,12 @@
|
|
|
44
44
|
"@reduxjs/toolkit": "^2.2.1",
|
|
45
45
|
"@remix-run/node": "^2.8.1",
|
|
46
46
|
"@remix-run/react": "^2.8.1",
|
|
47
|
-
"@zauru-sdk/common": "^6.
|
|
48
|
-
"@zauru-sdk/hooks": "^6.3.
|
|
47
|
+
"@zauru-sdk/common": "^6.9.0",
|
|
48
|
+
"@zauru-sdk/hooks": "^6.3.20",
|
|
49
49
|
"@zauru-sdk/icons": "^6.1.0",
|
|
50
50
|
"@zauru-sdk/redux": "^6.5.17",
|
|
51
51
|
"@zauru-sdk/types": "^6.24.0",
|
|
52
|
-
"@zauru-sdk/utils": "^6.
|
|
52
|
+
"@zauru-sdk/utils": "^6.24.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": "a46ebe234551d76d6a0ea9095be6e55540006ab3"
|
|
66
66
|
}
|
package/src/Buttons/Button.tsx
CHANGED
|
@@ -1,23 +1,163 @@
|
|
|
1
1
|
import type { ColorInterface } from "../NavBar/NavBar.types.js";
|
|
2
2
|
import { useFormContext } from "react-hook-form";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
useEffect,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
|
+
type KeyboardEvent as ReactKeyboardEvent,
|
|
8
|
+
type MouseEvent as ReactMouseEvent,
|
|
9
|
+
type ReactNode,
|
|
10
|
+
} from "react";
|
|
4
11
|
|
|
5
12
|
export type DropdownOption = {
|
|
6
13
|
label: string;
|
|
7
14
|
value: string;
|
|
8
|
-
onClick
|
|
15
|
+
onClick?: () => void;
|
|
16
|
+
children?: DropdownOption[];
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const SUBMENU_OPEN_DELAY_MS = 150;
|
|
20
|
+
const SUBMENU_CLOSE_DELAY_MS = 200;
|
|
21
|
+
|
|
22
|
+
const prefersReducedMotion = () =>
|
|
23
|
+
typeof window !== "undefined" &&
|
|
24
|
+
window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
25
|
+
|
|
26
|
+
type DropdownMenuItemProps = {
|
|
27
|
+
option: DropdownOption;
|
|
28
|
+
onLeafClick: () => void;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const DropdownMenuItem = ({ option, onLeafClick }: DropdownMenuItemProps) => {
|
|
32
|
+
const hasChildren = Boolean(option.children?.length);
|
|
33
|
+
const [isSubmenuOpen, setIsSubmenuOpen] = useState(false);
|
|
34
|
+
const openTimerRef = useRef<number | null>(null);
|
|
35
|
+
const closeTimerRef = useRef<number | null>(null);
|
|
36
|
+
|
|
37
|
+
const clearTimers = () => {
|
|
38
|
+
if (openTimerRef.current != null) {
|
|
39
|
+
window.clearTimeout(openTimerRef.current);
|
|
40
|
+
openTimerRef.current = null;
|
|
41
|
+
}
|
|
42
|
+
if (closeTimerRef.current != null) {
|
|
43
|
+
window.clearTimeout(closeTimerRef.current);
|
|
44
|
+
closeTimerRef.current = null;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
return () => {
|
|
50
|
+
clearTimers();
|
|
51
|
+
};
|
|
52
|
+
}, []);
|
|
53
|
+
|
|
54
|
+
const openSubmenu = (immediate = false) => {
|
|
55
|
+
if (!hasChildren) return;
|
|
56
|
+
clearTimers();
|
|
57
|
+
if (immediate || prefersReducedMotion()) {
|
|
58
|
+
setIsSubmenuOpen(true);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
openTimerRef.current = window.setTimeout(() => {
|
|
62
|
+
setIsSubmenuOpen(true);
|
|
63
|
+
}, SUBMENU_OPEN_DELAY_MS);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const closeSubmenu = (immediate = false) => {
|
|
67
|
+
clearTimers();
|
|
68
|
+
if (immediate || prefersReducedMotion()) {
|
|
69
|
+
setIsSubmenuOpen(false);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
closeTimerRef.current = window.setTimeout(() => {
|
|
73
|
+
setIsSubmenuOpen(false);
|
|
74
|
+
}, SUBMENU_CLOSE_DELAY_MS);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const handleClick = () => {
|
|
78
|
+
if (hasChildren) {
|
|
79
|
+
setIsSubmenuOpen((open) => !open);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
option.onClick?.();
|
|
83
|
+
onLeafClick();
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const handleKeyDown = (event: ReactKeyboardEvent<HTMLButtonElement>) => {
|
|
87
|
+
if (!hasChildren) return;
|
|
88
|
+
if (event.key === "ArrowRight" || event.key === "Enter") {
|
|
89
|
+
event.preventDefault();
|
|
90
|
+
openSubmenu(true);
|
|
91
|
+
}
|
|
92
|
+
if (event.key === "ArrowLeft" || event.key === "Escape") {
|
|
93
|
+
event.preventDefault();
|
|
94
|
+
closeSubmenu(true);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const itemClassName =
|
|
99
|
+
"flex w-full cursor-pointer items-center justify-between gap-2 px-4 py-2 text-left text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:ring-2 focus:ring-indigo-500 focus:ring-inset motion-reduce:transition-none";
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<div
|
|
103
|
+
className="relative"
|
|
104
|
+
onMouseEnter={() => openSubmenu()}
|
|
105
|
+
onMouseLeave={() => closeSubmenu()}
|
|
106
|
+
>
|
|
107
|
+
<button
|
|
108
|
+
type="button"
|
|
109
|
+
role="menuitem"
|
|
110
|
+
aria-haspopup={hasChildren ? "menu" : undefined}
|
|
111
|
+
aria-expanded={hasChildren ? isSubmenuOpen : undefined}
|
|
112
|
+
onClick={handleClick}
|
|
113
|
+
onKeyDown={handleKeyDown}
|
|
114
|
+
className={itemClassName}
|
|
115
|
+
>
|
|
116
|
+
<span>{option.label}</span>
|
|
117
|
+
{hasChildren ? (
|
|
118
|
+
<svg
|
|
119
|
+
aria-hidden="true"
|
|
120
|
+
className="h-4 w-4 shrink-0 text-gray-500"
|
|
121
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
122
|
+
viewBox="0 0 20 20"
|
|
123
|
+
fill="currentColor"
|
|
124
|
+
>
|
|
125
|
+
<path
|
|
126
|
+
fillRule="evenodd"
|
|
127
|
+
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
|
|
128
|
+
clipRule="evenodd"
|
|
129
|
+
/>
|
|
130
|
+
</svg>
|
|
131
|
+
) : null}
|
|
132
|
+
</button>
|
|
133
|
+
{hasChildren && isSubmenuOpen ? (
|
|
134
|
+
<div
|
|
135
|
+
role="menu"
|
|
136
|
+
className="absolute right-full top-0 z-20 mr-1 w-56 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
|
|
137
|
+
>
|
|
138
|
+
{option.children?.map((child) => (
|
|
139
|
+
<DropdownMenuItem
|
|
140
|
+
key={child.value}
|
|
141
|
+
option={child}
|
|
142
|
+
onLeafClick={onLeafClick}
|
|
143
|
+
/>
|
|
144
|
+
))}
|
|
145
|
+
</div>
|
|
146
|
+
) : null}
|
|
147
|
+
</div>
|
|
148
|
+
);
|
|
9
149
|
};
|
|
10
150
|
|
|
11
151
|
type Props = {
|
|
12
152
|
type?: "reset" | "button" | "submit" | undefined;
|
|
13
153
|
title?: string;
|
|
14
154
|
name?: string;
|
|
15
|
-
onClickSave?: (e:
|
|
155
|
+
onClickSave?: (e: ReactMouseEvent<HTMLButtonElement>) => void;
|
|
16
156
|
//Cargando...
|
|
17
157
|
loading?: boolean;
|
|
18
158
|
loadingText?: string;
|
|
19
159
|
selectedColor?: "indigo" | "green" | "red" | "yellow" | "gray";
|
|
20
|
-
children?:
|
|
160
|
+
children?: ReactNode;
|
|
21
161
|
className?: string;
|
|
22
162
|
disabled?: boolean;
|
|
23
163
|
enableFormErrorsValidation?: boolean;
|
|
@@ -119,9 +259,17 @@ export const Button = (props: Props) => {
|
|
|
119
259
|
}
|
|
120
260
|
};
|
|
121
261
|
|
|
262
|
+
const handleEscape = (event: KeyboardEvent) => {
|
|
263
|
+
if (event.key === "Escape") {
|
|
264
|
+
setIsDropdownOpen(false);
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
|
|
122
268
|
document.addEventListener("mousedown", handleClickOutside);
|
|
269
|
+
document.addEventListener("keydown", handleEscape);
|
|
123
270
|
return () => {
|
|
124
271
|
document.removeEventListener("mousedown", handleClickOutside);
|
|
272
|
+
document.removeEventListener("keydown", handleEscape);
|
|
125
273
|
};
|
|
126
274
|
}, []);
|
|
127
275
|
|
|
@@ -191,12 +339,13 @@ export const Button = (props: Props) => {
|
|
|
191
339
|
? children ?? loadingText
|
|
192
340
|
: children ?? dropdownTitle ?? title}
|
|
193
341
|
<svg
|
|
194
|
-
className={`ml-2 -mr-1 h-4 w-4 transition-transform ${
|
|
342
|
+
className={`ml-2 -mr-1 h-4 w-4 transition-transform motion-reduce:transition-none ${
|
|
195
343
|
isDropdownOpen ? "rotate-180" : ""
|
|
196
344
|
}`}
|
|
197
345
|
xmlns="http://www.w3.org/2000/svg"
|
|
198
346
|
viewBox="0 0 20 20"
|
|
199
347
|
fill="currentColor"
|
|
348
|
+
aria-hidden="true"
|
|
200
349
|
>
|
|
201
350
|
<path
|
|
202
351
|
fillRule="evenodd"
|
|
@@ -214,18 +363,11 @@ export const Button = (props: Props) => {
|
|
|
214
363
|
>
|
|
215
364
|
<div className="py-1">
|
|
216
365
|
{dropdownOptions.map((option) => (
|
|
217
|
-
<
|
|
366
|
+
<DropdownMenuItem
|
|
218
367
|
key={option.value}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
option.onClick();
|
|
223
|
-
setIsDropdownOpen(false);
|
|
224
|
-
}}
|
|
225
|
-
className="block w-full cursor-pointer px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-900 text-left"
|
|
226
|
-
>
|
|
227
|
-
{option.label}
|
|
228
|
-
</button>
|
|
368
|
+
option={option}
|
|
369
|
+
onLeafClick={() => setIsDropdownOpen(false)}
|
|
370
|
+
/>
|
|
229
371
|
))}
|
|
230
372
|
</div>
|
|
231
373
|
</div>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useFetcher } from "@remix-run/react";
|
|
2
2
|
import { useEffect, useState } from "react";
|
|
3
|
+
import { isOauthUserinfoUnauthorizedError } from "@zauru-sdk/common";
|
|
3
4
|
import { useValidateNotifications } from "@zauru-sdk/hooks";
|
|
4
5
|
import {
|
|
5
6
|
cleanLocalStorage,
|
|
@@ -69,6 +70,21 @@ export function ZauruLoginPage({
|
|
|
69
70
|
setIsClient(true);
|
|
70
71
|
}, []);
|
|
71
72
|
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
const alreadyReauthed = document.cookie
|
|
75
|
+
.split(";")
|
|
76
|
+
.some((part) => part.trim() === "zauru_oauth_reauth=1");
|
|
77
|
+
if (
|
|
78
|
+
fetcher.data?.error &&
|
|
79
|
+
!alreadyReauthed &&
|
|
80
|
+
isOauthUserinfoUnauthorizedError(
|
|
81
|
+
String(fetcher.data?.description ?? ""),
|
|
82
|
+
)
|
|
83
|
+
) {
|
|
84
|
+
window.location.replace("/logout?reauth=1");
|
|
85
|
+
}
|
|
86
|
+
}, [fetcher.data]);
|
|
87
|
+
|
|
72
88
|
useEffect(() => {
|
|
73
89
|
cleanLocalStorage(cleanLocalStorageArgs);
|
|
74
90
|
fetcher.submit({ action: "login" }, { method: "post" });
|