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.
- package/package.json +1 -1
- package/template/apps/api/libs/domain/auth/src/auth.service.ts +27 -1
- package/template/apps/api/libs/infra/clients/internal/email/dto/email.dto.ts +3 -3
- package/template/apps/api/libs/infra/clients/internal/volcengine-tts/dto/tts.dto.ts +4 -4
- package/template/apps/api/libs/infra/common/common.module.ts +10 -0
- package/template/apps/api/libs/infra/common/config/validation/env.validation.ts +1 -1
- package/template/apps/api/libs/infra/common/config/validation/keys.validation.ts +2 -2
- package/template/apps/api/libs/infra/common/config/validation/yaml.validation.ts +3 -3
- package/template/apps/api/libs/infra/common/decorators/device-info.decorator.ts +58 -0
- package/template/apps/api/libs/infra/common/decorators/team-info.decorator.ts +122 -0
- package/template/apps/api/libs/infra/common/encryption.service.ts +70 -0
- package/template/apps/api/libs/infra/common/index.ts +9 -0
- package/template/apps/api/libs/infra/shared-services/email/dto/email.dto.ts +3 -3
- package/template/apps/api/libs/infra/shared-services/email/email.service.ts +5 -1
- package/template/apps/api/libs/infra/shared-services/notification/index.ts +13 -0
- package/template/apps/api/libs/infra/shared-services/notification/notification.module.ts +10 -0
- package/template/apps/api/libs/infra/shared-services/notification/notification.service.ts +791 -0
- package/template/apps/web/components/client-only.tsx +28 -0
- package/template/apps/web/components/index.ts +23 -0
- package/template/apps/web/components/layout/app-navbar.tsx +109 -0
- package/template/apps/web/components/layout/app-shell.tsx +30 -0
- package/template/apps/web/components/layout/app-sidebar.tsx +206 -0
- package/template/apps/web/components/layout/index.ts +4 -0
- package/template/apps/web/components/layout/locale-switcher.tsx +57 -0
- package/template/apps/web/components/runtime-i18n-bridge.tsx +32 -0
- package/template/apps/web/components/state-components.tsx +214 -0
- package/template/apps/web/config.ts +22 -2
- package/template/apps/web/lib/api/cache-config.ts +32 -0
- package/template/apps/web/lib/api/contracts/client.ts +43 -1
- package/template/apps/web/lib/api/contracts/hooks/analytics.ts +32 -0
- package/template/apps/web/lib/api/contracts/hooks/index.ts +41 -2
- package/template/apps/web/lib/api/contracts/hooks/message.ts +60 -0
- package/template/apps/web/lib/api/contracts/hooks/system.ts +42 -0
- package/template/apps/web/lib/api/contracts/hooks/task.ts +54 -0
- package/template/apps/web/lib/api/contracts/hooks/user.ts +45 -0
- package/template/apps/web/lib/api/contracts/server-client.ts +1 -1
- package/template/apps/web/lib/api/prefetch.ts +128 -0
- package/template/apps/web/lib/api/query-client.ts +37 -0
- package/template/apps/web/lib/i18n/runtime-translator.ts +48 -0
- package/template/apps/web/lib/requests.ts +1 -1
- package/template/apps/web/providers/app-provider.tsx +1 -1
- package/template/apps/web/providers/auth-provider.tsx +228 -0
- package/template/apps/web/providers/index.tsx +28 -9
- package/template/apps/web/providers/intl-client-provider.tsx +43 -0
- 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
|
|
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 '
|
|
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
|
-
//
|
|
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 '
|
|
19
|
+
import { API_CONFIG } from '@/config';
|
|
20
20
|
import { APP_VERSION } from '../../version';
|
|
21
21
|
|
|
22
22
|
const API_BASE_URL = API_CONFIG.baseUrl;
|