@windrun-huaiin/third-ui 31.3.5 → 31.3.6
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/main/credit/credit-overview-nav-client.d.ts +16 -1
- package/dist/main/credit/credit-overview-nav-client.js +92 -30
- package/dist/main/credit/credit-overview-nav-client.mjs +92 -32
- package/dist/main/credit/index.d.ts +1 -1
- package/dist/main/credit/index.js +2 -0
- package/dist/main/credit/index.mjs +1 -1
- package/package.json +1 -1
- package/src/main/credit/credit-overview-nav-client.tsx +124 -28
- package/src/main/credit/index.ts +5 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type CreditOverviewTranslations } from './credit-overview-client';
|
|
2
2
|
import type { CreditOverviewData } from './types';
|
|
3
|
+
export declare const DEFAULT_CREDIT_OVERVIEW_REFRESH_EVENT = "windrun-huaiin:credit-overview:refresh";
|
|
3
4
|
export interface CreditOverviewPayload {
|
|
4
5
|
data: CreditOverviewData;
|
|
5
6
|
totalLabel: string;
|
|
@@ -8,5 +9,19 @@ export interface CreditOverviewPayload {
|
|
|
8
9
|
export interface CreditOverviewNavClientProps {
|
|
9
10
|
locale: string;
|
|
10
11
|
endpoint: string;
|
|
12
|
+
/**
|
|
13
|
+
* Browser event names that should trigger credit overview reload.
|
|
14
|
+
* Business apps can dispatch these events after credit balance changes.
|
|
15
|
+
*
|
|
16
|
+
* Default: ['windrun:credit-overview:refresh']
|
|
17
|
+
* Empty array means event listening is disabled.
|
|
18
|
+
*/
|
|
19
|
+
refreshEvents?: string[];
|
|
20
|
+
/**
|
|
21
|
+
* Optional debounce for refresh event bursts.
|
|
22
|
+
* Default: 300ms.
|
|
23
|
+
*/
|
|
24
|
+
refreshDebounceMs?: number;
|
|
11
25
|
}
|
|
12
|
-
export declare function
|
|
26
|
+
export declare function dispatchCreditOverviewRefresh(eventName?: string): void;
|
|
27
|
+
export declare function CreditOverviewNavClient({ locale, endpoint, refreshEvents, refreshDebounceMs, }: CreditOverviewNavClientProps): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -8,54 +8,114 @@ var React = require('react');
|
|
|
8
8
|
var creditNavButton = require('./credit-nav-button.js');
|
|
9
9
|
var creditOverviewClient = require('./credit-overview-client.js');
|
|
10
10
|
|
|
11
|
+
const DEFAULT_CREDIT_OVERVIEW_REFRESH_EVENT = 'windrun-huaiin:credit-overview:refresh';
|
|
11
12
|
function buildCreditOverviewUrl(endpoint, locale) {
|
|
12
13
|
const url = new URL(endpoint, window.location.origin);
|
|
13
14
|
url.searchParams.set('locale', locale);
|
|
14
15
|
return url.toString();
|
|
15
16
|
}
|
|
16
|
-
function
|
|
17
|
+
function normalizeRefreshEvents(refreshEvents) {
|
|
18
|
+
const events = refreshEvents !== null && refreshEvents !== void 0 ? refreshEvents : [DEFAULT_CREDIT_OVERVIEW_REFRESH_EVENT];
|
|
19
|
+
return Array.from(new Set(events
|
|
20
|
+
.map((eventName) => eventName.trim())
|
|
21
|
+
.filter((eventName) => eventName.length > 0)));
|
|
22
|
+
}
|
|
23
|
+
function buildRefreshEventsKey(refreshEvents) {
|
|
24
|
+
return normalizeRefreshEvents(refreshEvents).join('\n');
|
|
25
|
+
}
|
|
26
|
+
function dispatchCreditOverviewRefresh(eventName = DEFAULT_CREDIT_OVERVIEW_REFRESH_EVENT) {
|
|
27
|
+
if (typeof window === 'undefined') {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
window.dispatchEvent(new CustomEvent(eventName));
|
|
31
|
+
}
|
|
32
|
+
function CreditOverviewNavClient({ locale, endpoint, refreshEvents, refreshDebounceMs = 300, }) {
|
|
17
33
|
const { isLoaded, isSignedIn, userId } = nextjs.useAuth();
|
|
18
34
|
const [payload, setPayload] = React.useState(null);
|
|
35
|
+
const abortControllerRef = React.useRef(null);
|
|
36
|
+
const isMountedRef = React.useRef(false);
|
|
37
|
+
const refreshEventsKey = buildRefreshEventsKey(refreshEvents);
|
|
38
|
+
const normalizedRefreshEvents = React.useMemo(() => refreshEventsKey.split('\n').filter(Boolean), [refreshEventsKey]);
|
|
19
39
|
React.useEffect(() => {
|
|
40
|
+
isMountedRef.current = true;
|
|
41
|
+
return () => {
|
|
42
|
+
var _a;
|
|
43
|
+
isMountedRef.current = false;
|
|
44
|
+
(_a = abortControllerRef.current) === null || _a === void 0 ? void 0 : _a.abort();
|
|
45
|
+
};
|
|
46
|
+
}, []);
|
|
47
|
+
const loadCreditOverview = React.useCallback(() => tslib.__awaiter(this, void 0, void 0, function* () {
|
|
48
|
+
var _a;
|
|
49
|
+
(_a = abortControllerRef.current) === null || _a === void 0 ? void 0 : _a.abort();
|
|
50
|
+
const controller = new AbortController();
|
|
51
|
+
abortControllerRef.current = controller;
|
|
52
|
+
try {
|
|
53
|
+
const response = yield fetch(buildCreditOverviewUrl(endpoint, locale), {
|
|
54
|
+
cache: 'no-store',
|
|
55
|
+
credentials: 'same-origin',
|
|
56
|
+
signal: controller.signal,
|
|
57
|
+
});
|
|
58
|
+
if (!response.ok) {
|
|
59
|
+
if (!controller.signal.aborted && isMountedRef.current) {
|
|
60
|
+
setPayload(null);
|
|
61
|
+
}
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const nextPayload = (yield response.json());
|
|
65
|
+
if (!controller.signal.aborted && isMountedRef.current) {
|
|
66
|
+
setPayload(nextPayload);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
if (!controller.signal.aborted && isMountedRef.current) {
|
|
71
|
+
setPayload(null);
|
|
72
|
+
console.warn('[CreditOverviewNavClient] Failed to load credit overview', error);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
if (abortControllerRef.current === controller) {
|
|
77
|
+
abortControllerRef.current = null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}), [endpoint, locale]);
|
|
81
|
+
React.useEffect(() => {
|
|
82
|
+
var _a;
|
|
20
83
|
if (!isLoaded) {
|
|
21
84
|
return;
|
|
22
85
|
}
|
|
23
86
|
if (!isSignedIn) {
|
|
87
|
+
(_a = abortControllerRef.current) === null || _a === void 0 ? void 0 : _a.abort();
|
|
88
|
+
abortControllerRef.current = null;
|
|
24
89
|
setPayload(null);
|
|
25
90
|
return;
|
|
26
91
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
catch (error) {
|
|
47
|
-
if (!controller.signal.aborted) {
|
|
48
|
-
setPayload(null);
|
|
49
|
-
console.warn('[CreditOverviewNavClient] Failed to load credit overview', error);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
});
|
|
92
|
+
void loadCreditOverview();
|
|
93
|
+
}, [isLoaded, isSignedIn, loadCreditOverview, userId]);
|
|
94
|
+
React.useEffect(() => {
|
|
95
|
+
if (!isLoaded || !isSignedIn || normalizedRefreshEvents.length === 0) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
let debounceTimer = null;
|
|
99
|
+
const refresh = () => {
|
|
100
|
+
if (debounceTimer) {
|
|
101
|
+
window.clearTimeout(debounceTimer);
|
|
102
|
+
}
|
|
103
|
+
debounceTimer = window.setTimeout(() => {
|
|
104
|
+
void loadCreditOverview();
|
|
105
|
+
}, refreshDebounceMs);
|
|
106
|
+
};
|
|
107
|
+
for (const eventName of normalizedRefreshEvents) {
|
|
108
|
+
window.addEventListener(eventName, refresh);
|
|
53
109
|
}
|
|
54
|
-
loadCreditOverview();
|
|
55
110
|
return () => {
|
|
56
|
-
|
|
111
|
+
if (debounceTimer) {
|
|
112
|
+
window.clearTimeout(debounceTimer);
|
|
113
|
+
}
|
|
114
|
+
for (const eventName of normalizedRefreshEvents) {
|
|
115
|
+
window.removeEventListener(eventName, refresh);
|
|
116
|
+
}
|
|
57
117
|
};
|
|
58
|
-
}, [
|
|
118
|
+
}, [isLoaded, isSignedIn, loadCreditOverview, normalizedRefreshEvents, refreshDebounceMs]);
|
|
59
119
|
if (!payload) {
|
|
60
120
|
return null;
|
|
61
121
|
}
|
|
@@ -63,3 +123,5 @@ function CreditOverviewNavClient({ locale, endpoint, }) {
|
|
|
63
123
|
}
|
|
64
124
|
|
|
65
125
|
exports.CreditOverviewNavClient = CreditOverviewNavClient;
|
|
126
|
+
exports.DEFAULT_CREDIT_OVERVIEW_REFRESH_EVENT = DEFAULT_CREDIT_OVERVIEW_REFRESH_EVENT;
|
|
127
|
+
exports.dispatchCreditOverviewRefresh = dispatchCreditOverviewRefresh;
|
|
@@ -2,62 +2,122 @@
|
|
|
2
2
|
import { __awaiter } from 'tslib';
|
|
3
3
|
import { jsx } from 'react/jsx-runtime';
|
|
4
4
|
import { useAuth } from '@clerk/nextjs';
|
|
5
|
-
import { useState, useEffect } from 'react';
|
|
5
|
+
import { useState, useRef, useMemo, useEffect, useCallback } from 'react';
|
|
6
6
|
import { CreditNavButton } from './credit-nav-button.mjs';
|
|
7
7
|
import { CreditOverviewClient } from './credit-overview-client.mjs';
|
|
8
8
|
|
|
9
|
+
const DEFAULT_CREDIT_OVERVIEW_REFRESH_EVENT = 'windrun-huaiin:credit-overview:refresh';
|
|
9
10
|
function buildCreditOverviewUrl(endpoint, locale) {
|
|
10
11
|
const url = new URL(endpoint, window.location.origin);
|
|
11
12
|
url.searchParams.set('locale', locale);
|
|
12
13
|
return url.toString();
|
|
13
14
|
}
|
|
14
|
-
function
|
|
15
|
+
function normalizeRefreshEvents(refreshEvents) {
|
|
16
|
+
const events = refreshEvents !== null && refreshEvents !== void 0 ? refreshEvents : [DEFAULT_CREDIT_OVERVIEW_REFRESH_EVENT];
|
|
17
|
+
return Array.from(new Set(events
|
|
18
|
+
.map((eventName) => eventName.trim())
|
|
19
|
+
.filter((eventName) => eventName.length > 0)));
|
|
20
|
+
}
|
|
21
|
+
function buildRefreshEventsKey(refreshEvents) {
|
|
22
|
+
return normalizeRefreshEvents(refreshEvents).join('\n');
|
|
23
|
+
}
|
|
24
|
+
function dispatchCreditOverviewRefresh(eventName = DEFAULT_CREDIT_OVERVIEW_REFRESH_EVENT) {
|
|
25
|
+
if (typeof window === 'undefined') {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
window.dispatchEvent(new CustomEvent(eventName));
|
|
29
|
+
}
|
|
30
|
+
function CreditOverviewNavClient({ locale, endpoint, refreshEvents, refreshDebounceMs = 300, }) {
|
|
15
31
|
const { isLoaded, isSignedIn, userId } = useAuth();
|
|
16
32
|
const [payload, setPayload] = useState(null);
|
|
33
|
+
const abortControllerRef = useRef(null);
|
|
34
|
+
const isMountedRef = useRef(false);
|
|
35
|
+
const refreshEventsKey = buildRefreshEventsKey(refreshEvents);
|
|
36
|
+
const normalizedRefreshEvents = useMemo(() => refreshEventsKey.split('\n').filter(Boolean), [refreshEventsKey]);
|
|
17
37
|
useEffect(() => {
|
|
38
|
+
isMountedRef.current = true;
|
|
39
|
+
return () => {
|
|
40
|
+
var _a;
|
|
41
|
+
isMountedRef.current = false;
|
|
42
|
+
(_a = abortControllerRef.current) === null || _a === void 0 ? void 0 : _a.abort();
|
|
43
|
+
};
|
|
44
|
+
}, []);
|
|
45
|
+
const loadCreditOverview = useCallback(() => __awaiter(this, void 0, void 0, function* () {
|
|
46
|
+
var _a;
|
|
47
|
+
(_a = abortControllerRef.current) === null || _a === void 0 ? void 0 : _a.abort();
|
|
48
|
+
const controller = new AbortController();
|
|
49
|
+
abortControllerRef.current = controller;
|
|
50
|
+
try {
|
|
51
|
+
const response = yield fetch(buildCreditOverviewUrl(endpoint, locale), {
|
|
52
|
+
cache: 'no-store',
|
|
53
|
+
credentials: 'same-origin',
|
|
54
|
+
signal: controller.signal,
|
|
55
|
+
});
|
|
56
|
+
if (!response.ok) {
|
|
57
|
+
if (!controller.signal.aborted && isMountedRef.current) {
|
|
58
|
+
setPayload(null);
|
|
59
|
+
}
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const nextPayload = (yield response.json());
|
|
63
|
+
if (!controller.signal.aborted && isMountedRef.current) {
|
|
64
|
+
setPayload(nextPayload);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
if (!controller.signal.aborted && isMountedRef.current) {
|
|
69
|
+
setPayload(null);
|
|
70
|
+
console.warn('[CreditOverviewNavClient] Failed to load credit overview', error);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
if (abortControllerRef.current === controller) {
|
|
75
|
+
abortControllerRef.current = null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}), [endpoint, locale]);
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
var _a;
|
|
18
81
|
if (!isLoaded) {
|
|
19
82
|
return;
|
|
20
83
|
}
|
|
21
84
|
if (!isSignedIn) {
|
|
85
|
+
(_a = abortControllerRef.current) === null || _a === void 0 ? void 0 : _a.abort();
|
|
86
|
+
abortControllerRef.current = null;
|
|
22
87
|
setPayload(null);
|
|
23
88
|
return;
|
|
24
89
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
catch (error) {
|
|
45
|
-
if (!controller.signal.aborted) {
|
|
46
|
-
setPayload(null);
|
|
47
|
-
console.warn('[CreditOverviewNavClient] Failed to load credit overview', error);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
});
|
|
90
|
+
void loadCreditOverview();
|
|
91
|
+
}, [isLoaded, isSignedIn, loadCreditOverview, userId]);
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
if (!isLoaded || !isSignedIn || normalizedRefreshEvents.length === 0) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
let debounceTimer = null;
|
|
97
|
+
const refresh = () => {
|
|
98
|
+
if (debounceTimer) {
|
|
99
|
+
window.clearTimeout(debounceTimer);
|
|
100
|
+
}
|
|
101
|
+
debounceTimer = window.setTimeout(() => {
|
|
102
|
+
void loadCreditOverview();
|
|
103
|
+
}, refreshDebounceMs);
|
|
104
|
+
};
|
|
105
|
+
for (const eventName of normalizedRefreshEvents) {
|
|
106
|
+
window.addEventListener(eventName, refresh);
|
|
51
107
|
}
|
|
52
|
-
loadCreditOverview();
|
|
53
108
|
return () => {
|
|
54
|
-
|
|
109
|
+
if (debounceTimer) {
|
|
110
|
+
window.clearTimeout(debounceTimer);
|
|
111
|
+
}
|
|
112
|
+
for (const eventName of normalizedRefreshEvents) {
|
|
113
|
+
window.removeEventListener(eventName, refresh);
|
|
114
|
+
}
|
|
55
115
|
};
|
|
56
|
-
}, [
|
|
116
|
+
}, [isLoaded, isSignedIn, loadCreditOverview, normalizedRefreshEvents, refreshDebounceMs]);
|
|
57
117
|
if (!payload) {
|
|
58
118
|
return null;
|
|
59
119
|
}
|
|
60
120
|
return (jsx(CreditNavButton, { locale: locale, totalBalance: payload.data.totalBalance, totalLabel: payload.totalLabel, children: jsx(CreditOverviewClient, { locale: locale, data: payload.data, translations: payload.translations }) }));
|
|
61
121
|
}
|
|
62
122
|
|
|
63
|
-
export { CreditOverviewNavClient };
|
|
123
|
+
export { CreditOverviewNavClient, DEFAULT_CREDIT_OVERVIEW_REFRESH_EVENT, dispatchCreditOverviewRefresh };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { CreditOverviewClient } from './credit-overview-client';
|
|
2
|
-
export { CreditOverviewNavClient } from './credit-overview-nav-client';
|
|
2
|
+
export { CreditOverviewNavClient, DEFAULT_CREDIT_OVERVIEW_REFRESH_EVENT, dispatchCreditOverviewRefresh, } from './credit-overview-nav-client';
|
|
3
3
|
export { CreditNavButton } from './credit-nav-button';
|
|
4
4
|
export type { CreditOverviewTranslations } from './credit-overview-client';
|
|
5
5
|
export type { CreditOverviewNavClientProps, CreditOverviewPayload, } from './credit-overview-nav-client';
|
|
@@ -9,4 +9,6 @@ var creditNavButton = require('./credit-nav-button.js');
|
|
|
9
9
|
|
|
10
10
|
exports.CreditOverviewClient = creditOverviewClient.CreditOverviewClient;
|
|
11
11
|
exports.CreditOverviewNavClient = creditOverviewNavClient.CreditOverviewNavClient;
|
|
12
|
+
exports.DEFAULT_CREDIT_OVERVIEW_REFRESH_EVENT = creditOverviewNavClient.DEFAULT_CREDIT_OVERVIEW_REFRESH_EVENT;
|
|
13
|
+
exports.dispatchCreditOverviewRefresh = creditOverviewNavClient.dispatchCreditOverviewRefresh;
|
|
12
14
|
exports.CreditNavButton = creditNavButton.CreditNavButton;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
export { CreditOverviewClient } from './credit-overview-client.mjs';
|
|
3
|
-
export { CreditOverviewNavClient } from './credit-overview-nav-client.mjs';
|
|
3
|
+
export { CreditOverviewNavClient, DEFAULT_CREDIT_OVERVIEW_REFRESH_EVENT, dispatchCreditOverviewRefresh } from './credit-overview-nav-client.mjs';
|
|
4
4
|
export { CreditNavButton } from './credit-nav-button.mjs';
|
package/package.json
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useAuth } from '@clerk/nextjs';
|
|
4
|
-
import { useEffect, useState } from 'react';
|
|
4
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
5
5
|
import { CreditNavButton } from './credit-nav-button';
|
|
6
6
|
import { CreditOverviewClient, type CreditOverviewTranslations } from './credit-overview-client';
|
|
7
7
|
import type { CreditOverviewData } from './types';
|
|
8
8
|
|
|
9
|
+
export const DEFAULT_CREDIT_OVERVIEW_REFRESH_EVENT = 'windrun-huaiin:credit-overview:refresh';
|
|
10
|
+
|
|
9
11
|
export interface CreditOverviewPayload {
|
|
10
12
|
data: CreditOverviewData;
|
|
11
13
|
totalLabel: string;
|
|
@@ -15,6 +17,19 @@ export interface CreditOverviewPayload {
|
|
|
15
17
|
export interface CreditOverviewNavClientProps {
|
|
16
18
|
locale: string;
|
|
17
19
|
endpoint: string;
|
|
20
|
+
/**
|
|
21
|
+
* Browser event names that should trigger credit overview reload.
|
|
22
|
+
* Business apps can dispatch these events after credit balance changes.
|
|
23
|
+
*
|
|
24
|
+
* Default: ['windrun:credit-overview:refresh']
|
|
25
|
+
* Empty array means event listening is disabled.
|
|
26
|
+
*/
|
|
27
|
+
refreshEvents?: string[];
|
|
28
|
+
/**
|
|
29
|
+
* Optional debounce for refresh event bursts.
|
|
30
|
+
* Default: 300ms.
|
|
31
|
+
*/
|
|
32
|
+
refreshDebounceMs?: number;
|
|
18
33
|
}
|
|
19
34
|
|
|
20
35
|
function buildCreditOverviewUrl(endpoint: string, locale: string) {
|
|
@@ -23,12 +38,91 @@ function buildCreditOverviewUrl(endpoint: string, locale: string) {
|
|
|
23
38
|
return url.toString();
|
|
24
39
|
}
|
|
25
40
|
|
|
41
|
+
function normalizeRefreshEvents(refreshEvents: string[] | undefined) {
|
|
42
|
+
const events = refreshEvents ?? [DEFAULT_CREDIT_OVERVIEW_REFRESH_EVENT];
|
|
43
|
+
return Array.from(
|
|
44
|
+
new Set(
|
|
45
|
+
events
|
|
46
|
+
.map((eventName) => eventName.trim())
|
|
47
|
+
.filter((eventName) => eventName.length > 0),
|
|
48
|
+
),
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function buildRefreshEventsKey(refreshEvents: string[] | undefined) {
|
|
53
|
+
return normalizeRefreshEvents(refreshEvents).join('\n');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function dispatchCreditOverviewRefresh(
|
|
57
|
+
eventName = DEFAULT_CREDIT_OVERVIEW_REFRESH_EVENT,
|
|
58
|
+
) {
|
|
59
|
+
if (typeof window === 'undefined') {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
window.dispatchEvent(new CustomEvent(eventName));
|
|
64
|
+
}
|
|
65
|
+
|
|
26
66
|
export function CreditOverviewNavClient({
|
|
27
67
|
locale,
|
|
28
68
|
endpoint,
|
|
69
|
+
refreshEvents,
|
|
70
|
+
refreshDebounceMs = 300,
|
|
29
71
|
}: CreditOverviewNavClientProps) {
|
|
30
72
|
const { isLoaded, isSignedIn, userId } = useAuth();
|
|
31
73
|
const [payload, setPayload] = useState<CreditOverviewPayload | null>(null);
|
|
74
|
+
const abortControllerRef = useRef<AbortController | null>(null);
|
|
75
|
+
const isMountedRef = useRef(false);
|
|
76
|
+
const refreshEventsKey = buildRefreshEventsKey(refreshEvents);
|
|
77
|
+
const normalizedRefreshEvents = useMemo(
|
|
78
|
+
() => refreshEventsKey.split('\n').filter(Boolean),
|
|
79
|
+
[refreshEventsKey],
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
isMountedRef.current = true;
|
|
84
|
+
|
|
85
|
+
return () => {
|
|
86
|
+
isMountedRef.current = false;
|
|
87
|
+
abortControllerRef.current?.abort();
|
|
88
|
+
};
|
|
89
|
+
}, []);
|
|
90
|
+
|
|
91
|
+
const loadCreditOverview = useCallback(async () => {
|
|
92
|
+
abortControllerRef.current?.abort();
|
|
93
|
+
|
|
94
|
+
const controller = new AbortController();
|
|
95
|
+
abortControllerRef.current = controller;
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const response = await fetch(buildCreditOverviewUrl(endpoint, locale), {
|
|
99
|
+
cache: 'no-store',
|
|
100
|
+
credentials: 'same-origin',
|
|
101
|
+
signal: controller.signal,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
if (!response.ok) {
|
|
105
|
+
if (!controller.signal.aborted && isMountedRef.current) {
|
|
106
|
+
setPayload(null);
|
|
107
|
+
}
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const nextPayload = (await response.json()) as CreditOverviewPayload | null;
|
|
112
|
+
if (!controller.signal.aborted && isMountedRef.current) {
|
|
113
|
+
setPayload(nextPayload);
|
|
114
|
+
}
|
|
115
|
+
} catch (error) {
|
|
116
|
+
if (!controller.signal.aborted && isMountedRef.current) {
|
|
117
|
+
setPayload(null);
|
|
118
|
+
console.warn('[CreditOverviewNavClient] Failed to load credit overview', error);
|
|
119
|
+
}
|
|
120
|
+
} finally {
|
|
121
|
+
if (abortControllerRef.current === controller) {
|
|
122
|
+
abortControllerRef.current = null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}, [endpoint, locale]);
|
|
32
126
|
|
|
33
127
|
useEffect(() => {
|
|
34
128
|
if (!isLoaded) {
|
|
@@ -36,44 +130,46 @@ export function CreditOverviewNavClient({
|
|
|
36
130
|
}
|
|
37
131
|
|
|
38
132
|
if (!isSignedIn) {
|
|
133
|
+
abortControllerRef.current?.abort();
|
|
134
|
+
abortControllerRef.current = null;
|
|
39
135
|
setPayload(null);
|
|
40
136
|
return;
|
|
41
137
|
}
|
|
42
138
|
|
|
43
|
-
|
|
139
|
+
void loadCreditOverview();
|
|
140
|
+
}, [isLoaded, isSignedIn, loadCreditOverview, userId]);
|
|
44
141
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
signal: controller.signal,
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
if (!response.ok) {
|
|
53
|
-
if (!controller.signal.aborted) {
|
|
54
|
-
setPayload(null);
|
|
55
|
-
}
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
142
|
+
useEffect(() => {
|
|
143
|
+
if (!isLoaded || !isSignedIn || normalizedRefreshEvents.length === 0) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
58
146
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if (!controller.signal.aborted) {
|
|
65
|
-
setPayload(null);
|
|
66
|
-
console.warn('[CreditOverviewNavClient] Failed to load credit overview', error);
|
|
67
|
-
}
|
|
147
|
+
let debounceTimer: number | null = null;
|
|
148
|
+
|
|
149
|
+
const refresh = () => {
|
|
150
|
+
if (debounceTimer) {
|
|
151
|
+
window.clearTimeout(debounceTimer);
|
|
68
152
|
}
|
|
69
|
-
}
|
|
70
153
|
|
|
71
|
-
|
|
154
|
+
debounceTimer = window.setTimeout(() => {
|
|
155
|
+
void loadCreditOverview();
|
|
156
|
+
}, refreshDebounceMs);
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
for (const eventName of normalizedRefreshEvents) {
|
|
160
|
+
window.addEventListener(eventName, refresh);
|
|
161
|
+
}
|
|
72
162
|
|
|
73
163
|
return () => {
|
|
74
|
-
|
|
164
|
+
if (debounceTimer) {
|
|
165
|
+
window.clearTimeout(debounceTimer);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
for (const eventName of normalizedRefreshEvents) {
|
|
169
|
+
window.removeEventListener(eventName, refresh);
|
|
170
|
+
}
|
|
75
171
|
};
|
|
76
|
-
}, [
|
|
172
|
+
}, [isLoaded, isSignedIn, loadCreditOverview, normalizedRefreshEvents, refreshDebounceMs]);
|
|
77
173
|
|
|
78
174
|
if (!payload) {
|
|
79
175
|
return null;
|
package/src/main/credit/index.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
export { CreditOverviewClient } from './credit-overview-client';
|
|
4
|
-
export {
|
|
4
|
+
export {
|
|
5
|
+
CreditOverviewNavClient,
|
|
6
|
+
DEFAULT_CREDIT_OVERVIEW_REFRESH_EVENT,
|
|
7
|
+
dispatchCreditOverviewRefresh,
|
|
8
|
+
} from './credit-overview-nav-client';
|
|
5
9
|
export { CreditNavButton } from './credit-nav-button';
|
|
6
10
|
export type { CreditOverviewTranslations } from './credit-overview-client';
|
|
7
11
|
export type {
|