@hexdspace/react 0.0.11 → 0.0.13
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 +216 -4
- package/dist/index.js +595 -51
- package/package.json +5 -4
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import * as _tanstack_react_query from '@tanstack/react-query';
|
|
2
2
|
import { QueryClient, QueryKey } from '@tanstack/react-query';
|
|
3
|
-
import { ResultOk, ResultError } from '@hexdspace/util';
|
|
4
|
-
import
|
|
3
|
+
import { ResultOk, ResultError, AsyncResult, Result } from '@hexdspace/util';
|
|
4
|
+
import * as react from 'react';
|
|
5
|
+
import react__default, { CSSProperties, Dispatch, ReactNode } from 'react';
|
|
6
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
5
7
|
|
|
6
8
|
type NotificationVariant = 'success' | 'warning' | 'error' | 'info';
|
|
7
9
|
declare const DEFAULT_NOTIFICATION_CHANNEL = "app.notifications";
|
|
@@ -118,7 +120,7 @@ type NotificationHostProps = {
|
|
|
118
120
|
isDark?: () => boolean;
|
|
119
121
|
theme?: ToastTheme;
|
|
120
122
|
};
|
|
121
|
-
declare const NotificationHost:
|
|
123
|
+
declare const NotificationHost: react__default.FC<NotificationHostProps>;
|
|
122
124
|
|
|
123
125
|
type InstructionContext = {
|
|
124
126
|
queryClient: QueryClient;
|
|
@@ -163,4 +165,214 @@ type ResponsiveMutation<Args, Res> = {
|
|
|
163
165
|
|
|
164
166
|
declare function useResponsiveMutation<Args, Res>(responsiveMutation: ResponsiveMutation<Args, Res>, queryClient: QueryClient): _tanstack_react_query.UseMutationResult<UIOk<Res>, UIFail, Args, OptimisticSnapshot | undefined>;
|
|
165
167
|
|
|
166
|
-
|
|
168
|
+
type FactoryBuilder<TDeps, TController> = (deps: TDeps) => TController;
|
|
169
|
+
type FactoryDefaults<TDeps> = (overrides: Partial<TDeps>) => TDeps;
|
|
170
|
+
declare function controllerFactory<TDeps, TController>(builder: FactoryBuilder<TDeps, TController>, defaults: FactoryDefaults<TDeps>): (overrides?: Partial<TDeps>) => TController;
|
|
171
|
+
|
|
172
|
+
interface GenericResponse {
|
|
173
|
+
message: string;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
interface ErrorResponse {
|
|
177
|
+
error: string;
|
|
178
|
+
details?: unknown;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
interface HttpResponse<T = unknown> {
|
|
182
|
+
data: T;
|
|
183
|
+
status: number;
|
|
184
|
+
headers: Record<string, string>;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
declare class HttpError extends Error {
|
|
188
|
+
readonly status: number;
|
|
189
|
+
readonly response: ErrorResponse;
|
|
190
|
+
constructor(message: string, status: number, response: ErrorResponse);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
194
|
+
interface HttpClient {
|
|
195
|
+
get<T = unknown>(url: string, config?: RequestConfig): Promise<HttpResponse<T>>;
|
|
196
|
+
post<T = unknown>(url: string, data?: unknown, config?: RequestConfig): Promise<HttpResponse<T>>;
|
|
197
|
+
put<T = unknown>(url: string, data?: unknown, config?: RequestConfig): Promise<HttpResponse<T>>;
|
|
198
|
+
delete<T = unknown>(url: string, config?: RequestConfig): Promise<HttpResponse<T>>;
|
|
199
|
+
}
|
|
200
|
+
interface RequestConfig {
|
|
201
|
+
headers?: Record<string, string>;
|
|
202
|
+
timeout?: number;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
declare class AutoRefreshDecorator implements HttpClient {
|
|
206
|
+
private readonly httpClient;
|
|
207
|
+
private readonly refresh;
|
|
208
|
+
private static inFlightRefresh;
|
|
209
|
+
constructor(httpClient: HttpClient, refresh: (client: HttpClient) => AsyncResult<HttpError, GenericResponse>);
|
|
210
|
+
get<T = unknown>(url: string, config?: RequestConfig): Promise<HttpResponse<T>>;
|
|
211
|
+
post<T = unknown>(url: string, data?: unknown, config?: RequestConfig): Promise<HttpResponse<T>>;
|
|
212
|
+
put<T = unknown>(url: string, data?: unknown, config?: RequestConfig): Promise<HttpResponse<T>>;
|
|
213
|
+
delete<T = unknown>(url: string, config?: RequestConfig): Promise<HttpResponse<T>>;
|
|
214
|
+
private tryRequest;
|
|
215
|
+
private needsRefresh;
|
|
216
|
+
private performRefresh;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
declare const httpClient: AutoRefreshDecorator;
|
|
220
|
+
|
|
221
|
+
type MockHandler<T = unknown> = (payload: {
|
|
222
|
+
url: string;
|
|
223
|
+
data?: unknown;
|
|
224
|
+
config?: RequestConfig;
|
|
225
|
+
}) => Promise<HttpResponse<T>> | HttpResponse<T>;
|
|
226
|
+
declare class MockHttpClient implements HttpClient {
|
|
227
|
+
private handlers;
|
|
228
|
+
register<T>(method: HttpMethod, url: string, handler: MockHandler<T>): void;
|
|
229
|
+
respondWith<T>(method: HttpMethod, url: string, response: HttpResponse<T>): void;
|
|
230
|
+
respondWithError(method: HttpMethod, url: string, error: Error): void;
|
|
231
|
+
reset(): void;
|
|
232
|
+
get<T = unknown>(url: string, config?: RequestConfig): Promise<HttpResponse<T>>;
|
|
233
|
+
post<T = unknown>(url: string, data?: unknown, config?: RequestConfig): Promise<HttpResponse<T>>;
|
|
234
|
+
put<T = unknown>(url: string, data?: unknown, config?: RequestConfig): Promise<HttpResponse<T>>;
|
|
235
|
+
delete<T = unknown>(url: string, config?: RequestConfig): Promise<HttpResponse<T>>;
|
|
236
|
+
private invoke;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
interface User {
|
|
240
|
+
id: string;
|
|
241
|
+
email: string;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
interface LoginUser {
|
|
245
|
+
execute(email: string, password: string): AsyncResult<Error, User>;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
interface LogoutUser {
|
|
249
|
+
execute(): AsyncResult<Error, null>;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
interface RegisterUser {
|
|
253
|
+
execute(email: string, password: string): AsyncResult<Error, GenericResponse>;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
interface GetAuthenticatedUser {
|
|
257
|
+
execute(): AsyncResult<Error, User | null>;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
interface AuthProvider$1 {
|
|
261
|
+
getAuthenticatedUser(): AsyncResult<Error, User | null>;
|
|
262
|
+
login(email: string, password: string): AsyncResult<Error, User>;
|
|
263
|
+
register(email: string, password: string): AsyncResult<Error, GenericResponse>;
|
|
264
|
+
logout(): AsyncResult<Error, null>;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
declare class AuthController {
|
|
268
|
+
private readonly loginUser;
|
|
269
|
+
private readonly logoutUser;
|
|
270
|
+
private readonly registerUser;
|
|
271
|
+
private readonly getAuthenticatedUser;
|
|
272
|
+
constructor(loginUser: LoginUser, logoutUser: LogoutUser, registerUser: RegisterUser, getAuthenticatedUser: GetAuthenticatedUser);
|
|
273
|
+
login(email: string, password: string): AsyncResult<Error, User>;
|
|
274
|
+
logout(): AsyncResult<Error, null>;
|
|
275
|
+
register(email: string, password: string): AsyncResult<Error, {
|
|
276
|
+
message: string;
|
|
277
|
+
}>;
|
|
278
|
+
getCurrentUser(): AsyncResult<Error, User | null>;
|
|
279
|
+
}
|
|
280
|
+
type AuthControllerDeps = {
|
|
281
|
+
httpClient: HttpClient;
|
|
282
|
+
authProvider: AuthProvider$1;
|
|
283
|
+
loginUser: LoginUser;
|
|
284
|
+
logoutUser: LogoutUser;
|
|
285
|
+
registerUser: RegisterUser;
|
|
286
|
+
getAuthenticatedUser: GetAuthenticatedUser;
|
|
287
|
+
};
|
|
288
|
+
declare const createAuthController: (overrides?: Partial<AuthControllerDeps>) => AuthController;
|
|
289
|
+
declare const authController: AuthController;
|
|
290
|
+
|
|
291
|
+
type AuthState = {
|
|
292
|
+
status: 'loading';
|
|
293
|
+
} | {
|
|
294
|
+
status: 'authenticated';
|
|
295
|
+
user: User;
|
|
296
|
+
} | {
|
|
297
|
+
status: 'unauthenticated';
|
|
298
|
+
message?: string;
|
|
299
|
+
} | {
|
|
300
|
+
status: 'error';
|
|
301
|
+
error: string;
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
declare function useAuth(): AuthState;
|
|
305
|
+
|
|
306
|
+
declare function useAuthActions(): {
|
|
307
|
+
login: (email: string, password: string) => void;
|
|
308
|
+
logout: () => void;
|
|
309
|
+
register: (email: string, password: string) => Promise<Result<Error, {
|
|
310
|
+
message: string;
|
|
311
|
+
}>>;
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
declare function useAuthedUser(): User;
|
|
315
|
+
|
|
316
|
+
declare function useAuthController(): AuthController;
|
|
317
|
+
|
|
318
|
+
type AuthAction = {
|
|
319
|
+
type: 'REQUEST';
|
|
320
|
+
} | {
|
|
321
|
+
type: 'COMPLETE';
|
|
322
|
+
user: User;
|
|
323
|
+
} | {
|
|
324
|
+
type: 'SUCCESS';
|
|
325
|
+
message: string;
|
|
326
|
+
} | {
|
|
327
|
+
type: 'FAILED';
|
|
328
|
+
error: string;
|
|
329
|
+
} | {
|
|
330
|
+
type: 'LOGOUT';
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
declare function useAuthDispatch(): Dispatch<AuthAction>;
|
|
334
|
+
|
|
335
|
+
declare const AuthStateCtx: react.Context<AuthState | null>;
|
|
336
|
+
declare const AuthDispatchCtx: react.Context<Dispatch<AuthAction> | null>;
|
|
337
|
+
declare function AuthProvider({ children }: {
|
|
338
|
+
readonly children: ReactNode;
|
|
339
|
+
}): react_jsx_runtime.JSX.Element;
|
|
340
|
+
|
|
341
|
+
declare const AuthControllerCtx: react.Context<AuthController | null>;
|
|
342
|
+
type AuthControllerProviderProps = {
|
|
343
|
+
readonly children: ReactNode;
|
|
344
|
+
readonly controller: AuthController;
|
|
345
|
+
};
|
|
346
|
+
declare function AuthControllerProvider({ children, controller }: AuthControllerProviderProps): react_jsx_runtime.JSX.Element;
|
|
347
|
+
|
|
348
|
+
interface InputProps {
|
|
349
|
+
id: string;
|
|
350
|
+
label: string;
|
|
351
|
+
type: string;
|
|
352
|
+
value: string;
|
|
353
|
+
onChange: (e: react__default.ChangeEvent<HTMLInputElement>) => void;
|
|
354
|
+
placeholder: string;
|
|
355
|
+
className?: string;
|
|
356
|
+
}
|
|
357
|
+
declare const AuthFormInputField: react__default.FC<InputProps>;
|
|
358
|
+
|
|
359
|
+
type AuthFixtures = {
|
|
360
|
+
meError?: HttpError;
|
|
361
|
+
loginError?: HttpError;
|
|
362
|
+
registerError?: HttpError;
|
|
363
|
+
logoutError?: Error;
|
|
364
|
+
currentUser?: User | null;
|
|
365
|
+
};
|
|
366
|
+
declare class MockAuthHttpClient extends MockHttpClient {
|
|
367
|
+
private fixtures;
|
|
368
|
+
private readonly delayMs;
|
|
369
|
+
constructor(initial?: AuthFixtures, delayMs?: number);
|
|
370
|
+
setCurrentUser(user: User | null): void;
|
|
371
|
+
setLoginError(error: HttpError | undefined): void;
|
|
372
|
+
setRegisterError(error: HttpError | undefined): void;
|
|
373
|
+
setMeError(error: HttpError | undefined): void;
|
|
374
|
+
setLogoutError(error: Error | undefined): void;
|
|
375
|
+
private registerAuthRoutes;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export { AuthController, AuthControllerCtx, type AuthControllerDeps, AuthControllerProvider, AuthDispatchCtx, AuthFormInputField, AuthProvider, type AuthState, AuthStateCtx, type CacheInstruction, type CustomInstruction, DEFAULT_NOTIFICATION_CHANNEL, type ErrorResponse, type GenericResponse, HttpError, type HttpResponse, type Instruction, type InstructionContext, MockAuthHttpClient, MockHttpClient, type Notification, type NotificationAction, NotificationHost, type NotificationInstruction, type NotificationVariant, NotifierController, type OptimisticSnapshot, type ResolvedToastTheme, type ResponsiveMutation, type ToastActionTheme, type ToastTheme, type ToastTransition, type ToastifyCSSVars, type UIFail, type UIOk, type UIResult, type User, authController, controllerFactory, createAuthController, httpClient as fetchHttpClient, notifierController, resolveToastTheme, ui, useAuth, useAuthActions, useAuthController, useAuthDispatch, useAuthedUser, useResponsiveMutation };
|
package/dist/index.js
CHANGED
|
@@ -103,52 +103,11 @@ var notifierController = new NotifierController(sendNotification, subscribe);
|
|
|
103
103
|
|
|
104
104
|
// src/feature/notifier/infra/web/react/NotificationHost.tsx
|
|
105
105
|
import { useCallback, useEffect, useMemo as useMemo2 } from "react";
|
|
106
|
-
import { toast as toast2, ToastContainer } from "react-toastify";
|
|
106
|
+
import { cssTransition, Slide, toast as toast2, ToastContainer } from "react-toastify";
|
|
107
107
|
|
|
108
108
|
// src/feature/notifier/entity/notification.ts
|
|
109
109
|
var DEFAULT_NOTIFICATION_CHANNEL = "app.notifications";
|
|
110
110
|
|
|
111
|
-
// src/feature/notifier/infra/web/react/CustomToastTransition.tsx
|
|
112
|
-
import { cssTransition } from "react-toastify";
|
|
113
|
-
var CUSTOM_TRANSITION_STYLE_ID = "hexd-toast-transition-styles";
|
|
114
|
-
var CUSTOM_TRANSITION_STYLES = `
|
|
115
|
-
@keyframes slideIn {
|
|
116
|
-
from { transform: translateY(-20px); opacity: 0; }
|
|
117
|
-
to { transform: translateY(0); opacity: 1; }
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
@keyframes slideOut {
|
|
121
|
-
from { transform: translateY(0); opacity: 1; }
|
|
122
|
-
to { transform: translateY(-20px); opacity: 0; }
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
.slideIn {
|
|
126
|
-
animation: slideIn 0.35s forwards;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
.slideOut {
|
|
130
|
-
animation: slideOut 0.25s forwards;
|
|
131
|
-
}
|
|
132
|
-
`;
|
|
133
|
-
var ensureCustomToastTransitionStyles = () => {
|
|
134
|
-
if (typeof document === "undefined") return;
|
|
135
|
-
if (document.getElementById(CUSTOM_TRANSITION_STYLE_ID)) return;
|
|
136
|
-
const style = document.createElement("style");
|
|
137
|
-
style.id = CUSTOM_TRANSITION_STYLE_ID;
|
|
138
|
-
style.textContent = CUSTOM_TRANSITION_STYLES;
|
|
139
|
-
document.head.appendChild(style);
|
|
140
|
-
};
|
|
141
|
-
var SlideUp = cssTransition({
|
|
142
|
-
enter: "slideIn",
|
|
143
|
-
exit: "slideOut",
|
|
144
|
-
collapseDuration: 300
|
|
145
|
-
});
|
|
146
|
-
var buildToastTransition = (transition) => {
|
|
147
|
-
ensureCustomToastTransitionStyles();
|
|
148
|
-
if (!transition) return SlideUp;
|
|
149
|
-
return cssTransition(transition);
|
|
150
|
-
};
|
|
151
|
-
|
|
152
111
|
// src/feature/notifier/infra/web/react/ToastContent.tsx
|
|
153
112
|
import { useMemo, useState } from "react";
|
|
154
113
|
import { toast } from "react-toastify";
|
|
@@ -327,12 +286,7 @@ var DEFAULT_THEME_BASE = {
|
|
|
327
286
|
width: "min(24rem, calc(100vw - 2rem))",
|
|
328
287
|
contentPadding: "0.5rem 0.75rem 1rem 0.5rem",
|
|
329
288
|
bodyColumnGap: "0.75rem",
|
|
330
|
-
bodyRowGap: "0.25rem"
|
|
331
|
-
transition: {
|
|
332
|
-
enter: "slideIn",
|
|
333
|
-
exit: "slideOut",
|
|
334
|
-
collapseDuration: 300
|
|
335
|
-
}
|
|
289
|
+
bodyRowGap: "0.25rem"
|
|
336
290
|
};
|
|
337
291
|
function resolveToastTheme(theme) {
|
|
338
292
|
return {
|
|
@@ -350,7 +304,7 @@ function resolveToastTheme(theme) {
|
|
|
350
304
|
contentPadding: theme?.contentPadding ?? DEFAULT_THEME_BASE.contentPadding,
|
|
351
305
|
bodyColumnGap: theme?.bodyColumnGap ?? DEFAULT_THEME_BASE.bodyColumnGap,
|
|
352
306
|
bodyRowGap: theme?.bodyRowGap ?? DEFAULT_THEME_BASE.bodyRowGap,
|
|
353
|
-
transition: theme?.transition
|
|
307
|
+
transition: theme?.transition,
|
|
354
308
|
action: {
|
|
355
309
|
...DEFAULT_ACTION_THEME,
|
|
356
310
|
...theme?.action ?? {}
|
|
@@ -372,6 +326,10 @@ var NotificationHost = ({ channel = DEFAULT_NOTIFICATION_CHANNEL, isDark, theme
|
|
|
372
326
|
}
|
|
373
327
|
return isDark() ? "dark" : "light";
|
|
374
328
|
}, [isDark]);
|
|
329
|
+
const transition = useMemo2(() => {
|
|
330
|
+
const config = resolvedTheme.transition;
|
|
331
|
+
return config ? cssTransition(config) : Slide;
|
|
332
|
+
}, [resolvedTheme.transition]);
|
|
375
333
|
const style = useMemo2(() => ({
|
|
376
334
|
"--toastify-color-light": resolvedTheme.lightBg,
|
|
377
335
|
"--toastify-text-color-light": resolvedTheme.lightText,
|
|
@@ -386,12 +344,13 @@ var NotificationHost = ({ channel = DEFAULT_NOTIFICATION_CHANNEL, isDark, theme
|
|
|
386
344
|
"--toastify-toast-width": resolvedTheme.width
|
|
387
345
|
}), [resolvedTheme]);
|
|
388
346
|
const renderToast = useCallback((notification) => {
|
|
347
|
+
const variant = notification.variant ?? "info";
|
|
389
348
|
toast2(/* @__PURE__ */ jsx2(ToastContent, { notification, theme: resolvedTheme, mode }), {
|
|
390
349
|
toastId: notification.id,
|
|
391
|
-
icon: false
|
|
350
|
+
icon: false,
|
|
351
|
+
type: variant
|
|
392
352
|
});
|
|
393
353
|
}, [mode, resolvedTheme]);
|
|
394
|
-
const transition = useMemo2(() => buildToastTransition(resolvedTheme.transition), [resolvedTheme.transition]);
|
|
395
354
|
useEffect(() => {
|
|
396
355
|
let unsub;
|
|
397
356
|
let disposed = false;
|
|
@@ -467,12 +426,597 @@ var executeEffects = (effects, ctx) => {
|
|
|
467
426
|
}
|
|
468
427
|
});
|
|
469
428
|
};
|
|
429
|
+
|
|
430
|
+
// src/util/controller-factory.ts
|
|
431
|
+
function controllerFactory(builder, defaults) {
|
|
432
|
+
return (overrides = {}) => {
|
|
433
|
+
const resolvedDeps = defaults(overrides);
|
|
434
|
+
return builder({ ...resolvedDeps, ...overrides });
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// src/feature/http/entity/http-error.ts
|
|
439
|
+
var HttpError = class extends Error {
|
|
440
|
+
constructor(message, status, response) {
|
|
441
|
+
super(message);
|
|
442
|
+
this.status = status;
|
|
443
|
+
this.response = response;
|
|
444
|
+
this.name = "HttpError";
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
// src/feature/http/infra/fetch-http-client.ts
|
|
449
|
+
import { nok, ok } from "@hexdspace/util";
|
|
450
|
+
|
|
451
|
+
// src/feature/http/infra/auto-refresh-decorator.ts
|
|
452
|
+
var AutoRefreshDecorator = class _AutoRefreshDecorator {
|
|
453
|
+
constructor(httpClient2, refresh2) {
|
|
454
|
+
this.httpClient = httpClient2;
|
|
455
|
+
this.refresh = refresh2;
|
|
456
|
+
}
|
|
457
|
+
static inFlightRefresh = null;
|
|
458
|
+
async get(url, config) {
|
|
459
|
+
return this.tryRequest(() => this.httpClient.get(url, config));
|
|
460
|
+
}
|
|
461
|
+
async post(url, data, config) {
|
|
462
|
+
return this.tryRequest(() => this.httpClient.post(url, data, config));
|
|
463
|
+
}
|
|
464
|
+
async put(url, data, config) {
|
|
465
|
+
return this.tryRequest(() => this.httpClient.put(url, data, config));
|
|
466
|
+
}
|
|
467
|
+
async delete(url, config) {
|
|
468
|
+
return this.tryRequest(() => this.httpClient.delete(url, config));
|
|
469
|
+
}
|
|
470
|
+
async tryRequest(fn) {
|
|
471
|
+
try {
|
|
472
|
+
return await fn();
|
|
473
|
+
} catch (e) {
|
|
474
|
+
if (e instanceof HttpError && this.needsRefresh(e.status)) {
|
|
475
|
+
await this.performRefresh();
|
|
476
|
+
return fn();
|
|
477
|
+
}
|
|
478
|
+
throw e;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
needsRefresh(status) {
|
|
482
|
+
return [401, 403, 419, 440].includes(status);
|
|
483
|
+
}
|
|
484
|
+
async performRefresh() {
|
|
485
|
+
if (_AutoRefreshDecorator.inFlightRefresh) {
|
|
486
|
+
return _AutoRefreshDecorator.inFlightRefresh;
|
|
487
|
+
}
|
|
488
|
+
_AutoRefreshDecorator.inFlightRefresh = (async () => {
|
|
489
|
+
const res = await this.refresh(this.httpClient);
|
|
490
|
+
if (!res.ok) {
|
|
491
|
+
throw new HttpError("Token refresh failed", res.error.status, res.error.response);
|
|
492
|
+
}
|
|
493
|
+
})().finally(() => {
|
|
494
|
+
_AutoRefreshDecorator.inFlightRefresh = null;
|
|
495
|
+
});
|
|
496
|
+
return _AutoRefreshDecorator.inFlightRefresh;
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
// src/feature/http/infra/fetch-http-client.ts
|
|
501
|
+
var FetchHttpClient = class {
|
|
502
|
+
baseURL;
|
|
503
|
+
constructor(baseURL = "/api") {
|
|
504
|
+
this.baseURL = baseURL;
|
|
505
|
+
}
|
|
506
|
+
async get(url, config) {
|
|
507
|
+
return this.doRequest("GET", url, void 0, config);
|
|
508
|
+
}
|
|
509
|
+
async post(url, data, config) {
|
|
510
|
+
return this.doRequest("POST", url, data, config);
|
|
511
|
+
}
|
|
512
|
+
async put(url, data, config) {
|
|
513
|
+
return this.doRequest("PUT", url, data, config);
|
|
514
|
+
}
|
|
515
|
+
async delete(url, config) {
|
|
516
|
+
return this.doRequest("DELETE", url, void 0, config);
|
|
517
|
+
}
|
|
518
|
+
async doRequest(method, url, body, config) {
|
|
519
|
+
const isFormData = body instanceof FormData;
|
|
520
|
+
const headers = { ...config?.headers };
|
|
521
|
+
let parsedBody;
|
|
522
|
+
if (body !== void 0) {
|
|
523
|
+
parsedBody = isFormData ? body : JSON.stringify(body);
|
|
524
|
+
}
|
|
525
|
+
if (!isFormData) {
|
|
526
|
+
headers["Content-Type"] ??= "application/json";
|
|
527
|
+
}
|
|
528
|
+
try {
|
|
529
|
+
const res = await fetch(this.resolve(url), {
|
|
530
|
+
method,
|
|
531
|
+
body: parsedBody,
|
|
532
|
+
headers: { ...headers, ...config?.headers },
|
|
533
|
+
credentials: "include",
|
|
534
|
+
signal: config?.signal
|
|
535
|
+
});
|
|
536
|
+
const data = await (res.headers.get("content-type")?.includes("application/json") ? res.json() : res.text());
|
|
537
|
+
if (!res.ok) throw new HttpError(res.statusText, res.status, data);
|
|
538
|
+
return {
|
|
539
|
+
data,
|
|
540
|
+
status: res.status,
|
|
541
|
+
headers: Object.fromEntries(res.headers.entries())
|
|
542
|
+
};
|
|
543
|
+
} catch (err) {
|
|
544
|
+
if (err instanceof HttpError) throw err;
|
|
545
|
+
const message = err instanceof Error ? err.message : "HTTP request failed";
|
|
546
|
+
throw new HttpError(message, 0, { error: JSON.stringify(err) });
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
resolve(url) {
|
|
550
|
+
return url.startsWith("http") ? url : `${this.baseURL}${url}`;
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
async function refresh(httpClient2) {
|
|
554
|
+
try {
|
|
555
|
+
const res = await httpClient2.get("/auth/refresh");
|
|
556
|
+
return ok(res.data);
|
|
557
|
+
} catch (err) {
|
|
558
|
+
if (err instanceof HttpError) {
|
|
559
|
+
return nok(err);
|
|
560
|
+
} else {
|
|
561
|
+
return nok(new HttpError("Unknown error during refresh", 0, { error: JSON.stringify(err) }));
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
var httpClient = new AutoRefreshDecorator(new FetchHttpClient(), refresh);
|
|
566
|
+
|
|
567
|
+
// src/feature/http/infra/mock-http-client.ts
|
|
568
|
+
function getKey(method, url) {
|
|
569
|
+
return `${method}:${url}`;
|
|
570
|
+
}
|
|
571
|
+
var MockHttpClient = class {
|
|
572
|
+
handlers = /* @__PURE__ */ new Map();
|
|
573
|
+
register(method, url, handler) {
|
|
574
|
+
this.handlers.set(getKey(method, url), handler);
|
|
575
|
+
}
|
|
576
|
+
respondWith(method, url, response) {
|
|
577
|
+
this.register(method, url, () => response);
|
|
578
|
+
}
|
|
579
|
+
respondWithError(method, url, error) {
|
|
580
|
+
this.register(method, url, () => Promise.reject(error));
|
|
581
|
+
}
|
|
582
|
+
reset() {
|
|
583
|
+
this.handlers.clear();
|
|
584
|
+
}
|
|
585
|
+
get(url, config) {
|
|
586
|
+
return this.invoke("GET", url, void 0, config);
|
|
587
|
+
}
|
|
588
|
+
post(url, data, config) {
|
|
589
|
+
return this.invoke("POST", url, data, config);
|
|
590
|
+
}
|
|
591
|
+
put(url, data, config) {
|
|
592
|
+
return this.invoke("PUT", url, data, config);
|
|
593
|
+
}
|
|
594
|
+
delete(url, config) {
|
|
595
|
+
return this.invoke("DELETE", url, void 0, config);
|
|
596
|
+
}
|
|
597
|
+
invoke(method, url, data, config) {
|
|
598
|
+
const handler = this.handlers.get(getKey(method, url));
|
|
599
|
+
if (!handler) {
|
|
600
|
+
return Promise.reject(new Error(`No mock handler registered for ${method} ${url}`));
|
|
601
|
+
}
|
|
602
|
+
const result = handler({ url, data, config });
|
|
603
|
+
return Promise.resolve(result);
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
|
|
607
|
+
// src/feature/auth/infra/http-cookie-auth-provider.ts
|
|
608
|
+
import { nok as nok2, ok as ok2 } from "@hexdspace/util";
|
|
609
|
+
var HttpCookieAuthProvider = class {
|
|
610
|
+
constructor(httpClient2) {
|
|
611
|
+
this.httpClient = httpClient2;
|
|
612
|
+
}
|
|
613
|
+
async getAuthenticatedUser() {
|
|
614
|
+
try {
|
|
615
|
+
const response = await this.httpClient.get("/auth/me");
|
|
616
|
+
return ok2(response.data.user);
|
|
617
|
+
} catch (err) {
|
|
618
|
+
if (isUnauthorizedError(err)) {
|
|
619
|
+
return ok2(null);
|
|
620
|
+
} else if (err instanceof Error) {
|
|
621
|
+
return nok2(err);
|
|
622
|
+
} else {
|
|
623
|
+
return nok2(new Error("Unknown error during authentication"));
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
async login(email, password) {
|
|
628
|
+
try {
|
|
629
|
+
const body = { email, password };
|
|
630
|
+
await this.httpClient.post("/auth/login", body);
|
|
631
|
+
const userResponse = await this.httpClient.get("/auth/me");
|
|
632
|
+
return ok2(userResponse.data.user);
|
|
633
|
+
} catch (err) {
|
|
634
|
+
if (err instanceof Error) {
|
|
635
|
+
return nok2(err);
|
|
636
|
+
} else {
|
|
637
|
+
return nok2(new Error("Unknown error during authentication"));
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
async register(email, password) {
|
|
642
|
+
try {
|
|
643
|
+
const res = await this.httpClient.post("/auth/register", { email, password });
|
|
644
|
+
return ok2(res.data);
|
|
645
|
+
} catch (err) {
|
|
646
|
+
if (err instanceof Error) {
|
|
647
|
+
return nok2(err);
|
|
648
|
+
} else {
|
|
649
|
+
return nok2(new Error("Unknown error during authentication"));
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
async logout() {
|
|
654
|
+
try {
|
|
655
|
+
await this.httpClient.post("/auth/logout");
|
|
656
|
+
return ok2(null);
|
|
657
|
+
} catch (err) {
|
|
658
|
+
if (err instanceof Error) {
|
|
659
|
+
return nok2(err);
|
|
660
|
+
} else {
|
|
661
|
+
return nok2(new Error("Unknown error during logout"));
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
};
|
|
666
|
+
function isUnauthorizedError(error) {
|
|
667
|
+
return error instanceof HttpError && error.status === 401;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// src/feature/auth/application/use-case/login-user-use-case.ts
|
|
671
|
+
var LoginUserUseCase = class {
|
|
672
|
+
constructor(authProvider) {
|
|
673
|
+
this.authProvider = authProvider;
|
|
674
|
+
}
|
|
675
|
+
execute(email, password) {
|
|
676
|
+
return this.authProvider.login(email, password);
|
|
677
|
+
}
|
|
678
|
+
};
|
|
679
|
+
|
|
680
|
+
// src/feature/auth/application/use-case/logout-user-use-case.ts
|
|
681
|
+
var LogoutUserUseCase = class {
|
|
682
|
+
constructor(authProvider) {
|
|
683
|
+
this.authProvider = authProvider;
|
|
684
|
+
}
|
|
685
|
+
execute() {
|
|
686
|
+
return this.authProvider.logout();
|
|
687
|
+
}
|
|
688
|
+
};
|
|
689
|
+
|
|
690
|
+
// src/feature/auth/application/use-case/register-user-use-case.ts
|
|
691
|
+
var RegisterUserUseCase = class {
|
|
692
|
+
constructor(authProvider) {
|
|
693
|
+
this.authProvider = authProvider;
|
|
694
|
+
}
|
|
695
|
+
execute(email, password) {
|
|
696
|
+
return this.authProvider.register(email, password);
|
|
697
|
+
}
|
|
698
|
+
};
|
|
699
|
+
|
|
700
|
+
// src/feature/auth/application/use-case/get-authenticated-user-use-case.ts
|
|
701
|
+
var GetAuthenticatedUserUseCase = class {
|
|
702
|
+
constructor(authProvider) {
|
|
703
|
+
this.authProvider = authProvider;
|
|
704
|
+
}
|
|
705
|
+
execute() {
|
|
706
|
+
return this.authProvider.getAuthenticatedUser();
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
|
|
710
|
+
// src/feature/auth/interface/controller/auth-controller.ts
|
|
711
|
+
var AuthController = class {
|
|
712
|
+
constructor(loginUser, logoutUser, registerUser, getAuthenticatedUser) {
|
|
713
|
+
this.loginUser = loginUser;
|
|
714
|
+
this.logoutUser = logoutUser;
|
|
715
|
+
this.registerUser = registerUser;
|
|
716
|
+
this.getAuthenticatedUser = getAuthenticatedUser;
|
|
717
|
+
}
|
|
718
|
+
async login(email, password) {
|
|
719
|
+
return this.loginUser.execute(email, password);
|
|
720
|
+
}
|
|
721
|
+
async logout() {
|
|
722
|
+
return this.logoutUser.execute();
|
|
723
|
+
}
|
|
724
|
+
async register(email, password) {
|
|
725
|
+
return this.registerUser.execute(email, password);
|
|
726
|
+
}
|
|
727
|
+
async getCurrentUser() {
|
|
728
|
+
return this.getAuthenticatedUser.execute();
|
|
729
|
+
}
|
|
730
|
+
};
|
|
731
|
+
var createAuthController = controllerFactory(
|
|
732
|
+
(deps) => new AuthController(
|
|
733
|
+
deps.loginUser,
|
|
734
|
+
deps.logoutUser,
|
|
735
|
+
deps.registerUser,
|
|
736
|
+
deps.getAuthenticatedUser
|
|
737
|
+
),
|
|
738
|
+
(overrides) => {
|
|
739
|
+
const httpClient2 = overrides.httpClient ?? httpClient;
|
|
740
|
+
const authProvider = overrides.authProvider ?? new HttpCookieAuthProvider(httpClient2);
|
|
741
|
+
return {
|
|
742
|
+
httpClient: httpClient2,
|
|
743
|
+
authProvider,
|
|
744
|
+
loginUser: overrides.loginUser ?? new LoginUserUseCase(authProvider),
|
|
745
|
+
logoutUser: overrides.logoutUser ?? new LogoutUserUseCase(authProvider),
|
|
746
|
+
registerUser: overrides.registerUser ?? new RegisterUserUseCase(authProvider),
|
|
747
|
+
getAuthenticatedUser: overrides.getAuthenticatedUser ?? new GetAuthenticatedUserUseCase(authProvider)
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
);
|
|
751
|
+
var authController = createAuthController();
|
|
752
|
+
|
|
753
|
+
// src/feature/auth/interface/web/react/hook/useAuth.tsx
|
|
754
|
+
import { useContext as useContext2 } from "react";
|
|
755
|
+
|
|
756
|
+
// src/feature/auth/interface/web/react/AuthProvider.tsx
|
|
757
|
+
import { createContext as createContext2, useEffect as useEffect2, useReducer } from "react";
|
|
758
|
+
|
|
759
|
+
// src/feature/auth/interface/web/react/state/auth-reducer.ts
|
|
760
|
+
function authStateReducer(_state, action) {
|
|
761
|
+
switch (action.type) {
|
|
762
|
+
case "REQUEST":
|
|
763
|
+
return { status: "loading" };
|
|
764
|
+
case "SUCCESS":
|
|
765
|
+
return { status: "unauthenticated", message: action.message };
|
|
766
|
+
case "FAILED":
|
|
767
|
+
return { status: "error", error: action.error };
|
|
768
|
+
case "LOGOUT":
|
|
769
|
+
return { status: "unauthenticated" };
|
|
770
|
+
case "COMPLETE":
|
|
771
|
+
return { status: "authenticated", user: action.user };
|
|
772
|
+
default:
|
|
773
|
+
throw new Error(`Unknown state: ${JSON.stringify(action)}`);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// src/feature/auth/interface/web/react/hook/useAuthController.tsx
|
|
778
|
+
import { useContext } from "react";
|
|
779
|
+
|
|
780
|
+
// src/feature/auth/interface/web/react/AuthControllerProvider.tsx
|
|
781
|
+
import { createContext } from "react";
|
|
782
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
783
|
+
var AuthControllerCtx = createContext(null);
|
|
784
|
+
function AuthControllerProvider({ children, controller }) {
|
|
785
|
+
return /* @__PURE__ */ jsx3(AuthControllerCtx.Provider, { value: controller, children });
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// src/feature/auth/interface/web/react/hook/useAuthController.tsx
|
|
789
|
+
function useAuthController() {
|
|
790
|
+
const authController2 = useContext(AuthControllerCtx);
|
|
791
|
+
if (!authController2) {
|
|
792
|
+
throw new Error("useAuthController must be used inside <AuthControllerProvider>");
|
|
793
|
+
}
|
|
794
|
+
return authController2;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// src/feature/auth/interface/web/react/AuthProvider.tsx
|
|
798
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
799
|
+
var AuthStateCtx = createContext2(null);
|
|
800
|
+
var AuthDispatchCtx = createContext2(null);
|
|
801
|
+
function AuthProvider({ children }) {
|
|
802
|
+
const [state, dispatch] = useReducer(authStateReducer, {
|
|
803
|
+
status: "loading"
|
|
804
|
+
});
|
|
805
|
+
const authController2 = useAuthController();
|
|
806
|
+
useEffect2(() => {
|
|
807
|
+
const fetchActiveUser = async () => {
|
|
808
|
+
const res = await authController2.getCurrentUser();
|
|
809
|
+
if (res.ok) {
|
|
810
|
+
if (res.value) {
|
|
811
|
+
dispatch({ type: "COMPLETE", user: res.value });
|
|
812
|
+
} else {
|
|
813
|
+
dispatch({ type: "LOGOUT" });
|
|
814
|
+
}
|
|
815
|
+
} else {
|
|
816
|
+
dispatch({ type: "FAILED", error: res.error.message });
|
|
817
|
+
}
|
|
818
|
+
};
|
|
819
|
+
void fetchActiveUser();
|
|
820
|
+
}, [dispatch, authController2]);
|
|
821
|
+
return /* @__PURE__ */ jsx4(AuthDispatchCtx.Provider, { value: dispatch, children: /* @__PURE__ */ jsx4(AuthStateCtx.Provider, { value: state, children }) });
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// src/feature/auth/interface/web/react/hook/useAuth.tsx
|
|
825
|
+
function useAuth() {
|
|
826
|
+
const authState = useContext2(AuthStateCtx);
|
|
827
|
+
if (!authState) {
|
|
828
|
+
throw new Error("useAuth must be used inside <AuthProvider>");
|
|
829
|
+
}
|
|
830
|
+
return authState;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// src/feature/auth/interface/web/react/hook/useAuthActions.tsx
|
|
834
|
+
import { useCallback as useCallback2 } from "react";
|
|
835
|
+
|
|
836
|
+
// src/feature/auth/interface/web/react/hook/useAuthDispatch.tsx
|
|
837
|
+
import { useContext as useContext3 } from "react";
|
|
838
|
+
function useAuthDispatch() {
|
|
839
|
+
const dispatch = useContext3(AuthDispatchCtx);
|
|
840
|
+
if (!dispatch) {
|
|
841
|
+
throw new Error("useAuthDispatch must be used inside <AuthProvider>");
|
|
842
|
+
}
|
|
843
|
+
return dispatch;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
// src/feature/auth/interface/web/react/hook/useAuthActions.tsx
|
|
847
|
+
function useAuthActions() {
|
|
848
|
+
const dispatch = useAuthDispatch();
|
|
849
|
+
const authController2 = useAuthController();
|
|
850
|
+
const login = useCallback2((email, password) => {
|
|
851
|
+
dispatch({ type: "REQUEST" });
|
|
852
|
+
authController2.login(email, password).then((res) => {
|
|
853
|
+
dispatchLoginResult(res, dispatch);
|
|
854
|
+
});
|
|
855
|
+
}, [dispatch, authController2]);
|
|
856
|
+
const logout = useCallback2(() => {
|
|
857
|
+
authController2.logout().then(() => dispatch({ type: "LOGOUT" }));
|
|
858
|
+
}, [dispatch, authController2]);
|
|
859
|
+
const register = useCallback2(async (email, password) => {
|
|
860
|
+
dispatch({ type: "REQUEST" });
|
|
861
|
+
const res = await authController2.register(email, password);
|
|
862
|
+
dispatchRegisterResult(res, dispatch);
|
|
863
|
+
return res;
|
|
864
|
+
}, [dispatch, authController2]);
|
|
865
|
+
return { login, logout, register };
|
|
866
|
+
}
|
|
867
|
+
function dispatchLoginResult(res, dispatch) {
|
|
868
|
+
if (res.ok) {
|
|
869
|
+
dispatch({ type: "COMPLETE", user: res.value });
|
|
870
|
+
} else {
|
|
871
|
+
dispatch({ type: "FAILED", error: res.error.message });
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
function dispatchRegisterResult(res, dispatch) {
|
|
875
|
+
if (res.ok) {
|
|
876
|
+
dispatch({ type: "SUCCESS", message: res.value.message });
|
|
877
|
+
} else {
|
|
878
|
+
dispatch({ type: "FAILED", error: res.error.message });
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// src/feature/auth/interface/web/react/hook/useAuthedUser.tsx
|
|
883
|
+
function useAuthedUser() {
|
|
884
|
+
const auth = useAuth();
|
|
885
|
+
if (auth.status !== "authenticated") {
|
|
886
|
+
throw new Error("useAuthedUser called outside of an authenticated route");
|
|
887
|
+
}
|
|
888
|
+
return auth.user;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// src/feature/auth/interface/web/react/AuthFormInputField.tsx
|
|
892
|
+
import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
893
|
+
var inputLabelStyles = {
|
|
894
|
+
display: "block",
|
|
895
|
+
fontSize: "small",
|
|
896
|
+
fontWeight: "bolder",
|
|
897
|
+
marginBottom: "4px"
|
|
898
|
+
};
|
|
899
|
+
var inputFieldStyles = {
|
|
900
|
+
display: "block",
|
|
901
|
+
width: "100%",
|
|
902
|
+
padding: "0.5rem 0.75rem"
|
|
903
|
+
};
|
|
904
|
+
var AuthFormInputField = ({ id, label, type, value, onChange, placeholder, className }) => {
|
|
905
|
+
return /* @__PURE__ */ jsxs2("div", { children: [
|
|
906
|
+
/* @__PURE__ */ jsx5("label", { htmlFor: id, style: inputLabelStyles, children: label }),
|
|
907
|
+
/* @__PURE__ */ jsx5(
|
|
908
|
+
"input",
|
|
909
|
+
{
|
|
910
|
+
id,
|
|
911
|
+
name: id,
|
|
912
|
+
type,
|
|
913
|
+
required: true,
|
|
914
|
+
value,
|
|
915
|
+
onChange,
|
|
916
|
+
className,
|
|
917
|
+
placeholder,
|
|
918
|
+
style: inputFieldStyles
|
|
919
|
+
}
|
|
920
|
+
)
|
|
921
|
+
] });
|
|
922
|
+
};
|
|
923
|
+
|
|
924
|
+
// src/feature/auth/infra/mock-auth-http-client.ts
|
|
925
|
+
function httpResponse(data, status = 200) {
|
|
926
|
+
return { data, status, headers: {} };
|
|
927
|
+
}
|
|
928
|
+
function unauthorized() {
|
|
929
|
+
return new HttpError("Unauthorized", 401, { error: "Unauthorized" });
|
|
930
|
+
}
|
|
931
|
+
var MockAuthHttpClient = class extends MockHttpClient {
|
|
932
|
+
fixtures;
|
|
933
|
+
delayMs;
|
|
934
|
+
constructor(initial, delayMs = 0) {
|
|
935
|
+
super();
|
|
936
|
+
this.fixtures = { currentUser: null, ...initial };
|
|
937
|
+
this.delayMs = delayMs;
|
|
938
|
+
this.registerAuthRoutes();
|
|
939
|
+
}
|
|
940
|
+
setCurrentUser(user) {
|
|
941
|
+
this.fixtures.currentUser = user;
|
|
942
|
+
}
|
|
943
|
+
setLoginError(error) {
|
|
944
|
+
this.fixtures.loginError = error;
|
|
945
|
+
}
|
|
946
|
+
setRegisterError(error) {
|
|
947
|
+
this.fixtures.registerError = error;
|
|
948
|
+
}
|
|
949
|
+
setMeError(error) {
|
|
950
|
+
this.fixtures.meError = error;
|
|
951
|
+
}
|
|
952
|
+
setLogoutError(error) {
|
|
953
|
+
this.fixtures.logoutError = error;
|
|
954
|
+
}
|
|
955
|
+
registerAuthRoutes() {
|
|
956
|
+
this.register("GET", "/auth/me", async () => {
|
|
957
|
+
await new Promise((resolve) => setTimeout(resolve, this.delayMs));
|
|
958
|
+
if (this.fixtures.meError) {
|
|
959
|
+
throw this.fixtures.meError;
|
|
960
|
+
}
|
|
961
|
+
if (this.fixtures.currentUser) {
|
|
962
|
+
return httpResponse({ user: this.fixtures.currentUser });
|
|
963
|
+
}
|
|
964
|
+
throw unauthorized();
|
|
965
|
+
});
|
|
966
|
+
this.register("POST", "/auth/login", async (payload) => {
|
|
967
|
+
await new Promise((resolve) => setTimeout(resolve, this.delayMs));
|
|
968
|
+
if (this.fixtures.loginError) {
|
|
969
|
+
throw this.fixtures.loginError;
|
|
970
|
+
}
|
|
971
|
+
const body = payload.data;
|
|
972
|
+
this.fixtures.currentUser ||= {
|
|
973
|
+
id: "mock-user",
|
|
974
|
+
email: body.email
|
|
975
|
+
};
|
|
976
|
+
return httpResponse({ message: "Logged in" });
|
|
977
|
+
});
|
|
978
|
+
this.register("POST", "/auth/register", async () => {
|
|
979
|
+
await new Promise((resolve) => setTimeout(resolve, this.delayMs));
|
|
980
|
+
if (this.fixtures.registerError) {
|
|
981
|
+
throw this.fixtures.registerError;
|
|
982
|
+
}
|
|
983
|
+
return httpResponse({ message: "Registered" });
|
|
984
|
+
});
|
|
985
|
+
this.register("POST", "/auth/logout", async () => {
|
|
986
|
+
await new Promise((resolve) => setTimeout(resolve, this.delayMs));
|
|
987
|
+
if (this.fixtures.logoutError) {
|
|
988
|
+
throw this.fixtures.logoutError;
|
|
989
|
+
}
|
|
990
|
+
this.fixtures.currentUser = null;
|
|
991
|
+
return httpResponse(null);
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
};
|
|
470
995
|
export {
|
|
996
|
+
AuthController,
|
|
997
|
+
AuthControllerCtx,
|
|
998
|
+
AuthControllerProvider,
|
|
999
|
+
AuthDispatchCtx,
|
|
1000
|
+
AuthFormInputField,
|
|
1001
|
+
AuthProvider,
|
|
1002
|
+
AuthStateCtx,
|
|
471
1003
|
DEFAULT_NOTIFICATION_CHANNEL,
|
|
1004
|
+
HttpError,
|
|
1005
|
+
MockAuthHttpClient,
|
|
1006
|
+
MockHttpClient,
|
|
472
1007
|
NotificationHost,
|
|
473
1008
|
NotifierController,
|
|
1009
|
+
authController,
|
|
1010
|
+
controllerFactory,
|
|
1011
|
+
createAuthController,
|
|
1012
|
+
httpClient as fetchHttpClient,
|
|
474
1013
|
notifierController,
|
|
475
1014
|
resolveToastTheme,
|
|
476
1015
|
ui,
|
|
1016
|
+
useAuth,
|
|
1017
|
+
useAuthActions,
|
|
1018
|
+
useAuthController,
|
|
1019
|
+
useAuthDispatch,
|
|
1020
|
+
useAuthedUser,
|
|
477
1021
|
useResponsiveMutation
|
|
478
1022
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hexdspace/react",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -21,9 +21,10 @@
|
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@tanstack/react-query": "^5.90.11",
|
|
23
23
|
"lucide-react": "^0.555.0",
|
|
24
|
+
"react-router-dom": "^7.10.1",
|
|
24
25
|
"react-toastify": "^11.0.5",
|
|
25
26
|
"uuid": "^13.0.0",
|
|
26
|
-
"@hexdspace/util": "0.0.
|
|
27
|
+
"@hexdspace/util": "0.0.25"
|
|
27
28
|
},
|
|
28
29
|
"peerDependencies": {
|
|
29
30
|
"@tanstack/react-query": "^5.90.11",
|
|
@@ -31,8 +32,8 @@
|
|
|
31
32
|
},
|
|
32
33
|
"devDependencies": {
|
|
33
34
|
"@tanstack/react-query": "^5.90.11",
|
|
34
|
-
"react": "^19.2.
|
|
35
|
-
"
|
|
35
|
+
"@types/react": "^19.2.7",
|
|
36
|
+
"react": "^19.2.0"
|
|
36
37
|
},
|
|
37
38
|
"scripts": {
|
|
38
39
|
"build": "tsup src/index.ts --dts --format esm --clean",
|