@greatapps/common 1.1.272 → 1.1.275
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/dist/components/layouts/AppNavBar.mjs +2 -9
- package/dist/components/layouts/AppNavBar.mjs.map +1 -1
- package/dist/components/layouts/ProfilePopover.mjs +1 -7
- package/dist/components/layouts/ProfilePopover.mjs.map +1 -1
- package/dist/modules/whitelabel/services/whitelabel.service.mjs +32 -7
- package/dist/modules/whitelabel/services/whitelabel.service.mjs.map +1 -1
- package/dist/providers/auth.provider.mjs +18 -4
- package/dist/providers/auth.provider.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/layouts/AppNavBar.tsx +1 -6
- package/src/components/layouts/ProfilePopover.tsx +0 -7
- package/src/modules/whitelabel/services/whitelabel.service.ts +52 -25
- package/src/providers/auth.provider.tsx +56 -38
|
@@ -11,8 +11,7 @@ function AppNavBar({
|
|
|
11
11
|
showExpandButton = false,
|
|
12
12
|
onExpandClick,
|
|
13
13
|
menuItems = [],
|
|
14
|
-
onNotificationsViewAll
|
|
15
|
-
logoutRedirect
|
|
14
|
+
onNotificationsViewAll
|
|
16
15
|
}) {
|
|
17
16
|
const router = useRouter();
|
|
18
17
|
const handleLogoClick = onLogoClick ?? (() => router.push("/"));
|
|
@@ -27,13 +26,7 @@ function AppNavBar({
|
|
|
27
26
|
onExpandClick,
|
|
28
27
|
children: [
|
|
29
28
|
/* @__PURE__ */ jsx(NotificationsPopover, { onViewAll: onNotificationsViewAll }),
|
|
30
|
-
/* @__PURE__ */ jsx(
|
|
31
|
-
ProfilePopover,
|
|
32
|
-
{
|
|
33
|
-
menuItems,
|
|
34
|
-
logoutRedirect
|
|
35
|
-
}
|
|
36
|
-
)
|
|
29
|
+
/* @__PURE__ */ jsx(ProfilePopover, { menuItems })
|
|
37
30
|
]
|
|
38
31
|
}
|
|
39
32
|
);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/layouts/AppNavBar.tsx"],"sourcesContent":["\"use client\";\n\nimport { useRouter } from \"next/navigation\";\nimport { NavBar } from \"./NavBar\";\nimport { ProfilePopover } from \"./ProfilePopover\";\nimport { NotificationsPopover } from \"./NotificationsPopover\";\nimport type { ProfileMenuItem } from \"./ProfilePopover\";\n\nexport interface AppNavBarProps {\n onLogoClick?: () => void;\n /** Which app is currently active — controls which icon gets the indicator */\n activeApp?: \"gapps\" | \"gpages\";\n appIconHref?: string;\n showExpandButton?: boolean;\n onExpandClick?: () => void;\n menuItems?: ProfileMenuItem[];\n onNotificationsViewAll?: () => void;\n
|
|
1
|
+
{"version":3,"sources":["../../../src/components/layouts/AppNavBar.tsx"],"sourcesContent":["\"use client\";\n\nimport { useRouter } from \"next/navigation\";\nimport { NavBar } from \"./NavBar\";\nimport { ProfilePopover } from \"./ProfilePopover\";\nimport { NotificationsPopover } from \"./NotificationsPopover\";\nimport type { ProfileMenuItem } from \"./ProfilePopover\";\n\nexport interface AppNavBarProps {\n onLogoClick?: () => void;\n /** Which app is currently active — controls which icon gets the indicator */\n activeApp?: \"gapps\" | \"gpages\";\n appIconHref?: string;\n showExpandButton?: boolean;\n onExpandClick?: () => void;\n menuItems?: ProfileMenuItem[];\n onNotificationsViewAll?: () => void;\n}\n\nexport function AppNavBar({\n onLogoClick,\n activeApp,\n appIconHref = \"/\",\n showExpandButton = false,\n onExpandClick,\n menuItems = [],\n onNotificationsViewAll,\n}: AppNavBarProps) {\n const router = useRouter();\n\n const handleLogoClick = onLogoClick ?? (() => router.push(\"/\"));\n const handleAppIconClick = () => router.push(appIconHref);\n\n return (\n <NavBar\n onLogoClick={handleLogoClick}\n onAppIconClick={handleAppIconClick}\n activeApp={activeApp}\n showExpandButton={showExpandButton}\n onExpandClick={onExpandClick}\n >\n <NotificationsPopover onViewAll={onNotificationsViewAll} />\n <ProfilePopover menuItems={menuItems} />\n </NavBar>\n );\n}\n"],"mappings":";AAkCI,SAOE,KAPF;AAhCJ,SAAS,iBAAiB;AAC1B,SAAS,cAAc;AACvB,SAAS,sBAAsB;AAC/B,SAAS,4BAA4B;AAc9B,SAAS,UAAU;AAAA,EACxB;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd,mBAAmB;AAAA,EACnB;AAAA,EACA,YAAY,CAAC;AAAA,EACb;AACF,GAAmB;AACjB,QAAM,SAAS,UAAU;AAEzB,QAAM,kBAAkB,gBAAgB,MAAM,OAAO,KAAK,GAAG;AAC7D,QAAM,qBAAqB,MAAM,OAAO,KAAK,WAAW;AAExD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAa;AAAA,MACb,gBAAgB;AAAA,MAChB;AAAA,MACA;AAAA,MACA;AAAA,MAEA;AAAA,4BAAC,wBAAqB,WAAW,wBAAwB;AAAA,QACzD,oBAAC,kBAAe,WAAsB;AAAA;AAAA;AAAA,EACxC;AAEJ;","names":[]}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from "react";
|
|
4
|
-
import { useRouter } from "next/navigation";
|
|
5
4
|
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
|
6
5
|
import { Popover } from "../ui/overlay/Popover";
|
|
7
6
|
import { Progress } from "../ui/feedback/Progress";
|
|
@@ -17,10 +16,8 @@ function ProfilePopover({
|
|
|
17
16
|
side = "top",
|
|
18
17
|
align = "start",
|
|
19
18
|
contentClassName = "absolute bottom-2 left-[26px]",
|
|
20
|
-
menuItems = []
|
|
21
|
-
logoutRedirect = "/login"
|
|
19
|
+
menuItems = []
|
|
22
20
|
}) {
|
|
23
|
-
const router = useRouter();
|
|
24
21
|
const { user, logout } = useAuth();
|
|
25
22
|
const [isLoggingOut, setIsLoggingOut] = useState(false);
|
|
26
23
|
const { summary, subscription, isSubscriptionLoading, isCreditsLoading } = useIaCredits();
|
|
@@ -31,9 +28,6 @@ function ProfilePopover({
|
|
|
31
28
|
await logout();
|
|
32
29
|
} finally {
|
|
33
30
|
setIsLoggingOut(false);
|
|
34
|
-
if (logoutRedirect !== "/login") {
|
|
35
|
-
router.push(logoutRedirect);
|
|
36
|
-
}
|
|
37
31
|
}
|
|
38
32
|
};
|
|
39
33
|
const hasSubscription = !isSubscriptionLoading && subscription !== null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/layouts/ProfilePopover.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState } from \"react\";\nimport
|
|
1
|
+
{"version":3,"sources":["../../../src/components/layouts/ProfilePopover.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState } from \"react\";\nimport * as PopoverPrimitive from \"@radix-ui/react-popover\";\nimport { Popover } from \"../ui/overlay/Popover\";\nimport { Progress } from \"../ui/feedback/Progress\";\nimport { Separator } from \"../ui/data-display/Separator\";\nimport { NavBarItem } from \"./NavBarItem\";\nimport { UserAvatar } from \"../ui/data-display/UserAvatar\";\nimport { LoadingOverlay } from \"../ui/feedback/LoadingOverlay\";\nimport { useAuth } from \"../../providers/auth.provider\";\nimport { cn } from \"../../infra/utils/clsx\";\nimport { useIaCredits } from \"../../modules/ia-credits/hooks/ia-credits.hook\";\nimport { useBuyCreditsModal } from \"../../store/useBuyCreditsModal\";\n\nexport interface ProfileMenuItem {\n icon?: React.ReactNode;\n label: string;\n onClick: () => void;\n}\n\nexport interface ProfilePopoverProps {\n side?: \"top\" | \"bottom\" | \"left\" | \"right\";\n align?: \"start\" | \"center\" | \"end\";\n contentClassName?: string;\n menuItems?: ProfileMenuItem[];\n}\n\nfunction ProfilePopover({\n side = \"top\",\n align = \"start\",\n contentClassName = \"absolute bottom-2 left-[26px]\",\n menuItems = [],\n}: ProfilePopoverProps) {\n const { user, logout } = useAuth();\n const [isLoggingOut, setIsLoggingOut] = useState(false);\n\n const { summary, subscription, isSubscriptionLoading, isCreditsLoading } =\n useIaCredits();\n const { openModal: openBuyCreditsModal } = useBuyCreditsModal();\n\n const handleLogout = async () => {\n try {\n setIsLoggingOut(true);\n await logout();\n } finally {\n setIsLoggingOut(false);\n }\n };\n\n const hasSubscription = !isSubscriptionLoading && subscription !== null;\n\n const shouldShowCredits = hasSubscription && subscription.id_wl === 1;\n const creditsPercentage =\n shouldShowCredits && summary.totalCredits > 0\n ? (summary.usedCredits / summary.totalCredits) * 100\n : 0;\n\n return (\n <>\n <LoadingOverlay\n isLoading={isLoggingOut}\n message=\"Saindo da sua conta...\"\n />\n <Popover>\n <PopoverPrimitive.Trigger className=\"cursor-pointer\">\n {user && (\n <UserAvatar\n key={user.id}\n photo={user.photo}\n name={user.name}\n size={40}\n />\n )}\n </PopoverPrimitive.Trigger>\n\n <PopoverPrimitive.Portal>\n <PopoverPrimitive.Content\n className={cn(\n \"w-64.75 p-0 bg-white rounded-2xl shadow-md border border-gray-200 z-99\",\n contentClassName,\n )}\n side={side}\n align={align}\n sideOffset={8}\n >\n <div className=\"grid grid-cols-[32px_1fr] items-center gap-3 p-3 w-full\">\n {user && (\n <UserAvatar\n key={user.id}\n photo={user.photo}\n name={user.name}\n size={32}\n />\n )}\n\n <div className=\"flex flex-col gap-0.5 min-w-0\">\n <span className=\"paragraph-small-semibold text-gray-950 truncate\">\n {user?.name || \"\"}\n </span>\n <span className=\"paragraph-xsmall-medium text-gray-500 truncate\">\n {user?.email || \"\"}\n </span>\n </div>\n </div>\n\n <div className=\"flex flex-col gap-2 border-t border-gray-200 rounded-2xl p-1.5\">\n {shouldShowCredits && (\n <div\n className={cn(\n \"flex flex-col bg-gray-950 rounded-lg gap-4 w-full p-4 transition-colors\",\n \"cursor-pointer hover:bg-gray-900\",\n )}\n onClick={() => openBuyCreditsModal()}\n >\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"paragraph-xsmall-semibold text-white truncate\">\n Créditos usados\n </span>\n <span className=\"paragraph-xsmall-medium text-white/50\">\n {summary.usedCredits.toLocaleString(\"pt-BR\")}/\n {summary.totalCredits.toLocaleString(\"pt-BR\")}\n </span>\n </div>\n <Progress\n className=\"bg-white/10\"\n indicatorColor=\"bg-white\"\n value={creditsPercentage}\n />\n </div>\n )}\n\n <div className=\"flex flex-col gap-2\">\n {menuItems.length > 0 && (\n <div className=\"flex flex-col\">\n {menuItems.map((item, index) => (\n <NavBarItem\n key={index}\n icon={item.icon}\n label={item.label}\n onClick={item.onClick}\n />\n ))}\n </div>\n )}\n <Separator />\n <NavBarItem label=\"Sair\" onClick={handleLogout} />\n </div>\n </div>\n </PopoverPrimitive.Content>\n </PopoverPrimitive.Portal>\n </Popover>\n </>\n );\n}\n\nexport { ProfilePopover };\n"],"mappings":"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","names":[]}
|
|
@@ -4,14 +4,22 @@ class WhitelabelService {
|
|
|
4
4
|
getApiUrl() {
|
|
5
5
|
const apiUrl = process.env.GAPPS_R3_API_URL;
|
|
6
6
|
if (!apiUrl) {
|
|
7
|
-
throw new ApiError(
|
|
7
|
+
throw new ApiError(
|
|
8
|
+
"GAPPS_R3_API_URL not configured",
|
|
9
|
+
"CONFIG_ERROR",
|
|
10
|
+
500
|
|
11
|
+
);
|
|
8
12
|
}
|
|
9
13
|
return apiUrl;
|
|
10
14
|
}
|
|
11
15
|
getToken() {
|
|
12
16
|
const token = process.env.WHITELABEL_TOKEN_MASTER;
|
|
13
17
|
if (!token) {
|
|
14
|
-
throw new ApiError(
|
|
18
|
+
throw new ApiError(
|
|
19
|
+
"WHITELABEL_TOKEN_MASTER not configured",
|
|
20
|
+
"CONFIG_ERROR",
|
|
21
|
+
500
|
|
22
|
+
);
|
|
15
23
|
}
|
|
16
24
|
return token;
|
|
17
25
|
}
|
|
@@ -38,7 +46,9 @@ class WhitelabelService {
|
|
|
38
46
|
}
|
|
39
47
|
});
|
|
40
48
|
if (!response.ok) {
|
|
41
|
-
console.error("[WhitelabelService] Failed to fetch whitelabel token", {
|
|
49
|
+
console.error("[WhitelabelService] Failed to fetch whitelabel token", {
|
|
50
|
+
response
|
|
51
|
+
});
|
|
42
52
|
throw new ApiError(
|
|
43
53
|
`Failed to fetch whitelabel token: ${response.status}`,
|
|
44
54
|
"FETCH_ERROR",
|
|
@@ -67,11 +77,18 @@ class WhitelabelService {
|
|
|
67
77
|
async getTokenByWhitelabelId(idWl) {
|
|
68
78
|
const apiUrl = this.getApiUrl();
|
|
69
79
|
const masterToken = this.getToken();
|
|
70
|
-
const url = `${apiUrl}/pt-br/${idWl}/tokens?limit=1&page=1&sort=id:desc`;
|
|
80
|
+
const url = `${apiUrl}/v1/pt-br/${idWl}/tokens?limit=1&page=1&sort=id:desc`;
|
|
71
81
|
const response = await fetch(url, {
|
|
72
82
|
method: "GET",
|
|
73
83
|
headers: { authorization: masterToken }
|
|
74
84
|
});
|
|
85
|
+
console.debug("[WhitelabelService] Fetching token by whitelabel ID", {
|
|
86
|
+
url,
|
|
87
|
+
config: {
|
|
88
|
+
method: "GET",
|
|
89
|
+
headers: { authorization: masterToken }
|
|
90
|
+
}
|
|
91
|
+
});
|
|
75
92
|
if (!response.ok) {
|
|
76
93
|
throw new ApiError(
|
|
77
94
|
`Failed to fetch whitelabel token: ${response.status}`,
|
|
@@ -81,7 +98,11 @@ class WhitelabelService {
|
|
|
81
98
|
}
|
|
82
99
|
const result = await response.json();
|
|
83
100
|
if (result.status !== 1 || !result.data?.length) {
|
|
84
|
-
throw new ApiError(
|
|
101
|
+
throw new ApiError(
|
|
102
|
+
"Token do whitelabel n\xE3o encontrado",
|
|
103
|
+
"WL_TOKEN_NOT_FOUND",
|
|
104
|
+
404
|
|
105
|
+
);
|
|
85
106
|
}
|
|
86
107
|
return result.data[0].token;
|
|
87
108
|
}
|
|
@@ -89,11 +110,15 @@ class WhitelabelService {
|
|
|
89
110
|
hostname = process.env.WHITELABEL_DOMAIN || hostname;
|
|
90
111
|
const cache = this.createCache();
|
|
91
112
|
const cacheKey = this.getCacheKey(hostname);
|
|
92
|
-
console.log("[WhitelabelService] Revalidating cache for domain", {
|
|
113
|
+
console.log("[WhitelabelService] Revalidating cache for domain", {
|
|
114
|
+
hostname
|
|
115
|
+
});
|
|
93
116
|
await cache.delete(cacheKey);
|
|
94
117
|
const data = await this.fetchFromApi(hostname);
|
|
95
118
|
await cache.insert(cacheKey, JSON.stringify(data), 604800);
|
|
96
|
-
console.log("[WhitelabelService] Cache revalidated for domain", {
|
|
119
|
+
console.log("[WhitelabelService] Cache revalidated for domain", {
|
|
120
|
+
hostname
|
|
121
|
+
});
|
|
97
122
|
return data;
|
|
98
123
|
}
|
|
99
124
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/modules/whitelabel/services/whitelabel.service.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"sources":["../../../../src/modules/whitelabel/services/whitelabel.service.ts"],"sourcesContent":["import greatCache from \"@greatapps/cache\";\r\nimport { ApiError } from \"../../../infra/api/types\";\r\nimport { WhitelabelTokenApiResponse, WhitelabelTokenData } from \"../schema\";\r\n\r\nclass WhitelabelService {\r\n private getApiUrl(): string {\r\n const apiUrl = process.env.GAPPS_R3_API_URL;\r\n if (!apiUrl) {\r\n throw new ApiError(\r\n \"GAPPS_R3_API_URL not configured\",\r\n \"CONFIG_ERROR\",\r\n 500,\r\n );\r\n }\r\n return apiUrl;\r\n }\r\n\r\n private getToken(): string {\r\n const token = process.env.WHITELABEL_TOKEN_MASTER;\r\n if (!token) {\r\n throw new ApiError(\r\n \"WHITELABEL_TOKEN_MASTER not configured\",\r\n \"CONFIG_ERROR\",\r\n 500,\r\n );\r\n }\r\n return token;\r\n }\r\n\r\n private createCache() {\r\n return new greatCache({\r\n service: \"whitelabel-service\",\r\n version: \"1.0\",\r\n domain: \"whitelabel-cache.greatapps.com.br\",\r\n ambient: process.env.NODE_ENV || \"development\",\r\n });\r\n }\r\n\r\n private getCacheKey(hostname: string): string {\r\n return `whitelabel-token-${hostname}`;\r\n }\r\n\r\n private async fetchFromApi(hostname: string): Promise<WhitelabelTokenData> {\r\n const apiUrl = this.getApiUrl();\r\n const whitelabelMasterToken = this.getToken();\r\n const url = `${apiUrl}/v1/pt-br/1/whitelabel/${hostname}/token`;\r\n\r\n console.log(\"[WhitelabelService] Fetching token for domain\", { url });\r\n\r\n const response = await fetch(url, {\r\n method: \"GET\",\r\n headers: {\r\n authorization: whitelabelMasterToken,\r\n },\r\n });\r\n\r\n if (!response.ok) {\r\n console.error(\"[WhitelabelService] Failed to fetch whitelabel token\", {\r\n response,\r\n });\r\n throw new ApiError(\r\n `Failed to fetch whitelabel token: ${response.status}`,\r\n \"FETCH_ERROR\",\r\n response.status,\r\n );\r\n }\r\n\r\n const result: WhitelabelTokenApiResponse = await response.json();\r\n\r\n if (result.status !== 1 || !result.data?.length) {\r\n throw new ApiError(\"Whitelabel token not found\", \"TOKEN_NOT_FOUND\", 404);\r\n }\r\n\r\n return result.data[0];\r\n }\r\n\r\n async getTokenByDomain(hostname: string): Promise<WhitelabelTokenData> {\r\n hostname = process.env.WHITELABEL_DOMAIN || hostname;\r\n\r\n const cache = this.createCache();\r\n const cacheKey = this.getCacheKey(hostname);\r\n\r\n const cachedData = await cache.select(cacheKey);\r\n\r\n if (cachedData.status == 1 && \"data\" in cachedData && cachedData.data) {\r\n console.log(\"[WhitelabelService] Cache hit for domain\", { hostname });\r\n return JSON.parse(cachedData.data) as WhitelabelTokenData;\r\n }\r\n\r\n const data = await this.fetchFromApi(hostname);\r\n\r\n await cache.insert(cacheKey, JSON.stringify(data), 604800);\r\n\r\n return data;\r\n }\r\n\r\n async getTokenByWhitelabelId(idWl: number): Promise<string> {\r\n const apiUrl = this.getApiUrl();\r\n const masterToken = this.getToken();\r\n const url = `${apiUrl}/v1/pt-br/${idWl}/tokens?limit=1&page=1&sort=id:desc`;\r\n\r\n const response = await fetch(url, {\r\n method: \"GET\",\r\n headers: { authorization: masterToken },\r\n });\r\n\r\n console.debug(\"[WhitelabelService] Fetching token by whitelabel ID\", {\r\n url,\r\n config: {\r\n method: \"GET\",\r\n headers: { authorization: masterToken },\r\n },\r\n });\r\n\r\n \r\n\r\n if (!response.ok) {\r\n throw new ApiError(\r\n `Failed to fetch whitelabel token: ${response.status}`,\r\n \"WL_TOKEN_NOT_FOUND\",\r\n response.status,\r\n );\r\n }\r\n\r\n const result: WhitelabelTokenApiResponse = await response.json();\r\n\r\n if (result.status !== 1 || !result.data?.length) {\r\n throw new ApiError(\r\n \"Token do whitelabel não encontrado\",\r\n \"WL_TOKEN_NOT_FOUND\",\r\n 404,\r\n );\r\n }\r\n\r\n return result.data[0].token;\r\n }\r\n\r\n async revalidateByDomain(hostname: string): Promise<WhitelabelTokenData> {\r\n hostname = process.env.WHITELABEL_DOMAIN || hostname;\r\n\r\n const cache = this.createCache();\r\n const cacheKey = this.getCacheKey(hostname);\r\n\r\n console.log(\"[WhitelabelService] Revalidating cache for domain\", {\r\n hostname,\r\n });\r\n\r\n await cache.delete(cacheKey);\r\n\r\n const data = await this.fetchFromApi(hostname);\r\n\r\n await cache.insert(cacheKey, JSON.stringify(data), 604800);\r\n\r\n console.log(\"[WhitelabelService] Cache revalidated for domain\", {\r\n hostname,\r\n });\r\n\r\n return data;\r\n }\r\n}\r\n\r\nexport const whitelabelService = new WhitelabelService();\r\n"],"mappings":"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","names":[]}
|
|
@@ -16,7 +16,7 @@ import { useLogin } from "../modules/auth/hooks/login.hook";
|
|
|
16
16
|
import { useRegister } from "../modules/auth/hooks/register.hook";
|
|
17
17
|
import { useLogout } from "../modules/auth/hooks/logout.hook";
|
|
18
18
|
import { useCurrentAccount, ACCOUNT_QUERY_KEY } from "../modules/accounts/hooks/current-account.hook";
|
|
19
|
-
import {
|
|
19
|
+
import { useWhitelabelUrls } from "./whitelabel.provider";
|
|
20
20
|
const AuthContext = createContext(
|
|
21
21
|
void 0
|
|
22
22
|
);
|
|
@@ -26,7 +26,7 @@ function AuthProvider({ children }) {
|
|
|
26
26
|
const { isLoading: isSessionLoading } = useUserValidateSession();
|
|
27
27
|
const queryClient = useQueryClient();
|
|
28
28
|
const setUserData = useSetUserData();
|
|
29
|
-
const
|
|
29
|
+
const { accountsUrl, pagesUrl } = useWhitelabelUrls();
|
|
30
30
|
const { mutateAsync: loginMutate, isPending: isLogging } = useLogin();
|
|
31
31
|
const { mutateAsync: registerMutate, isPending: isRegistering } = useRegister();
|
|
32
32
|
const { mutateAsync: logoutMutate, isPending: isLoggingOut } = useLogout();
|
|
@@ -62,15 +62,29 @@ function AuthProvider({ children }) {
|
|
|
62
62
|
},
|
|
63
63
|
[queryClient, setUserData]
|
|
64
64
|
);
|
|
65
|
+
const resolveLogoutRedirect = useCallback(() => {
|
|
66
|
+
if (typeof window === "undefined") {
|
|
67
|
+
return "/login";
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const currentOrigin = window.location.origin;
|
|
71
|
+
const pagesOrigin = pagesUrl ? new URL(pagesUrl).origin : "";
|
|
72
|
+
if (pagesOrigin && currentOrigin === pagesOrigin) {
|
|
73
|
+
return accountsUrl || "/login";
|
|
74
|
+
}
|
|
75
|
+
} catch {
|
|
76
|
+
}
|
|
77
|
+
return "/login";
|
|
78
|
+
}, [accountsUrl, pagesUrl]);
|
|
65
79
|
const logout = useCallback(async () => {
|
|
66
80
|
try {
|
|
67
81
|
await logoutMutate(void 0);
|
|
68
82
|
} catch {
|
|
69
83
|
} finally {
|
|
70
84
|
queryClient.clear();
|
|
71
|
-
|
|
85
|
+
window.location.assign(resolveLogoutRedirect());
|
|
72
86
|
}
|
|
73
|
-
}, [queryClient,
|
|
87
|
+
}, [queryClient, logoutMutate, resolveLogoutRedirect]);
|
|
74
88
|
const value = useMemo(
|
|
75
89
|
() => ({
|
|
76
90
|
user: user ?? null,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/providers/auth.provider.tsx"],"sourcesContent":["\"use client\";\r\n\r\nimport {\r\n createContext,\r\n ReactNode,\r\n useCallback,\r\n useContext,\r\n useMemo,\r\n} from \"react\";\r\nimport { useQueryClient } from \"@tanstack/react-query\";\r\nimport {\
|
|
1
|
+
{"version":3,"sources":["../../src/providers/auth.provider.tsx"],"sourcesContent":["\"use client\";\r\n\r\nimport {\r\n createContext,\r\n ReactNode,\r\n useCallback,\r\n useContext,\r\n useMemo,\r\n} from \"react\";\r\nimport { useQueryClient } from \"@tanstack/react-query\";\r\nimport {\n useUserQuery,\n useSetUserData,\n useUserValidateSession,\n} from \"../modules/auth/hooks/useUserQuery\";\nimport { useLogin } from \"../modules/auth/hooks/login.hook\";\r\nimport { useRegister } from \"../modules/auth/hooks/register.hook\";\r\nimport { useLogout } from \"../modules/auth/hooks/logout.hook\";\r\nimport {\n AuthState,\n LoginRequest,\n RegisterRequest,\n} from \"../modules/auth/schema\";\n\r\nexport type LoginResult =\n | { result: \"success\" }\n | { result: \"two_factor_required\"; cookie: string }\n | { result: \"error\"; message: string };\nimport { useCurrentAccount, ACCOUNT_QUERY_KEY } from \"../modules/accounts/hooks/current-account.hook\";\nimport { useWhitelabelUrls } from \"./whitelabel.provider\";\n\ninterface AuthContextProps extends AuthState {\n login: (credentials: LoginRequest) => Promise<LoginResult>;\n register: (data: RegisterRequest) => Promise<void>;\n logout: () => Promise<void>;\n}\n\r\nexport const AuthContext = createContext<AuthContextProps | undefined>(\r\n undefined\r\n);\r\n\r\ninterface AuthProviderProps {\r\n children: ReactNode;\r\n}\r\n\r\nexport function AuthProvider({ children }: AuthProviderProps) {\r\n const { data: user, isLoading: isQueryLoading } = useUserQuery();\r\n const { data: account, isLoading: isAccountLoading } = useCurrentAccount();\r\n const { isLoading: isSessionLoading } = useUserValidateSession();\r\n\r\n const queryClient = useQueryClient();\n const setUserData = useSetUserData();\n const { accountsUrl, pagesUrl } = useWhitelabelUrls();\n\r\n const { mutateAsync: loginMutate, isPending: isLogging } = useLogin();\n const { mutateAsync: registerMutate, isPending: isRegistering } =\r\n useRegister();\r\n const { mutateAsync: logoutMutate, isPending: isLoggingOut } = useLogout();\r\n\r\n const isAuthenticated = !!user;\r\n const isLoading =\r\n isLogging ||\r\n isRegistering ||\r\n isLoggingOut ||\r\n isQueryLoading ||\r\n isAccountLoading ||\r\n isSessionLoading;\r\n\r\n const login = useCallback(\r\n async (credentials: LoginRequest): Promise<LoginResult> => {\r\n try {\r\n const data = await loginMutate(credentials);\r\n\r\n if (data.result === \"two_factor_required\") {\r\n return { result: \"two_factor_required\", cookie: data.cookie };\r\n }\r\n\r\n queryClient.clear();\r\n setUserData(data.user);\r\n queryClient.setQueryData(ACCOUNT_QUERY_KEY, data.account);\r\n\r\n return { result: \"success\" };\r\n } catch (error) {\r\n const message =\r\n error instanceof Error ? error.message : \"Ocorreu um erro ao fazer login. Tente novamente.\";\r\n return { result: \"error\", message };\r\n }\r\n },\r\n [queryClient, setUserData]\r\n );\r\n\r\n const register = useCallback(\r\n async (data: RegisterRequest): Promise<void> => {\r\n await registerMutate(data, {\r\n onSuccess: (data) => {\r\n queryClient.clear();\r\n setUserData(data.user);\r\n queryClient.setQueryData(ACCOUNT_QUERY_KEY, data.account);\r\n },\r\n });\r\n },\r\n [queryClient, setUserData]\r\n );\r\n\r\n const resolveLogoutRedirect = useCallback(() => {\n if (typeof window === \"undefined\") {\n return \"/login\";\n }\n\n try {\n const currentOrigin = window.location.origin;\n const pagesOrigin = pagesUrl ? new URL(pagesUrl).origin : \"\";\n\n if (pagesOrigin && currentOrigin === pagesOrigin) {\n return accountsUrl || \"/login\";\n }\n } catch {\n // fallback below\n }\n\n return \"/login\";\n }, [accountsUrl, pagesUrl]);\n\n const logout = useCallback(async (): Promise<void> => {\n try {\n await logoutMutate(undefined);\n } catch {\n // cookie já removido no servidor mesmo se a API falhou\n } finally {\n queryClient.clear();\n window.location.assign(resolveLogoutRedirect());\n }\n }, [queryClient, logoutMutate, resolveLogoutRedirect]);\n\r\n const value: AuthContextProps = useMemo(\r\n () => ({\r\n user: user ?? null,\r\n account: account ?? null,\r\n isAuthenticated,\r\n isLoading,\r\n login,\r\n register,\r\n logout,\r\n }),\r\n [user, account, isAuthenticated, isLoading, login, register, logout]\r\n );\r\n\r\n return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;\r\n}\r\n\r\nexport function useAuth(): AuthContextProps {\r\n const context = useContext(AuthContext);\r\n\r\n if (context === undefined) {\r\n throw new Error(\"useAuth deve ser usado dentro de um AuthProvider\");\r\n }\r\n\r\n return context;\r\n}\r\n"],"mappings":"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","names":["data"]}
|
package/package.json
CHANGED
|
@@ -15,7 +15,6 @@ export interface AppNavBarProps {
|
|
|
15
15
|
onExpandClick?: () => void;
|
|
16
16
|
menuItems?: ProfileMenuItem[];
|
|
17
17
|
onNotificationsViewAll?: () => void;
|
|
18
|
-
logoutRedirect?: string;
|
|
19
18
|
}
|
|
20
19
|
|
|
21
20
|
export function AppNavBar({
|
|
@@ -26,7 +25,6 @@ export function AppNavBar({
|
|
|
26
25
|
onExpandClick,
|
|
27
26
|
menuItems = [],
|
|
28
27
|
onNotificationsViewAll,
|
|
29
|
-
logoutRedirect,
|
|
30
28
|
}: AppNavBarProps) {
|
|
31
29
|
const router = useRouter();
|
|
32
30
|
|
|
@@ -42,10 +40,7 @@ export function AppNavBar({
|
|
|
42
40
|
onExpandClick={onExpandClick}
|
|
43
41
|
>
|
|
44
42
|
<NotificationsPopover onViewAll={onNotificationsViewAll} />
|
|
45
|
-
<ProfilePopover
|
|
46
|
-
menuItems={menuItems}
|
|
47
|
-
logoutRedirect={logoutRedirect}
|
|
48
|
-
/>
|
|
43
|
+
<ProfilePopover menuItems={menuItems} />
|
|
49
44
|
</NavBar>
|
|
50
45
|
);
|
|
51
46
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useState } from "react";
|
|
4
|
-
import { useRouter } from "next/navigation";
|
|
5
4
|
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
|
6
5
|
import { Popover } from "../ui/overlay/Popover";
|
|
7
6
|
import { Progress } from "../ui/feedback/Progress";
|
|
@@ -25,7 +24,6 @@ export interface ProfilePopoverProps {
|
|
|
25
24
|
align?: "start" | "center" | "end";
|
|
26
25
|
contentClassName?: string;
|
|
27
26
|
menuItems?: ProfileMenuItem[];
|
|
28
|
-
logoutRedirect?: string;
|
|
29
27
|
}
|
|
30
28
|
|
|
31
29
|
function ProfilePopover({
|
|
@@ -33,9 +31,7 @@ function ProfilePopover({
|
|
|
33
31
|
align = "start",
|
|
34
32
|
contentClassName = "absolute bottom-2 left-[26px]",
|
|
35
33
|
menuItems = [],
|
|
36
|
-
logoutRedirect = "/login",
|
|
37
34
|
}: ProfilePopoverProps) {
|
|
38
|
-
const router = useRouter();
|
|
39
35
|
const { user, logout } = useAuth();
|
|
40
36
|
const [isLoggingOut, setIsLoggingOut] = useState(false);
|
|
41
37
|
|
|
@@ -49,9 +45,6 @@ function ProfilePopover({
|
|
|
49
45
|
await logout();
|
|
50
46
|
} finally {
|
|
51
47
|
setIsLoggingOut(false);
|
|
52
|
-
if (logoutRedirect !== "/login") {
|
|
53
|
-
router.push(logoutRedirect);
|
|
54
|
-
}
|
|
55
48
|
}
|
|
56
49
|
};
|
|
57
50
|
|
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import { WhitelabelTokenApiResponse, WhitelabelTokenData } from '../schema';
|
|
1
|
+
import greatCache from "@greatapps/cache";
|
|
2
|
+
import { ApiError } from "../../../infra/api/types";
|
|
3
|
+
import { WhitelabelTokenApiResponse, WhitelabelTokenData } from "../schema";
|
|
5
4
|
|
|
6
5
|
class WhitelabelService {
|
|
7
6
|
private getApiUrl(): string {
|
|
8
7
|
const apiUrl = process.env.GAPPS_R3_API_URL;
|
|
9
8
|
if (!apiUrl) {
|
|
10
|
-
throw new ApiError(
|
|
9
|
+
throw new ApiError(
|
|
10
|
+
"GAPPS_R3_API_URL not configured",
|
|
11
|
+
"CONFIG_ERROR",
|
|
12
|
+
500,
|
|
13
|
+
);
|
|
11
14
|
}
|
|
12
15
|
return apiUrl;
|
|
13
16
|
}
|
|
@@ -15,17 +18,21 @@ class WhitelabelService {
|
|
|
15
18
|
private getToken(): string {
|
|
16
19
|
const token = process.env.WHITELABEL_TOKEN_MASTER;
|
|
17
20
|
if (!token) {
|
|
18
|
-
throw new ApiError(
|
|
21
|
+
throw new ApiError(
|
|
22
|
+
"WHITELABEL_TOKEN_MASTER not configured",
|
|
23
|
+
"CONFIG_ERROR",
|
|
24
|
+
500,
|
|
25
|
+
);
|
|
19
26
|
}
|
|
20
27
|
return token;
|
|
21
28
|
}
|
|
22
29
|
|
|
23
30
|
private createCache() {
|
|
24
31
|
return new greatCache({
|
|
25
|
-
service:
|
|
26
|
-
version:
|
|
27
|
-
domain:
|
|
28
|
-
ambient: process.env.NODE_ENV ||
|
|
32
|
+
service: "whitelabel-service",
|
|
33
|
+
version: "1.0",
|
|
34
|
+
domain: "whitelabel-cache.greatapps.com.br",
|
|
35
|
+
ambient: process.env.NODE_ENV || "development",
|
|
29
36
|
});
|
|
30
37
|
}
|
|
31
38
|
|
|
@@ -38,28 +45,30 @@ class WhitelabelService {
|
|
|
38
45
|
const whitelabelMasterToken = this.getToken();
|
|
39
46
|
const url = `${apiUrl}/v1/pt-br/1/whitelabel/${hostname}/token`;
|
|
40
47
|
|
|
41
|
-
console.log(
|
|
48
|
+
console.log("[WhitelabelService] Fetching token for domain", { url });
|
|
42
49
|
|
|
43
50
|
const response = await fetch(url, {
|
|
44
|
-
method:
|
|
51
|
+
method: "GET",
|
|
45
52
|
headers: {
|
|
46
53
|
authorization: whitelabelMasterToken,
|
|
47
54
|
},
|
|
48
55
|
});
|
|
49
56
|
|
|
50
57
|
if (!response.ok) {
|
|
51
|
-
console.error(
|
|
58
|
+
console.error("[WhitelabelService] Failed to fetch whitelabel token", {
|
|
59
|
+
response,
|
|
60
|
+
});
|
|
52
61
|
throw new ApiError(
|
|
53
62
|
`Failed to fetch whitelabel token: ${response.status}`,
|
|
54
|
-
|
|
55
|
-
response.status
|
|
63
|
+
"FETCH_ERROR",
|
|
64
|
+
response.status,
|
|
56
65
|
);
|
|
57
66
|
}
|
|
58
67
|
|
|
59
68
|
const result: WhitelabelTokenApiResponse = await response.json();
|
|
60
69
|
|
|
61
70
|
if (result.status !== 1 || !result.data?.length) {
|
|
62
|
-
throw new ApiError(
|
|
71
|
+
throw new ApiError("Whitelabel token not found", "TOKEN_NOT_FOUND", 404);
|
|
63
72
|
}
|
|
64
73
|
|
|
65
74
|
return result.data[0];
|
|
@@ -73,8 +82,8 @@ class WhitelabelService {
|
|
|
73
82
|
|
|
74
83
|
const cachedData = await cache.select(cacheKey);
|
|
75
84
|
|
|
76
|
-
if (cachedData.status == 1 &&
|
|
77
|
-
console.log(
|
|
85
|
+
if (cachedData.status == 1 && "data" in cachedData && cachedData.data) {
|
|
86
|
+
console.log("[WhitelabelService] Cache hit for domain", { hostname });
|
|
78
87
|
return JSON.parse(cachedData.data) as WhitelabelTokenData;
|
|
79
88
|
}
|
|
80
89
|
|
|
@@ -88,25 +97,39 @@ class WhitelabelService {
|
|
|
88
97
|
async getTokenByWhitelabelId(idWl: number): Promise<string> {
|
|
89
98
|
const apiUrl = this.getApiUrl();
|
|
90
99
|
const masterToken = this.getToken();
|
|
91
|
-
const url = `${apiUrl}/pt-br/${idWl}/tokens?limit=1&page=1&sort=id:desc`;
|
|
100
|
+
const url = `${apiUrl}/v1/pt-br/${idWl}/tokens?limit=1&page=1&sort=id:desc`;
|
|
92
101
|
|
|
93
102
|
const response = await fetch(url, {
|
|
94
|
-
method:
|
|
103
|
+
method: "GET",
|
|
95
104
|
headers: { authorization: masterToken },
|
|
96
105
|
});
|
|
97
106
|
|
|
107
|
+
console.debug("[WhitelabelService] Fetching token by whitelabel ID", {
|
|
108
|
+
url,
|
|
109
|
+
config: {
|
|
110
|
+
method: "GET",
|
|
111
|
+
headers: { authorization: masterToken },
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
|
|
98
117
|
if (!response.ok) {
|
|
99
118
|
throw new ApiError(
|
|
100
119
|
`Failed to fetch whitelabel token: ${response.status}`,
|
|
101
|
-
|
|
102
|
-
response.status
|
|
120
|
+
"WL_TOKEN_NOT_FOUND",
|
|
121
|
+
response.status,
|
|
103
122
|
);
|
|
104
123
|
}
|
|
105
124
|
|
|
106
125
|
const result: WhitelabelTokenApiResponse = await response.json();
|
|
107
126
|
|
|
108
127
|
if (result.status !== 1 || !result.data?.length) {
|
|
109
|
-
throw new ApiError(
|
|
128
|
+
throw new ApiError(
|
|
129
|
+
"Token do whitelabel não encontrado",
|
|
130
|
+
"WL_TOKEN_NOT_FOUND",
|
|
131
|
+
404,
|
|
132
|
+
);
|
|
110
133
|
}
|
|
111
134
|
|
|
112
135
|
return result.data[0].token;
|
|
@@ -118,7 +141,9 @@ class WhitelabelService {
|
|
|
118
141
|
const cache = this.createCache();
|
|
119
142
|
const cacheKey = this.getCacheKey(hostname);
|
|
120
143
|
|
|
121
|
-
console.log(
|
|
144
|
+
console.log("[WhitelabelService] Revalidating cache for domain", {
|
|
145
|
+
hostname,
|
|
146
|
+
});
|
|
122
147
|
|
|
123
148
|
await cache.delete(cacheKey);
|
|
124
149
|
|
|
@@ -126,7 +151,9 @@ class WhitelabelService {
|
|
|
126
151
|
|
|
127
152
|
await cache.insert(cacheKey, JSON.stringify(data), 604800);
|
|
128
153
|
|
|
129
|
-
console.log(
|
|
154
|
+
console.log("[WhitelabelService] Cache revalidated for domain", {
|
|
155
|
+
hostname,
|
|
156
|
+
});
|
|
130
157
|
|
|
131
158
|
return data;
|
|
132
159
|
}
|
|
@@ -8,32 +8,32 @@ import {
|
|
|
8
8
|
useMemo,
|
|
9
9
|
} from "react";
|
|
10
10
|
import { useQueryClient } from "@tanstack/react-query";
|
|
11
|
-
import {
|
|
12
|
-
useUserQuery,
|
|
13
|
-
useSetUserData,
|
|
14
|
-
useUserValidateSession,
|
|
15
|
-
} from "../modules/auth/hooks/useUserQuery";
|
|
11
|
+
import {
|
|
12
|
+
useUserQuery,
|
|
13
|
+
useSetUserData,
|
|
14
|
+
useUserValidateSession,
|
|
15
|
+
} from "../modules/auth/hooks/useUserQuery";
|
|
16
16
|
import { useLogin } from "../modules/auth/hooks/login.hook";
|
|
17
17
|
import { useRegister } from "../modules/auth/hooks/register.hook";
|
|
18
18
|
import { useLogout } from "../modules/auth/hooks/logout.hook";
|
|
19
|
-
import {
|
|
20
|
-
AuthState,
|
|
21
|
-
LoginRequest,
|
|
22
|
-
RegisterRequest,
|
|
23
|
-
} from "../modules/auth/schema";
|
|
24
|
-
|
|
25
|
-
export type LoginResult =
|
|
26
|
-
| { result: "success" }
|
|
27
|
-
| { result: "two_factor_required"; cookie: string }
|
|
28
|
-
| { result: "error"; message: string };
|
|
29
|
-
import { useCurrentAccount, ACCOUNT_QUERY_KEY } from "../modules/accounts/hooks/current-account.hook";
|
|
30
|
-
import {
|
|
31
|
-
|
|
32
|
-
interface AuthContextProps extends AuthState {
|
|
33
|
-
login: (credentials: LoginRequest) => Promise<LoginResult>;
|
|
34
|
-
register: (data: RegisterRequest) => Promise<void>;
|
|
35
|
-
logout: () => Promise<void>;
|
|
36
|
-
}
|
|
19
|
+
import {
|
|
20
|
+
AuthState,
|
|
21
|
+
LoginRequest,
|
|
22
|
+
RegisterRequest,
|
|
23
|
+
} from "../modules/auth/schema";
|
|
24
|
+
|
|
25
|
+
export type LoginResult =
|
|
26
|
+
| { result: "success" }
|
|
27
|
+
| { result: "two_factor_required"; cookie: string }
|
|
28
|
+
| { result: "error"; message: string };
|
|
29
|
+
import { useCurrentAccount, ACCOUNT_QUERY_KEY } from "../modules/accounts/hooks/current-account.hook";
|
|
30
|
+
import { useWhitelabelUrls } from "./whitelabel.provider";
|
|
31
|
+
|
|
32
|
+
interface AuthContextProps extends AuthState {
|
|
33
|
+
login: (credentials: LoginRequest) => Promise<LoginResult>;
|
|
34
|
+
register: (data: RegisterRequest) => Promise<void>;
|
|
35
|
+
logout: () => Promise<void>;
|
|
36
|
+
}
|
|
37
37
|
|
|
38
38
|
export const AuthContext = createContext<AuthContextProps | undefined>(
|
|
39
39
|
undefined
|
|
@@ -48,12 +48,11 @@ export function AuthProvider({ children }: AuthProviderProps) {
|
|
|
48
48
|
const { data: account, isLoading: isAccountLoading } = useCurrentAccount();
|
|
49
49
|
const { isLoading: isSessionLoading } = useUserValidateSession();
|
|
50
50
|
|
|
51
|
-
const queryClient = useQueryClient();
|
|
52
|
-
const setUserData = useSetUserData();
|
|
53
|
-
|
|
54
|
-
const router = useRouter();
|
|
51
|
+
const queryClient = useQueryClient();
|
|
52
|
+
const setUserData = useSetUserData();
|
|
53
|
+
const { accountsUrl, pagesUrl } = useWhitelabelUrls();
|
|
55
54
|
|
|
56
|
-
const { mutateAsync: loginMutate, isPending: isLogging } = useLogin();
|
|
55
|
+
const { mutateAsync: loginMutate, isPending: isLogging } = useLogin();
|
|
57
56
|
const { mutateAsync: registerMutate, isPending: isRegistering } =
|
|
58
57
|
useRegister();
|
|
59
58
|
const { mutateAsync: logoutMutate, isPending: isLoggingOut } = useLogout();
|
|
@@ -103,16 +102,35 @@ export function AuthProvider({ children }: AuthProviderProps) {
|
|
|
103
102
|
[queryClient, setUserData]
|
|
104
103
|
);
|
|
105
104
|
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
105
|
+
const resolveLogoutRedirect = useCallback(() => {
|
|
106
|
+
if (typeof window === "undefined") {
|
|
107
|
+
return "/login";
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
const currentOrigin = window.location.origin;
|
|
112
|
+
const pagesOrigin = pagesUrl ? new URL(pagesUrl).origin : "";
|
|
113
|
+
|
|
114
|
+
if (pagesOrigin && currentOrigin === pagesOrigin) {
|
|
115
|
+
return accountsUrl || "/login";
|
|
116
|
+
}
|
|
117
|
+
} catch {
|
|
118
|
+
// fallback below
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return "/login";
|
|
122
|
+
}, [accountsUrl, pagesUrl]);
|
|
123
|
+
|
|
124
|
+
const logout = useCallback(async (): Promise<void> => {
|
|
125
|
+
try {
|
|
126
|
+
await logoutMutate(undefined);
|
|
127
|
+
} catch {
|
|
128
|
+
// cookie já removido no servidor mesmo se a API falhou
|
|
129
|
+
} finally {
|
|
130
|
+
queryClient.clear();
|
|
131
|
+
window.location.assign(resolveLogoutRedirect());
|
|
132
|
+
}
|
|
133
|
+
}, [queryClient, logoutMutate, resolveLogoutRedirect]);
|
|
116
134
|
|
|
117
135
|
const value: AuthContextProps = useMemo(
|
|
118
136
|
() => ({
|