@tpzdsp/next-toolkit 1.14.2 → 1.15.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.
Files changed (30) hide show
  1. package/package.json +2 -1
  2. package/src/assets/styles/globals.css +5 -1
  3. package/src/components/ErrorBoundary/ErrorBoundary.stories.tsx +1 -1
  4. package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +1 -1
  5. package/src/components/ErrorBoundary/ErrorBoundary.tsx +1 -1
  6. package/src/components/InfoBox/InfoBox.tsx +7 -4
  7. package/src/components/accordion/Accordion.test.tsx +5 -10
  8. package/src/components/accordion/Accordion.tsx +4 -7
  9. package/src/components/divider/RuleDivider.test.tsx +4 -4
  10. package/src/components/form/Input.test.tsx +3 -11
  11. package/src/components/form/Input.tsx +2 -2
  12. package/src/components/form/TextArea.test.tsx +3 -5
  13. package/src/components/form/TextArea.tsx +2 -2
  14. package/src/components/layout/header/Header.stories.tsx +3 -3
  15. package/src/components/layout/header/Header.test.tsx +3 -3
  16. package/src/components/layout/header/HeaderNavClient.test.tsx +3 -3
  17. package/src/components/select/Select.stories.tsx +5 -5
  18. package/src/components/select/Select.test.tsx +2 -2
  19. package/src/components/select/Select.tsx +3 -4
  20. package/src/components/select/SelectSkeleton.test.tsx +1 -2
  21. package/src/components/select/SelectSkeleton.tsx +3 -3
  22. package/src/components/select/common.ts +2 -3
  23. package/src/http/constants.ts +4 -0
  24. package/src/http/fetch.ts +2 -0
  25. package/src/http/logger.test.ts +346 -0
  26. package/src/http/logger.ts +412 -76
  27. package/src/map/useKeyboardDrawing.ts +8 -4
  28. package/src/utils/constants.ts +8 -0
  29. package/src/utils/utils.ts +4 -4
  30. package/src/components/theme/ThemeProvider.tsx +0 -30
@@ -2,60 +2,376 @@ import type { BetterFetchPlugin, RequestContext } from '@better-fetch/fetch';
2
2
 
3
3
  import { Header } from './constants';
4
4
 
5
- // Tries to print the request/response body as a string, and clamps the length it it's too long.
6
- const preview = (data: unknown, maxLength: number): string | null => {
5
+ const MAX_PREVIEW_LENGTH = 150;
6
+
7
+ // Tries to print data as a string.
8
+ const stringify = (data: unknown): string | null => {
7
9
  if (data == null) {
8
10
  return null;
9
11
  }
10
12
 
11
13
  try {
12
- let str = typeof data === 'string' ? data : JSON.stringify(data);
13
-
14
- if (str.length > maxLength) {
15
- str = str.slice(0, maxLength) + '…';
16
- }
17
-
18
- return str;
14
+ return typeof data === 'string' ? data : JSON.stringify(data);
19
15
  } catch {
20
16
  return '[unserializable]';
21
17
  }
22
18
  };
23
19
 
20
+ // Tries to print the request/response body as a string, and clamps the length if it's too long.
21
+ const preview = (data: unknown): string | null => {
22
+ const str = stringify(data);
23
+
24
+ if (str == null) {
25
+ return null;
26
+ }
27
+
28
+ if (str.length > MAX_PREVIEW_LENGTH) {
29
+ return str.slice(0, MAX_PREVIEW_LENGTH) + '…';
30
+ }
31
+
32
+ return str;
33
+ };
34
+
24
35
  type RequestMeta = {
25
36
  requestId: string;
26
37
  };
38
+
39
+ type RequestLoggerMessageExtras = {
40
+ loggerOverrides?: {
41
+ requestFormat?: (args: { request: MetaContext }) => string;
42
+
43
+ successFormat?: (args: {
44
+ request: MetaContext;
45
+ response: Response;
46
+ data: unknown;
47
+ durationMs: number;
48
+ }) => string;
49
+
50
+ errorFormat?: (args: {
51
+ request: MetaContext;
52
+ response?: Response;
53
+ error: unknown;
54
+ durationMs: number;
55
+ }) => string;
56
+
57
+ retryFormat?: (args: { request: MetaContext; attempt: number }) => string;
58
+ };
59
+ };
60
+
27
61
  type MetaContext = RequestContext & {
28
62
  meta: RequestMeta;
29
- };
63
+ } & RequestLoggerMessageExtras;
30
64
 
31
65
  const UNKNOWN_ID = '???';
32
66
 
33
- /**
34
- * Config options for the logger.
35
- */
67
+ type RequestResponseToggle = {
68
+ request?: boolean;
69
+ response?: boolean;
70
+ };
71
+
36
72
  type RequestLoggerOptions = {
37
73
  /**
38
- * Enables/Disabled the logger. Can be a function/async function to toggle the logger dynamically
74
+ * Enables/disables the logger. Can be a function/async function to toggle dynamically.
39
75
  */
40
76
  enabled?: boolean | (() => Promise<boolean> | boolean);
77
+
41
78
  /**
42
- * Controls how much of the request and response body is shown in the logs.
79
+ * Whether bodies should be truncated to `maxPreviewLength`.
80
+ * If false, the full body is printed and is placed on the following line.
43
81
  */
44
- maxPreviewLength?: number;
82
+ truncateBody?: boolean;
83
+
84
+ /**
85
+ * Built-in output toggles.
86
+ */
87
+ showMethod?: boolean;
88
+ showUrl?: boolean;
89
+ showStatus?: boolean;
90
+ showDuration?: boolean;
91
+ showAcceptHeader?: boolean;
92
+ showContentType?: boolean;
93
+ showRetryAttempt?: boolean;
94
+ showBody?: RequestResponseToggle;
95
+ showHeaders?: RequestResponseToggle;
96
+ };
97
+
98
+ const defaultOptions = {
99
+ truncateBody: true,
100
+ showMethod: true,
101
+ showUrl: true,
102
+ showStatus: true,
103
+ showDuration: true,
104
+ showAcceptHeader: true,
105
+ showContentType: true,
106
+ showRetryAttempt: true,
107
+ showBody: {
108
+ request: true,
109
+ response: true,
110
+ },
111
+ showHeaders: {
112
+ request: false,
113
+ response: false,
114
+ },
115
+ } as const;
116
+
117
+ const getOptionOrDefault = <K extends keyof RequestLoggerOptions>(
118
+ options: RequestLoggerOptions | undefined,
119
+ key: K,
120
+ ): NonNullable<RequestLoggerOptions[K]> => {
121
+ return options?.[key] ?? (defaultOptions as Required<RequestLoggerOptions>)[key];
122
+ };
123
+
124
+ const formatHeaders = (headers: Headers): string => {
125
+ const entries = Array.from(headers.entries());
126
+
127
+ if (entries.length === 0) {
128
+ return 'N/A';
129
+ }
130
+
131
+ return entries.map(([key, value]) => `${key}: ${value}`).join(', ');
132
+ };
133
+
134
+ const joinLines = (lines: (string | null | undefined)[]): string =>
135
+ lines.filter((line): line is string => Boolean(line)).join('\n');
136
+
137
+ const formatPrimaryLine = ({
138
+ icon,
139
+ request,
140
+ id,
141
+ showMethod,
142
+ showUrl,
143
+ suffix,
144
+ }: {
145
+ icon: string;
146
+ request: MetaContext;
147
+ id: string;
148
+ showMethod: boolean;
149
+ showUrl: boolean;
150
+ suffix?: string;
151
+ }): string => {
152
+ const parts = [
153
+ icon,
154
+ showMethod ? `[${request.method.toUpperCase()}]` : null,
155
+ `(${id})`,
156
+ showUrl ? `${request.url ?? ''}` : null,
157
+ suffix ?? null,
158
+ ].filter(Boolean);
159
+
160
+ return parts.join(' ');
161
+ };
162
+
163
+ const formatStatusDurationLine = ({
164
+ status,
165
+ durationMs,
166
+ showStatus,
167
+ showDuration,
168
+ }: {
169
+ status?: number | string;
170
+ durationMs: number;
171
+ showStatus: boolean;
172
+ showDuration: boolean;
173
+ }): string | null => {
174
+ const parts = [
175
+ showStatus ? `${status ?? 'unknown'}` : null,
176
+ showDuration ? `${durationMs.toFixed(1)}ms` : null,
177
+ ].filter(Boolean);
178
+
179
+ return parts.length > 0 ? `\t↳ ${parts.join(' | ')}` : null;
180
+ };
181
+
182
+ const formatBodyLine = ({
183
+ label,
184
+ body,
185
+ truncateBody,
186
+ }: {
187
+ label: string;
188
+ body: unknown;
189
+ truncateBody: boolean;
190
+ }): string | null => {
191
+ const rendered = truncateBody ? preview(body) : stringify(body);
192
+
193
+ if (!rendered) {
194
+ return null;
195
+ }
196
+
197
+ return truncateBody ? `\t↳ ${label}: ${rendered}` : `\t↳ ${label}:\n${rendered}`;
198
+ };
199
+
200
+ const getDurationMs = (timings: Map<string, number>, id: string): number =>
201
+ performance.now() - (timings.get(id) ?? 0);
202
+
203
+ const defaultRequestFormatter = ({
204
+ request,
205
+ options,
206
+ }: {
207
+ request: MetaContext;
208
+ options?: RequestLoggerOptions;
209
+ }): string => {
210
+ const id = request.meta?.requestId ?? UNKNOWN_ID;
211
+
212
+ const showMethod = getOptionOrDefault(options, 'showMethod');
213
+ const showUrl = getOptionOrDefault(options, 'showUrl');
214
+ const showAcceptHeader = getOptionOrDefault(options, 'showAcceptHeader');
215
+ const showRequestContentType = getOptionOrDefault(options, 'showContentType');
216
+ const showRequestHeaders = getOptionOrDefault(options, 'showHeaders').request;
217
+ const showRequestBody = getOptionOrDefault(options, 'showBody').request;
218
+ const truncateBody = getOptionOrDefault(options, 'truncateBody');
219
+
220
+ return joinLines([
221
+ formatPrimaryLine({
222
+ icon: '🚀',
223
+ request,
224
+ id,
225
+ showMethod,
226
+ showUrl,
227
+ }),
228
+ showRequestContentType
229
+ ? `\t↳ Req. Content-Type: ${request.headers.get(Header.ContentType) ?? 'N/A'}`
230
+ : null,
231
+ showAcceptHeader ? `\t↳ Req. Accept: ${request.headers.get(Header.Accept) ?? 'N/A'}` : null,
232
+ showRequestHeaders ? `\t↳ Req. Headers: ${formatHeaders(request.headers)}` : null,
233
+ showRequestBody
234
+ ? formatBodyLine({
235
+ label: 'Req. Body',
236
+ body: request.body,
237
+ truncateBody,
238
+ })
239
+ : null,
240
+ ]);
241
+ };
242
+
243
+ const defaultSuccessFormatter = ({
244
+ request,
245
+ response,
246
+ data,
247
+ durationMs,
248
+ options,
249
+ }: {
250
+ request: MetaContext;
251
+ response: Response;
252
+ data: unknown;
253
+ durationMs: number;
254
+ options?: RequestLoggerOptions;
255
+ }): string => {
256
+ const id = request.meta?.requestId ?? UNKNOWN_ID;
257
+
258
+ const showMethod = getOptionOrDefault(options, 'showMethod');
259
+ const showUrl = getOptionOrDefault(options, 'showUrl');
260
+ const showStatus = getOptionOrDefault(options, 'showStatus');
261
+ const showDuration = getOptionOrDefault(options, 'showDuration');
262
+ const showResponseContentType = getOptionOrDefault(options, 'showContentType');
263
+ const showResponseHeaders = getOptionOrDefault(options, 'showHeaders').response;
264
+ const showResponseBody = getOptionOrDefault(options, 'showBody').response;
265
+ const truncateBody = getOptionOrDefault(options, 'truncateBody');
266
+
267
+ return joinLines([
268
+ formatPrimaryLine({
269
+ icon: '✅',
270
+ request,
271
+ id,
272
+ showMethod,
273
+ showUrl,
274
+ }),
275
+ formatStatusDurationLine({
276
+ status: response.status,
277
+ durationMs,
278
+ showStatus,
279
+ showDuration,
280
+ }),
281
+ showResponseContentType
282
+ ? `\t↳ Res. Content-Type: ${response.headers.get(Header.ContentType) ?? 'N/A'}`
283
+ : null,
284
+ showResponseHeaders ? `\t↳ Res. Headers: ${formatHeaders(response.headers)}` : null,
285
+ showResponseBody
286
+ ? formatBodyLine({
287
+ label: 'Res. Body',
288
+ body: data,
289
+ truncateBody,
290
+ })
291
+ : null,
292
+ ]);
293
+ };
294
+
295
+ const defaultRetryFormatter = ({
296
+ request,
297
+ attempt,
298
+ options,
299
+ }: {
300
+ request: MetaContext;
301
+ attempt: number;
302
+ options?: RequestLoggerOptions;
303
+ }): string => {
304
+ const id = request.meta?.requestId ?? UNKNOWN_ID;
305
+
306
+ const showMethod = getOptionOrDefault(options, 'showMethod');
307
+ const showUrl = getOptionOrDefault(options, 'showUrl');
308
+ const showRetryAttempt = getOptionOrDefault(options, 'showRetryAttempt');
309
+
310
+ return joinLines([
311
+ formatPrimaryLine({
312
+ icon: '🔁',
313
+ request,
314
+ id,
315
+ showMethod,
316
+ showUrl,
317
+ suffix: 'Retrying...',
318
+ }),
319
+ showRetryAttempt ? `\t↳ Attempt: ${attempt}` : null,
320
+ ]);
321
+ };
322
+
323
+ const defaultErrorFormatter = ({
324
+ request,
325
+ response,
326
+ error,
327
+ durationMs,
328
+ options,
329
+ }: {
330
+ request: MetaContext;
331
+ response?: Response;
332
+ error: unknown;
333
+ durationMs: number;
334
+ options?: RequestLoggerOptions;
335
+ }): string => {
336
+ const id = request.meta?.requestId ?? UNKNOWN_ID;
337
+
338
+ const showMethod = getOptionOrDefault(options, 'showMethod');
339
+ const showUrl = getOptionOrDefault(options, 'showUrl');
340
+ const showStatus = getOptionOrDefault(options, 'showStatus');
341
+ const showDuration = getOptionOrDefault(options, 'showDuration');
342
+ const showResponseHeaders = getOptionOrDefault(options, 'showHeaders').response;
343
+ const truncateBody = getOptionOrDefault(options, 'truncateBody');
344
+
345
+ return joinLines([
346
+ formatPrimaryLine({
347
+ icon: '❌',
348
+ request,
349
+ id,
350
+ showMethod,
351
+ showUrl,
352
+ }),
353
+ formatStatusDurationLine({
354
+ status: response?.status ?? 'unknown',
355
+ durationMs,
356
+ showStatus,
357
+ showDuration,
358
+ }),
359
+ showResponseHeaders && response ? `\t↳ Res. Headers: ${formatHeaders(response.headers)}` : null,
360
+ truncateBody
361
+ ? `\t↳ Error: ${preview(error)}`
362
+ : `\t↳ Error:\n${stringify(error) ?? '[unserializable]'}`,
363
+ ]);
45
364
  };
46
365
 
47
366
  /**
48
- * A logger for the fetch functions that prints detailed information on each request and respective response.
49
- * Each request is given a unique ID which is paired with a response to make finding matching request/response pairs easier.
50
- *
51
- * It currently, prints the method, url, `Accept` and `Content-Type` headers, and the body of the request, and it prints the
52
- * `Content-Type` header, status code, response body and duration of the response, for both successful and failed responses.
367
+ * A logger for fetch functions that prints detailed information for each request and response.
368
+ * Each request is given a unique ID which is paired with its response.
53
369
  */
54
- export const requestLogger = (options?: RequestLoggerOptions): BetterFetchPlugin => {
370
+ export const requestLogger = (
371
+ options?: RequestLoggerOptions,
372
+ ): BetterFetchPlugin<RequestLoggerMessageExtras> => {
55
373
  const enabled =
56
- typeof options?.enabled !== 'function' ? () => options?.enabled ?? true : options?.enabled;
57
-
58
- const maxPreview = options?.maxPreviewLength ?? 150;
374
+ typeof options?.enabled !== 'function' ? () => options?.enabled ?? true : options.enabled;
59
375
 
60
376
  const timings = new Map<string, number>();
61
377
 
@@ -64,99 +380,119 @@ export const requestLogger = (options?: RequestLoggerOptions): BetterFetchPlugin
64
380
  return {
65
381
  id: 'logger',
66
382
  name: 'Logger',
67
- version: '0.1.0',
383
+ version: '0.3.0',
68
384
  hooks: {
69
385
  hookOptions: {
70
386
  cloneResponse: true,
71
387
  },
72
388
 
73
389
  onRequest: async (context) => {
74
- if (!(await enabled?.())) {
390
+ if (!(await enabled())) {
75
391
  return;
76
392
  }
77
393
 
78
- const metaContext = context as MetaContext;
394
+ const request = context as MetaContext;
79
395
 
80
- metaContext.meta = metaContext.meta ?? {};
396
+ request.meta = request.meta ?? ({} as RequestMeta);
397
+ request.loggerOverrides = request.loggerOverrides ?? {};
81
398
 
82
399
  const id = genId();
83
400
 
84
- metaContext.meta.requestId = id;
85
- context.headers.set(Header.XRequestId, id);
401
+ request.meta.requestId = id;
402
+ request.headers.set(Header.XRequestId, id);
86
403
 
87
404
  timings.set(id, performance.now());
88
405
 
89
- const { url, method, body, headers } = context;
90
-
91
- const bodyPreview = body ? preview(body, maxPreview) : '';
92
- const contentType = headers.get(Header.ContentType) ?? 'N/A';
93
- const accept = headers.get(Header.Accept) ?? 'N/A';
94
-
95
406
  const message =
96
- `🚀 [${method}] (${id}) ${url}\n` +
97
- `\t↳ Content-Type: ${contentType}\n` +
98
- `\t↳ Accept: ${accept}` +
99
- (bodyPreview ? `\n\t↳ Body: ${bodyPreview}` : '');
407
+ request.loggerOverrides.requestFormat?.({ request }) ??
408
+ defaultRequestFormatter({
409
+ request,
410
+ options,
411
+ });
100
412
 
101
- console.log(message);
413
+ console.log(`${message}\n`);
102
414
  },
103
415
 
104
416
  onSuccess: async (context) => {
105
- if (!(await enabled?.())) {
417
+ if (!(await enabled())) {
106
418
  return;
107
419
  }
108
420
 
109
- const { response, data, request } = context;
421
+ const request = context.request as MetaContext;
422
+ const { response, data } = context;
110
423
 
111
- const id = (request as MetaContext).meta.requestId ?? UNKNOWN_ID;
112
-
113
- const status = response.status;
114
- const bodyPreview = preview(data, maxPreview);
115
- const contentType = response.headers.get(Header.ContentType) ?? 'N/A';
116
- const duration = (performance.now() - (timings.get(id) ?? 0)).toFixed(1);
424
+ const id = request.meta?.requestId ?? UNKNOWN_ID;
425
+ const durationMs = getDurationMs(timings, id);
117
426
 
118
427
  const message =
119
- `✅ [${request.method}] (${id}) ${request.url}\n` +
120
- `\t↳ Status: ${status} (${duration}ms)\n` +
121
- `\t↳ Respone Content-Type: ${contentType}\n` +
122
- `\t↳ Response: ${bodyPreview}`;
123
-
124
- console.log(message);
428
+ request.loggerOverrides?.successFormat?.({
429
+ request,
430
+ response,
431
+ data,
432
+ durationMs,
433
+ }) ??
434
+ defaultSuccessFormatter({
435
+ request,
436
+ response,
437
+ data,
438
+ durationMs,
439
+ options,
440
+ });
441
+
442
+ console.log(`${message}\n`);
443
+ timings.delete(id);
125
444
  },
126
445
 
127
- onRetry: async (ctx) => {
128
- if (!(await enabled?.())) {
446
+ onRetry: async (context) => {
447
+ if (!(await enabled())) {
129
448
  return;
130
449
  }
131
450
 
132
- const id = (ctx.request as MetaContext).meta.requestId ?? UNKNOWN_ID;
451
+ const request = context.request as MetaContext;
452
+ const attempt = (request.retryAttempt ?? 0) + 1;
133
453
 
134
- console.warn(
135
- `🔁 [${ctx.request.method}] (${id}) Retrying ${ctx.request.url}... Attempt: ${
136
- (ctx.request.retryAttempt ?? 0) + 1
137
- }`,
138
- );
454
+ const message =
455
+ request.loggerOverrides?.retryFormat?.({
456
+ request,
457
+ attempt,
458
+ }) ??
459
+ defaultRetryFormatter({
460
+ request,
461
+ attempt,
462
+ options,
463
+ });
464
+
465
+ console.warn(`${message}\n`);
139
466
  },
140
467
 
141
468
  onError: async (context) => {
142
- if (!(await enabled?.())) {
469
+ if (!(await enabled())) {
143
470
  return;
144
471
  }
145
472
 
146
- const { response, error, request } = context;
147
-
148
- const id = (request as MetaContext).meta.requestId ?? UNKNOWN_ID;
473
+ const request = context.request as MetaContext;
474
+ const { response, error } = context;
149
475
 
150
- const status = response?.status ?? 'unknown';
151
- const errorPreview = preview(error, maxPreview);
152
- const duration = (performance.now() - (timings.get(id) ?? 0)).toFixed(1);
476
+ const id = request.meta?.requestId ?? UNKNOWN_ID;
477
+ const durationMs = getDurationMs(timings, id);
153
478
 
154
479
  const message =
155
- `❌ [${request.method}] (${id}) ${request.url}\n` +
156
- `\t↳ Status: ${status} (${duration}ms)\n` +
157
- `\t↳ Error: ${errorPreview}`;
158
-
159
- console.error(message);
480
+ request.loggerOverrides?.errorFormat?.({
481
+ request,
482
+ response,
483
+ error,
484
+ durationMs,
485
+ }) ??
486
+ defaultErrorFormatter({
487
+ request,
488
+ response,
489
+ error,
490
+ durationMs,
491
+ options,
492
+ });
493
+
494
+ console.error(`${message}\n`);
495
+ timings.delete(id);
160
496
  },
161
497
  },
162
498
  };
@@ -70,19 +70,23 @@ export const useKeyboardDrawing = ({
70
70
  }
71
71
 
72
72
  switch (event.key) {
73
- case KeyboardKeys.ArrowUp:
73
+ case KeyboardKeys.W:
74
+ case KeyboardKeys.I:
74
75
  moveCursor(0, -1);
75
76
  event.preventDefault();
76
77
  break;
77
- case KeyboardKeys.ArrowDown:
78
+ case KeyboardKeys.S:
79
+ case KeyboardKeys.K:
78
80
  moveCursor(0, 1);
79
81
  event.preventDefault();
80
82
  break;
81
- case KeyboardKeys.ArrowLeft:
83
+ case KeyboardKeys.A:
84
+ case KeyboardKeys.J:
82
85
  moveCursor(-1, 0);
83
86
  event.preventDefault();
84
87
  break;
85
- case KeyboardKeys.ArrowRight:
88
+ case KeyboardKeys.D:
89
+ case KeyboardKeys.L:
86
90
  moveCursor(1, 0);
87
91
  event.preventDefault();
88
92
  break;
@@ -11,5 +11,13 @@ export const KeyboardKeys = {
11
11
  Space: ' ',
12
12
  Escape: 'Escape',
13
13
  F: 'f',
14
+ W: 'w',
15
+ A: 'a',
16
+ S: 's',
17
+ D: 'd',
18
+ I: 'i',
19
+ J: 'j',
20
+ K: 'k',
21
+ L: 'l',
14
22
  Tab: 'Tab',
15
23
  } as const;
@@ -3,10 +3,10 @@ import { twMerge } from 'tailwind-merge';
3
3
 
4
4
  import type { Credentials, DecodedJWT } from '../types/auth';
5
5
 
6
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
7
- const isDecodedJWT = (value: object): value is DecodedJWT => {
8
- return 'email' in value && 'name' in value && 'groupInfoIds' in value;
9
- };
6
+ // eslint-disable-next-line sonarjs/no-commented-code
7
+ /* const isDecodedJWT = (value: object): value is DecodedJWT => {
8
+ return 'email' in value && 'name' in value && 'groupInfoIds' in value;
9
+ }; */
10
10
 
11
11
  export const decodeAuthToken = async (token: string): Promise<Credentials | null> => {
12
12
  if (!token) {
@@ -1,30 +0,0 @@
1
- 'use client';
2
-
3
- import type { ReactNode } from 'react';
4
-
5
- import { ThemeContext } from '../../contexts';
6
- import { useLocalStorage } from '../../hooks';
7
-
8
- export type ThemeProviderProps = {
9
- children: ReactNode;
10
- defaultTheme?: 'light' | 'dark';
11
- };
12
-
13
- export const ThemeProvider = ({ children, defaultTheme = 'light' }: ThemeProviderProps) => {
14
- const [theme, setTheme] = useLocalStorage<'light' | 'dark'>('theme', defaultTheme);
15
-
16
- const toggleTheme = () => {
17
- setTheme(theme === 'light' ? 'dark' : 'light');
18
- };
19
-
20
- const value = {
21
- theme,
22
- toggleTheme,
23
- };
24
-
25
- return (
26
- <ThemeContext.Provider value={value}>
27
- <div className={theme === 'dark' ? 'dark' : ''}>{children}</div>
28
- </ThemeContext.Provider>
29
- );
30
- };