@choiceform/shared-auth 0.1.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 (44) hide show
  1. package/README.md +452 -0
  2. package/dist/components/auth-sync.d.ts +9 -0
  3. package/dist/components/auth-sync.d.ts.map +1 -0
  4. package/dist/components/auth-sync.js +60 -0
  5. package/dist/components/protected-route.d.ts +18 -0
  6. package/dist/components/protected-route.d.ts.map +1 -0
  7. package/dist/components/protected-route.js +28 -0
  8. package/dist/components/sign-in-page.d.ts +49 -0
  9. package/dist/components/sign-in-page.d.ts.map +1 -0
  10. package/dist/components/sign-in-page.js +33 -0
  11. package/dist/config.d.ts +50 -0
  12. package/dist/config.d.ts.map +1 -0
  13. package/dist/config.js +14 -0
  14. package/dist/core.d.ts +2162 -0
  15. package/dist/core.d.ts.map +1 -0
  16. package/dist/core.js +37 -0
  17. package/dist/hooks/use-auth-init.d.ts +7 -0
  18. package/dist/hooks/use-auth-init.d.ts.map +1 -0
  19. package/dist/hooks/use-auth-init.js +41 -0
  20. package/dist/index.d.ts +22 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +24 -0
  23. package/dist/init.d.ts +2167 -0
  24. package/dist/init.d.ts.map +1 -0
  25. package/dist/init.js +17 -0
  26. package/dist/lib/auth-client.d.ts +2120 -0
  27. package/dist/lib/auth-client.d.ts.map +1 -0
  28. package/dist/lib/auth-client.js +11 -0
  29. package/dist/store/actions.d.ts +60 -0
  30. package/dist/store/actions.d.ts.map +1 -0
  31. package/dist/store/actions.js +234 -0
  32. package/dist/store/computed.d.ts +12 -0
  33. package/dist/store/computed.d.ts.map +1 -0
  34. package/dist/store/computed.js +14 -0
  35. package/dist/store/state.d.ts +16 -0
  36. package/dist/store/state.d.ts.map +1 -0
  37. package/dist/store/state.js +52 -0
  38. package/dist/store/utils.d.ts +103 -0
  39. package/dist/store/utils.d.ts.map +1 -0
  40. package/dist/store/utils.js +198 -0
  41. package/dist/types.d.ts +37 -0
  42. package/dist/types.d.ts.map +1 -0
  43. package/dist/types.js +4 -0
  44. package/package.json +65 -0
package/README.md ADDED
@@ -0,0 +1,452 @@
1
+ # @choiceform/shared-auth
2
+
3
+ 共享认证包 - 用于 Choiceform 项目的统一认证解决方案
4
+
5
+ ## 功能特性
6
+
7
+ - ✅ Better Auth 集成
8
+ - ✅ Legend State 状态管理
9
+ - ✅ GitHub OAuth 支持
10
+ - ✅ Token 自动管理和持久化
11
+ - ✅ 路由保护组件
12
+ - ✅ 登录页面组件(可定制)
13
+ - ✅ 完整的 TypeScript 类型支持
14
+ - ✅ 预配置的 API 客户端(自动添加认证头)
15
+ - ✅ 用户管理器工具
16
+ - ✅ 响应式状态管理(基于 Legend State)
17
+
18
+ ## 安装
19
+
20
+ ```bash
21
+ pnpm add @choiceform/shared-auth
22
+ ```
23
+
24
+ ## 环境变量配置
25
+
26
+ 在使用认证功能前,需要在项目根目录配置环境变量:
27
+
28
+ ```bash
29
+ # .env
30
+ VITE_AUTH_API_URL=http://localhost:4320
31
+ ```
32
+
33
+ **注意:** 如果你的项目仍在使用 `VITE_CORE_AI_API_URL`,代码会自动向后兼容,但建议迁移到新的 `VITE_AUTH_API_URL`。
34
+
35
+ ## 快速开始
36
+
37
+ ### 1. 初始化认证系统
38
+
39
+ ```typescript
40
+ import { initAuth } from "@choiceform/shared-auth"
41
+ import { adminClient, organizationClient } from "better-auth/client/plugins"
42
+
43
+ export const auth = initAuth({
44
+ baseURL: import.meta.env.VITE_AUTH_API_URL || "http://localhost:4320",
45
+ tokenStorageKey: "auth-token",
46
+ defaultRedirectAfterLogin: "/explore",
47
+ signInPath: "/sign-in",
48
+ plugins: [adminClient(), organizationClient({ teams: { enabled: true } })],
49
+ })
50
+ ```
51
+
52
+ ### 2. 在应用中使用
53
+
54
+ ```typescript
55
+ import { AuthSync, ProtectedRoute, useAuthInit } from "@choiceform/shared-auth"
56
+ import { auth } from "./lib/auth"
57
+
58
+ function App() {
59
+ useAuthInit(auth)
60
+
61
+ return (
62
+ <>
63
+ <AuthSync auth={auth} />
64
+ <Router>
65
+ <Route path="/sign-in" element={<SignInPage />} />
66
+ <Route
67
+ path="/*"
68
+ element={
69
+ <ProtectedRoute auth={auth} signInPath="/sign-in">
70
+ <YourApp />
71
+ </ProtectedRoute>
72
+ }
73
+ />
74
+ </Router>
75
+ </>
76
+ )
77
+ }
78
+ ```
79
+
80
+ ### 3. 使用登录页面组件
81
+
82
+ ```typescript
83
+ import { SignInPage } from "@choiceform/shared-auth"
84
+ import { auth } from "./lib/auth"
85
+
86
+ function LoginPage() {
87
+ return (
88
+ <SignInPage
89
+ auth={auth}
90
+ logo={<YourLogo />}
91
+ title="Your App Title"
92
+ description="Your app description"
93
+ provider="github"
94
+ redirectAfterLogin="/dashboard"
95
+ />
96
+ )
97
+ }
98
+ ```
99
+
100
+ ## API 文档
101
+
102
+ ### `initAuth(config)`
103
+
104
+ 快速初始化认证系统(使用默认配置)。
105
+
106
+ **参数:**
107
+
108
+ - `baseURL`: Better Auth API 基础 URL(必需)
109
+ - `tokenStorageKey?`: Token 存储的 localStorage key(默认: `'auth-token'`)
110
+ - `defaultRedirectAfterLogin?`: 登录后默认跳转地址(默认: `'/explore'`)
111
+ - `signInPath?`: 登录页面路径(默认: `'/sign-in'`)
112
+ - `plugins?`: Better Auth 插件列表(默认包含 `adminClient()` 和 `organizationClient()`)
113
+
114
+ **返回:** `AuthInstance`
115
+
116
+ ### `createAuth(config)`
117
+
118
+ 创建认证系统实例(更灵活的配置选项)。
119
+
120
+ **参数:**
121
+
122
+ - `baseURL`: Better Auth API 基础 URL(必需)
123
+ - `tokenStorageKey?`: Token 存储的 localStorage key(默认: `'auth-token'`)
124
+ - `defaultRedirectAfterLogin?`: 登录后默认跳转地址(默认: `'/explore'`)
125
+ - `signInPath?`: 登录页面路径(默认: `'/sign-in'`)
126
+ - `plugins?`: Better Auth 插件列表
127
+ - `callbackURLBuilder?`: 自定义回调 URL 构建函数
128
+ - `getSessionEndpoint?`: 获取 session 的端点路径(默认: `'/v1/auth/get-session'`)
129
+
130
+ **返回:** `AuthInstance`
131
+
132
+ ### `AuthInstance`
133
+
134
+ 认证实例包含以下属性和方法:
135
+
136
+ **核心属性:**
137
+
138
+ - `authStore`: Legend State store(响应式状态)
139
+ - `authActions`: 认证操作(signIn, signOut, initialize 等)
140
+ - `authComputed`: 计算属性(isInitializing 等)
141
+ - `authClient`: Better Auth 客户端
142
+ - `tokenStorage`: Token 存储工具
143
+
144
+ **工具方法(已绑定到实例):**
145
+
146
+ - `getCurrentUser()`: 获取当前用户
147
+ - `getCurrentUserId()`: 获取当前用户ID
148
+ - `getCurrentUserIdSafe()`: 安全获取当前用户ID
149
+ - `isAuthenticated()`: 检查是否已认证
150
+ - `isLoading()`: 检查是否正在加载
151
+ - `isLoaded()`: 检查是否已加载
152
+ - `waitForAuth()`: 等待认证完成
153
+ - `getAuthToken()`: 获取认证 Token
154
+ - `getAuthHeaders()`: 获取认证 Headers
155
+ - `apiClient`: 预配置的 API 客户端(包含 get/post/put/delete 方法)
156
+ - `userManager`: 用户管理器(包含 getUser/getUserId 方法)
157
+
158
+ ### `ProtectedRoute`
159
+
160
+ 路由保护组件。
161
+
162
+ **Props:**
163
+
164
+ - `auth`: AuthInstance
165
+ - `children`: React.ReactNode
166
+ - `signInPath?`: 登录页面路径(默认: `'/sign-in'`)
167
+ - `loadingComponent?`: 加载中组件
168
+ - `loadingMessage?`: 加载中消息(默认: `'Checking authentication...'`)
169
+
170
+ ### `SignInPage`
171
+
172
+ 登录页面组件。
173
+
174
+ **Props:**
175
+
176
+ - `auth`: AuthInstance
177
+ - `className?`: 自定义样式类名
178
+ - `logo?`: Logo 组件
179
+ - `title?`: 标题
180
+ - `description?`: 描述
181
+ - `provider?`: OAuth 提供商(默认: `'github'`)
182
+ - `redirectAfterLogin?`: 登录后跳转地址(默认: `'/explore'`)
183
+ - `signInButtonText?`: 登录按钮文本(默认: `'Sign in with GitHub'`)
184
+ - `signingInButtonText?`: 登录中按钮文本(默认: `'Redirecting to GitHub...'`)
185
+ - `signInButtonIcon?`: 登录按钮图标
186
+
187
+ ## 工具函数
188
+
189
+ ### 使用已绑定的工具方法(推荐)
190
+
191
+ 认证实例已包含所有工具方法,可以直接使用:
192
+
193
+ ```typescript
194
+ import { auth } from "./lib/auth"
195
+
196
+ // 获取当前用户
197
+ const user = auth.getCurrentUser()
198
+
199
+ // 获取当前用户ID
200
+ const userId = auth.getCurrentUserId()
201
+ const userIdSafe = auth.getCurrentUserIdSafe()
202
+
203
+ // 检查认证状态
204
+ const authenticated = auth.isAuthenticated()
205
+ const loading = auth.isLoading()
206
+ const loaded = auth.isLoaded()
207
+
208
+ // 等待认证完成
209
+ await auth.waitForAuth()
210
+
211
+ // 获取认证 Token
212
+ const token = await auth.getAuthToken()
213
+
214
+ // 获取认证 Headers
215
+ const headers = await auth.getAuthHeaders()
216
+
217
+ // 使用预配置的 API 客户端
218
+ const response = await auth.apiClient.get("/api/users")
219
+ const data = await auth.apiClient.post("/api/data", { name: "test" })
220
+
221
+ // 使用用户管理器
222
+ const user = auth.userManager.getUser()
223
+ const userId = auth.userManager.getUserId()
224
+ ```
225
+
226
+ ### 使用独立的工具函数
227
+
228
+ 如果需要独立的工具函数(需要手动传入参数),可以从包中导入:
229
+
230
+ ```typescript
231
+ import {
232
+ getCurrentUser,
233
+ getCurrentUserId,
234
+ isAuthenticated,
235
+ getAuthToken,
236
+ getAuthHeaders,
237
+ createApiClient,
238
+ createUserManager,
239
+ } from "@choiceform/shared-auth"
240
+
241
+ // 使用独立的工具函数(需要传入 authStore 等参数)
242
+ const user = getCurrentUser(auth.authStore)
243
+ const userId = getCurrentUserId(auth.authStore)
244
+ const authenticated = isAuthenticated(auth.authStore)
245
+
246
+ // 创建 API 客户端
247
+ const apiClient = createApiClient(
248
+ auth.authStore,
249
+ auth.tokenStorage,
250
+ auth.authActions,
251
+ auth.authClient
252
+ )
253
+
254
+ // 创建用户管理器
255
+ const userManager = createUserManager(auth.authStore)
256
+ ```
257
+
258
+ ## 类型定义
259
+
260
+ ```typescript
261
+ import type {
262
+ SessionUser,
263
+ Session,
264
+ AuthState,
265
+ AuthConfig,
266
+ AuthInstance,
267
+ AuthActions,
268
+ TokenStorage,
269
+ } from "@choiceform/shared-auth"
270
+ ```
271
+
272
+ ### 类型说明
273
+
274
+ - `SessionUser`: 用户信息类型
275
+ - `Session`: 会话信息类型
276
+ - `AuthState`: 认证状态类型(包含 user, isAuthenticated, loading 等)
277
+ - `AuthConfig`: 认证配置类型
278
+ - `AuthInstance`: 认证实例类型(`initAuth` 或 `createAuth` 的返回值)
279
+ - `AuthActions`: 认证操作类型
280
+ - `TokenStorage`: Token 存储工具类型
281
+
282
+ ## 环境变量
283
+
284
+ ### 必需的环境变量
285
+
286
+ - `VITE_AUTH_API_URL`: Better Auth API 基础 URL
287
+
288
+ ### 示例配置
289
+
290
+ 参考项目根目录的 `.env.example` 文件:
291
+
292
+ ```bash
293
+ # .env
294
+ VITE_AUTH_API_URL=http://localhost:4320
295
+ ```
296
+
297
+ ### 向后兼容
298
+
299
+ 如果你的项目仍在使用 `VITE_CORE_AI_API_URL`,代码会自动向后兼容:
300
+
301
+ ```typescript
302
+ baseURL: import.meta.env.VITE_AUTH_API_URL || import.meta.env.VITE_CORE_AI_API_URL || "http://localhost:4320"
303
+ ```
304
+
305
+ 但建议迁移到新的 `VITE_AUTH_API_URL`。
306
+
307
+ ## 响应式状态使用
308
+
309
+ 由于认证状态使用 Legend State 管理,你可以在 React 组件中使用 `use$` hook 来响应式地访问状态:
310
+
311
+ ```typescript
312
+ import { use$ } from "@legendapp/state/react"
313
+ import { auth } from "./lib/auth"
314
+
315
+ function UserProfile() {
316
+ // 响应式获取用户信息
317
+ const user = use$(auth.authStore.user)
318
+ const isAuthenticated = use$(auth.authStore.isAuthenticated)
319
+ const loading = use$(auth.authStore.loading)
320
+
321
+ if (loading) {
322
+ return <div>Loading...</div>
323
+ }
324
+
325
+ if (!isAuthenticated) {
326
+ return <div>Please sign in</div>
327
+ }
328
+
329
+ return (
330
+ <div>
331
+ <h1>Welcome, {user?.name}</h1>
332
+ <p>Email: {user?.email}</p>
333
+ </div>
334
+ )
335
+ }
336
+ ```
337
+
338
+ ### 使用计算属性
339
+
340
+ ```typescript
341
+ import { use$ } from "@legendapp/state/react"
342
+ import { auth } from "./lib/auth"
343
+
344
+ function AuthStatus() {
345
+ // 使用计算属性检查初始化状态
346
+ const isInitializing = use$(auth.authComputed.isInitializing)
347
+
348
+ if (isInitializing) {
349
+ return <div>Initializing...</div>
350
+ }
351
+
352
+ return <div>Ready</div>
353
+ }
354
+ ```
355
+
356
+ ## 最佳实践
357
+
358
+ ### 1. 在组件中使用响应式状态
359
+
360
+ 优先使用 `use$` hook 来访问响应式状态,而不是直接调用 `getCurrentUser()` 等方法:
361
+
362
+ ```typescript
363
+ // ✅ 推荐:响应式更新
364
+ const user = use$(auth.authStore.user)
365
+
366
+ // ❌ 不推荐:非响应式
367
+ const user = auth.getCurrentUser()
368
+ ```
369
+
370
+ ### 2. 使用 API 客户端进行 API 调用
371
+
372
+ 使用预配置的 `apiClient` 可以自动处理认证头和处理 401 响应:
373
+
374
+ ```typescript
375
+ // ✅ 推荐:使用 apiClient
376
+ const response = await auth.apiClient.get("/api/users")
377
+ const data = await response.json()
378
+
379
+ // ❌ 不推荐:手动添加认证头
380
+ const headers = await auth.getAuthHeaders()
381
+ const response = await fetch("/api/users", { headers })
382
+ ```
383
+
384
+ ### 3. 等待认证完成
385
+
386
+ 在进行需要认证的操作前,使用 `waitForAuth()` 确保认证已初始化:
387
+
388
+ ```typescript
389
+ // ✅ 推荐:等待认证完成
390
+ await auth.waitForAuth()
391
+ if (auth.isAuthenticated()) {
392
+ // 执行需要认证的操作
393
+ }
394
+
395
+ // ❌ 不推荐:直接检查状态(可能尚未初始化)
396
+ if (auth.isAuthenticated()) {
397
+ // 可能不准确
398
+ }
399
+ ```
400
+
401
+ ### 4. 错误处理
402
+
403
+ 认证操作可能会失败,确保适当处理错误:
404
+
405
+ ```typescript
406
+ try {
407
+ await auth.authActions.signIn("github", redirectUrl)
408
+ } catch (error) {
409
+ console.error("Sign in failed:", error)
410
+ // 显示错误提示给用户
411
+ }
412
+ ```
413
+
414
+ ### 5. 退出登录
415
+
416
+ 退出登录时使用 `signOut` 方法:
417
+
418
+ ```typescript
419
+ async function handleSignOut() {
420
+ try {
421
+ await auth.authActions.signOut()
422
+ // 用户将被重定向到登录页面
423
+ } catch (error) {
424
+ console.error("Sign out failed:", error)
425
+ }
426
+ }
427
+ ```
428
+
429
+ ## 常见问题
430
+
431
+ ### Q: 如何在非 React 环境中使用?
432
+
433
+ A: 可以使用独立的工具函数,它们不依赖 React:
434
+
435
+ ```typescript
436
+ import { getCurrentUser, isAuthenticated } from "@choiceform/shared-auth"
437
+
438
+ const user = getCurrentUser(auth.authStore)
439
+ const authenticated = isAuthenticated(auth.authStore)
440
+ ```
441
+
442
+ ### Q: 如何处理 Token 过期?
443
+
444
+ A: `apiClient` 会自动处理 401 响应,当检测到未授权时会调用 `handleUnauthorized`,通常会清除认证状态并重定向到登录页面。
445
+
446
+ ### Q: 如何自定义认证流程?
447
+
448
+ A: 可以使用 `createAuth` 替代 `initAuth`,它提供更多配置选项,包括自定义回调 URL 构建函数等。
449
+
450
+ ## 许可证
451
+
452
+ MIT
@@ -0,0 +1,9 @@
1
+ import type { AuthInstance } from "../core";
2
+ /**
3
+ * Better Auth 认证状态同步组件
4
+ * 使用官方的 useSession hook 同步认证状态
5
+ */
6
+ export declare function AuthSync({ auth }: {
7
+ auth: AuthInstance;
8
+ }): null;
9
+ //# sourceMappingURL=auth-sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-sync.d.ts","sourceRoot":"","sources":["../../src/components/auth-sync.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AA8C3C;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,QA8BxD"}
@@ -0,0 +1,60 @@
1
+ import { useEffect } from "react";
2
+ /**
3
+ * 将日期转换为 ISO 字符串
4
+ */
5
+ function toISOString(date) {
6
+ if (!date)
7
+ return undefined;
8
+ return typeof date === "string" ? date : new Date(date).toISOString();
9
+ }
10
+ /**
11
+ * 将 Better Auth session 用户数据映射为 SessionUser
12
+ */
13
+ function mapSessionUserToSessionUser(sessionUser, sessionCreatedAt) {
14
+ return {
15
+ banExpires: sessionUser.banExpires,
16
+ banReason: sessionUser.banReason,
17
+ banned: sessionUser.banned,
18
+ createdAt: toISOString(sessionUser.createdAt) ?? "",
19
+ email: sessionUser.email,
20
+ emailVerified: sessionUser.emailVerified,
21
+ id: sessionUser.id,
22
+ image: sessionUser.image || undefined,
23
+ lastLoginAt: toISOString(sessionCreatedAt),
24
+ name: sessionUser.name,
25
+ role: sessionUser.role,
26
+ updatedAt: toISOString(sessionUser.updatedAt) ?? "",
27
+ };
28
+ }
29
+ /**
30
+ * Better Auth 认证状态同步组件
31
+ * 使用官方的 useSession hook 同步认证状态
32
+ */
33
+ export function AuthSync({ auth }) {
34
+ const { authClient, authActions } = auth;
35
+ const { data: session, isPending, error } = authClient.useSession();
36
+ useEffect(() => {
37
+ // 根据 session 状态更新 store
38
+ if (isPending) {
39
+ authActions.setLoading(true);
40
+ }
41
+ else {
42
+ authActions.setLoading(false);
43
+ if (error) {
44
+ authActions.setError(error.message || "Authentication error");
45
+ authActions.initialize(null, true);
46
+ }
47
+ else if (session) {
48
+ // 映射用户数据:使用 session 的创建时间作为最后登录时间
49
+ const user = mapSessionUserToSessionUser(session.user, session.session?.createdAt);
50
+ authActions.initialize(user, true);
51
+ }
52
+ else {
53
+ // 没有 session,清空用户信息
54
+ authActions.initialize(null, true);
55
+ }
56
+ }
57
+ }, [session, isPending, error, authActions]);
58
+ // 这是一个隐形组件,不渲染任何内容
59
+ return null;
60
+ }
@@ -0,0 +1,18 @@
1
+ import React from "react";
2
+ import type { AuthInstance } from "../core";
3
+ interface ProtectedRouteProps {
4
+ auth: AuthInstance;
5
+ children: React.ReactNode;
6
+ loadingComponent?: React.ComponentType<{
7
+ message?: string;
8
+ }>;
9
+ loadingMessage?: string;
10
+ signInPath?: string;
11
+ }
12
+ /**
13
+ * 路由保护组件
14
+ * 等待认证初始化完成,根据认证状态进行路由保护
15
+ */
16
+ export declare const ProtectedRoute: React.FC<ProtectedRouteProps>;
17
+ export default ProtectedRoute;
18
+ //# sourceMappingURL=protected-route.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protected-route.d.ts","sourceRoot":"","sources":["../../src/components/protected-route.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAE3C,UAAU,mBAAmB;IAC3B,IAAI,EAAE,YAAY,CAAA;IAClB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,gBAAgB,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC5D,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED;;;GAGG;AACH,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CA8BxD,CAAA;AAED,eAAe,cAAc,CAAA"}
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { observer } from "@legendapp/state/react";
3
+ import { Navigate } from "react-router";
4
+ /**
5
+ * 路由保护组件
6
+ * 等待认证初始化完成,根据认证状态进行路由保护
7
+ */
8
+ export const ProtectedRoute = observer(({ children, auth, signInPath = "/sign-in", loadingComponent, loadingMessage }) => {
9
+ const { authStore, authComputed } = auth;
10
+ // 检查各种状态
11
+ const isInitializing = authComputed.isInitializing.get();
12
+ const isAuthenticated = authStore.isAuthenticated.get();
13
+ // 检查初始化状态
14
+ if (isInitializing) {
15
+ if (loadingComponent) {
16
+ const LoadingComponent = loadingComponent;
17
+ return _jsx(LoadingComponent, { message: loadingMessage });
18
+ }
19
+ return _jsx("div", { children: loadingMessage || "Checking authentication..." });
20
+ }
21
+ // 检查认证状态
22
+ if (!isAuthenticated) {
23
+ return (_jsx(Navigate, { to: signInPath, replace: true }));
24
+ }
25
+ // 所有检查通过,渲染子组件
26
+ return _jsx(_Fragment, { children: children });
27
+ });
28
+ export default ProtectedRoute;
@@ -0,0 +1,49 @@
1
+ import React from "react";
2
+ import type { AuthInstance } from "../core";
3
+ interface SignInPageProps {
4
+ auth: AuthInstance;
5
+ /**
6
+ * 自定义样式类名
7
+ */
8
+ className?: string;
9
+ /**
10
+ * 描述
11
+ */
12
+ description?: string;
13
+ /**
14
+ * 自定义 Logo 组件
15
+ */
16
+ logo?: React.ReactNode;
17
+ /**
18
+ * OAuth 提供商
19
+ * @default 'github'
20
+ */
21
+ provider?: string;
22
+ /**
23
+ * 登录成功后的重定向路径
24
+ * @default '/explore'
25
+ */
26
+ redirectAfterLogin?: string;
27
+ /**
28
+ * 登录按钮图标
29
+ */
30
+ signInButtonIcon?: React.ReactNode;
31
+ /**
32
+ * 登录按钮文本
33
+ */
34
+ signInButtonText?: string;
35
+ /**
36
+ * 登录中按钮文本
37
+ */
38
+ signingInButtonText?: string;
39
+ /**
40
+ * 标题
41
+ */
42
+ title?: string;
43
+ }
44
+ /**
45
+ * 登录页面组件
46
+ */
47
+ export declare function SignInPage({ auth, redirectAfterLogin, provider, logo, title, description, signInButtonText, signingInButtonText, signInButtonIcon, className, }: SignInPageProps): import("react/jsx-runtime").JSX.Element;
48
+ export {};
49
+ //# sourceMappingURL=sign-in-page.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sign-in-page.d.ts","sourceRoot":"","sources":["../../src/components/sign-in-page.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAA;AAIlD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAE3C,UAAU,eAAe;IACvB,IAAI,EAAE,YAAY,CAAA;IAClB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;OAEG;IACH,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B;;OAEG;IACH,gBAAgB,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAClC;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB;;OAEG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,EACzB,IAAI,EACJ,kBAA+B,EAC/B,QAAmB,EACnB,IAAI,EACJ,KAAK,EACL,WAAW,EACX,gBAAwC,EACxC,mBAAgD,EAChD,gBAAgB,EAChB,SAAS,GACV,EAAE,eAAe,2CAyDjB"}
@@ -0,0 +1,33 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
+ import { useNavigate } from "react-router";
4
+ import { use$ } from "@legendapp/state/react";
5
+ import { Button } from "@choiceform/design-system";
6
+ /**
7
+ * 登录页面组件
8
+ */
9
+ export function SignInPage({ auth, redirectAfterLogin = "/explore", provider = "github", logo, title, description, signInButtonText = "Sign in with GitHub", signingInButtonText = "Redirecting to GitHub...", signInButtonIcon, className, }) {
10
+ const navigate = useNavigate();
11
+ const { authStore, authActions } = auth;
12
+ const { isAuthenticated, error } = use$(authStore);
13
+ const [isSigningIn, setIsSigningIn] = useState(false);
14
+ useEffect(() => {
15
+ if (isAuthenticated) {
16
+ navigate(redirectAfterLogin);
17
+ }
18
+ }, [isAuthenticated, navigate, redirectAfterLogin]);
19
+ const handleSignIn = async () => {
20
+ // 设置本地 loading 状态
21
+ setIsSigningIn(true);
22
+ try {
23
+ await authActions.signIn(provider, `${window.location.origin}${redirectAfterLogin}`);
24
+ // OAuth 会重定向,所以这里的代码可能不会执行
25
+ // loading 状态会在页面刷新后重置
26
+ }
27
+ catch (error) {
28
+ // 如果出错,重置 loading 状态
29
+ setIsSigningIn(false);
30
+ }
31
+ };
32
+ return (_jsx("div", { className: className || "flex min-h-screen items-center justify-center bg-gray-50 dark:bg-gray-900", children: _jsxs("div", { className: "w-full max-w-md space-y-8", children: [logo && (_jsxs("div", { className: "mb-8", children: [_jsx("div", { className: "flex items-center gap-2", children: logo }), title && (_jsxs("div", { className: "p-4", children: [title && _jsx("p", { className: "text-heading-large mt-2", children: title }), description && (_jsx("p", { className: "text-secondary-foreground text-body-large mt-2", children: description }))] }))] })), _jsxs("div", { className: "flex flex-col items-start justify-start gap-2 p-4", children: [_jsxs(Button, { onClick: handleSignIn, disabled: isSigningIn, loading: isSigningIn, size: "large", children: [signInButtonIcon && _jsx("span", { className: "flex items-center", children: signInButtonIcon }), _jsx("span", { children: isSigningIn ? signingInButtonText : signInButtonText })] }), error && _jsx("p", { className: "text-danger-foreground", children: error })] })] }) }));
33
+ }