@goralys/core 1.4.4 → 1.6.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/LICENSE CHANGED
@@ -1,165 +1,165 @@
1
- GNU LESSER GENERAL PUBLIC LICENSE
2
- Version 3, 29 June 2007
3
-
4
- Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
5
- Everyone is permitted to copy and distribute verbatim copies
6
- of this license document, but changing it is not allowed.
7
-
8
-
9
- This version of the GNU Lesser General Public License incorporates
10
- the terms and conditions of version 3 of the GNU General Public
11
- License, supplemented by the additional permissions listed below.
12
-
13
- 0. Additional Definitions.
14
-
15
- As used herein, "this License" refers to version 3 of the GNU Lesser
16
- General Public License, and the "GNU GPL" refers to version 3 of the GNU
17
- General Public License.
18
-
19
- "The Library" refers to a covered work governed by this License,
20
- other than an Application or a Combined Work as defined below.
21
-
22
- An "Application" is any work that makes use of an interface provided
23
- by the Library, but which is not otherwise based on the Library.
24
- Defining a subclass of a class defined by the Library is deemed a mode
25
- of using an interface provided by the Library.
26
-
27
- A "Combined Work" is a work produced by combining or linking an
28
- Application with the Library. The particular version of the Library
29
- with which the Combined Work was made is also called the "Linked
30
- Version".
31
-
32
- The "Minimal Corresponding Source" for a Combined Work means the
33
- Corresponding Source for the Combined Work, excluding any source code
34
- for portions of the Combined Work that, considered in isolation, are
35
- based on the Application, and not on the Linked Version.
36
-
37
- The "Corresponding Application Code" for a Combined Work means the
38
- object code and/or source code for the Application, including any data
39
- and utility programs needed for reproducing the Combined Work from the
40
- Application, but excluding the System Libraries of the Combined Work.
41
-
42
- 1. Exception to Section 3 of the GNU GPL.
43
-
44
- You may convey a covered work under sections 3 and 4 of this License
45
- without being bound by section 3 of the GNU GPL.
46
-
47
- 2. Conveying Modified Versions.
48
-
49
- If you modify a copy of the Library, and, in your modifications, a
50
- facility refers to a function or data to be supplied by an Application
51
- that uses the facility (other than as an argument passed when the
52
- facility is invoked), then you may convey a copy of the modified
53
- version:
54
-
55
- a) under this License, provided that you make a good faith effort to
56
- ensure that, in the event an Application does not supply the
57
- function or data, the facility still operates, and performs
58
- whatever part of its purpose remains meaningful, or
59
-
60
- b) under the GNU GPL, with none of the additional permissions of
61
- this License applicable to that copy.
62
-
63
- 3. Object Code Incorporating Material from Library Header Files.
64
-
65
- The object code form of an Application may incorporate material from
66
- a header file that is part of the Library. You may convey such object
67
- code under terms of your choice, provided that, if the incorporated
68
- material is not limited to numerical parameters, data structure
69
- layouts and accessors, or small macros, inline functions and templates
70
- (ten or fewer lines in length), you do both of the following:
71
-
72
- a) Give prominent notice with each copy of the object code that the
73
- Library is used in it and that the Library and its use are
74
- covered by this License.
75
-
76
- b) Accompany the object code with a copy of the GNU GPL and this license
77
- document.
78
-
79
- 4. Combined Works.
80
-
81
- You may convey a Combined Work under terms of your choice that,
82
- taken together, effectively do not restrict modification of the
83
- portions of the Library contained in the Combined Work and reverse
84
- engineering for debugging such modifications, if you also do each of
85
- the following:
86
-
87
- a) Give prominent notice with each copy of the Combined Work that
88
- the Library is used in it and that the Library and its use are
89
- covered by this License.
90
-
91
- b) Accompany the Combined Work with a copy of the GNU GPL and this license
92
- document.
93
-
94
- c) For a Combined Work that displays copyright notices during
95
- execution, include the copyright notice for the Library among
96
- these notices, as well as a reference directing the user to the
97
- copies of the GNU GPL and this license document.
98
-
99
- d) Do one of the following:
100
-
101
- 0) Convey the Minimal Corresponding Source under the terms of this
102
- License, and the Corresponding Application Code in a form
103
- suitable for, and under terms that permit, the user to
104
- recombine or relink the Application with a modified version of
105
- the Linked Version to produce a modified Combined Work, in the
106
- manner specified by section 6 of the GNU GPL for conveying
107
- Corresponding Source.
108
-
109
- 1) Use a suitable shared library mechanism for linking with the
110
- Library. A suitable mechanism is one that (a) uses at run time
111
- a copy of the Library already present on the user's computer
112
- system, and (b) will operate properly with a modified version
113
- of the Library that is interface-compatible with the Linked
114
- Version.
115
-
116
- e) Provide Installation Information, but only if you would otherwise
117
- be required to provide such information under section 6 of the
118
- GNU GPL, and only to the extent that such information is
119
- necessary to install and execute a modified version of the
120
- Combined Work produced by recombining or relinking the
121
- Application with a modified version of the Linked Version. (If
122
- you use option 4d0, the Installation Information must accompany
123
- the Minimal Corresponding Source and Corresponding Application
124
- Code. If you use option 4d1, you must provide the Installation
125
- Information in the manner specified by section 6 of the GNU GPL
126
- for conveying Corresponding Source.)
127
-
128
- 5. Combined Libraries.
129
-
130
- You may place library facilities that are a work based on the
131
- Library side by side in a single library together with other library
132
- facilities that are not Applications and are not covered by this
133
- License, and convey such a combined library under terms of your
134
- choice, if you do both of the following:
135
-
136
- a) Accompany the combined library with a copy of the same work based
137
- on the Library, uncombined with any other library facilities,
138
- conveyed under the terms of this License.
139
-
140
- b) Give prominent notice with the combined library that part of it
141
- is a work based on the Library, and explaining where to find the
142
- accompanying uncombined form of the same work.
143
-
144
- 6. Revised Versions of the GNU Lesser General Public License.
145
-
146
- The Free Software Foundation may publish revised and/or new versions
147
- of the GNU Lesser General Public License from time to time. Such new
148
- versions will be similar in spirit to the present version, but may
149
- differ in detail to address new problems or concerns.
150
-
151
- Each version is given a distinguishing version number. If the
152
- Library as you received it specifies that a certain numbered version
153
- of the GNU Lesser General Public License "or any later version"
154
- applies to it, you have the option of following the terms and
155
- conditions either of that published version or of any later version
156
- published by the Free Software Foundation. If the Library as you
157
- received it does not specify a version number of the GNU Lesser
158
- General Public License, you may choose any version of the GNU Lesser
159
- General Public License ever published by the Free Software Foundation.
160
-
161
- If the Library as you received it specifies that a proxy can decide
162
- whether future versions of the GNU Lesser General Public License shall
163
- apply, that proxy's public statement of acceptance of any version is
164
- permanent authorization for you to choose that version for the
1
+ GNU LESSER GENERAL PUBLIC LICENSE
2
+ Version 3, 29 June 2007
3
+
4
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
5
+ Everyone is permitted to copy and distribute verbatim copies
6
+ of this license document, but changing it is not allowed.
7
+
8
+
9
+ This version of the GNU Lesser General Public License incorporates
10
+ the terms and conditions of version 3 of the GNU General Public
11
+ License, supplemented by the additional permissions listed below.
12
+
13
+ 0. Additional Definitions.
14
+
15
+ As used herein, "this License" refers to version 3 of the GNU Lesser
16
+ General Public License, and the "GNU GPL" refers to version 3 of the GNU
17
+ General Public License.
18
+
19
+ "The Library" refers to a covered work governed by this License,
20
+ other than an Application or a Combined Work as defined below.
21
+
22
+ An "Application" is any work that makes use of an interface provided
23
+ by the Library, but which is not otherwise based on the Library.
24
+ Defining a subclass of a class defined by the Library is deemed a mode
25
+ of using an interface provided by the Library.
26
+
27
+ A "Combined Work" is a work produced by combining or linking an
28
+ Application with the Library. The particular version of the Library
29
+ with which the Combined Work was made is also called the "Linked
30
+ Version".
31
+
32
+ The "Minimal Corresponding Source" for a Combined Work means the
33
+ Corresponding Source for the Combined Work, excluding any source code
34
+ for portions of the Combined Work that, considered in isolation, are
35
+ based on the Application, and not on the Linked Version.
36
+
37
+ The "Corresponding Application Code" for a Combined Work means the
38
+ object code and/or source code for the Application, including any data
39
+ and utility programs needed for reproducing the Combined Work from the
40
+ Application, but excluding the System Libraries of the Combined Work.
41
+
42
+ 1. Exception to Section 3 of the GNU GPL.
43
+
44
+ You may convey a covered work under sections 3 and 4 of this License
45
+ without being bound by section 3 of the GNU GPL.
46
+
47
+ 2. Conveying Modified Versions.
48
+
49
+ If you modify a copy of the Library, and, in your modifications, a
50
+ facility refers to a function or data to be supplied by an Application
51
+ that uses the facility (other than as an argument passed when the
52
+ facility is invoked), then you may convey a copy of the modified
53
+ version:
54
+
55
+ a) under this License, provided that you make a good faith effort to
56
+ ensure that, in the event an Application does not supply the
57
+ function or data, the facility still operates, and performs
58
+ whatever part of its purpose remains meaningful, or
59
+
60
+ b) under the GNU GPL, with none of the additional permissions of
61
+ this License applicable to that copy.
62
+
63
+ 3. Object Code Incorporating Material from Library Header Files.
64
+
65
+ The object code form of an Application may incorporate material from
66
+ a header file that is part of the Library. You may convey such object
67
+ code under terms of your choice, provided that, if the incorporated
68
+ material is not limited to numerical parameters, data structure
69
+ layouts and accessors, or small macros, inline functions and templates
70
+ (ten or fewer lines in length), you do both of the following:
71
+
72
+ a) Give prominent notice with each copy of the object code that the
73
+ Library is used in it and that the Library and its use are
74
+ covered by this License.
75
+
76
+ b) Accompany the object code with a copy of the GNU GPL and this license
77
+ document.
78
+
79
+ 4. Combined Works.
80
+
81
+ You may convey a Combined Work under terms of your choice that,
82
+ taken together, effectively do not restrict modification of the
83
+ portions of the Library contained in the Combined Work and reverse
84
+ engineering for debugging such modifications, if you also do each of
85
+ the following:
86
+
87
+ a) Give prominent notice with each copy of the Combined Work that
88
+ the Library is used in it and that the Library and its use are
89
+ covered by this License.
90
+
91
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
92
+ document.
93
+
94
+ c) For a Combined Work that displays copyright notices during
95
+ execution, include the copyright notice for the Library among
96
+ these notices, as well as a reference directing the user to the
97
+ copies of the GNU GPL and this license document.
98
+
99
+ d) Do one of the following:
100
+
101
+ 0) Convey the Minimal Corresponding Source under the terms of this
102
+ License, and the Corresponding Application Code in a form
103
+ suitable for, and under terms that permit, the user to
104
+ recombine or relink the Application with a modified version of
105
+ the Linked Version to produce a modified Combined Work, in the
106
+ manner specified by section 6 of the GNU GPL for conveying
107
+ Corresponding Source.
108
+
109
+ 1) Use a suitable shared library mechanism for linking with the
110
+ Library. A suitable mechanism is one that (a) uses at run time
111
+ a copy of the Library already present on the user's computer
112
+ system, and (b) will operate properly with a modified version
113
+ of the Library that is interface-compatible with the Linked
114
+ Version.
115
+
116
+ e) Provide Installation Information, but only if you would otherwise
117
+ be required to provide such information under section 6 of the
118
+ GNU GPL, and only to the extent that such information is
119
+ necessary to install and execute a modified version of the
120
+ Combined Work produced by recombining or relinking the
121
+ Application with a modified version of the Linked Version. (If
122
+ you use option 4d0, the Installation Information must accompany
123
+ the Minimal Corresponding Source and Corresponding Application
124
+ Code. If you use option 4d1, you must provide the Installation
125
+ Information in the manner specified by section 6 of the GNU GPL
126
+ for conveying Corresponding Source.)
127
+
128
+ 5. Combined Libraries.
129
+
130
+ You may place library facilities that are a work based on the
131
+ Library side by side in a single library together with other library
132
+ facilities that are not Applications and are not covered by this
133
+ License, and convey such a combined library under terms of your
134
+ choice, if you do both of the following:
135
+
136
+ a) Accompany the combined library with a copy of the same work based
137
+ on the Library, uncombined with any other library facilities,
138
+ conveyed under the terms of this License.
139
+
140
+ b) Give prominent notice with the combined library that part of it
141
+ is a work based on the Library, and explaining where to find the
142
+ accompanying uncombined form of the same work.
143
+
144
+ 6. Revised Versions of the GNU Lesser General Public License.
145
+
146
+ The Free Software Foundation may publish revised and/or new versions
147
+ of the GNU Lesser General Public License from time to time. Such new
148
+ versions will be similar in spirit to the present version, but may
149
+ differ in detail to address new problems or concerns.
150
+
151
+ Each version is given a distinguishing version number. If the
152
+ Library as you received it specifies that a certain numbered version
153
+ of the GNU Lesser General Public License "or any later version"
154
+ applies to it, you have the option of following the terms and
155
+ conditions either of that published version or of any later version
156
+ published by the Free Software Foundation. If the Library as you
157
+ received it does not specify a version number of the GNU Lesser
158
+ General Public License, you may choose any version of the GNU Lesser
159
+ General Public License ever published by the Free Software Foundation.
160
+
161
+ If the Library as you received it specifies that a proxy can decide
162
+ whether future versions of the GNU Lesser General Public License shall
163
+ apply, that proxy's public statement of acceptance of any version is
164
+ permanent authorization for you to choose that version for the
165
165
  Library.
package/README.md CHANGED
@@ -1,47 +1,47 @@
1
- ## Goralys Core
2
-
3
- ---
4
-
5
- This package contains the shared business logic for the [Goralys](https://github.com/SAMSAM-55/Goralys) applications –
6
- web
7
- and mobile.
8
-
9
- This package bundles the reusable code for the web frontend and the mobile app to avoid code redundancy and duplication
10
- accross
11
- platforms.
12
-
13
- ### Content Overview
14
-
15
- - `hooks/` – Reusable React hooks (independant from the DOM)
16
- - `lib/` – Fetch logic, event listeners, etc.
17
- - `types/` – Shared TypeScript types
18
-
19
- The UI components are **not** part of this package as they are specific to each platform (web / mobile), because the
20
- technologies
21
- used for rendering are different (HTML/CSS for the web vs React Native components).
22
-
23
- ### Setup
24
-
25
- ```bash
26
- pnpm add @goralys/core
27
- ```
28
-
29
- ### Development
30
-
31
- This package is part of the Goralys monorepo and is managed via pnpm workspaces
32
-
33
- ```bash
34
- # From the project root
35
- cd goralys-core
36
- pnpm install
37
- pnpm build
38
- pnpm dev
39
- ```
40
-
41
- ### License
42
-
43
- This package is distributed under the LGPL-2.1-or-later license, see [LICENSE](LICENSE) for more information.
44
-
45
- ### Contact
46
-
1
+ ## Goralys Core
2
+
3
+ ---
4
+
5
+ This package contains the shared business logic for the [Goralys](https://github.com/SAMSAM-55/Goralys) applications –
6
+ web
7
+ and mobile.
8
+
9
+ This package bundles the reusable code for the web frontend and the mobile app to avoid code redundancy and duplication
10
+ accross
11
+ platforms.
12
+
13
+ ### Content Overview
14
+
15
+ - `hooks/` – Reusable React hooks (independant from the DOM)
16
+ - `lib/` – Fetch logic, event listeners, etc.
17
+ - `types/` – Shared TypeScript types
18
+
19
+ The UI components are **not** part of this package as they are specific to each platform (web / mobile), because the
20
+ technologies
21
+ used for rendering are different (HTML/CSS for the web vs React Native components).
22
+
23
+ ### Setup
24
+
25
+ ```bash
26
+ pnpm add @goralys/core
27
+ ```
28
+
29
+ ### Development
30
+
31
+ This package is part of the Goralys monorepo and is managed via pnpm workspaces
32
+
33
+ ```bash
34
+ # From the project root
35
+ cd goralys-core
36
+ pnpm install
37
+ pnpm build
38
+ pnpm dev
39
+ ```
40
+
41
+ ### License
42
+
43
+ This package is distributed under the LGPL-2.1-or-later license, see [LICENSE](LICENSE) for more information.
44
+
45
+ ### Contact
46
+
47
47
  sami.saubion@gmail.com – main developer and maintainer of the project
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
 
@@ -59,11 +74,20 @@ type UserRole = {
59
74
  declare const USER_ROLES: readonly ["admin", "teacher", "student", "none"];
60
75
  type UserData = {
61
76
  username: string;
62
- full_name: string;
77
+ fullName: string;
63
78
  role: UserRole["role"];
64
- public_id: string;
79
+ publicId: string;
65
80
  email: string | null | undefined;
66
81
  };
82
+ type UserProfile = {
83
+ id: number;
84
+ username: string;
85
+ pubId: string;
86
+ fullName: string;
87
+ role: UserRole["role"];
88
+ email: string;
89
+ createdAt: PHPDateTime;
90
+ };
67
91
  type User = {
68
92
  username: string;
69
93
  publicId: string;
@@ -76,14 +100,19 @@ type ConfirmOptions = {
76
100
  title: string;
77
101
  message: string;
78
102
  };
103
+ type AuthTokenContext = "admin-panel" | "profile";
104
+ type AuthToken = {
105
+ username: string;
106
+ name: string;
107
+ expires: PHPDateTime;
108
+ created: PHPDateTime;
109
+ };
79
110
 
80
- type Toast = {
81
- type: "error" | "warning" | "info" | "success";
82
- title: string;
83
- message: string;
84
- expires?: number;
111
+ declare function useAuthTokens(context: AuthTokenContext, target?: string | null): {
112
+ tokens: AuthToken[] | null;
113
+ refetch: () => Promise<undefined | void>;
114
+ syncKey: string;
85
115
  };
86
- type ToastFn = (toast: Toast, duration?: number) => void;
87
116
 
88
117
  type SubjectStatus = "not_submitted" | "submitted" | "rejected" | "approved";
89
118
  declare const SubjectStatusConfig: Record<SubjectStatus, {
@@ -121,7 +150,7 @@ type DraftModalResult = {
121
150
  declare const getStatusLabel: (status: SubjectStatus) => string;
122
151
  declare const getStatusHelper: (status: SubjectStatus, role: UserRole["role"]) => string;
123
152
 
124
- declare function useSubjects(role: UserRole["role"], showToast: ToastFn): {
153
+ declare function useSubjects(role: UserRole["role"]): {
125
154
  subjects: Subject[] | null;
126
155
  refetch: () => Promise<undefined | void>;
127
156
  syncKey: string;
@@ -151,21 +180,40 @@ type SupportTicket = {
151
180
  createdAt: PHPDateTime;
152
181
  };
153
182
 
154
- declare function useSupportTickets(showToast: ToastFn): {
183
+ declare function useSupportTickets(): {
155
184
  supportTickets: SupportTicket[] | null;
156
185
  refetch: () => Promise<undefined | void>;
157
186
  syncKey: string;
158
187
  };
159
188
 
189
+ interface UseSyncedResourceOptions<T> {
190
+ name: string;
191
+ cacheKey?: string;
192
+ syncKey?: string;
193
+ fetcher: () => Promise<Response | null | undefined>;
194
+ parse: (data: unknown) => T | null;
195
+ requireAuth?: boolean;
196
+ guard?: () => string | null | undefined;
197
+ }
198
+ /**
199
+ * Shared implementation behind useSubjects / useAuthTokens: cookies sync, localStorage cache, single-flight de-duplication, and a cross-tab
200
+ * `cookiesOnChange` listener that re-fetches once another tab invalidates the sync flag.
201
+ */
202
+ declare function useSyncedResource<T>({ name, cacheKey, syncKey, fetcher, parse, requireAuth, guard }: UseSyncedResourceOptions<T>): {
203
+ data: T | null;
204
+ refetch: () => Promise<undefined | void>;
205
+ syncKey: string;
206
+ };
207
+
160
208
  interface UserFetchResult {
161
209
  users: User[] | null;
162
210
  refetch: () => Promise<void | undefined>;
163
211
  syncKey: string;
164
212
  }
165
- declare function useUsers(showToast: ToastFn): UserFetchResult;
166
- declare function useVirtualUsers(showToast: ToastFn): UserFetchResult;
167
- declare function useAdmins(showToast: ToastFn): UserFetchResult;
168
- declare function useVirtualAdmins(showToast: ToastFn): UserFetchResult;
213
+ declare function useUsers(): UserFetchResult;
214
+ declare function useVirtualUsers(): UserFetchResult;
215
+ declare function useAdmins(): UserFetchResult;
216
+ declare function useVirtualAdmins(): UserFetchResult;
169
217
 
170
218
  declare const LOGO_PATH: {
171
219
  main: string;
@@ -183,6 +231,8 @@ declare const EMAIL_KEY = "email";
183
231
  declare const PUB_ID_KEY = "public-id";
184
232
  declare const SUBJECT_SYNC_RADIX = "subjects-synced-";
185
233
  declare const SUBJECT_CACHE_RADIX = "subjects-cache-";
234
+ declare const AUTH_TOKEN_SYNC_RADIX = "auth-tokens-sync-";
235
+ declare const AUTH_TOKEN_CACHE_RADIX = "auth-tokens-cache-";
186
236
  declare const USER_CACHE_RADIX = "users-cache-";
187
237
  declare const USER_SYNC_RADIX = "users-synced-";
188
238
  declare const ADMIN_CACHE_RADIX = "admins-cache-";
@@ -191,6 +241,8 @@ declare const SUPPORT_TICKET_CACHE = "support-tickets-cache";
191
241
  declare const SUPPORT_TICKET_SYNC = "support-tickets-synced";
192
242
  declare const SUBJECT_SYNCS: Record<UserRole["role"], string>;
193
243
  declare const SUBJECT_CACHES: Record<UserRole["role"], string>;
244
+ declare const AUTH_TOKEN_SYNCS: Record<AuthTokenContext, string | undefined>;
245
+ declare const AUTH_TOKEN_CACHES: Record<AuthTokenContext, string | undefined>;
194
246
  type UserType = {
195
247
  type: "users-virtual" | "users-real" | "admins-virtual" | "admins-real";
196
248
  };
@@ -221,11 +273,20 @@ declare const helpContent: Section[];
221
273
  declare function isFoolsDay(): boolean;
222
274
 
223
275
  declare function buildArray<T>(...items: (T | false | null | undefined)[]): T[];
276
+ declare const fromPhpDate: (phpDate: PHPDateTime) => Date;
224
277
  declare const parsePhpDateTime: (phpDate: PHPDateTime) => string;
278
+ declare function separateNames(fullName: string): {
279
+ firstName: string;
280
+ lastName: string;
281
+ };
225
282
 
226
283
  declare function emitAuthEvent(event: AuthEvent): void;
227
284
  declare function onAuthEvent(callback: (event: AuthEvent) => void): () => void;
228
285
 
286
+ declare function fetchTokensProfile(): Promise<Response | null>;
287
+ declare function fetchTokensAdminPanel(target: string): Promise<Response | null>;
288
+ declare function fetchAuthTokensForContext(ctx: AuthTokenContext, target?: string | null): Promise<Response | null>;
289
+
229
290
  declare function emitUserEvent(event: UserEvent): void;
230
291
  declare function onUserEvent(callback: (event: UserEvent) => void): () => void;
231
292
 
@@ -245,7 +306,7 @@ declare function isHTTPMethod(maybeMethod: string): maybeMethod is HttpMethod;
245
306
  declare function goralysFetchClient(method: HttpMethod, input: string | URL | Request, payload?: Record<string, string | number | boolean | null> | FormData, requestOptions?: RequestInit): Promise<Response>;
246
307
  declare function fetchCsrfClient(formId: string): Promise<string | null>;
247
308
  declare function buildApiUrl(endpoint: string, params: Record<string, string | null>, domain?: boolean): string;
248
- declare function handleToastRequest(r: Response, showToast: (payload: Toast, duration?: number) => void, redirect?: boolean, duration?: number): Promise<boolean>;
309
+ declare function handleToastRequest(r: Response, showToast: ToastFn, redirect?: boolean, duration?: number): Promise<boolean>;
249
310
 
250
311
  declare class GoralysActionHandler {
251
312
  handle: (r: Response) => Promise<void>;
@@ -291,4 +352,4 @@ declare function fetchVirtualUsersClient(): Promise<Response>;
291
352
  declare function fetchAdminsClient(): Promise<Response>;
292
353
  declare function fetchVirtualAdminsClient(): Promise<Response>;
293
354
 
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 };
355
+ 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 UserProfile, 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, separateNames, 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/useSubjects.ts
68
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
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,15 @@ 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": void 0,
115
+ profile: AUTH_TOKEN_SYNC_RADIX + "profile"
116
+ };
117
+ var AUTH_TOKEN_CACHES = {
118
+ "admin-panel": void 0,
119
+ // no caching
120
+ profile: AUTH_TOKEN_CACHE_RADIX + "profile"
121
+ };
122
122
  var USER_SYNCS = {
123
123
  "users-real": USER_SYNC_RADIX + "users",
124
124
  "users-virtual": USER_SYNC_RADIX + "users-virtual",
@@ -332,6 +332,28 @@ Attention : les administrateurs ont acc\xE8s \xE0 toutes les fonctionnalit\xE9s
332
332
  }
333
333
  ];
334
334
 
335
+ // src/lib/auth/auth-event.ts
336
+ var listeners = /* @__PURE__ */ new Set();
337
+ function emitAuthEvent(event) {
338
+ listeners.forEach((l) => l(event));
339
+ }
340
+ function onAuthEvent(callback) {
341
+ listeners.add(callback);
342
+ return () => {
343
+ listeners.delete(callback);
344
+ };
345
+ }
346
+
347
+ // src/lib/fetch/navigation-event.ts
348
+ var listeners2 = /* @__PURE__ */ new Set();
349
+ function emitNavigationEvent(event) {
350
+ listeners2.forEach((l) => l(event));
351
+ }
352
+ function onNavigationEvent(listener) {
353
+ listeners2.add(listener);
354
+ return () => listeners2.delete(listener);
355
+ }
356
+
335
357
  // src/lib/user/user.client.ts
336
358
  async function cacheUserDataClient() {
337
359
  const res = await goralysFetchClient("GET", "user/profile");
@@ -340,9 +362,9 @@ async function cacheUserDataClient() {
340
362
  }
341
363
  const data = (await res.json())["data"];
342
364
  cookiesSet(USERNAME_KEY, data.username, "/", 1.5 * 60 * 60);
343
- cookiesSet(FULL_NAME_KEY, data.full_name, "/", 1.5 * 60 * 60);
365
+ cookiesSet(FULL_NAME_KEY, data.fullName, "/", 1.5 * 60 * 60);
344
366
  cookiesSet(ROLE_KEY, data.role, "/", 1.5 * 60 * 60);
345
- cookiesSet(PUB_ID_KEY, data.public_id, "/", 1.5 * 60 * 60);
367
+ cookiesSet(PUB_ID_KEY, data.publicId, "/", 1.5 * 60 * 60);
346
368
  if (data?.email)
347
369
  cookiesSet(EMAIL_KEY, data.email, "/", 1.5 * 60 * 60);
348
370
  else
@@ -495,56 +517,55 @@ async function handleToastRequest(r, showToast, redirect = true, duration) {
495
517
  return false;
496
518
  }
497
519
 
498
- // src/lib/subjects/subjects.client.ts
499
- async function fetchStudentSubjectsClient() {
500
- const csrfToken = await fetchCsrfClient("get-student-subjects");
501
- if (!csrfToken)
520
+ // src/lib/auth/fetch-auth-tokens.ts
521
+ async function fetchTokensProfile() {
522
+ const username = cookiesGet(USERNAME_KEY);
523
+ if (!username) {
524
+ console.error("[fetchTokensProfile] No username cookies, aborting");
502
525
  return null;
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)
508
- return null;
509
- return await goralysFetchClient("GET", buildApiUrl("subjects/teacher", { "csrf-token": csrfToken }));
526
+ }
527
+ const csrf = await fetchCsrfClient("get-auth-tokens");
528
+ return await goralysFetchClient("GET", buildApiUrl("user/tokens", { "csrf-token": csrf }));
510
529
  }
511
- async function fetchAdminSubjectsClient() {
512
- const csrfToken = await fetchCsrfClient("get-admin-subjects");
513
- if (!csrfToken)
530
+ async function fetchTokensAdminPanel(target) {
531
+ const username = cookiesGet(USERNAME_KEY);
532
+ if (!username) {
533
+ console.error("[fetchTokensProfile] No username cookies, aborting");
514
534
  return null;
515
- return await goralysFetchClient("GET", buildApiUrl("subjects/admin", { "csrf-token": csrfToken }));
516
- }
517
- async function fetchSubjectsForRoleClient(role) {
518
- switch (role.role) {
519
- case "student":
520
- return await fetchStudentSubjectsClient();
521
- case "teacher":
522
- return await fetchTeacherSubjectsClient();
523
- case "admin":
524
- return await fetchAdminSubjectsClient();
525
- default:
526
- return null;
535
+ }
536
+ const csrf = await fetchCsrfClient("get-auth-tokens");
537
+ return await goralysFetchClient("GET", buildApiUrl("user/tokens/any", { pubId: target, "csrf-token": csrf }));
538
+ }
539
+ async function fetchAuthTokensForContext(ctx, target = null) {
540
+ switch (ctx) {
541
+ case "profile":
542
+ return await fetchTokensProfile();
543
+ case "admin-panel":
544
+ return await fetchTokensAdminPanel(target);
527
545
  }
528
546
  }
529
547
 
530
- // src/hooks/useSubjects.ts
531
- function useSubjects(role, showToast) {
532
- const [subjects, setSubjects] = useState(null);
548
+ // src/hooks/use-synced-ressource.ts
549
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
550
+ function useSyncedResource({ name, cacheKey, syncKey, fetcher, parse, requireAuth = true, guard }) {
551
+ const caching = !!cacheKey && !!syncKey;
552
+ const [data, setData] = useState(null);
553
+ const showToast = getToastConfig().getShowToast();
533
554
  const showToastRef = useRef(showToast);
534
555
  useEffect(() => {
535
556
  showToastRef.current = showToast;
536
557
  }, [showToast]);
537
558
  const inFlightRef = useRef(null);
538
- const fetchSubjects = useCallback(async () => {
539
- const cacheKey = SUBJECT_CACHES[role];
540
- const syncKey = SUBJECT_SYNCS[role];
541
- console.log("[useSubjects] fetchSubjects called", { role });
542
- if (!isAuthenticated()) {
543
- console.log("[useSubjects] not authenticated, aborting");
559
+ const fetchResource = useCallback(async () => {
560
+ const abortReason = guard?.();
561
+ if (abortReason) {
562
+ console.error(`[${name}] ${abortReason}`);
563
+ return;
564
+ }
565
+ if (requireAuth && !isAuthenticated()) {
544
566
  return;
545
567
  }
546
568
  if (inFlightRef.current) {
547
- console.log("[useSubjects] in-flight, waiting");
548
569
  return inFlightRef.current;
549
570
  }
550
571
  let resolve;
@@ -552,225 +573,158 @@ function useSubjects(role, showToast) {
552
573
  resolve = r;
553
574
  });
554
575
  try {
555
- const syncValue = cookiesGet(syncKey);
556
- if (syncValue == "1") {
557
- const raw = storageGet(cacheKey);
558
- if (raw === null || raw === void 0) {
559
- cookiesSet(syncKey, "0");
560
- storageRemove(cacheKey);
561
- await fetchSubjects();
576
+ if (caching) {
577
+ const syncValue = cookiesGet(syncKey);
578
+ if (syncValue == "1") {
579
+ const raw2 = storageGet(cacheKey);
580
+ if (raw2 === null || raw2 === void 0) {
581
+ cookiesSet(syncKey, "0");
582
+ storageRemove(cacheKey);
583
+ inFlightRef.current = null;
584
+ resolve();
585
+ await fetchResource();
586
+ return;
587
+ }
588
+ const cached = JSON.parse(raw2 ?? "null");
589
+ setData((prev) => JSON.stringify(prev) === JSON.stringify(cached) ? prev : cached);
562
590
  return;
563
591
  }
564
- const cached = JSON.parse(raw ?? "null");
565
- setSubjects((prev) => {
566
- if (JSON.stringify(prev) === JSON.stringify(cached))
567
- return prev;
568
- return cached;
569
- });
570
- return;
571
592
  }
572
- const res = await fetchSubjectsForRoleClient({ role });
593
+ const res = await fetcher();
573
594
  if (res)
574
595
  await handleToastRequest(res, showToastRef.current, false);
575
- const data = await res?.json();
576
- cookiesSet(syncKey, "1");
577
- storageSet(cacheKey, JSON.stringify(data));
578
- console.log("[useSubjects] set syncKey and cached to localStorage");
579
- const result = Array.isArray(data) ? data : null;
580
- setSubjects((prev) => {
581
- if (JSON.stringify(prev) === JSON.stringify(result))
582
- return prev;
583
- return result;
584
- });
596
+ const raw = await res?.json();
597
+ if (caching) {
598
+ cookiesSet(syncKey, "1");
599
+ storageSet(cacheKey, JSON.stringify(raw));
600
+ }
601
+ const result = parse(raw);
602
+ setData((prev) => JSON.stringify(prev) === JSON.stringify(result) ? prev : result);
585
603
  } finally {
586
604
  inFlightRef.current = null;
587
605
  resolve();
588
606
  }
589
- }, [role, showToastRef]);
607
+ }, [cacheKey, syncKey, fetcher, parse, requireAuth, guard, name, caching]);
590
608
  useEffect(() => {
591
609
  const onChange = () => {
592
- if (inFlightRef.current)
610
+ if (inFlightRef.current || !caching)
593
611
  return;
594
- if (cookiesGet(SUBJECT_SYNCS[role]) != "1") {
595
- void fetchSubjects();
612
+ if (cookiesGet(syncKey) != "1") {
613
+ void fetchResource();
596
614
  }
597
615
  };
598
616
  return cookiesOnChange(onChange);
599
- }, [fetchSubjects, role]);
617
+ }, [fetchResource, syncKey, caching]);
600
618
  useEffect(() => {
601
- void fetchSubjects();
602
- }, [fetchSubjects]);
603
- return useMemo(() => ({
604
- subjects,
605
- refetch: fetchSubjects,
606
- syncKey: SUBJECT_SYNCS[role]
607
- }), [subjects, fetchSubjects, role]);
619
+ void fetchResource();
620
+ }, [fetchResource]);
621
+ return useMemo(() => ({ data, refetch: fetchResource, syncKey: syncKey ?? "" }), [data, fetchResource, syncKey]);
608
622
  }
609
623
 
610
- // src/hooks/useSupportTickets.ts
611
- import { useCallback as useCallback2, useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2, useState as useState2 } from "react";
612
- function useSupportTickets(showToast) {
613
- const [supportTickets, setSupportTickets] = useState2(null);
614
- const showToastRef = useRef2(showToast);
615
- useEffect2(() => {
616
- showToastRef.current = showToast;
617
- }, [showToast]);
618
- const inFlightRef = useRef2(null);
619
- const fetchSupportTickets = useCallback2(async () => {
620
- const syncKey = SUPPORT_TICKET_SYNC;
621
- const cacheKey = SUPPORT_TICKET_CACHE;
622
- if (!isAuthenticated()) {
623
- console.log("[useSupportTickets] not authenticated, aborting");
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]);
624
+ // src/hooks/use-auth-tokens.ts
625
+ function useAuthTokens(context, target = null) {
626
+ const guard = useCallback2(() => context === "admin-panel" && !target ? "Cannot use admin panel context without target" : null, [context, target]);
627
+ const fetcher = useCallback2(async () => context === "admin-panel" ? await fetchTokensAdminPanel(target) : await fetchTokensProfile(), [context, target]);
628
+ const parse = useCallback2((data) => Array.isArray(data) ? data : null, []);
629
+ const { data: tokens, refetch, syncKey } = useSyncedResource({
630
+ name: "useAuthTokens",
631
+ cacheKey: AUTH_TOKEN_CACHES[context],
632
+ syncKey: AUTH_TOKEN_SYNCS[context],
633
+ fetcher,
634
+ parse,
635
+ guard
636
+ });
637
+ return { tokens, refetch, syncKey };
687
638
  }
688
639
 
689
- // src/hooks/useUsers.ts
690
- import { useCallback as useCallback3, useEffect as useEffect3, useMemo as useMemo3, useRef as useRef3, useState as useState3 } from "react";
691
- function useUserCollection(fetchFn, type, showToast) {
692
- const syncKey = USER_SYNCS[type];
693
- const cacheKey = USER_CACHES[type];
694
- const [users, setUsers] = useState3(null);
695
- const showToastRef = useRef3(showToast);
696
- useEffect3(() => {
697
- showToastRef.current = showToast;
698
- }, [showToast]);
699
- const inFlightRef = useRef3(null);
700
- const fetchUsers = useCallback3(async () => {
701
- if (!isAuthenticated()) {
702
- console.log("[useUsers] not authenticated, aborting");
703
- return;
704
- }
705
- if (inFlightRef.current)
706
- return inFlightRef.current;
707
- let resolve;
708
- inFlightRef.current = new Promise((r) => {
709
- resolve = r;
710
- });
711
- try {
712
- const syncValue = cookiesGet(syncKey);
713
- if (syncValue == "1") {
714
- const raw = storageGet(cacheKey);
715
- if (raw === null || raw === void 0) {
716
- cookiesSet(syncKey, "0");
717
- storageRemove(cacheKey);
718
- await fetchUsers();
719
- return;
720
- }
721
- const cached = JSON.parse(raw ?? "null");
722
- setUsers((prev) => {
723
- if (JSON.stringify(prev) === JSON.stringify(cached))
724
- return prev;
725
- return cached;
726
- });
727
- return;
728
- }
729
- const res = await fetchFn();
730
- if (res)
731
- await handleToastRequest(res, showToastRef.current, false);
732
- const data = await res?.json();
733
- if (res?.ok) {
734
- cookiesSet(syncKey, "1");
735
- storageSet(cacheKey, JSON.stringify(data));
736
- }
737
- const result = Array.isArray(data) ? data : null;
738
- setUsers((prev) => {
739
- if (JSON.stringify(prev) === JSON.stringify(result))
740
- return prev;
741
- return result;
742
- });
743
- } finally {
744
- inFlightRef.current = null;
745
- resolve();
746
- }
747
- }, [fetchFn, cacheKey, syncKey]);
748
- useEffect3(() => {
749
- const onChange = () => {
750
- if (inFlightRef.current)
751
- return;
752
- if (cookiesGet(syncKey) != "1") {
753
- void fetchUsers();
754
- }
755
- };
756
- return cookiesOnChange(onChange);
757
- }, [fetchUsers, syncKey]);
758
- useEffect3(() => {
759
- void fetchUsers();
760
- }, [fetchUsers]);
761
- return useMemo3(() => ({ users, refetch: fetchUsers, syncKey }), [users, fetchUsers, syncKey]);
640
+ // src/hooks/use-subjects.ts
641
+ import { useCallback as useCallback3 } from "react";
642
+
643
+ // src/lib/subjects/subjects.client.ts
644
+ async function fetchStudentSubjectsClient() {
645
+ const csrfToken = await fetchCsrfClient("get-student-subjects");
646
+ if (!csrfToken)
647
+ return null;
648
+ return await goralysFetchClient("GET", buildApiUrl("subjects/student", { "csrf-token": csrfToken }));
649
+ }
650
+ async function fetchTeacherSubjectsClient() {
651
+ const csrfToken = await fetchCsrfClient("get-teacher-subjects");
652
+ if (!csrfToken)
653
+ return null;
654
+ return await goralysFetchClient("GET", buildApiUrl("subjects/teacher", { "csrf-token": csrfToken }));
655
+ }
656
+ async function fetchAdminSubjectsClient() {
657
+ const csrfToken = await fetchCsrfClient("get-admin-subjects");
658
+ if (!csrfToken)
659
+ return null;
660
+ return await goralysFetchClient("GET", buildApiUrl("subjects/admin", { "csrf-token": csrfToken }));
661
+ }
662
+ async function fetchSubjectsForRoleClient(role) {
663
+ switch (role.role) {
664
+ case "student":
665
+ return await fetchStudentSubjectsClient();
666
+ case "teacher":
667
+ return await fetchTeacherSubjectsClient();
668
+ case "admin":
669
+ return await fetchAdminSubjectsClient();
670
+ default:
671
+ return null;
672
+ }
762
673
  }
763
- function useUsers(showToast) {
764
- return useUserCollection(fetchUsersClient, "users-real", showToast);
674
+
675
+ // src/hooks/use-subjects.ts
676
+ function useSubjects(role) {
677
+ const fetcher = useCallback3(async () => fetchSubjectsForRoleClient({ role }), [role]);
678
+ const parse = useCallback3((data) => Array.isArray(data) ? data : null, []);
679
+ const { data: subjects, refetch, syncKey } = useSyncedResource({
680
+ name: "useSubjects",
681
+ cacheKey: SUBJECT_CACHES[role],
682
+ syncKey: SUBJECT_SYNCS[role],
683
+ fetcher,
684
+ parse
685
+ });
686
+ return { subjects, refetch, syncKey };
687
+ }
688
+
689
+ // src/hooks/use-support-tickets.ts
690
+ import { useCallback as useCallback4 } from "react";
691
+ function useSupportTickets() {
692
+ const fetcher = useCallback4(async () => await goralysFetchClient("GET", buildApiUrl("support/tickets", { "csrf-token": await fetchCsrfClient("get-tickets") })), []);
693
+ const parse = useCallback4((data) => Array.isArray(data) ? data : null, []);
694
+ const { data: supportTickets, refetch, syncKey } = useSyncedResource({
695
+ name: "useSupportTickets",
696
+ cacheKey: SUPPORT_TICKET_CACHE,
697
+ syncKey: SUPPORT_TICKET_SYNC,
698
+ fetcher,
699
+ parse
700
+ });
701
+ return { supportTickets, refetch, syncKey };
765
702
  }
766
- function useVirtualUsers(showToast) {
767
- return useUserCollection(fetchVirtualUsersClient, "users-virtual", showToast);
703
+
704
+ // src/hooks/use-users-collection.ts
705
+ import { useCallback as useCallback5 } from "react";
706
+ function useUsersCollection(fetcher, type) {
707
+ const parse = useCallback5((data) => Array.isArray(data) ? data : null, []);
708
+ const { data: users, refetch, syncKey } = useSyncedResource({
709
+ name: "useUsersCollection",
710
+ cacheKey: USER_CACHES[type],
711
+ syncKey: USER_SYNCS[type],
712
+ fetcher,
713
+ parse
714
+ });
715
+ return { users, refetch, syncKey };
768
716
  }
769
- function useAdmins(showToast) {
770
- return useUserCollection(fetchAdminsClient, "admins-real", showToast);
717
+ function useUsers() {
718
+ return useUsersCollection(fetchUsersClient, "users-real");
771
719
  }
772
- function useVirtualAdmins(showToast) {
773
- return useUserCollection(fetchVirtualAdminsClient, "admins-virtual", showToast);
720
+ function useVirtualUsers() {
721
+ return useUsersCollection(fetchVirtualUsersClient, "users-virtual");
722
+ }
723
+ function useAdmins() {
724
+ return useUsersCollection(fetchAdminsClient, "admins-real");
725
+ }
726
+ function useVirtualAdmins() {
727
+ return useUsersCollection(fetchVirtualAdminsClient, "admins-virtual");
774
728
  }
775
729
 
776
730
  // src/lib/asset.ts
@@ -793,8 +747,9 @@ function isFoolsDay() {
793
747
  function buildArray(...items) {
794
748
  return items.filter((item) => Boolean(item));
795
749
  }
750
+ var fromPhpDate = (phpDate) => new Date(phpDate.date);
796
751
  var parsePhpDateTime = (phpDate) => {
797
- return new Date(phpDate.date).toLocaleString("fr-FR", {
752
+ return fromPhpDate(phpDate).toLocaleString("fr-FR", {
798
753
  year: "numeric",
799
754
  month: "2-digit",
800
755
  day: "2-digit",
@@ -802,6 +757,25 @@ var parsePhpDateTime = (phpDate) => {
802
757
  minute: "2-digit"
803
758
  });
804
759
  };
760
+ function separateNames(fullName) {
761
+ const parts = fullName.split(" ");
762
+ const firstNameParts = [];
763
+ const lastNameParts = [];
764
+ let current = "first";
765
+ parts.forEach((p) => {
766
+ if (p.toUpperCase() === p)
767
+ current = "last";
768
+ switch (current) {
769
+ case "first":
770
+ firstNameParts.push(p);
771
+ break;
772
+ case "last":
773
+ lastNameParts.push(p);
774
+ break;
775
+ }
776
+ });
777
+ return { firstName: firstNameParts.join(" "), lastName: lastNameParts.join(" ") };
778
+ }
805
779
 
806
780
  // src/lib/subjects/subjects-utils.ts
807
781
  var SUBJECTS = [
@@ -957,6 +931,10 @@ var USER_ROLES = ["admin", "teacher", "student", "none"];
957
931
  export {
958
932
  ADMIN_CACHE_RADIX,
959
933
  ADMIN_SYNC_RADIX,
934
+ AUTH_TOKEN_CACHES,
935
+ AUTH_TOKEN_CACHE_RADIX,
936
+ AUTH_TOKEN_SYNCS,
937
+ AUTH_TOKEN_SYNC_RADIX,
960
938
  COOKIES_DISMISSED_KEY,
961
939
  EMAIL_KEY,
962
940
  FLASH_TOAST_KEY,
@@ -988,6 +966,7 @@ export {
988
966
  configureCookies,
989
967
  configureGoralysClient,
990
968
  configureStorage,
969
+ configureToast,
991
970
  cookiesGet,
992
971
  cookiesGetAll,
993
972
  cookiesOnChange,
@@ -998,16 +977,21 @@ export {
998
977
  emitUserEvent,
999
978
  emptyUserCacheClient,
1000
979
  fetchAdminsClient,
980
+ fetchAuthTokensForContext,
1001
981
  fetchCsrfClient,
1002
982
  fetchSubjectsForRoleClient,
983
+ fetchTokensAdminPanel,
984
+ fetchTokensProfile,
1003
985
  fetchUsersClient,
1004
986
  fetchVirtualAdminsClient,
1005
987
  fetchVirtualUsersClient,
988
+ fromPhpDate,
1006
989
  getGoralysClientConfig,
1007
990
  getLongFromShort,
1008
991
  getShortFromLong,
1009
992
  getStatusHelper,
1010
993
  getStatusLabel,
994
+ getToastConfig,
1011
995
  goralysFetchClient,
1012
996
  handleToastRequest,
1013
997
  helpContent,
@@ -1019,14 +1003,17 @@ export {
1019
1003
  parsePhpDateTime,
1020
1004
  reasonsConfig,
1021
1005
  searchFields,
1006
+ separateNames,
1022
1007
  storageGet,
1023
1008
  storageKeyAt,
1024
1009
  storageRemove,
1025
1010
  storageSet,
1026
1011
  storageSize,
1027
1012
  useAdmins,
1013
+ useAuthTokens,
1028
1014
  useSubjects,
1029
1015
  useSupportTickets,
1016
+ useSyncedResource,
1030
1017
  useUsers,
1031
1018
  useVirtualAdmins,
1032
1019
  useVirtualUsers
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goralys/core",
3
- "version": "1.4.4",
3
+ "version": "1.6.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": "node -e \"require('fs').rmSync('./src/index.ts',{force:true})\" && barrelsby -d ./src -l top"
33
35
  },
34
36
  "peerDependencies": {
35
37
  "react": ">=18"