@goralys/core 1.4.4 → 1.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/dist/index.d.ts +62 -14
- package/dist/index.js +210 -251
- package/package.json +4 -2
package/dist/index.d.ts
CHANGED
|
@@ -45,11 +45,26 @@ type CheckAuthImpl = () => boolean;
|
|
|
45
45
|
declare function configureCheckAuth(impl: CheckAuthImpl): void;
|
|
46
46
|
declare function isAuthenticated(): boolean;
|
|
47
47
|
|
|
48
|
+
type Toast = {
|
|
49
|
+
type: "error" | "warning" | "info" | "success";
|
|
50
|
+
title: string;
|
|
51
|
+
message: string;
|
|
52
|
+
expires?: number;
|
|
53
|
+
};
|
|
54
|
+
type ToastFn = (toast: Toast, duration?: number) => void;
|
|
55
|
+
|
|
56
|
+
interface ToastConfig {
|
|
57
|
+
getShowToast: () => ToastFn;
|
|
58
|
+
}
|
|
59
|
+
declare function configureToast(cfg: ToastConfig): void;
|
|
60
|
+
declare function getToastConfig(): ToastConfig;
|
|
61
|
+
|
|
48
62
|
interface GoralysCoreConfig {
|
|
49
63
|
client: GoralysClientConfig;
|
|
50
64
|
storage: StorageAdapter;
|
|
51
65
|
cookies: CookiesAdapter;
|
|
52
66
|
auth: CheckAuthImpl;
|
|
67
|
+
toast: ToastConfig;
|
|
53
68
|
}
|
|
54
69
|
declare function configGoralysCore(cfg: GoralysCoreConfig): void;
|
|
55
70
|
|
|
@@ -76,14 +91,19 @@ type ConfirmOptions = {
|
|
|
76
91
|
title: string;
|
|
77
92
|
message: string;
|
|
78
93
|
};
|
|
94
|
+
type AuthTokenContext = "admin-panel" | "profile";
|
|
95
|
+
type AuthToken = {
|
|
96
|
+
username: string;
|
|
97
|
+
name: string;
|
|
98
|
+
expires: Date;
|
|
99
|
+
created: Date;
|
|
100
|
+
};
|
|
79
101
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
expires?: number;
|
|
102
|
+
declare function useAuthTokens(context: AuthTokenContext, target?: string | null): {
|
|
103
|
+
tokens: AuthToken[] | null;
|
|
104
|
+
refetch: () => Promise<undefined | void>;
|
|
105
|
+
syncKey: string;
|
|
85
106
|
};
|
|
86
|
-
type ToastFn = (toast: Toast, duration?: number) => void;
|
|
87
107
|
|
|
88
108
|
type SubjectStatus = "not_submitted" | "submitted" | "rejected" | "approved";
|
|
89
109
|
declare const SubjectStatusConfig: Record<SubjectStatus, {
|
|
@@ -121,7 +141,7 @@ type DraftModalResult = {
|
|
|
121
141
|
declare const getStatusLabel: (status: SubjectStatus) => string;
|
|
122
142
|
declare const getStatusHelper: (status: SubjectStatus, role: UserRole["role"]) => string;
|
|
123
143
|
|
|
124
|
-
declare function useSubjects(role: UserRole["role"]
|
|
144
|
+
declare function useSubjects(role: UserRole["role"]): {
|
|
125
145
|
subjects: Subject[] | null;
|
|
126
146
|
refetch: () => Promise<undefined | void>;
|
|
127
147
|
syncKey: string;
|
|
@@ -151,21 +171,40 @@ type SupportTicket = {
|
|
|
151
171
|
createdAt: PHPDateTime;
|
|
152
172
|
};
|
|
153
173
|
|
|
154
|
-
declare function useSupportTickets(
|
|
174
|
+
declare function useSupportTickets(): {
|
|
155
175
|
supportTickets: SupportTicket[] | null;
|
|
156
176
|
refetch: () => Promise<undefined | void>;
|
|
157
177
|
syncKey: string;
|
|
158
178
|
};
|
|
159
179
|
|
|
180
|
+
interface UseSyncedResourceOptions<T> {
|
|
181
|
+
name: string;
|
|
182
|
+
cacheKey: string;
|
|
183
|
+
syncKey: string;
|
|
184
|
+
fetcher: () => Promise<Response | null | undefined>;
|
|
185
|
+
parse: (data: unknown) => T | null;
|
|
186
|
+
requireAuth?: boolean;
|
|
187
|
+
guard?: () => string | null | undefined;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Shared implementation behind useSubjects / useAuthTokens: cookies sync, localStorage cache, single-flight de-duplication, and a cross-tab
|
|
191
|
+
* `cookiesOnChange` listener that re-fetches once another tab invalidates the sync flag.
|
|
192
|
+
*/
|
|
193
|
+
declare function useSyncedResource<T>({ name, cacheKey, syncKey, fetcher, parse, requireAuth, guard }: UseSyncedResourceOptions<T>): {
|
|
194
|
+
data: T | null;
|
|
195
|
+
refetch: () => Promise<undefined | void>;
|
|
196
|
+
syncKey: string;
|
|
197
|
+
};
|
|
198
|
+
|
|
160
199
|
interface UserFetchResult {
|
|
161
200
|
users: User[] | null;
|
|
162
201
|
refetch: () => Promise<void | undefined>;
|
|
163
202
|
syncKey: string;
|
|
164
203
|
}
|
|
165
|
-
declare function useUsers(
|
|
166
|
-
declare function useVirtualUsers(
|
|
167
|
-
declare function useAdmins(
|
|
168
|
-
declare function useVirtualAdmins(
|
|
204
|
+
declare function useUsers(): UserFetchResult;
|
|
205
|
+
declare function useVirtualUsers(): UserFetchResult;
|
|
206
|
+
declare function useAdmins(): UserFetchResult;
|
|
207
|
+
declare function useVirtualAdmins(): UserFetchResult;
|
|
169
208
|
|
|
170
209
|
declare const LOGO_PATH: {
|
|
171
210
|
main: string;
|
|
@@ -183,6 +222,8 @@ declare const EMAIL_KEY = "email";
|
|
|
183
222
|
declare const PUB_ID_KEY = "public-id";
|
|
184
223
|
declare const SUBJECT_SYNC_RADIX = "subjects-synced-";
|
|
185
224
|
declare const SUBJECT_CACHE_RADIX = "subjects-cache-";
|
|
225
|
+
declare const AUTH_TOKEN_SYNC_RADIX = "auth-tokens-sync-";
|
|
226
|
+
declare const AUTH_TOKEN_CACHE_RADIX = "auth-tokens-cache-";
|
|
186
227
|
declare const USER_CACHE_RADIX = "users-cache-";
|
|
187
228
|
declare const USER_SYNC_RADIX = "users-synced-";
|
|
188
229
|
declare const ADMIN_CACHE_RADIX = "admins-cache-";
|
|
@@ -191,6 +232,8 @@ declare const SUPPORT_TICKET_CACHE = "support-tickets-cache";
|
|
|
191
232
|
declare const SUPPORT_TICKET_SYNC = "support-tickets-synced";
|
|
192
233
|
declare const SUBJECT_SYNCS: Record<UserRole["role"], string>;
|
|
193
234
|
declare const SUBJECT_CACHES: Record<UserRole["role"], string>;
|
|
235
|
+
declare const AUTH_TOKEN_SYNCS: Record<AuthTokenContext, string>;
|
|
236
|
+
declare const AUTH_TOKEN_CACHES: Record<AuthTokenContext, string>;
|
|
194
237
|
type UserType = {
|
|
195
238
|
type: "users-virtual" | "users-real" | "admins-virtual" | "admins-real";
|
|
196
239
|
};
|
|
@@ -221,11 +264,16 @@ declare const helpContent: Section[];
|
|
|
221
264
|
declare function isFoolsDay(): boolean;
|
|
222
265
|
|
|
223
266
|
declare function buildArray<T>(...items: (T | false | null | undefined)[]): T[];
|
|
267
|
+
declare const fromPhpDate: (phpDate: PHPDateTime) => Date;
|
|
224
268
|
declare const parsePhpDateTime: (phpDate: PHPDateTime) => string;
|
|
225
269
|
|
|
226
270
|
declare function emitAuthEvent(event: AuthEvent): void;
|
|
227
271
|
declare function onAuthEvent(callback: (event: AuthEvent) => void): () => void;
|
|
228
272
|
|
|
273
|
+
declare function fetchTokensProfile(): Promise<Response | null>;
|
|
274
|
+
declare function fetchTokensAdminPanel(target: string): Promise<Response | null>;
|
|
275
|
+
declare function fetchAuthTokensForContext(ctx: AuthTokenContext, target?: string | null): Promise<Response | null>;
|
|
276
|
+
|
|
229
277
|
declare function emitUserEvent(event: UserEvent): void;
|
|
230
278
|
declare function onUserEvent(callback: (event: UserEvent) => void): () => void;
|
|
231
279
|
|
|
@@ -245,7 +293,7 @@ declare function isHTTPMethod(maybeMethod: string): maybeMethod is HttpMethod;
|
|
|
245
293
|
declare function goralysFetchClient(method: HttpMethod, input: string | URL | Request, payload?: Record<string, string | number | boolean | null> | FormData, requestOptions?: RequestInit): Promise<Response>;
|
|
246
294
|
declare function fetchCsrfClient(formId: string): Promise<string | null>;
|
|
247
295
|
declare function buildApiUrl(endpoint: string, params: Record<string, string | null>, domain?: boolean): string;
|
|
248
|
-
declare function handleToastRequest(r: Response, showToast:
|
|
296
|
+
declare function handleToastRequest(r: Response, showToast: ToastFn, redirect?: boolean, duration?: number): Promise<boolean>;
|
|
249
297
|
|
|
250
298
|
declare class GoralysActionHandler {
|
|
251
299
|
handle: (r: Response) => Promise<void>;
|
|
@@ -291,4 +339,4 @@ declare function fetchVirtualUsersClient(): Promise<Response>;
|
|
|
291
339
|
declare function fetchAdminsClient(): Promise<Response>;
|
|
292
340
|
declare function fetchVirtualAdminsClient(): Promise<Response>;
|
|
293
341
|
|
|
294
|
-
export { ADMIN_CACHE_RADIX, ADMIN_SYNC_RADIX, type AuthEvent, COOKIES_DISMISSED_KEY, type CheckAuthImpl, type ConfirmOptions, type CookieValue, type CookiesAdapter, type DraftModalResult, EMAIL_KEY, FLASH_TOAST_KEY, FULL_NAME_KEY, GoralysActionHandler, type GoralysClientConfig, HTTP_METHODS, type HttpMethod, LOGO_PATH, PERSISTANT_COOKIES, PERSISTANT_LOCALS, type PHPDateTime, PUB_ID_KEY, ROLE_KEY, SUBJECT_CACHES, SUBJECT_CACHE_RADIX, SUBJECT_SYNCS, SUBJECT_SYNC_RADIX, SUPPORT_TICKET_CACHE, SUPPORT_TICKET_SYNC, type StorageAdapter, type Subject, type SubjectStatus, SubjectStatusConfig, type SubjectsSearchField, type SupportReasons, type SupportTicket, type Toast, type ToastFn, USERNAME_KEY, USER_CACHES, USER_CACHE_RADIX, USER_ROLES, USER_SYNCS, USER_SYNC_RADIX, type User, type UserData, type UserEvent, type UserRole, type UserType, buildApiUrl, buildArray, cacheUserDataClient, configGoralysCore, configureCheckAuth, configureCookies, configureGoralysClient, configureStorage, cookiesGet, cookiesGetAll, cookiesOnChange, cookiesRemove, cookiesSet, emitAuthEvent, emitNavigationEvent, emitUserEvent, emptyUserCacheClient, fetchAdminsClient, fetchCsrfClient, fetchSubjectsForRoleClient, fetchUsersClient, fetchVirtualAdminsClient, fetchVirtualUsersClient, getGoralysClientConfig, getLongFromShort, getShortFromLong, getStatusHelper, getStatusLabel, goralysFetchClient, handleToastRequest, helpContent, isAuthenticated, isFoolsDay, isHTTPMethod, onAuthEvent, onNavigationEvent, onUserEvent, parsePhpDateTime, reasonsConfig, searchFields, storageGet, storageKeyAt, storageRemove, storageSet, storageSize, useAdmins, useSubjects, useSupportTickets, useUsers, useVirtualAdmins, useVirtualUsers };
|
|
342
|
+
export { ADMIN_CACHE_RADIX, ADMIN_SYNC_RADIX, AUTH_TOKEN_CACHES, AUTH_TOKEN_CACHE_RADIX, AUTH_TOKEN_SYNCS, AUTH_TOKEN_SYNC_RADIX, type AuthEvent, type AuthToken, type AuthTokenContext, COOKIES_DISMISSED_KEY, type CheckAuthImpl, type ConfirmOptions, type CookieValue, type CookiesAdapter, type DraftModalResult, EMAIL_KEY, FLASH_TOAST_KEY, FULL_NAME_KEY, GoralysActionHandler, type GoralysClientConfig, HTTP_METHODS, type HttpMethod, LOGO_PATH, PERSISTANT_COOKIES, PERSISTANT_LOCALS, type PHPDateTime, PUB_ID_KEY, ROLE_KEY, SUBJECT_CACHES, SUBJECT_CACHE_RADIX, SUBJECT_SYNCS, SUBJECT_SYNC_RADIX, SUPPORT_TICKET_CACHE, SUPPORT_TICKET_SYNC, type StorageAdapter, type Subject, type SubjectStatus, SubjectStatusConfig, type SubjectsSearchField, type SupportReasons, type SupportTicket, type Toast, type ToastConfig, type ToastFn, USERNAME_KEY, USER_CACHES, USER_CACHE_RADIX, USER_ROLES, USER_SYNCS, USER_SYNC_RADIX, type UseSyncedResourceOptions, type User, type UserData, type UserEvent, type UserRole, type UserType, buildApiUrl, buildArray, cacheUserDataClient, configGoralysCore, configureCheckAuth, configureCookies, configureGoralysClient, configureStorage, configureToast, cookiesGet, cookiesGetAll, cookiesOnChange, cookiesRemove, cookiesSet, emitAuthEvent, emitNavigationEvent, emitUserEvent, emptyUserCacheClient, fetchAdminsClient, fetchAuthTokensForContext, fetchCsrfClient, fetchSubjectsForRoleClient, fetchTokensAdminPanel, fetchTokensProfile, fetchUsersClient, fetchVirtualAdminsClient, fetchVirtualUsersClient, fromPhpDate, getGoralysClientConfig, getLongFromShort, getShortFromLong, getStatusHelper, getStatusLabel, getToastConfig, goralysFetchClient, handleToastRequest, helpContent, isAuthenticated, isFoolsDay, isHTTPMethod, onAuthEvent, onNavigationEvent, onUserEvent, parsePhpDateTime, reasonsConfig, searchFields, storageGet, storageKeyAt, storageRemove, storageSet, storageSize, useAdmins, useAuthTokens, useSubjects, useSupportTickets, useSyncedResource, useUsers, useVirtualAdmins, useVirtualUsers };
|
package/dist/index.js
CHANGED
|
@@ -4,9 +4,8 @@ function configureGoralysClient(cfg) {
|
|
|
4
4
|
config = cfg;
|
|
5
5
|
}
|
|
6
6
|
function getGoralysClientConfig() {
|
|
7
|
-
if (!config)
|
|
7
|
+
if (!config)
|
|
8
8
|
throw new Error("Goralys client not configured. Call configureGoralysClient() first.");
|
|
9
|
-
}
|
|
10
9
|
return config;
|
|
11
10
|
}
|
|
12
11
|
|
|
@@ -56,38 +55,28 @@ function isAuthenticated() {
|
|
|
56
55
|
return checkAuthImpl();
|
|
57
56
|
}
|
|
58
57
|
|
|
58
|
+
// src/lib/toast/config.ts
|
|
59
|
+
var config2 = null;
|
|
60
|
+
function configureToast(cfg) {
|
|
61
|
+
config2 = cfg;
|
|
62
|
+
}
|
|
63
|
+
function getToastConfig() {
|
|
64
|
+
if (!config2)
|
|
65
|
+
throw new Error("Toasts have not been configured yet. Call configureToast() first.");
|
|
66
|
+
return config2;
|
|
67
|
+
}
|
|
68
|
+
|
|
59
69
|
// src/config.ts
|
|
60
70
|
function configGoralysCore(cfg) {
|
|
61
71
|
configureGoralysClient(cfg.client);
|
|
62
72
|
configureCookies(cfg.cookies);
|
|
63
73
|
configureStorage(cfg.storage);
|
|
64
74
|
configureCheckAuth(cfg.auth);
|
|
75
|
+
configureToast(cfg.toast);
|
|
65
76
|
}
|
|
66
77
|
|
|
67
|
-
// src/hooks/
|
|
68
|
-
import { useCallback
|
|
69
|
-
|
|
70
|
-
// src/lib/auth/auth-event.ts
|
|
71
|
-
var listeners = /* @__PURE__ */ new Set();
|
|
72
|
-
function emitAuthEvent(event) {
|
|
73
|
-
listeners.forEach((l) => l(event));
|
|
74
|
-
}
|
|
75
|
-
function onAuthEvent(callback) {
|
|
76
|
-
listeners.add(callback);
|
|
77
|
-
return () => {
|
|
78
|
-
listeners.delete(callback);
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// src/lib/fetch/navigation-event.ts
|
|
83
|
-
var listeners2 = /* @__PURE__ */ new Set();
|
|
84
|
-
function emitNavigationEvent(event) {
|
|
85
|
-
listeners2.forEach((l) => l(event));
|
|
86
|
-
}
|
|
87
|
-
function onNavigationEvent(listener) {
|
|
88
|
-
listeners2.add(listener);
|
|
89
|
-
return () => listeners2.delete(listener);
|
|
90
|
-
}
|
|
78
|
+
// src/hooks/use-auth-tokens.ts
|
|
79
|
+
import { useCallback as useCallback2 } from "react";
|
|
91
80
|
|
|
92
81
|
// src/lib/config.ts
|
|
93
82
|
var COOKIES_DISMISSED_KEY = "cookie-banner-dismissed";
|
|
@@ -101,6 +90,8 @@ var EMAIL_KEY = "email";
|
|
|
101
90
|
var PUB_ID_KEY = "public-id";
|
|
102
91
|
var SUBJECT_SYNC_RADIX = "subjects-synced-";
|
|
103
92
|
var SUBJECT_CACHE_RADIX = "subjects-cache-";
|
|
93
|
+
var AUTH_TOKEN_SYNC_RADIX = "auth-tokens-sync-";
|
|
94
|
+
var AUTH_TOKEN_CACHE_RADIX = "auth-tokens-cache-";
|
|
104
95
|
var USER_CACHE_RADIX = "users-cache-";
|
|
105
96
|
var USER_SYNC_RADIX = "users-synced-";
|
|
106
97
|
var ADMIN_CACHE_RADIX = "admins-cache-";
|
|
@@ -119,6 +110,14 @@ var SUBJECT_CACHES = {
|
|
|
119
110
|
admin: SUBJECT_CACHE_RADIX + "admin",
|
|
120
111
|
none: SUBJECT_CACHE_RADIX + "unknown"
|
|
121
112
|
};
|
|
113
|
+
var AUTH_TOKEN_SYNCS = {
|
|
114
|
+
"admin-panel": AUTH_TOKEN_SYNC_RADIX + "admin-panel",
|
|
115
|
+
profile: AUTH_TOKEN_SYNC_RADIX + "profile"
|
|
116
|
+
};
|
|
117
|
+
var AUTH_TOKEN_CACHES = {
|
|
118
|
+
"admin-panel": AUTH_TOKEN_CACHE_RADIX + "admin-panel",
|
|
119
|
+
profile: AUTH_TOKEN_CACHE_RADIX + "profile"
|
|
120
|
+
};
|
|
122
121
|
var USER_SYNCS = {
|
|
123
122
|
"users-real": USER_SYNC_RADIX + "users",
|
|
124
123
|
"users-virtual": USER_SYNC_RADIX + "users-virtual",
|
|
@@ -332,6 +331,28 @@ Attention : les administrateurs ont acc\xE8s \xE0 toutes les fonctionnalit\xE9s
|
|
|
332
331
|
}
|
|
333
332
|
];
|
|
334
333
|
|
|
334
|
+
// src/lib/auth/auth-event.ts
|
|
335
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
336
|
+
function emitAuthEvent(event) {
|
|
337
|
+
listeners.forEach((l) => l(event));
|
|
338
|
+
}
|
|
339
|
+
function onAuthEvent(callback) {
|
|
340
|
+
listeners.add(callback);
|
|
341
|
+
return () => {
|
|
342
|
+
listeners.delete(callback);
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// src/lib/fetch/navigation-event.ts
|
|
347
|
+
var listeners2 = /* @__PURE__ */ new Set();
|
|
348
|
+
function emitNavigationEvent(event) {
|
|
349
|
+
listeners2.forEach((l) => l(event));
|
|
350
|
+
}
|
|
351
|
+
function onNavigationEvent(listener) {
|
|
352
|
+
listeners2.add(listener);
|
|
353
|
+
return () => listeners2.delete(listener);
|
|
354
|
+
}
|
|
355
|
+
|
|
335
356
|
// src/lib/user/user.client.ts
|
|
336
357
|
async function cacheUserDataClient() {
|
|
337
358
|
const res = await goralysFetchClient("GET", "user/profile");
|
|
@@ -495,56 +516,52 @@ async function handleToastRequest(r, showToast, redirect = true, duration) {
|
|
|
495
516
|
return false;
|
|
496
517
|
}
|
|
497
518
|
|
|
498
|
-
// src/lib/
|
|
499
|
-
async function
|
|
500
|
-
const
|
|
501
|
-
if (!
|
|
502
|
-
|
|
503
|
-
return await goralysFetchClient("GET", buildApiUrl("subjects/student", { "csrf-token": csrfToken }));
|
|
504
|
-
}
|
|
505
|
-
async function fetchTeacherSubjectsClient() {
|
|
506
|
-
const csrfToken = await fetchCsrfClient("get-teacher-subjects");
|
|
507
|
-
if (!csrfToken)
|
|
519
|
+
// src/lib/auth/fetch-auth-tokens.ts
|
|
520
|
+
async function fetchTokensProfile() {
|
|
521
|
+
const username = cookiesGet(USERNAME_KEY);
|
|
522
|
+
if (!username) {
|
|
523
|
+
console.error("[fetchTokensProfile] No username cookies, aborting");
|
|
508
524
|
return null;
|
|
509
|
-
|
|
525
|
+
}
|
|
526
|
+
return await goralysFetchClient("GET", "user/tokens");
|
|
510
527
|
}
|
|
511
|
-
async function
|
|
512
|
-
const
|
|
513
|
-
if (!
|
|
528
|
+
async function fetchTokensAdminPanel(target) {
|
|
529
|
+
const username = cookiesGet(USERNAME_KEY);
|
|
530
|
+
if (!username) {
|
|
531
|
+
console.error("[fetchTokensProfile] No username cookies, aborting");
|
|
514
532
|
return null;
|
|
515
|
-
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
return await fetchAdminSubjectsClient();
|
|
525
|
-
default:
|
|
526
|
-
return null;
|
|
533
|
+
}
|
|
534
|
+
return await goralysFetchClient("GET", "user/tokens/any", { username: target });
|
|
535
|
+
}
|
|
536
|
+
async function fetchAuthTokensForContext(ctx, target = null) {
|
|
537
|
+
switch (ctx) {
|
|
538
|
+
case "profile":
|
|
539
|
+
return await fetchTokensProfile();
|
|
540
|
+
case "admin-panel":
|
|
541
|
+
return await fetchTokensAdminPanel(target);
|
|
527
542
|
}
|
|
528
543
|
}
|
|
529
544
|
|
|
530
|
-
// src/hooks/
|
|
531
|
-
|
|
532
|
-
|
|
545
|
+
// src/hooks/use-synced-ressource.ts
|
|
546
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
547
|
+
function useSyncedResource({ name, cacheKey, syncKey, fetcher, parse, requireAuth = true, guard }) {
|
|
548
|
+
const [data, setData] = useState(null);
|
|
549
|
+
const showToast = getToastConfig().getShowToast();
|
|
533
550
|
const showToastRef = useRef(showToast);
|
|
534
551
|
useEffect(() => {
|
|
535
552
|
showToastRef.current = showToast;
|
|
536
553
|
}, [showToast]);
|
|
537
554
|
const inFlightRef = useRef(null);
|
|
538
|
-
const
|
|
539
|
-
const
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
555
|
+
const fetchResource = useCallback(async () => {
|
|
556
|
+
const abortReason = guard?.();
|
|
557
|
+
if (abortReason) {
|
|
558
|
+
console.error(`[${name}] ${abortReason}`);
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
if (requireAuth && !isAuthenticated()) {
|
|
544
562
|
return;
|
|
545
563
|
}
|
|
546
564
|
if (inFlightRef.current) {
|
|
547
|
-
console.log("[useSubjects] in-flight, waiting");
|
|
548
565
|
return inFlightRef.current;
|
|
549
566
|
}
|
|
550
567
|
let resolve;
|
|
@@ -554,223 +571,152 @@ function useSubjects(role, showToast) {
|
|
|
554
571
|
try {
|
|
555
572
|
const syncValue = cookiesGet(syncKey);
|
|
556
573
|
if (syncValue == "1") {
|
|
557
|
-
const
|
|
558
|
-
if (
|
|
574
|
+
const raw2 = storageGet(cacheKey);
|
|
575
|
+
if (raw2 === null || raw2 === void 0) {
|
|
559
576
|
cookiesSet(syncKey, "0");
|
|
560
577
|
storageRemove(cacheKey);
|
|
561
|
-
|
|
578
|
+
inFlightRef.current = null;
|
|
579
|
+
resolve();
|
|
580
|
+
await fetchResource();
|
|
562
581
|
return;
|
|
563
582
|
}
|
|
564
|
-
const cached = JSON.parse(
|
|
565
|
-
|
|
566
|
-
if (JSON.stringify(prev) === JSON.stringify(cached))
|
|
567
|
-
return prev;
|
|
568
|
-
return cached;
|
|
569
|
-
});
|
|
583
|
+
const cached = JSON.parse(raw2 ?? "null");
|
|
584
|
+
setData((prev) => JSON.stringify(prev) === JSON.stringify(cached) ? prev : cached);
|
|
570
585
|
return;
|
|
571
586
|
}
|
|
572
|
-
const res = await
|
|
587
|
+
const res = await fetcher();
|
|
573
588
|
if (res)
|
|
574
589
|
await handleToastRequest(res, showToastRef.current, false);
|
|
575
|
-
const
|
|
590
|
+
const raw = await res?.json();
|
|
576
591
|
cookiesSet(syncKey, "1");
|
|
577
|
-
storageSet(cacheKey, JSON.stringify(
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
setSubjects((prev) => {
|
|
581
|
-
if (JSON.stringify(prev) === JSON.stringify(result))
|
|
582
|
-
return prev;
|
|
583
|
-
return result;
|
|
584
|
-
});
|
|
592
|
+
storageSet(cacheKey, JSON.stringify(raw));
|
|
593
|
+
const result = parse(raw);
|
|
594
|
+
setData((prev) => JSON.stringify(prev) === JSON.stringify(result) ? prev : result);
|
|
585
595
|
} finally {
|
|
586
596
|
inFlightRef.current = null;
|
|
587
597
|
resolve();
|
|
588
598
|
}
|
|
589
|
-
}, [
|
|
599
|
+
}, [cacheKey, syncKey, fetcher, parse, requireAuth, guard, name]);
|
|
590
600
|
useEffect(() => {
|
|
591
601
|
const onChange = () => {
|
|
592
602
|
if (inFlightRef.current)
|
|
593
603
|
return;
|
|
594
|
-
if (cookiesGet(
|
|
595
|
-
void
|
|
604
|
+
if (cookiesGet(syncKey) != "1") {
|
|
605
|
+
void fetchResource();
|
|
596
606
|
}
|
|
597
607
|
};
|
|
598
608
|
return cookiesOnChange(onChange);
|
|
599
|
-
}, [
|
|
609
|
+
}, [fetchResource, syncKey]);
|
|
600
610
|
useEffect(() => {
|
|
601
|
-
void
|
|
602
|
-
}, [
|
|
603
|
-
return useMemo(() => ({
|
|
604
|
-
subjects,
|
|
605
|
-
refetch: fetchSubjects,
|
|
606
|
-
syncKey: SUBJECT_SYNCS[role]
|
|
607
|
-
}), [subjects, fetchSubjects, role]);
|
|
611
|
+
void fetchResource();
|
|
612
|
+
}, [fetchResource]);
|
|
613
|
+
return useMemo(() => ({ data, refetch: fetchResource, syncKey }), [data, fetchResource, syncKey]);
|
|
608
614
|
}
|
|
609
615
|
|
|
610
|
-
// src/hooks/
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
const
|
|
614
|
-
const
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
return;
|
|
625
|
-
}
|
|
626
|
-
if (inFlightRef.current) {
|
|
627
|
-
console.log("[useSupportTickets] in-flight, waiting");
|
|
628
|
-
return inFlightRef.current;
|
|
629
|
-
}
|
|
630
|
-
let resolve;
|
|
631
|
-
inFlightRef.current = new Promise((r) => {
|
|
632
|
-
resolve = r;
|
|
633
|
-
});
|
|
634
|
-
try {
|
|
635
|
-
const syncValue = cookiesGet(syncKey);
|
|
636
|
-
if (syncValue == "1") {
|
|
637
|
-
const raw = storageGet(cacheKey);
|
|
638
|
-
if (raw === null || raw === void 0) {
|
|
639
|
-
cookiesSet(syncKey, "0");
|
|
640
|
-
storageRemove(cacheKey);
|
|
641
|
-
await fetchSupportTickets();
|
|
642
|
-
return;
|
|
643
|
-
}
|
|
644
|
-
const cached = JSON.parse(raw ?? "null");
|
|
645
|
-
setSupportTickets((prev) => {
|
|
646
|
-
if (JSON.stringify(prev) === JSON.stringify(cached))
|
|
647
|
-
return prev;
|
|
648
|
-
return cached;
|
|
649
|
-
});
|
|
650
|
-
return;
|
|
651
|
-
}
|
|
652
|
-
const res = await goralysFetchClient("GET", buildApiUrl("support/tickets", { "csrf-token": await fetchCsrfClient("get-tickets") }));
|
|
653
|
-
if (res)
|
|
654
|
-
await handleToastRequest(res, showToastRef.current, false);
|
|
655
|
-
const data = await res?.json();
|
|
656
|
-
cookiesSet(syncKey, "1");
|
|
657
|
-
storageSet(cacheKey, JSON.stringify(data));
|
|
658
|
-
const result = Array.isArray(data) ? data : null;
|
|
659
|
-
setSupportTickets((prev) => {
|
|
660
|
-
if (JSON.stringify(prev) === JSON.stringify(result))
|
|
661
|
-
return prev;
|
|
662
|
-
return result;
|
|
663
|
-
});
|
|
664
|
-
} finally {
|
|
665
|
-
inFlightRef.current = null;
|
|
666
|
-
resolve();
|
|
667
|
-
}
|
|
668
|
-
}, [showToastRef]);
|
|
669
|
-
useEffect2(() => {
|
|
670
|
-
const onChange = () => {
|
|
671
|
-
if (inFlightRef.current)
|
|
672
|
-
return;
|
|
673
|
-
if (cookiesGet(SUPPORT_TICKET_SYNC) != "1") {
|
|
674
|
-
void fetchSupportTickets();
|
|
675
|
-
}
|
|
676
|
-
};
|
|
677
|
-
return cookiesOnChange(onChange);
|
|
678
|
-
}, [fetchSupportTickets]);
|
|
679
|
-
useEffect2(() => {
|
|
680
|
-
void fetchSupportTickets();
|
|
681
|
-
}, [fetchSupportTickets]);
|
|
682
|
-
return useMemo2(() => ({
|
|
683
|
-
supportTickets,
|
|
684
|
-
refetch: fetchSupportTickets,
|
|
685
|
-
syncKey: SUPPORT_TICKET_SYNC
|
|
686
|
-
}), [supportTickets, fetchSupportTickets]);
|
|
616
|
+
// src/hooks/use-auth-tokens.ts
|
|
617
|
+
function useAuthTokens(context, target = null) {
|
|
618
|
+
const guard = useCallback2(() => context === "admin-panel" && !target ? "Cannot use admin panel context without target" : null, [context, target]);
|
|
619
|
+
const fetcher = useCallback2(async () => context === "admin-panel" ? await fetchTokensAdminPanel(target) : await fetchTokensProfile(), [context, target]);
|
|
620
|
+
const parse = useCallback2((data) => Array.isArray(data) ? data : null, []);
|
|
621
|
+
const { data: tokens, refetch, syncKey } = useSyncedResource({
|
|
622
|
+
name: "useAuthTokens",
|
|
623
|
+
cacheKey: AUTH_TOKEN_CACHES[context],
|
|
624
|
+
syncKey: AUTH_TOKEN_SYNCS[context],
|
|
625
|
+
fetcher,
|
|
626
|
+
parse,
|
|
627
|
+
guard
|
|
628
|
+
});
|
|
629
|
+
return { tokens, refetch, syncKey };
|
|
687
630
|
}
|
|
688
631
|
|
|
689
|
-
// src/hooks/
|
|
690
|
-
import { useCallback as useCallback3
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
const
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
const
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
632
|
+
// src/hooks/use-subjects.ts
|
|
633
|
+
import { useCallback as useCallback3 } from "react";
|
|
634
|
+
|
|
635
|
+
// src/lib/subjects/subjects.client.ts
|
|
636
|
+
async function fetchStudentSubjectsClient() {
|
|
637
|
+
const csrfToken = await fetchCsrfClient("get-student-subjects");
|
|
638
|
+
if (!csrfToken)
|
|
639
|
+
return null;
|
|
640
|
+
return await goralysFetchClient("GET", buildApiUrl("subjects/student", { "csrf-token": csrfToken }));
|
|
641
|
+
}
|
|
642
|
+
async function fetchTeacherSubjectsClient() {
|
|
643
|
+
const csrfToken = await fetchCsrfClient("get-teacher-subjects");
|
|
644
|
+
if (!csrfToken)
|
|
645
|
+
return null;
|
|
646
|
+
return await goralysFetchClient("GET", buildApiUrl("subjects/teacher", { "csrf-token": csrfToken }));
|
|
647
|
+
}
|
|
648
|
+
async function fetchAdminSubjectsClient() {
|
|
649
|
+
const csrfToken = await fetchCsrfClient("get-admin-subjects");
|
|
650
|
+
if (!csrfToken)
|
|
651
|
+
return null;
|
|
652
|
+
return await goralysFetchClient("GET", buildApiUrl("subjects/admin", { "csrf-token": csrfToken }));
|
|
653
|
+
}
|
|
654
|
+
async function fetchSubjectsForRoleClient(role) {
|
|
655
|
+
switch (role.role) {
|
|
656
|
+
case "student":
|
|
657
|
+
return await fetchStudentSubjectsClient();
|
|
658
|
+
case "teacher":
|
|
659
|
+
return await fetchTeacherSubjectsClient();
|
|
660
|
+
case "admin":
|
|
661
|
+
return await fetchAdminSubjectsClient();
|
|
662
|
+
default:
|
|
663
|
+
return null;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// src/hooks/use-subjects.ts
|
|
668
|
+
function useSubjects(role) {
|
|
669
|
+
const fetcher = useCallback3(async () => fetchSubjectsForRoleClient({ role }), [role]);
|
|
670
|
+
const parse = useCallback3((data) => Array.isArray(data) ? data : null, []);
|
|
671
|
+
const { data: subjects, refetch, syncKey } = useSyncedResource({
|
|
672
|
+
name: "useSubjects",
|
|
673
|
+
cacheKey: SUBJECT_CACHES[role],
|
|
674
|
+
syncKey: SUBJECT_SYNCS[role],
|
|
675
|
+
fetcher,
|
|
676
|
+
parse
|
|
677
|
+
});
|
|
678
|
+
return { subjects, refetch, syncKey };
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// src/hooks/use-support-tickets.ts
|
|
682
|
+
import { useCallback as useCallback4 } from "react";
|
|
683
|
+
function useSupportTickets() {
|
|
684
|
+
const fetcher = useCallback4(async () => await goralysFetchClient("GET", buildApiUrl("support/tickets", { "csrf-token": await fetchCsrfClient("get-tickets") })), []);
|
|
685
|
+
const parse = useCallback4((data) => Array.isArray(data) ? data : null, []);
|
|
686
|
+
const { data: supportTickets, refetch, syncKey } = useSyncedResource({
|
|
687
|
+
name: "useSupportTickets",
|
|
688
|
+
cacheKey: SUPPORT_TICKET_CACHE,
|
|
689
|
+
syncKey: SUPPORT_TICKET_SYNC,
|
|
690
|
+
fetcher,
|
|
691
|
+
parse
|
|
692
|
+
});
|
|
693
|
+
return { supportTickets, refetch, syncKey };
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// src/hooks/use-users-collection.ts
|
|
697
|
+
import { useCallback as useCallback5 } from "react";
|
|
698
|
+
function useUsersCollection(fetcher, type) {
|
|
699
|
+
const parse = useCallback5((data) => Array.isArray(data) ? data : null, []);
|
|
700
|
+
const { data: users, refetch, syncKey } = useSyncedResource({
|
|
701
|
+
name: "useUsersCollection",
|
|
702
|
+
cacheKey: USER_CACHES[type],
|
|
703
|
+
syncKey: USER_SYNCS[type],
|
|
704
|
+
fetcher,
|
|
705
|
+
parse
|
|
706
|
+
});
|
|
707
|
+
return { users, refetch, syncKey };
|
|
762
708
|
}
|
|
763
|
-
function useUsers(
|
|
764
|
-
return
|
|
709
|
+
function useUsers() {
|
|
710
|
+
return useUsersCollection(fetchUsersClient, "users-real");
|
|
765
711
|
}
|
|
766
|
-
function useVirtualUsers(
|
|
767
|
-
return
|
|
712
|
+
function useVirtualUsers() {
|
|
713
|
+
return useUsersCollection(fetchVirtualUsersClient, "users-virtual");
|
|
768
714
|
}
|
|
769
|
-
function useAdmins(
|
|
770
|
-
return
|
|
715
|
+
function useAdmins() {
|
|
716
|
+
return useUsersCollection(fetchAdminsClient, "admins-real");
|
|
771
717
|
}
|
|
772
|
-
function useVirtualAdmins(
|
|
773
|
-
return
|
|
718
|
+
function useVirtualAdmins() {
|
|
719
|
+
return useUsersCollection(fetchVirtualAdminsClient, "admins-virtual");
|
|
774
720
|
}
|
|
775
721
|
|
|
776
722
|
// src/lib/asset.ts
|
|
@@ -793,8 +739,9 @@ function isFoolsDay() {
|
|
|
793
739
|
function buildArray(...items) {
|
|
794
740
|
return items.filter((item) => Boolean(item));
|
|
795
741
|
}
|
|
742
|
+
var fromPhpDate = (phpDate) => new Date(phpDate.date);
|
|
796
743
|
var parsePhpDateTime = (phpDate) => {
|
|
797
|
-
return
|
|
744
|
+
return fromPhpDate(phpDate).toLocaleString("fr-FR", {
|
|
798
745
|
year: "numeric",
|
|
799
746
|
month: "2-digit",
|
|
800
747
|
day: "2-digit",
|
|
@@ -957,6 +904,10 @@ var USER_ROLES = ["admin", "teacher", "student", "none"];
|
|
|
957
904
|
export {
|
|
958
905
|
ADMIN_CACHE_RADIX,
|
|
959
906
|
ADMIN_SYNC_RADIX,
|
|
907
|
+
AUTH_TOKEN_CACHES,
|
|
908
|
+
AUTH_TOKEN_CACHE_RADIX,
|
|
909
|
+
AUTH_TOKEN_SYNCS,
|
|
910
|
+
AUTH_TOKEN_SYNC_RADIX,
|
|
960
911
|
COOKIES_DISMISSED_KEY,
|
|
961
912
|
EMAIL_KEY,
|
|
962
913
|
FLASH_TOAST_KEY,
|
|
@@ -988,6 +939,7 @@ export {
|
|
|
988
939
|
configureCookies,
|
|
989
940
|
configureGoralysClient,
|
|
990
941
|
configureStorage,
|
|
942
|
+
configureToast,
|
|
991
943
|
cookiesGet,
|
|
992
944
|
cookiesGetAll,
|
|
993
945
|
cookiesOnChange,
|
|
@@ -998,16 +950,21 @@ export {
|
|
|
998
950
|
emitUserEvent,
|
|
999
951
|
emptyUserCacheClient,
|
|
1000
952
|
fetchAdminsClient,
|
|
953
|
+
fetchAuthTokensForContext,
|
|
1001
954
|
fetchCsrfClient,
|
|
1002
955
|
fetchSubjectsForRoleClient,
|
|
956
|
+
fetchTokensAdminPanel,
|
|
957
|
+
fetchTokensProfile,
|
|
1003
958
|
fetchUsersClient,
|
|
1004
959
|
fetchVirtualAdminsClient,
|
|
1005
960
|
fetchVirtualUsersClient,
|
|
961
|
+
fromPhpDate,
|
|
1006
962
|
getGoralysClientConfig,
|
|
1007
963
|
getLongFromShort,
|
|
1008
964
|
getShortFromLong,
|
|
1009
965
|
getStatusHelper,
|
|
1010
966
|
getStatusLabel,
|
|
967
|
+
getToastConfig,
|
|
1011
968
|
goralysFetchClient,
|
|
1012
969
|
handleToastRequest,
|
|
1013
970
|
helpContent,
|
|
@@ -1025,8 +982,10 @@ export {
|
|
|
1025
982
|
storageSet,
|
|
1026
983
|
storageSize,
|
|
1027
984
|
useAdmins,
|
|
985
|
+
useAuthTokens,
|
|
1028
986
|
useSubjects,
|
|
1029
987
|
useSupportTickets,
|
|
988
|
+
useSyncedResource,
|
|
1030
989
|
useUsers,
|
|
1031
990
|
useVirtualAdmins,
|
|
1032
991
|
useVirtualUsers
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goralys/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"license": "LGPL-2.1-or-later",
|
|
5
5
|
"description": "Shared business logic for the Goralys web application",
|
|
6
6
|
"repository": {
|
|
@@ -27,9 +27,11 @@
|
|
|
27
27
|
"access": "public"
|
|
28
28
|
},
|
|
29
29
|
"scripts": {
|
|
30
|
+
"prebuild": "pnpm barrel",
|
|
30
31
|
"build": "tsup",
|
|
31
32
|
"typeCheck": "tsgo --noEmit",
|
|
32
|
-
"dev": "tsup --watch"
|
|
33
|
+
"dev": "tsup --watch",
|
|
34
|
+
"barrel": "rm ./src/index.ts && barrelsby -d ./src -l top"
|
|
33
35
|
},
|
|
34
36
|
"peerDependencies": {
|
|
35
37
|
"react": ">=18"
|