create-pardx-scaffold 0.1.10 → 0.1.11

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 (45) hide show
  1. package/package.json +1 -1
  2. package/template/apps/api/libs/domain/auth/src/auth.service.ts +27 -1
  3. package/template/apps/api/libs/infra/clients/internal/email/dto/email.dto.ts +3 -3
  4. package/template/apps/api/libs/infra/clients/internal/volcengine-tts/dto/tts.dto.ts +4 -4
  5. package/template/apps/api/libs/infra/common/common.module.ts +10 -0
  6. package/template/apps/api/libs/infra/common/config/validation/env.validation.ts +1 -1
  7. package/template/apps/api/libs/infra/common/config/validation/keys.validation.ts +2 -2
  8. package/template/apps/api/libs/infra/common/config/validation/yaml.validation.ts +3 -3
  9. package/template/apps/api/libs/infra/common/decorators/device-info.decorator.ts +58 -0
  10. package/template/apps/api/libs/infra/common/decorators/team-info.decorator.ts +122 -0
  11. package/template/apps/api/libs/infra/common/encryption.service.ts +70 -0
  12. package/template/apps/api/libs/infra/common/index.ts +9 -0
  13. package/template/apps/api/libs/infra/shared-services/email/dto/email.dto.ts +3 -3
  14. package/template/apps/api/libs/infra/shared-services/email/email.service.ts +5 -1
  15. package/template/apps/api/libs/infra/shared-services/notification/index.ts +13 -0
  16. package/template/apps/api/libs/infra/shared-services/notification/notification.module.ts +10 -0
  17. package/template/apps/api/libs/infra/shared-services/notification/notification.service.ts +791 -0
  18. package/template/apps/web/components/client-only.tsx +28 -0
  19. package/template/apps/web/components/index.ts +23 -0
  20. package/template/apps/web/components/layout/app-navbar.tsx +109 -0
  21. package/template/apps/web/components/layout/app-shell.tsx +30 -0
  22. package/template/apps/web/components/layout/app-sidebar.tsx +206 -0
  23. package/template/apps/web/components/layout/index.ts +4 -0
  24. package/template/apps/web/components/layout/locale-switcher.tsx +57 -0
  25. package/template/apps/web/components/runtime-i18n-bridge.tsx +32 -0
  26. package/template/apps/web/components/state-components.tsx +214 -0
  27. package/template/apps/web/config.ts +22 -2
  28. package/template/apps/web/lib/api/cache-config.ts +32 -0
  29. package/template/apps/web/lib/api/contracts/client.ts +43 -1
  30. package/template/apps/web/lib/api/contracts/hooks/analytics.ts +32 -0
  31. package/template/apps/web/lib/api/contracts/hooks/index.ts +41 -2
  32. package/template/apps/web/lib/api/contracts/hooks/message.ts +60 -0
  33. package/template/apps/web/lib/api/contracts/hooks/system.ts +42 -0
  34. package/template/apps/web/lib/api/contracts/hooks/task.ts +54 -0
  35. package/template/apps/web/lib/api/contracts/hooks/user.ts +45 -0
  36. package/template/apps/web/lib/api/contracts/server-client.ts +1 -1
  37. package/template/apps/web/lib/api/prefetch.ts +128 -0
  38. package/template/apps/web/lib/api/query-client.ts +37 -0
  39. package/template/apps/web/lib/i18n/runtime-translator.ts +48 -0
  40. package/template/apps/web/lib/requests.ts +1 -1
  41. package/template/apps/web/providers/app-provider.tsx +1 -1
  42. package/template/apps/web/providers/auth-provider.tsx +228 -0
  43. package/template/apps/web/providers/index.tsx +28 -9
  44. package/template/apps/web/providers/intl-client-provider.tsx +43 -0
  45. package/template/apps/web/vitest.config.ts +4 -0
@@ -0,0 +1,214 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * 公共状态组件
5
+ * 用于统一页面加载、错误、空状态的展示
6
+ */
7
+
8
+ import { Loader2 } from 'lucide-react';
9
+ import { Button } from '@repo/ui';
10
+ import type { ReactNode } from 'react';
11
+ import { useTranslations } from 'next-intl';
12
+
13
+ // ============================================================================
14
+ // Loading Spinner
15
+ // ============================================================================
16
+
17
+ interface LoadingSpinnerProps {
18
+ /** 自定义类名 */
19
+ className?: string;
20
+ /** 图标大小 */
21
+ size?: 'sm' | 'md' | 'lg';
22
+ /** 最小高度 */
23
+ minHeight?: string | number;
24
+ }
25
+
26
+ const sizeMap = {
27
+ sm: 'size-4',
28
+ md: 'size-8',
29
+ lg: 'size-12',
30
+ };
31
+
32
+ /**
33
+ * 统一的加载状态组件
34
+ */
35
+ export function LoadingSpinner({
36
+ className,
37
+ size = 'md',
38
+ minHeight = 400,
39
+ }: LoadingSpinnerProps) {
40
+ const minHeightStyle =
41
+ typeof minHeight === 'number' ? `${minHeight}px` : minHeight;
42
+
43
+ return (
44
+ <div
45
+ className={`flex items-center justify-center ${className ?? ''}`}
46
+ style={{ minHeight: minHeightStyle }}
47
+ >
48
+ <Loader2
49
+ className={`${sizeMap[size]} animate-spin text-primary`}
50
+ />
51
+ </div>
52
+ );
53
+ }
54
+
55
+ // ============================================================================
56
+ // Error State
57
+ // ============================================================================
58
+
59
+ interface ErrorStateProps {
60
+ /** 错误消息 */
61
+ message?: string;
62
+ /** 重试回调 */
63
+ onRetry?: () => void;
64
+ /** 重试按钮文字 */
65
+ retryText?: string;
66
+ /** 自定义类名 */
67
+ className?: string;
68
+ /** 最小高度 */
69
+ minHeight?: string | number;
70
+ }
71
+
72
+ /**
73
+ * 统一的错误状态组件
74
+ */
75
+ export function ErrorState({
76
+ message,
77
+ onRetry,
78
+ retryText,
79
+ className,
80
+ minHeight = 400,
81
+ }: ErrorStateProps) {
82
+ const t = useTranslations('common');
83
+ const displayMessage = message ?? t('messages.loadFailed');
84
+ const displayRetry = retryText ?? t('actions.retry');
85
+ const minHeightStyle =
86
+ typeof minHeight === 'number' ? `${minHeight}px` : minHeight;
87
+
88
+ return (
89
+ <div
90
+ className={`flex flex-col items-center justify-center gap-4 ${className ?? ''}`}
91
+ style={{ minHeight: minHeightStyle }}
92
+ >
93
+ <p className="text-destructive">{displayMessage}</p>
94
+ {onRetry && (
95
+ <Button
96
+ variant="outline"
97
+ onClick={onRetry}
98
+ className="rounded-lg"
99
+ >
100
+ {displayRetry}
101
+ </Button>
102
+ )}
103
+ </div>
104
+ );
105
+ }
106
+
107
+ // ============================================================================
108
+ // Empty State
109
+ // ============================================================================
110
+
111
+ interface EmptyStateProps {
112
+ /** 标题 */
113
+ title?: string;
114
+ /** 描述 */
115
+ description?: string;
116
+ /** 图标 */
117
+ icon?: ReactNode;
118
+ /** 操作按钮 */
119
+ action?: ReactNode;
120
+ /** 自定义类名 */
121
+ className?: string;
122
+ /** 最小高度 */
123
+ minHeight?: string | number;
124
+ }
125
+
126
+ /**
127
+ * 统一的空状态组件
128
+ */
129
+ export function EmptyState({
130
+ title,
131
+ description,
132
+ icon,
133
+ action,
134
+ className,
135
+ minHeight = 200,
136
+ }: EmptyStateProps) {
137
+ const t = useTranslations('common');
138
+ const displayTitle = title ?? t('messages.noData');
139
+ const minHeightStyle =
140
+ typeof minHeight === 'number' ? `${minHeight}px` : minHeight;
141
+
142
+ return (
143
+ <div
144
+ className={`flex flex-col items-center justify-center gap-4 ${className ?? ''}`}
145
+ style={{ minHeight: minHeightStyle }}
146
+ >
147
+ {icon}
148
+ <div className="text-center">
149
+ <p className="text-muted-foreground">{displayTitle}</p>
150
+ {description && (
151
+ <p className="text-sm text-muted-foreground/60 mt-1">{description}</p>
152
+ )}
153
+ </div>
154
+ {action}
155
+ </div>
156
+ );
157
+ }
158
+
159
+ // ============================================================================
160
+ // Page Loading (Full Page)
161
+ // ============================================================================
162
+
163
+ interface PageLoadingProps {
164
+ /** 自定义类名 */
165
+ className?: string;
166
+ }
167
+
168
+ /**
169
+ * 全页面加载状态
170
+ */
171
+ export function PageLoading({ className }: PageLoadingProps) {
172
+ return (
173
+ <div
174
+ className={`p-6 flex items-center justify-center min-h-[400px] ${className ?? ''}`}
175
+ >
176
+ <Loader2 className="size-8 animate-spin text-primary" />
177
+ </div>
178
+ );
179
+ }
180
+
181
+ // ============================================================================
182
+ // Page Error (Full Page with Shell)
183
+ // ============================================================================
184
+
185
+ interface PageErrorProps {
186
+ /** 错误消息 */
187
+ message?: string;
188
+ /** 重试回调 */
189
+ onRetry?: () => void;
190
+ }
191
+
192
+ /**
193
+ * 全页面错误状态
194
+ */
195
+ export function PageError({ message, onRetry }: PageErrorProps) {
196
+ const t = useTranslations('common');
197
+ const displayMessage = message ?? t('messages.loadFailed');
198
+ const retryLabel = t('actions.retry');
199
+
200
+ return (
201
+ <div className="p-6 flex flex-col items-center justify-center min-h-[400px] gap-4">
202
+ <p className="text-destructive">{displayMessage}</p>
203
+ {onRetry && (
204
+ <Button
205
+ variant="outline"
206
+ onClick={onRetry}
207
+ className="rounded-lg"
208
+ >
209
+ {retryLabel}
210
+ </Button>
211
+ )}
212
+ </div>
213
+ );
214
+ }
@@ -3,6 +3,24 @@
3
3
  // 在 .env.local 文件中设置以下环境变量:
4
4
  // - NEXT_PUBLIC_SERVER_BASE_URL: 登录、上传文件、测评等接口的基础地址
5
5
 
6
+ /**
7
+ * 获取 Agno API 基础地址(用于智能体相关接口)
8
+ */
9
+ const getAgnoApiBaseUrl = (): string => {
10
+ const baseUrl = process.env.NEXT_AGNO_API_BASE_URL;
11
+ if (!baseUrl) {
12
+ const defaultUrl = 'http://127.0.0.1:8000/api';
13
+ if (
14
+ process.env.NODE_ENV === 'development' ||
15
+ process.env.NEXT_PHASE === 'phase-production-build'
16
+ ) {
17
+ return defaultUrl;
18
+ }
19
+ return defaultUrl;
20
+ }
21
+ return baseUrl;
22
+ };
23
+
6
24
  /**
7
25
  * 获取 Server 基础地址(用于登录、上传文件、测评等)
8
26
  */
@@ -80,8 +98,10 @@ export const API_CONFIG = {
80
98
  // API 基础地址(用于登录、上传文件、测评等)
81
99
  baseUrl: getServerBaseUrl() + '/api',
82
100
  apiHealthUrl: getServerBaseUrl() + '/health',
101
+ // Agno API 基础地址(用于智能体相关接口)
102
+ agnoApiBaseUrl: getAgnoApiBaseUrl(),
83
103
 
84
- // API 端点路径 这些断点是不适用ts-rest-api的,用于登录和校验权限的断点,如果未来需要使用ts-rest-api,则将这些断点迁移到ts-rest-api中
104
+ // API 端点路径 这些断点是不适用ts-rest-api的,用于登录和校验权限的断点,如果未来需要使用ts-rest-api,则将这些端点迁移到ts-rest-api中
85
105
  endpoints: {
86
106
  // 登录端点
87
107
  login: '/sign/in/mobile/password',
@@ -113,4 +133,4 @@ export const BRAND_CONFIG = {
113
133
  logo: getBrandLogo(),
114
134
  title: getBrandTitle(),
115
135
  description: getBrandDescription(),
116
- };
136
+ };
@@ -234,3 +234,35 @@ export function getSearchQueryOptions() {
234
234
  refetchOnWindowFocus: false,
235
235
  };
236
236
  }
237
+
238
+ // ============================================================================
239
+ // 安全查询辅助函数
240
+ // ============================================================================
241
+
242
+ /**
243
+ * 获取详情查询的安全配置
244
+ * 防止在 ID 为空时发起请求
245
+ */
246
+ export function getSafeDetailQueryOptions(id: string | undefined) {
247
+ return {
248
+ staleTime: cacheTime.medium,
249
+ gcTime: gcTime.medium,
250
+ refetchOnWindowFocus: false,
251
+ refetchOnMount: false,
252
+ enabled: Boolean(id), // 关键:ID 不存在时不发起请求
253
+ };
254
+ }
255
+
256
+ /**
257
+ * 获取条件查询的安全配置
258
+ * 当条件不满足时不发起请求
259
+ */
260
+ export function getConditionalQueryOptions(condition: boolean) {
261
+ return {
262
+ staleTime: cacheTime.medium,
263
+ gcTime: gcTime.medium,
264
+ refetchOnWindowFocus: false,
265
+ refetchOnMount: false,
266
+ enabled: condition, // 条件不满足时不发起请求
267
+ };
268
+ }
@@ -10,12 +10,14 @@ import {
10
10
  settingContract,
11
11
  signContract,
12
12
  smsContract,
13
+ systemContract,
14
+ taskContract,
13
15
  uploaderContract,
14
16
  userContract,
15
17
  } from '@repo/contracts';
16
18
  import { getHeaders } from '@repo/utils/headers';
17
19
  import { API_VERSION_HEADER, APP_BUILD_HEADER } from '@repo/constants';
18
- import { API_CONFIG } from '../../config';
20
+ import { API_CONFIG } from '@/config';
19
21
  import { getToken, ensureValidToken, clearToken } from '../../api';
20
22
  import { APP_VERSION } from '@/lib/version';
21
23
  import {
@@ -311,6 +313,21 @@ export const uploaderClient = initClient(uploaderContract, clientOptions);
311
313
  */
312
314
  export const analyticsClient = initClient(analyticsContract, clientOptions);
313
315
 
316
+ /**
317
+ * Message API - Direct client
318
+ */
319
+ export const messageClient = initClient(messageContract, clientOptions);
320
+
321
+ /**
322
+ * Task API - Direct client
323
+ */
324
+ export const taskClient = initClient(taskContract, clientOptions);
325
+
326
+ /**
327
+ * System API - Direct client
328
+ */
329
+ export const systemClient = initClient(systemContract, clientOptions);
330
+
314
331
  // ============================================================================
315
332
  // React Query Clients (for hooks)
316
333
  // ============================================================================
@@ -335,6 +352,21 @@ export const settingApi = initQueryClient(settingContract, clientOptions);
335
352
  */
336
353
  export const analyticsApi = initQueryClient(analyticsContract, clientOptions);
337
354
 
355
+ /**
356
+ * User API - React Query hooks
357
+ */
358
+ export const userApi = initQueryClient(userContract, clientOptions);
359
+
360
+ /**
361
+ * Task API - React Query hooks
362
+ */
363
+ export const taskApi = initQueryClient(taskContract, clientOptions);
364
+
365
+ /**
366
+ * System API - React Query hooks
367
+ */
368
+ export const systemApi = initQueryClient(systemContract, clientOptions);
369
+
338
370
  // ============================================================================
339
371
  // Generic ts-rest Client (for custom contracts)
340
372
  // ============================================================================
@@ -347,6 +379,9 @@ export const analyticsApi = initQueryClient(analyticsContract, clientOptions);
347
379
  * Note: For imperative calls (non-hook usage), use the direct clients:
348
380
  * - analyticsClient (for Analytics)
349
381
  * - messageClient (for Message)
382
+ * - userClient (for User)
383
+ * - taskClient (for Task)
384
+ * - systemClient (for System)
350
385
  * - etc.
351
386
  */
352
387
  export const tsRestClient = {
@@ -356,6 +391,13 @@ export const tsRestClient = {
356
391
  message: messageApi,
357
392
  setting: settingApi,
358
393
  download: downloadApi,
394
+ user: userApi,
395
+ task: taskApi,
396
+ system: systemApi,
359
397
  // Direct clients (for imperative calls)
360
398
  analyticsClient,
399
+ messageClient,
400
+ userClient,
401
+ taskClient,
402
+ systemClient,
361
403
  };
@@ -0,0 +1,32 @@
1
+ 'use client';
2
+
3
+ import { tsRestClient, analyticsClient } from '../client';
4
+
5
+ /**
6
+ * Analytics Query Keys
7
+ * Used for React Query cache management
8
+ */
9
+ export const analyticsKeys = {
10
+ all: ['analytics'] as const,
11
+ track: () => [...analyticsKeys.all, 'track'] as const,
12
+ trackBatch: () => [...analyticsKeys.all, 'trackBatch'] as const,
13
+ };
14
+
15
+ // ============================================================================
16
+ // Analytics Mutation Hooks
17
+ // Analytics events are typically sent via mutations, not queries
18
+ // ============================================================================
19
+
20
+ /**
21
+ * Track a single analytics event
22
+ */
23
+ export function useTrackEvent() {
24
+ return tsRestClient.analytics.track.useMutation();
25
+ }
26
+
27
+ /**
28
+ * Track multiple analytics events in batch
29
+ */
30
+ export function useTrackEventBatch() {
31
+ return tsRestClient.analytics.trackBatch.useMutation();
32
+ }
@@ -1,6 +1,45 @@
1
1
  'use client';
2
2
 
3
- // Notification hooks
3
+ // User hooks
4
+ export {
5
+ userKeys,
6
+ useUserInfo,
7
+ useUserCheck,
8
+ useUserContact,
9
+ } from './user';
10
+
11
+ // Message hooks
12
+ export {
13
+ messageKeys,
14
+ useMessages,
15
+ useUnreadMessageCount,
16
+ useSetMessagesRead,
17
+ } from './message';
18
+
19
+ // Analytics hooks
20
+ export {
21
+ analyticsKeys,
22
+ useTrackEvent,
23
+ useTrackEventBatch,
24
+ } from './analytics';
25
+
26
+ // Task hooks
27
+ export {
28
+ taskKeys,
29
+ useCheckTask,
30
+ useTaskList,
31
+ useCheckTasks,
32
+ } from './task';
33
+
34
+ // System hooks
35
+ export {
36
+ systemKeys,
37
+ usePermissionConfig,
38
+ useServiceReady,
39
+ useServiceHealth,
40
+ } from './system';
41
+
42
+ // Notification hooks (placeholder)
4
43
  export {
5
44
  notificationKeys,
6
45
  useNotifications,
@@ -19,4 +58,4 @@ export {
19
58
  useSetPassword,
20
59
  useBindEmail,
21
60
  useBindPhone,
22
- } from './setting';
61
+ } from './setting';
@@ -0,0 +1,60 @@
1
+ 'use client';
2
+
3
+ import { useQueryClient } from '@tanstack/react-query';
4
+ import { tsRestClient, messageClient } from '../client';
5
+ import type { z } from 'zod';
6
+ import type { MessageListQuerySchema } from '@repo/contracts';
7
+
8
+ type MessageListQuery = z.input<typeof MessageListQuerySchema>;
9
+
10
+ /**
11
+ * Message Query Keys
12
+ * Used for React Query cache management
13
+ */
14
+ export const messageKeys = {
15
+ all: ['messages'] as const,
16
+ list: (query?: MessageListQuery) => [...messageKeys.all, 'list', query] as const,
17
+ unreadCount: () => [...messageKeys.all, 'unreadCount'] as const,
18
+ };
19
+
20
+ // ============================================================================
21
+ // Message Query Hooks
22
+ // ============================================================================
23
+
24
+ /**
25
+ * Get list of messages
26
+ * @param query - Query parameters (page, limit, type, isRead)
27
+ */
28
+ export function useMessages(query?: MessageListQuery) {
29
+ const queryKey = messageKeys.list(query);
30
+ const { page = 1, limit = 20, ...rest } = query ?? {};
31
+ return tsRestClient.message.list.useQuery(
32
+ queryKey,
33
+ { query: { page, limit, ...rest } },
34
+ { queryKey },
35
+ );
36
+ }
37
+
38
+ /**
39
+ * Get unread message count
40
+ */
41
+ export function useUnreadMessageCount() {
42
+ const queryKey = messageKeys.unreadCount();
43
+ return tsRestClient.message.getUnreadCount.useQuery(queryKey, {});
44
+ }
45
+
46
+ // ============================================================================
47
+ // Message Mutation Hooks
48
+ // ============================================================================
49
+
50
+ /**
51
+ * Set messages as read
52
+ */
53
+ export function useSetMessagesRead() {
54
+ const queryClient = useQueryClient();
55
+ return tsRestClient.message.setRead.useMutation({
56
+ onSuccess: () => {
57
+ queryClient.invalidateQueries({ queryKey: messageKeys.all });
58
+ },
59
+ });
60
+ }
@@ -0,0 +1,42 @@
1
+ 'use client';
2
+
3
+ import { tsRestClient, systemClient } from '../client';
4
+
5
+ /**
6
+ * System Query Keys
7
+ * Used for React Query cache management
8
+ */
9
+ export const systemKeys = {
10
+ all: ['system'] as const,
11
+ permissionConfig: () => [...systemKeys.all, 'permissionConfig'] as const,
12
+ ready: () => [...systemKeys.all, 'ready'] as const,
13
+ health: () => [...systemKeys.all, 'health'] as const,
14
+ };
15
+
16
+ // ============================================================================
17
+ // System Query Hooks
18
+ // ============================================================================
19
+
20
+ /**
21
+ * Get permission configuration
22
+ */
23
+ export function usePermissionConfig() {
24
+ return tsRestClient.system.getPermissionConfig.useQuery(
25
+ systemKeys.permissionConfig(),
26
+ {},
27
+ );
28
+ }
29
+
30
+ /**
31
+ * Check if service is ready
32
+ */
33
+ export function useServiceReady() {
34
+ return tsRestClient.system.checkServiceReady.useQuery(systemKeys.ready(), {});
35
+ }
36
+
37
+ /**
38
+ * Check service health status
39
+ */
40
+ export function useServiceHealth() {
41
+ return tsRestClient.system.checkHealth.useQuery(systemKeys.health(), {});
42
+ }
@@ -0,0 +1,54 @@
1
+ 'use client';
2
+
3
+ import { useQueryClient } from '@tanstack/react-query';
4
+ import { tsRestClient, taskClient } from '../client';
5
+
6
+ /**
7
+ * Task Query Keys
8
+ * Used for React Query cache management
9
+ */
10
+ export const taskKeys = {
11
+ all: ['tasks'] as const,
12
+ check: (taskId: string) => [...taskKeys.all, 'check', taskId] as const,
13
+ list: () => [...taskKeys.all, 'list'] as const,
14
+ };
15
+
16
+ // ============================================================================
17
+ // Task Query Hooks
18
+ // ============================================================================
19
+
20
+ /**
21
+ * Check if a task is completed
22
+ * @param taskId - Task UUID
23
+ */
24
+ export function useCheckTask(taskId: string) {
25
+ const queryKey = taskKeys.check(taskId);
26
+ return tsRestClient.task.checkTask.useQuery(
27
+ queryKey,
28
+ { params: { taskId } },
29
+ { queryKey, enabled: Boolean(taskId) },
30
+ );
31
+ }
32
+
33
+ /**
34
+ * Get user's task list
35
+ */
36
+ export function useTaskList() {
37
+ return tsRestClient.task.getTaskList.useQuery(taskKeys.list(), {});
38
+ }
39
+
40
+ // ============================================================================
41
+ // Task Mutation Hooks
42
+ // ============================================================================
43
+
44
+ /**
45
+ * Check multiple tasks completion status
46
+ */
47
+ export function useCheckTasks() {
48
+ const queryClient = useQueryClient();
49
+ return tsRestClient.task.checkTasks.useMutation({
50
+ onSuccess: () => {
51
+ queryClient.invalidateQueries({ queryKey: taskKeys.all });
52
+ },
53
+ });
54
+ }
@@ -0,0 +1,45 @@
1
+ 'use client';
2
+
3
+ import { tsRestClient, userClient } from '../client';
4
+
5
+ /**
6
+ * User Query Keys
7
+ * Used for React Query cache management
8
+ */
9
+ export const userKeys = {
10
+ all: ['user'] as const,
11
+ info: () => [...userKeys.all, 'info'] as const,
12
+ check: () => [...userKeys.all, 'check'] as const,
13
+ contact: (userId: string) => [...userKeys.all, 'contact', userId] as const,
14
+ };
15
+
16
+ // ============================================================================
17
+ // User Info Hooks
18
+ // ============================================================================
19
+
20
+ /**
21
+ * Get current user's account info
22
+ */
23
+ export function useUserInfo() {
24
+ return tsRestClient.user.getInfo.useQuery(userKeys.info(), {});
25
+ }
26
+
27
+ /**
28
+ * Check user info (returns userId)
29
+ */
30
+ export function useUserCheck() {
31
+ return tsRestClient.user.check.useQuery(userKeys.check(), {});
32
+ }
33
+
34
+ /**
35
+ * Get user contact info by userId
36
+ * @param userId - User UUID
37
+ */
38
+ export function useUserContact(userId: string) {
39
+ const queryKey = userKeys.contact(userId);
40
+ return tsRestClient.user.getContact.useQuery(
41
+ queryKey,
42
+ { params: { userId } },
43
+ { queryKey, enabled: Boolean(userId) },
44
+ );
45
+ }
@@ -16,7 +16,7 @@ import {
16
16
  DEVICE_ID_HEADER,
17
17
  MPTRAIL_HEADER,
18
18
  } from '@repo/constants';
19
- import { API_CONFIG } from '../../config';
19
+ import { API_CONFIG } from '@/config';
20
20
  import { APP_VERSION } from '../../version';
21
21
 
22
22
  const API_BASE_URL = API_CONFIG.baseUrl;