sa2kit 2.0.1 → 2.0.2

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 (264) hide show
  1. package/README.md +1 -1
  2. package/dist/CollisionBalls-BpHufX3H.d.mts +41 -0
  3. package/dist/CollisionBalls-BpHufX3H.d.ts +41 -0
  4. package/dist/ConfigService-BxK06xP6.d.mts +262 -0
  5. package/dist/ConfigService-BxK06xP6.d.ts +262 -0
  6. package/dist/UniversalFileService-BpvbZitV.d.mts +139 -0
  7. package/dist/UniversalFileService-GsP6D3Rc.d.ts +139 -0
  8. package/dist/audioDetection/index.d.mts +449 -0
  9. package/dist/audioDetection/index.d.ts +449 -0
  10. package/dist/audioDetection/index.js +1244 -0
  11. package/dist/audioDetection/index.js.map +1 -0
  12. package/dist/audioDetection/index.mjs +1227 -0
  13. package/dist/audioDetection/index.mjs.map +1 -0
  14. package/dist/auth/legacy/core/index.d.mts +42 -0
  15. package/dist/auth/legacy/core/index.d.ts +42 -0
  16. package/dist/auth/legacy/core/index.js +242 -0
  17. package/dist/auth/legacy/core/index.js.map +1 -0
  18. package/dist/auth/legacy/core/index.mjs +226 -0
  19. package/dist/auth/legacy/core/index.mjs.map +1 -0
  20. package/dist/auth/legacy/db/index.d.mts +5 -0
  21. package/dist/auth/legacy/db/index.d.ts +5 -0
  22. package/dist/auth/legacy/db/index.js +261 -0
  23. package/dist/auth/legacy/db/index.js.map +1 -0
  24. package/dist/auth/legacy/db/index.mjs +250 -0
  25. package/dist/auth/legacy/db/index.mjs.map +1 -0
  26. package/dist/auth/legacy/index.d.mts +5 -0
  27. package/dist/auth/legacy/index.d.ts +5 -0
  28. package/dist/auth/legacy/index.js +1107 -0
  29. package/dist/auth/legacy/index.js.map +1 -0
  30. package/dist/auth/legacy/index.mjs +1086 -0
  31. package/dist/auth/legacy/index.mjs.map +1 -0
  32. package/dist/auth/legacy/logic/index.d.mts +9 -0
  33. package/dist/auth/legacy/logic/index.d.ts +9 -0
  34. package/dist/auth/legacy/logic/index.js +194 -0
  35. package/dist/auth/legacy/logic/index.js.map +1 -0
  36. package/dist/auth/legacy/logic/index.mjs +187 -0
  37. package/dist/auth/legacy/logic/index.mjs.map +1 -0
  38. package/dist/auth/legacy/miniapp/index.d.mts +5 -0
  39. package/dist/auth/legacy/miniapp/index.d.ts +5 -0
  40. package/dist/auth/legacy/miniapp/index.js +506 -0
  41. package/dist/auth/legacy/miniapp/index.js.map +1 -0
  42. package/dist/auth/legacy/miniapp/index.mjs +487 -0
  43. package/dist/auth/legacy/miniapp/index.mjs.map +1 -0
  44. package/dist/auth/legacy/routes/index.d.mts +53 -0
  45. package/dist/auth/legacy/routes/index.d.ts +53 -0
  46. package/dist/auth/legacy/routes/index.js +278 -0
  47. package/dist/auth/legacy/routes/index.js.map +1 -0
  48. package/dist/auth/legacy/routes/index.mjs +271 -0
  49. package/dist/auth/legacy/routes/index.mjs.map +1 -0
  50. package/dist/auth/legacy/schema/index.d.mts +401 -0
  51. package/dist/auth/legacy/schema/index.d.ts +401 -0
  52. package/dist/auth/legacy/schema/index.js +50 -0
  53. package/dist/auth/legacy/schema/index.js.map +1 -0
  54. package/dist/auth/legacy/schema/index.mjs +44 -0
  55. package/dist/auth/legacy/schema/index.mjs.map +1 -0
  56. package/dist/auth/legacy/server/index.d.mts +13 -0
  57. package/dist/auth/legacy/server/index.d.ts +13 -0
  58. package/dist/auth/legacy/server/index.js +21 -0
  59. package/dist/auth/legacy/server/index.js.map +1 -0
  60. package/dist/auth/legacy/server/index.mjs +19 -0
  61. package/dist/auth/legacy/server/index.mjs.map +1 -0
  62. package/dist/auth/legacy/services/index.d.mts +40 -0
  63. package/dist/auth/legacy/services/index.d.ts +40 -0
  64. package/dist/auth/legacy/services/index.js +258 -0
  65. package/dist/auth/legacy/services/index.js.map +1 -0
  66. package/dist/auth/legacy/services/index.mjs +252 -0
  67. package/dist/auth/legacy/services/index.mjs.map +1 -0
  68. package/dist/auth/legacy/ui/miniapp/index.d.mts +10 -0
  69. package/dist/auth/legacy/ui/miniapp/index.d.ts +10 -0
  70. package/dist/auth/legacy/ui/miniapp/index.js +298 -0
  71. package/dist/auth/legacy/ui/miniapp/index.js.map +1 -0
  72. package/dist/auth/legacy/ui/miniapp/index.mjs +290 -0
  73. package/dist/auth/legacy/ui/miniapp/index.mjs.map +1 -0
  74. package/dist/auth/legacy/ui/web/index.d.mts +22 -0
  75. package/dist/auth/legacy/ui/web/index.d.ts +22 -0
  76. package/dist/auth/legacy/ui/web/index.js +899 -0
  77. package/dist/auth/legacy/ui/web/index.js.map +1 -0
  78. package/dist/auth/legacy/ui/web/index.mjs +889 -0
  79. package/dist/auth/legacy/ui/web/index.mjs.map +1 -0
  80. package/dist/auth/legacy/web/index.d.mts +5 -0
  81. package/dist/auth/legacy/web/index.d.ts +5 -0
  82. package/dist/auth/legacy/web/index.js +1107 -0
  83. package/dist/auth/legacy/web/index.js.map +1 -0
  84. package/dist/auth/legacy/web/index.mjs +1086 -0
  85. package/dist/auth/legacy/web/index.mjs.map +1 -0
  86. package/dist/auth/rn/index.d.mts +64 -0
  87. package/dist/auth/rn/index.d.ts +64 -0
  88. package/dist/auth/rn/index.js +765 -0
  89. package/dist/auth/rn/index.js.map +1 -0
  90. package/dist/auth/rn/index.mjs +754 -0
  91. package/dist/auth/rn/index.mjs.map +1 -0
  92. package/dist/base-api-client-ACKKt13v.d.mts +277 -0
  93. package/dist/base-api-client-ACKKt13v.d.ts +277 -0
  94. package/dist/boothVaultService-Cn4WPhjg.d.mts +83 -0
  95. package/dist/boothVaultService-Cn4WPhjg.d.ts +83 -0
  96. package/dist/business/index.d.mts +6 -0
  97. package/dist/business/index.d.ts +6 -0
  98. package/dist/business/index.js +1682 -0
  99. package/dist/business/index.js.map +1 -0
  100. package/dist/business/index.mjs +1675 -0
  101. package/dist/business/index.mjs.map +1 -0
  102. package/dist/calendar/index.d.mts +1325 -0
  103. package/dist/calendar/index.d.ts +1325 -0
  104. package/dist/calendar/index.js +5964 -0
  105. package/dist/calendar/index.js.map +1 -0
  106. package/dist/calendar/index.mjs +5878 -0
  107. package/dist/calendar/index.mjs.map +1 -0
  108. package/dist/components/index.d.mts +405 -0
  109. package/dist/components/index.d.ts +405 -0
  110. package/dist/components/index.js +2516 -0
  111. package/dist/components/index.js.map +1 -0
  112. package/dist/components/index.mjs +2396 -0
  113. package/dist/components/index.mjs.map +1 -0
  114. package/dist/drizzle-schema-BNhqj2AZ.d.mts +1114 -0
  115. package/dist/drizzle-schema-BNhqj2AZ.d.ts +1114 -0
  116. package/dist/festivalCard/index.d.mts +75 -0
  117. package/dist/festivalCard/index.d.ts +75 -0
  118. package/dist/festivalCard/index.js +1492 -0
  119. package/dist/festivalCard/index.js.map +1 -0
  120. package/dist/festivalCard/index.mjs +1475 -0
  121. package/dist/festivalCard/index.mjs.map +1 -0
  122. package/dist/festivalCard/server/index.d.mts +120 -0
  123. package/dist/festivalCard/server/index.d.ts +120 -0
  124. package/dist/festivalCard/server/index.js +272 -0
  125. package/dist/festivalCard/server/index.js.map +1 -0
  126. package/dist/festivalCard/server/index.mjs +265 -0
  127. package/dist/festivalCard/server/index.mjs.map +1 -0
  128. package/dist/festivalCardService-CZomuQ4E.d.mts +80 -0
  129. package/dist/festivalCardService-CZomuQ4E.d.ts +80 -0
  130. package/dist/index-1Ag7IBXN.d.ts +144 -0
  131. package/dist/index-DNKZ7-R_.d.mts +184 -0
  132. package/dist/index-DNKZ7-R_.d.ts +184 -0
  133. package/dist/index-DSel44Ke.d.mts +93 -0
  134. package/dist/index-DSel44Ke.d.ts +93 -0
  135. package/dist/index-DdeZSeTJ.d.mts +144 -0
  136. package/dist/index-DrPcMJPc.d.mts +250 -0
  137. package/dist/index-DrPcMJPc.d.ts +250 -0
  138. package/dist/index.d.mts +5333 -0
  139. package/dist/index.d.ts +5333 -0
  140. package/dist/index.js +18809 -0
  141. package/dist/index.js.map +1 -0
  142. package/dist/index.mjs +18533 -0
  143. package/dist/index.mjs.map +1 -0
  144. package/dist/mikuContest/ui/web/index.d.mts +2 -0
  145. package/dist/mikuContest/ui/web/index.d.ts +2 -0
  146. package/dist/mikuContest/ui/web/index.js +353 -0
  147. package/dist/mikuContest/ui/web/index.js.map +1 -0
  148. package/dist/mikuContest/ui/web/index.mjs +343 -0
  149. package/dist/mikuContest/ui/web/index.mjs.map +1 -0
  150. package/dist/mikuFireworks3D/index.d.mts +268 -0
  151. package/dist/mikuFireworks3D/index.d.ts +268 -0
  152. package/dist/mikuFireworks3D/index.js +1267 -0
  153. package/dist/mikuFireworks3D/index.js.map +1 -0
  154. package/dist/mikuFireworks3D/index.mjs +1228 -0
  155. package/dist/mikuFireworks3D/index.mjs.map +1 -0
  156. package/dist/mikuFusionGame/index.d.mts +117 -0
  157. package/dist/mikuFusionGame/index.d.ts +117 -0
  158. package/dist/mikuFusionGame/index.js +1208 -0
  159. package/dist/mikuFusionGame/index.js.map +1 -0
  160. package/dist/mikuFusionGame/index.mjs +1195 -0
  161. package/dist/mikuFusionGame/index.mjs.map +1 -0
  162. package/dist/mmd/admin/index.d.mts +487 -0
  163. package/dist/mmd/admin/index.d.ts +487 -0
  164. package/dist/mmd/admin/index.js +1058 -0
  165. package/dist/mmd/admin/index.js.map +1 -0
  166. package/dist/mmd/admin/index.mjs +1027 -0
  167. package/dist/mmd/admin/index.mjs.map +1 -0
  168. package/dist/mmd/index.d.mts +2467 -0
  169. package/dist/mmd/index.d.ts +2467 -0
  170. package/dist/mmd/index.js +10119 -0
  171. package/dist/mmd/index.js.map +1 -0
  172. package/dist/mmd/index.mjs +10028 -0
  173. package/dist/mmd/index.mjs.map +1 -0
  174. package/dist/mmd/server/index.d.mts +139 -0
  175. package/dist/mmd/server/index.d.ts +139 -0
  176. package/dist/mmd/server/index.js +424 -0
  177. package/dist/mmd/server/index.js.map +1 -0
  178. package/dist/mmd/server/index.mjs +404 -0
  179. package/dist/mmd/server/index.mjs.map +1 -0
  180. package/dist/music/index.d.mts +74 -0
  181. package/dist/music/index.d.ts +74 -0
  182. package/dist/music/index.js +830 -0
  183. package/dist/music/index.js.map +1 -0
  184. package/dist/music/index.mjs +809 -0
  185. package/dist/music/index.mjs.map +1 -0
  186. package/dist/music/server/index.d.mts +1 -0
  187. package/dist/music/server/index.d.ts +1 -0
  188. package/dist/music/server/index.js +194 -0
  189. package/dist/music/server/index.js.map +1 -0
  190. package/dist/music/server/index.mjs +182 -0
  191. package/dist/music/server/index.mjs.map +1 -0
  192. package/dist/navigation/index.d.mts +93 -0
  193. package/dist/navigation/index.d.ts +93 -0
  194. package/dist/navigation/index.js +453 -0
  195. package/dist/navigation/index.js.map +1 -0
  196. package/dist/navigation/index.mjs +443 -0
  197. package/dist/navigation/index.mjs.map +1 -0
  198. package/dist/portfolio/index.d.mts +66 -0
  199. package/dist/portfolio/index.d.ts +66 -0
  200. package/dist/portfolio/index.js +736 -0
  201. package/dist/portfolio/index.js.map +1 -0
  202. package/dist/portfolio/index.mjs +724 -0
  203. package/dist/portfolio/index.mjs.map +1 -0
  204. package/dist/qqbot/server/index.d.mts +216 -0
  205. package/dist/qqbot/server/index.d.ts +216 -0
  206. package/dist/qqbot/server/index.js +394 -0
  207. package/dist/qqbot/server/index.js.map +1 -0
  208. package/dist/qqbot/server/index.mjs +385 -0
  209. package/dist/qqbot/server/index.mjs.map +1 -0
  210. package/dist/qqbot/ui/web/index.d.mts +10 -0
  211. package/dist/qqbot/ui/web/index.d.ts +10 -0
  212. package/dist/qqbot/ui/web/index.js +105 -0
  213. package/dist/qqbot/ui/web/index.js.map +1 -0
  214. package/dist/qqbot/ui/web/index.mjs +99 -0
  215. package/dist/qqbot/ui/web/index.mjs.map +1 -0
  216. package/dist/screenReceiver/index.d.mts +86 -0
  217. package/dist/screenReceiver/index.d.ts +86 -0
  218. package/dist/screenReceiver/index.js +281 -0
  219. package/dist/screenReceiver/index.js.map +1 -0
  220. package/dist/screenReceiver/index.mjs +273 -0
  221. package/dist/screenReceiver/index.mjs.map +1 -0
  222. package/dist/testYourself/admin/index.d.mts +58 -0
  223. package/dist/testYourself/admin/index.d.ts +58 -0
  224. package/dist/testYourself/admin/index.js +1009 -0
  225. package/dist/testYourself/admin/index.js.map +1 -0
  226. package/dist/testYourself/admin/index.mjs +1002 -0
  227. package/dist/testYourself/admin/index.mjs.map +1 -0
  228. package/dist/testYourself/index.d.mts +53 -0
  229. package/dist/testYourself/index.d.ts +53 -0
  230. package/dist/testYourself/index.js +2551 -0
  231. package/dist/testYourself/index.js.map +1 -0
  232. package/dist/testYourself/index.mjs +2531 -0
  233. package/dist/testYourself/index.mjs.map +1 -0
  234. package/dist/testYourself/server/index.d.mts +1029 -0
  235. package/dist/testYourself/server/index.d.ts +1029 -0
  236. package/dist/testYourself/server/index.js +825 -0
  237. package/dist/testYourself/server/index.js.map +1 -0
  238. package/dist/testYourself/server/index.mjs +816 -0
  239. package/dist/testYourself/server/index.mjs.map +1 -0
  240. package/dist/types-BTiaMsBz.d.mts +292 -0
  241. package/dist/types-DyG3ZV9V.d.mts +270 -0
  242. package/dist/types-DyG3ZV9V.d.ts +270 -0
  243. package/dist/types-ERmJyjx8.d.ts +292 -0
  244. package/dist/types-HorDyIRv.d.mts +303 -0
  245. package/dist/types-HorDyIRv.d.ts +303 -0
  246. package/dist/vocaloidBooth/index.d.mts +64 -0
  247. package/dist/vocaloidBooth/index.d.ts +64 -0
  248. package/dist/vocaloidBooth/index.js +376 -0
  249. package/dist/vocaloidBooth/index.js.map +1 -0
  250. package/dist/vocaloidBooth/index.mjs +362 -0
  251. package/dist/vocaloidBooth/index.mjs.map +1 -0
  252. package/dist/vocaloidBooth/server/index.d.mts +111 -0
  253. package/dist/vocaloidBooth/server/index.d.ts +111 -0
  254. package/dist/vocaloidBooth/server/index.js +247 -0
  255. package/dist/vocaloidBooth/server/index.js.map +1 -0
  256. package/dist/vocaloidBooth/server/index.mjs +237 -0
  257. package/dist/vocaloidBooth/server/index.mjs.map +1 -0
  258. package/dist/vocaloidBooth/web/index.d.mts +3 -0
  259. package/dist/vocaloidBooth/web/index.d.ts +3 -0
  260. package/dist/vocaloidBooth/web/index.js +376 -0
  261. package/dist/vocaloidBooth/web/index.js.map +1 -0
  262. package/dist/vocaloidBooth/web/index.mjs +362 -0
  263. package/dist/vocaloidBooth/web/index.mjs.map +1 -0
  264. package/package.json +1 -1
@@ -0,0 +1,1682 @@
1
+ 'use strict';
2
+
3
+ var React5 = require('react');
4
+ var reactDom = require('react-dom');
5
+ var lucideReact = require('lucide-react');
6
+
7
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
8
+
9
+ var React5__default = /*#__PURE__*/_interopDefault(React5);
10
+
11
+ var __defProp = Object.defineProperty;
12
+ var __export = (target, all) => {
13
+ for (var name in all)
14
+ __defProp(target, name, { get: all[name], enumerable: true });
15
+ };
16
+
17
+ // src/business/auth-legacy/index.ts
18
+ var auth_legacy_exports = {};
19
+ __export(auth_legacy_exports, {
20
+ AuthGuard: () => AuthGuard,
21
+ AuthProvider: () => AuthProvider,
22
+ ForgotPasswordModal: () => ForgotPasswordModal,
23
+ LoginModal: () => LoginModal,
24
+ RegisterModal: () => RegisterModal,
25
+ UserMenu: () => UserMenu,
26
+ UserRole: () => UserRole,
27
+ calculateSessionExpiry: () => calculateSessionExpiry,
28
+ generateSessionToken: () => generateSessionToken,
29
+ getUserDisplayName: () => getUserDisplayName,
30
+ isActiveUser: () => isActiveUser,
31
+ isAdmin: () => isAdmin,
32
+ isSessionExpired: () => isSessionExpired,
33
+ useAuth: () => useAuth,
34
+ validatePassword: () => validatePassword,
35
+ validatePhoneNumber: () => validatePhoneNumber
36
+ });
37
+ var AuthContext = React5.createContext(void 0);
38
+ function extractUser(data) {
39
+ return data?.user ?? data?.data?.user ?? null;
40
+ }
41
+ function extractValid(data) {
42
+ if (typeof data?.valid === "boolean") return data.valid;
43
+ if (typeof data?.data?.valid === "boolean") return data.data.valid;
44
+ return false;
45
+ }
46
+ function AuthProvider({ children }) {
47
+ const [user, setUser] = React5.useState(null);
48
+ const [loading, setLoading] = React5.useState(true);
49
+ const [isAuthenticated, setIsAuthenticated] = React5.useState(false);
50
+ const isMountedRef = React5.useRef(true);
51
+ const safeSetState = React5.useCallback((updater) => {
52
+ if (isMountedRef.current) {
53
+ updater();
54
+ }
55
+ }, []);
56
+ const validateSession = React5.useCallback(async () => {
57
+ console.log("\u{1F50D} [AuthContext] \u5F00\u59CB\u9A8C\u8BC1\u4F1A\u8BDD...");
58
+ try {
59
+ const response = await fetch("/api/auth/validate");
60
+ console.log("\u{1F4E1} [AuthContext] \u4F1A\u8BDD\u9A8C\u8BC1\u54CD\u5E94\u72B6\u6001:", response.status);
61
+ const data = await response.json();
62
+ console.log("\u{1F4C4} [AuthContext] \u4F1A\u8BDD\u9A8C\u8BC1\u54CD\u5E94\u6570\u636E:", data);
63
+ const resolvedUser = extractUser(data);
64
+ const resolvedValid = extractValid(data);
65
+ safeSetState(() => {
66
+ if (resolvedValid && resolvedUser) {
67
+ console.log("\u2705 [AuthContext] \u4F1A\u8BDD\u9A8C\u8BC1\u6210\u529F, \u7528\u6237:", resolvedUser);
68
+ setUser(resolvedUser);
69
+ setIsAuthenticated(true);
70
+ } else {
71
+ console.log("\u274C [AuthContext] \u4F1A\u8BDD\u9A8C\u8BC1\u5931\u8D25:", data.message);
72
+ setUser(null);
73
+ setIsAuthenticated(false);
74
+ }
75
+ setLoading(false);
76
+ });
77
+ } catch (error) {
78
+ console.error("\u{1F4A5} [AuthContext] \u4F1A\u8BDD\u9A8C\u8BC1\u5F02\u5E38:", error);
79
+ safeSetState(() => {
80
+ setUser(null);
81
+ setIsAuthenticated(false);
82
+ setLoading(false);
83
+ });
84
+ }
85
+ }, [safeSetState]);
86
+ const login = React5.useCallback(async (credentials) => {
87
+ console.log("\u{1F511} [AuthContext] \u5F00\u59CB\u767B\u5F55...");
88
+ console.log("\u{1F4DD} [AuthContext] \u767B\u5F55\u51ED\u636E:", { phone: credentials.phone, password: "***" });
89
+ try {
90
+ console.log("\u{1F4E4} [AuthContext] \u53D1\u9001\u767B\u5F55\u8BF7\u6C42\u5230 /api/auth/login");
91
+ const response = await fetch("/api/auth/login", {
92
+ method: "POST",
93
+ headers: { "Content-Type": "application/json" },
94
+ body: JSON.stringify(credentials)
95
+ });
96
+ console.log("\u{1F4E1} [AuthContext] \u6536\u5230\u54CD\u5E94\uFF0C\u72B6\u6001\u7801:", response.status);
97
+ const data = await response.json();
98
+ console.log("\u{1F4C4} [AuthContext] \u54CD\u5E94\u6570\u636E:", data);
99
+ const resolvedUser = extractUser(data);
100
+ if (data.success && resolvedUser) {
101
+ console.log("\u2705 [AuthContext] \u767B\u5F55\u6210\u529F, \u5F00\u59CB\u66F4\u65B0\u5168\u5C40\u72B6\u6001");
102
+ console.log("\u{1F464} [AuthContext] \u7528\u6237\u6570\u636E:", resolvedUser);
103
+ console.log("\u{1F4CA} [AuthContext] \u66F4\u65B0\u524D\u72B6\u6001:", {
104
+ currentUser: user ? `${user.name || "\u672A\u8BBE\u7F6E"} (${user.phone})` : null,
105
+ currentIsAuthenticated: isAuthenticated,
106
+ currentLoading: loading
107
+ });
108
+ console.log("\u{1F504} [AuthContext] \u6267\u884C\u5168\u5C40\u72B6\u6001\u66F4\u65B0...");
109
+ safeSetState(() => {
110
+ console.log("\u{1F504} [AuthContext] \u6B63\u5728\u8BBE\u7F6E\u7528\u6237:", resolvedUser);
111
+ setUser(resolvedUser);
112
+ console.log("\u{1F504} [AuthContext] \u6B63\u5728\u8BBE\u7F6E\u8BA4\u8BC1\u72B6\u6001: true");
113
+ setIsAuthenticated(true);
114
+ console.log("\u{1F504} [AuthContext] \u6B63\u5728\u8BBE\u7F6E\u52A0\u8F7D\u72B6\u6001: false");
115
+ setLoading(false);
116
+ console.log("\u2705 [AuthContext] \u5168\u5C40\u72B6\u6001\u66F4\u65B0\u5B8C\u6210");
117
+ });
118
+ setTimeout(() => {
119
+ console.log("\u{1F389} [AuthContext] \u5EF6\u8FDF\u786E\u8BA4 - \u5168\u5C40\u767B\u5F55\u72B6\u6001\u5E94\u8BE5\u5DF2\u66F4\u65B0:", {
120
+ user: resolvedUser,
121
+ isAuthenticated: true
122
+ });
123
+ }, 0);
124
+ console.log("\u{1F680} [AuthContext] \u8FD4\u56DE\u6210\u529F\u7ED3\u679C");
125
+ return { success: true, user: resolvedUser };
126
+ } else {
127
+ console.log("\u274C [AuthContext] \u767B\u5F55\u5931\u8D25:", data.message);
128
+ return { success: false, message: data.message };
129
+ }
130
+ } catch (error) {
131
+ console.error("\u{1F4A5} [AuthContext] \u767B\u5F55\u5F02\u5E38:", error);
132
+ return { success: false, message: "\u767B\u5F55\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5" };
133
+ }
134
+ }, [safeSetState, user, isAuthenticated, loading]);
135
+ const register = React5.useCallback(async (userData) => {
136
+ console.log("\u{1F4DD} [AuthContext] \u5F00\u59CB\u6CE8\u518C...");
137
+ try {
138
+ const response = await fetch("/api/auth/register", {
139
+ method: "POST",
140
+ headers: { "Content-Type": "application/json" },
141
+ body: JSON.stringify(userData)
142
+ });
143
+ const data = await response.json();
144
+ console.log("\u{1F4E1} [AuthContext] \u6CE8\u518C\u54CD\u5E94:", data);
145
+ const resolvedUser = extractUser(data);
146
+ if (data.success && resolvedUser) {
147
+ console.log("\u2705 [AuthContext] \u6CE8\u518C\u6210\u529F, \u7ACB\u5373\u66F4\u65B0\u5168\u5C40\u72B6\u6001");
148
+ safeSetState(() => {
149
+ setUser(resolvedUser);
150
+ setIsAuthenticated(true);
151
+ setLoading(false);
152
+ });
153
+ console.log("\u{1F680} [AuthContext] \u8FD4\u56DE\u6CE8\u518C\u6210\u529F\u7ED3\u679C");
154
+ return { success: true, user: resolvedUser };
155
+ } else {
156
+ console.log("\u274C [AuthContext] \u6CE8\u518C\u5931\u8D25:", data.message);
157
+ return { success: false, message: data.message };
158
+ }
159
+ } catch (error) {
160
+ console.error("\u{1F4A5} [AuthContext] \u6CE8\u518C\u5F02\u5E38:", error);
161
+ return { success: false, message: "\u6CE8\u518C\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5" };
162
+ }
163
+ }, [safeSetState]);
164
+ const logout = React5.useCallback(async () => {
165
+ console.log("\u{1F6AA} [AuthContext] \u5F00\u59CB\u767B\u51FA...");
166
+ try {
167
+ await fetch("/api/auth/logout", { method: "POST" });
168
+ safeSetState(() => {
169
+ setUser(null);
170
+ setIsAuthenticated(false);
171
+ });
172
+ console.log("\u2705 [AuthContext] \u767B\u51FA\u6210\u529F, \u5168\u5C40\u72B6\u6001\u5DF2\u6E05\u9664");
173
+ } catch (error) {
174
+ console.error("\u{1F4A5} [AuthContext] \u767B\u51FA\u5931\u8D25:", error);
175
+ }
176
+ }, [safeSetState]);
177
+ const refreshUser = React5.useCallback(() => {
178
+ console.log("\u{1F504} [AuthContext] \u5237\u65B0\u7528\u6237\u4FE1\u606F...");
179
+ setLoading(true);
180
+ validateSession();
181
+ }, [validateSession]);
182
+ React5.useEffect(() => {
183
+ isMountedRef.current = true;
184
+ return () => {
185
+ isMountedRef.current = false;
186
+ };
187
+ }, []);
188
+ React5.useEffect(() => {
189
+ console.log("\u{1F680} [AuthContext] \u521D\u59CB\u5316, \u5F00\u59CB\u9A8C\u8BC1\u4F1A\u8BDD");
190
+ validateSession();
191
+ }, [validateSession]);
192
+ React5.useEffect(() => {
193
+ console.log("\u{1F4CA} [AuthContext] \u5168\u5C40\u72B6\u6001\u53D8\u5316:", {
194
+ isAuthenticated,
195
+ user: user ? `${user.name || "\u672A\u8BBE\u7F6E"} (${user.phone})` : null,
196
+ loading
197
+ });
198
+ }, [isAuthenticated, user, loading]);
199
+ const value = {
200
+ user,
201
+ loading,
202
+ isAuthenticated,
203
+ login,
204
+ register,
205
+ logout,
206
+ refreshUser
207
+ };
208
+ return /* @__PURE__ */ React5__default.default.createElement(AuthContext.Provider, { value }, children);
209
+ }
210
+ function useAuth() {
211
+ const context = React5.useContext(AuthContext);
212
+ if (context === void 0) {
213
+ throw new Error("useAuth must be used within an AuthProvider");
214
+ }
215
+ return context;
216
+ }
217
+
218
+ // src/business/auth-legacy/utils/authUtils.ts
219
+ function validatePhoneNumber(phone) {
220
+ return /^1[3-9]\d{9}$/.test(phone);
221
+ }
222
+ function validatePassword(password) {
223
+ if (!password) {
224
+ return { valid: false, message: "\u5BC6\u7801\u4E0D\u80FD\u4E3A\u7A7A" };
225
+ }
226
+ if (password.length < 6) {
227
+ return { valid: false, message: "\u5BC6\u7801\u957F\u5EA6\u81F3\u5C116\u4F4D" };
228
+ }
229
+ return { valid: true };
230
+ }
231
+ function generateSessionToken() {
232
+ return Math.random().toString(36).substring(2) + Date.now().toString(36) + Math.random().toString(36).substring(2);
233
+ }
234
+ function isAdmin(user) {
235
+ return user?.role === "admin";
236
+ }
237
+ function isActiveUser(user) {
238
+ return user?.isActive === true;
239
+ }
240
+ function getUserDisplayName(user) {
241
+ return user.name || user.phone || "\u672A\u77E5\u7528\u6237";
242
+ }
243
+ function calculateSessionExpiry(days = 30) {
244
+ return new Date(Date.now() + days * 24 * 60 * 60 * 1e3);
245
+ }
246
+ function isSessionExpired(expiresAt) {
247
+ return /* @__PURE__ */ new Date() > new Date(expiresAt);
248
+ }
249
+ function ForgotPasswordModal({ isOpen, onClose, onSuccess }) {
250
+ const [formData, setFormData] = React5.useState({
251
+ phone: "",
252
+ newPassword: "",
253
+ confirmPassword: "",
254
+ verificationCode: ""
255
+ });
256
+ const [showPassword, setShowPassword] = React5.useState(false);
257
+ const [showConfirmPassword, setShowConfirmPassword] = React5.useState(false);
258
+ const [error, setError] = React5.useState("");
259
+ const [loading, setLoading] = React5.useState(false);
260
+ const [countdown, setCountdown] = React5.useState(0);
261
+ const [mounted, setMounted] = React5.useState(false);
262
+ React5.useEffect(() => {
263
+ setMounted(true);
264
+ }, []);
265
+ const handleInputChange = (e) => {
266
+ const { name, value } = e.target;
267
+ setFormData((prev) => ({ ...prev, [name]: value }));
268
+ if (error) setError("");
269
+ };
270
+ const validateForm = () => {
271
+ if (!formData.phone || !formData.newPassword || !formData.confirmPassword || !formData.verificationCode) {
272
+ setError("\u8BF7\u586B\u5199\u5B8C\u6574\u4FE1\u606F");
273
+ return false;
274
+ }
275
+ if (!validatePhoneNumber(formData.phone)) {
276
+ setError("\u8BF7\u8F93\u5165\u6B63\u786E\u7684\u624B\u673A\u53F7\u683C\u5F0F");
277
+ return false;
278
+ }
279
+ const passwordValidation = validatePassword(formData.newPassword);
280
+ if (!passwordValidation.valid) {
281
+ setError(passwordValidation.message || "\u5BC6\u7801\u683C\u5F0F\u9519\u8BEF");
282
+ return false;
283
+ }
284
+ if (formData.newPassword !== formData.confirmPassword) {
285
+ setError("\u4E24\u6B21\u8F93\u5165\u7684\u5BC6\u7801\u4E0D\u4E00\u81F4");
286
+ return false;
287
+ }
288
+ if (!/^\d{6}$/.test(formData.verificationCode)) {
289
+ setError("\u8BF7\u8F93\u51656\u4F4D\u6570\u5B57\u9A8C\u8BC1\u7801");
290
+ return false;
291
+ }
292
+ return true;
293
+ };
294
+ const handleSendCode = async () => {
295
+ if (!formData.phone) {
296
+ setError("\u8BF7\u8F93\u5165\u624B\u673A\u53F7");
297
+ return;
298
+ }
299
+ if (!validatePhoneNumber(formData.phone)) {
300
+ setError("\u8BF7\u8F93\u5165\u6B63\u786E\u7684\u624B\u673A\u53F7\u683C\u5F0F");
301
+ return;
302
+ }
303
+ try {
304
+ setLoading(true);
305
+ setError("");
306
+ const response = await fetch("/api/auth/send-verification-code", {
307
+ method: "POST",
308
+ headers: { "Content-Type": "application/json" },
309
+ body: JSON.stringify({ phone: formData.phone })
310
+ });
311
+ const data = await response.json();
312
+ if (data.success) {
313
+ setCountdown(60);
314
+ const timer = setInterval(() => {
315
+ setCountdown((prev) => {
316
+ if (prev <= 1) {
317
+ clearInterval(timer);
318
+ return 0;
319
+ }
320
+ return prev - 1;
321
+ });
322
+ }, 1e3);
323
+ console.log("\u2705 \u9A8C\u8BC1\u7801\u53D1\u9001\u6210\u529F");
324
+ } else {
325
+ setError(data.message || "\u53D1\u9001\u9A8C\u8BC1\u7801\u5931\u8D25");
326
+ }
327
+ } catch (error2) {
328
+ console.error("\u53D1\u9001\u9A8C\u8BC1\u7801\u5F02\u5E38:", error2);
329
+ setError("\u53D1\u9001\u9A8C\u8BC1\u7801\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5");
330
+ } finally {
331
+ setLoading(false);
332
+ }
333
+ };
334
+ const handleSubmit = async (e) => {
335
+ e.preventDefault();
336
+ setError("");
337
+ setLoading(true);
338
+ try {
339
+ if (!validateForm()) {
340
+ return;
341
+ }
342
+ const response = await fetch("/api/auth/reset-password", {
343
+ method: "POST",
344
+ headers: { "Content-Type": "application/json" },
345
+ body: JSON.stringify({
346
+ phone: formData.phone,
347
+ newPassword: formData.newPassword,
348
+ verificationCode: formData.verificationCode
349
+ })
350
+ });
351
+ const data = await response.json();
352
+ if (data.success) {
353
+ onSuccess();
354
+ } else {
355
+ setError(data.message || "\u91CD\u7F6E\u5BC6\u7801\u5931\u8D25");
356
+ }
357
+ } catch (error2) {
358
+ setError("\u91CD\u7F6E\u5BC6\u7801\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5");
359
+ } finally {
360
+ setLoading(false);
361
+ }
362
+ };
363
+ const handleOverlayClick = (e) => {
364
+ if (e.target === e.currentTarget) {
365
+ onClose();
366
+ }
367
+ };
368
+ if (!isOpen || !mounted) return null;
369
+ const modalContent = /* @__PURE__ */ React5__default.default.createElement(
370
+ "div",
371
+ {
372
+ className: "fixed top-0 left-0 right-0 bottom-0 w-screen h-screen bg-black/50 backdrop-blur-sm flex items-center justify-center z-[9999] p-4",
373
+ style: { margin: 0 },
374
+ onClick: handleOverlayClick
375
+ },
376
+ /* @__PURE__ */ React5__default.default.createElement(
377
+ "div",
378
+ {
379
+ className: "bg-white rounded-2xl shadow-xl w-full max-w-[420px] max-h-[90vh] overflow-y-auto relative",
380
+ onClick: (e) => e.stopPropagation()
381
+ },
382
+ /* @__PURE__ */ React5__default.default.createElement(
383
+ "button",
384
+ {
385
+ className: "absolute top-5 right-5 bg-transparent border-none text-gray-500 cursor-pointer p-2 rounded-lg transition-all hover:bg-gray-100 hover:text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 min-w-9 min-h-9 flex items-center justify-center",
386
+ onClick: onClose
387
+ },
388
+ /* @__PURE__ */ React5__default.default.createElement(lucideReact.X, { size: 20 })
389
+ ),
390
+ /* @__PURE__ */ React5__default.default.createElement("div", { className: "px-6 pt-6 pb-4 text-center border-b border-gray-100" }, /* @__PURE__ */ React5__default.default.createElement("h2", { className: "text-2xl font-semibold text-gray-800 mb-2" }, "\u91CD\u7F6E\u5BC6\u7801"), /* @__PURE__ */ React5__default.default.createElement("p", { className: "text-gray-500 text-sm" }, "\u8BF7\u8F93\u5165\u624B\u673A\u53F7\u548C\u9A8C\u8BC1\u7801\u91CD\u7F6E\u5BC6\u7801")),
391
+ /* @__PURE__ */ React5__default.default.createElement("form", { onSubmit: handleSubmit, className: "p-6" }, /* @__PURE__ */ React5__default.default.createElement("div", { className: "mb-5" }, /* @__PURE__ */ React5__default.default.createElement("label", { htmlFor: "phone", className: "block mb-1.5 text-sm font-medium text-gray-700" }, "\u624B\u673A\u53F7"), /* @__PURE__ */ React5__default.default.createElement("div", { className: "relative flex items-center" }, /* @__PURE__ */ React5__default.default.createElement(lucideReact.Phone, { size: 18, className: "absolute left-4 text-gray-400 z-[1] pointer-events-none" }), /* @__PURE__ */ React5__default.default.createElement(
392
+ "input",
393
+ {
394
+ id: "phone",
395
+ name: "phone",
396
+ type: "tel",
397
+ value: formData.phone,
398
+ onChange: handleInputChange,
399
+ placeholder: "\u8BF7\u8F93\u5165\u624B\u673A\u53F7",
400
+ className: "w-full py-3 px-4 pl-12 border-2 border-gray-200 rounded-lg text-base transition-all box-border min-h-12 bg-white focus:outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500/10 placeholder:text-gray-400 disabled:bg-gray-50 disabled:text-gray-500 disabled:cursor-not-allowed",
401
+ disabled: loading
402
+ }
403
+ ))), /* @__PURE__ */ React5__default.default.createElement("div", { className: "mb-5" }, /* @__PURE__ */ React5__default.default.createElement("label", { htmlFor: "verificationCode", className: "block mb-1.5 text-sm font-medium text-gray-700" }, "\u9A8C\u8BC1\u7801"), /* @__PURE__ */ React5__default.default.createElement("div", { className: "relative flex items-center" }, /* @__PURE__ */ React5__default.default.createElement(
404
+ "input",
405
+ {
406
+ id: "verificationCode",
407
+ name: "verificationCode",
408
+ type: "text",
409
+ value: formData.verificationCode,
410
+ onChange: handleInputChange,
411
+ placeholder: "\u8BF7\u8F93\u5165\u9A8C\u8BC1\u7801",
412
+ className: "w-full py-3 px-4 pr-28 border-2 border-gray-200 rounded-lg text-base transition-all box-border min-h-12 bg-white focus:outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500/10 placeholder:text-gray-400 disabled:bg-gray-50 disabled:text-gray-500 disabled:cursor-not-allowed",
413
+ disabled: loading,
414
+ maxLength: 6
415
+ }
416
+ ), /* @__PURE__ */ React5__default.default.createElement(
417
+ "button",
418
+ {
419
+ type: "button",
420
+ className: "absolute right-2 top-1/2 -translate-y-1/2 bg-transparent border-none text-blue-500 text-sm font-medium cursor-pointer px-2 py-1 rounded transition-all whitespace-nowrap hover:bg-blue-50 disabled:text-gray-400 disabled:cursor-not-allowed",
421
+ onClick: handleSendCode,
422
+ disabled: loading || countdown > 0
423
+ },
424
+ countdown > 0 ? `${countdown}\u79D2\u540E\u91CD\u8BD5` : "\u53D1\u9001\u9A8C\u8BC1\u7801"
425
+ ))), /* @__PURE__ */ React5__default.default.createElement("div", { className: "mb-5" }, /* @__PURE__ */ React5__default.default.createElement("label", { htmlFor: "newPassword", className: "block mb-1.5 text-sm font-medium text-gray-700" }, "\u65B0\u5BC6\u7801"), /* @__PURE__ */ React5__default.default.createElement("div", { className: "relative flex items-center" }, /* @__PURE__ */ React5__default.default.createElement(lucideReact.Lock, { size: 18, className: "absolute left-4 text-gray-400 z-[1] pointer-events-none" }), /* @__PURE__ */ React5__default.default.createElement(
426
+ "input",
427
+ {
428
+ id: "newPassword",
429
+ name: "newPassword",
430
+ type: showPassword ? "text" : "password",
431
+ value: formData.newPassword,
432
+ onChange: handleInputChange,
433
+ placeholder: "\u8BF7\u8F93\u5165\u65B0\u5BC6\u7801",
434
+ className: "w-full py-3 px-4 pl-12 pr-12 border-2 border-gray-200 rounded-lg text-base transition-all box-border min-h-12 bg-white focus:outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500/10 placeholder:text-gray-400 disabled:bg-gray-50 disabled:text-gray-500 disabled:cursor-not-allowed",
435
+ disabled: loading
436
+ }
437
+ ), /* @__PURE__ */ React5__default.default.createElement(
438
+ "button",
439
+ {
440
+ type: "button",
441
+ className: "absolute right-4 bg-transparent border-none text-gray-400 cursor-pointer p-1 rounded transition-all flex items-center justify-center min-w-6 min-h-6 hover:text-gray-600 hover:bg-gray-100 disabled:cursor-not-allowed disabled:opacity-50",
442
+ onClick: () => setShowPassword(!showPassword),
443
+ disabled: loading
444
+ },
445
+ showPassword ? /* @__PURE__ */ React5__default.default.createElement(lucideReact.EyeOff, { size: 18 }) : /* @__PURE__ */ React5__default.default.createElement(lucideReact.Eye, { size: 18 })
446
+ ))), /* @__PURE__ */ React5__default.default.createElement("div", { className: "mb-5" }, /* @__PURE__ */ React5__default.default.createElement("label", { htmlFor: "confirmPassword", className: "block mb-1.5 text-sm font-medium text-gray-700" }, "\u786E\u8BA4\u5BC6\u7801"), /* @__PURE__ */ React5__default.default.createElement("div", { className: "relative flex items-center" }, /* @__PURE__ */ React5__default.default.createElement(lucideReact.Lock, { size: 18, className: "absolute left-4 text-gray-400 z-[1] pointer-events-none" }), /* @__PURE__ */ React5__default.default.createElement(
447
+ "input",
448
+ {
449
+ id: "confirmPassword",
450
+ name: "confirmPassword",
451
+ type: showConfirmPassword ? "text" : "password",
452
+ value: formData.confirmPassword,
453
+ onChange: handleInputChange,
454
+ placeholder: "\u8BF7\u518D\u6B21\u8F93\u5165\u65B0\u5BC6\u7801",
455
+ className: "w-full py-3 px-4 pl-12 pr-12 border-2 border-gray-200 rounded-lg text-base transition-all box-border min-h-12 bg-white focus:outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500/10 placeholder:text-gray-400 disabled:bg-gray-50 disabled:text-gray-500 disabled:cursor-not-allowed",
456
+ disabled: loading
457
+ }
458
+ ), /* @__PURE__ */ React5__default.default.createElement(
459
+ "button",
460
+ {
461
+ type: "button",
462
+ className: "absolute right-4 bg-transparent border-none text-gray-400 cursor-pointer p-1 rounded transition-all flex items-center justify-center min-w-6 min-h-6 hover:text-gray-600 hover:bg-gray-100 disabled:cursor-not-allowed disabled:opacity-50",
463
+ onClick: () => setShowConfirmPassword(!showConfirmPassword),
464
+ disabled: loading
465
+ },
466
+ showConfirmPassword ? /* @__PURE__ */ React5__default.default.createElement(lucideReact.EyeOff, { size: 18 }) : /* @__PURE__ */ React5__default.default.createElement(lucideReact.Eye, { size: 18 })
467
+ ))), error && /* @__PURE__ */ React5__default.default.createElement("div", { className: "text-red-500 text-sm my-4 p-3 bg-red-50 border border-red-200 rounded-lg leading-relaxed" }, error), /* @__PURE__ */ React5__default.default.createElement(
468
+ "button",
469
+ {
470
+ type: "submit",
471
+ className: "w-full bg-blue-500 text-white border-none py-3.5 px-6 rounded-lg text-base font-medium cursor-pointer transition-all mt-2 min-h-[52px] hover:bg-blue-600 hover:-translate-y-0.5 hover:shadow-lg hover:shadow-blue-500/30 active:translate-y-0 disabled:bg-gray-400 disabled:cursor-not-allowed disabled:transform-none disabled:shadow-none focus:outline-none focus:ring-2 focus:ring-blue-700 focus:ring-offset-2",
472
+ disabled: loading
473
+ },
474
+ loading ? "\u63D0\u4EA4\u4E2D..." : "\u91CD\u7F6E\u5BC6\u7801"
475
+ ))
476
+ )
477
+ );
478
+ return reactDom.createPortal(modalContent, document.body);
479
+ }
480
+
481
+ // src/business/auth-legacy/components/LoginModal.tsx
482
+ function LoginModal({ isOpen, onClose, onSuccess, onSwitchToRegister }) {
483
+ const { login } = useAuth();
484
+ const [formData, setFormData] = React5.useState({
485
+ phone: "",
486
+ password: ""
487
+ });
488
+ const [showPassword, setShowPassword] = React5.useState(false);
489
+ const [error, setError] = React5.useState("");
490
+ const [loading, setLoading] = React5.useState(false);
491
+ const [showForgotPassword, setShowForgotPassword] = React5.useState(false);
492
+ const [mounted, setMounted] = React5.useState(false);
493
+ React5.useEffect(() => {
494
+ setMounted(true);
495
+ }, []);
496
+ const fillDemoAccount = (type) => {
497
+ if (process.env.NODE_ENV === "development") {
498
+ const accounts = {
499
+ admin: { phone: "13800138000", password: "admin123456" },
500
+ user: { phone: "13900139000", password: "test123456" }
501
+ };
502
+ setFormData(accounts[type]);
503
+ setError("");
504
+ }
505
+ };
506
+ const handleInputChange = (e) => {
507
+ const { name, value } = e.target;
508
+ setFormData((prev) => ({ ...prev, [name]: value }));
509
+ if (error) setError("");
510
+ };
511
+ const handleSubmit = async (e) => {
512
+ e.preventDefault();
513
+ setError("");
514
+ setLoading(true);
515
+ console.log("\u{1F504} [LoginModal] handleSubmit \u5F00\u59CB");
516
+ try {
517
+ if (!formData.phone || !formData.password) {
518
+ console.log("\u274C [LoginModal] \u524D\u7AEF\u9A8C\u8BC1\u5931\u8D25: \u4FE1\u606F\u4E0D\u5B8C\u6574");
519
+ setError("\u8BF7\u586B\u5199\u5B8C\u6574\u4FE1\u606F");
520
+ return;
521
+ }
522
+ if (!validatePhoneNumber(formData.phone)) {
523
+ console.log("\u274C [LoginModal] \u524D\u7AEF\u9A8C\u8BC1\u5931\u8D25: \u624B\u673A\u53F7\u683C\u5F0F\u9519\u8BEF");
524
+ setError("\u8BF7\u8F93\u5165\u6B63\u786E\u7684\u624B\u673A\u53F7");
525
+ return;
526
+ }
527
+ console.log("\u2705 [LoginModal] \u524D\u7AEF\u9A8C\u8BC1\u901A\u8FC7");
528
+ console.log("\u{1F511} [LoginModal] \u63D0\u4EA4\u767B\u5F55\u8868\u5355:", {
529
+ phone: formData.phone,
530
+ password: "***"
531
+ });
532
+ console.log("\u{1F4DE} [LoginModal] \u51C6\u5907\u8C03\u7528 useAuth.login()...");
533
+ const result = await login(formData);
534
+ console.log("\u{1F4E1} [LoginModal] useAuth.login() \u8FD4\u56DE\u7ED3\u679C:", result);
535
+ if (result.success) {
536
+ console.log("\u2705 [LoginModal] \u767B\u5F55\u6210\u529F\uFF0C\u51C6\u5907\u8C03\u7528 onSuccess()");
537
+ console.log("\u{1F464} [LoginModal] \u767B\u5F55\u6210\u529F\u7684\u7528\u6237\u4FE1\u606F:", result.user);
538
+ setTimeout(() => {
539
+ console.log("\u{1F3AF} [LoginModal] \u8C03\u7528 onSuccess \u56DE\u8C03");
540
+ onSuccess();
541
+ console.log("\u{1F3C1} [LoginModal] onSuccess \u8C03\u7528\u5B8C\u6210");
542
+ }, 100);
543
+ } else {
544
+ console.log("\u274C [LoginModal] \u767B\u5F55\u5931\u8D25:", result.message);
545
+ setError(result.message || "\u767B\u5F55\u5931\u8D25");
546
+ }
547
+ } catch (error2) {
548
+ console.error("\u{1F4A5} [LoginModal] \u767B\u5F55\u5F02\u5E38:", error2);
549
+ setError("\u767B\u5F55\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5");
550
+ } finally {
551
+ console.log("\u{1F51A} [LoginModal] handleSubmit \u7ED3\u675F\uFF0C\u8BBE\u7F6E loading = false");
552
+ setLoading(false);
553
+ }
554
+ };
555
+ const handleOverlayClick = (e) => {
556
+ if (e.target === e.currentTarget) {
557
+ onClose();
558
+ }
559
+ };
560
+ if (!isOpen || !mounted) return null;
561
+ const modalContent = /* @__PURE__ */ React5__default.default.createElement(React5__default.default.Fragment, null, /* @__PURE__ */ React5__default.default.createElement(
562
+ "div",
563
+ {
564
+ className: "fixed top-0 left-0 right-0 bottom-0 w-screen h-screen bg-black/50 backdrop-blur-sm flex items-center justify-center z-[9999] p-4",
565
+ style: { margin: 0 },
566
+ onClick: handleOverlayClick
567
+ },
568
+ /* @__PURE__ */ React5__default.default.createElement(
569
+ "div",
570
+ {
571
+ className: "bg-white rounded-2xl shadow-xl w-full max-w-[420px] max-h-[90vh] overflow-y-auto relative",
572
+ onClick: (e) => e.stopPropagation()
573
+ },
574
+ /* @__PURE__ */ React5__default.default.createElement(
575
+ "button",
576
+ {
577
+ className: "absolute top-5 right-5 bg-transparent border-none text-gray-500 cursor-pointer p-2 rounded-lg transition-all hover:bg-gray-100 hover:text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 min-w-9 min-h-9 flex items-center justify-center",
578
+ onClick: onClose
579
+ },
580
+ /* @__PURE__ */ React5__default.default.createElement(lucideReact.X, { size: 20 })
581
+ ),
582
+ /* @__PURE__ */ React5__default.default.createElement("div", { className: "px-6 pt-6 pb-4 text-center border-b border-gray-100" }, /* @__PURE__ */ React5__default.default.createElement("h2", { className: "text-2xl font-semibold text-gray-800 mb-2" }, "\u7528\u6237\u767B\u5F55"), /* @__PURE__ */ React5__default.default.createElement("p", { className: "text-gray-500 text-sm" }, "\u8BF7\u8F93\u5165\u60A8\u7684\u624B\u673A\u53F7\u548C\u5BC6\u7801")),
583
+ process.env.NODE_ENV === "development" && /* @__PURE__ */ React5__default.default.createElement("div", { className: "px-6 py-4 bg-amber-50 border-b border-gray-100" }, /* @__PURE__ */ React5__default.default.createElement("p", { className: "text-xs text-amber-800 font-medium mb-2" }, "\u5F00\u53D1\u73AF\u5883\u5FEB\u6377\u767B\u5F55\uFF1A"), /* @__PURE__ */ React5__default.default.createElement("div", { className: "flex gap-2" }, /* @__PURE__ */ React5__default.default.createElement(
584
+ "button",
585
+ {
586
+ type: "button",
587
+ onClick: () => fillDemoAccount("admin"),
588
+ className: "px-3 py-1.5 text-xs bg-amber-400 text-amber-900 border-none rounded-md cursor-pointer transition-all font-medium hover:bg-amber-500"
589
+ },
590
+ "\u7BA1\u7406\u5458\u8D26\u53F7"
591
+ ), /* @__PURE__ */ React5__default.default.createElement(
592
+ "button",
593
+ {
594
+ type: "button",
595
+ onClick: () => fillDemoAccount("user"),
596
+ className: "px-3 py-1.5 text-xs bg-amber-400 text-amber-900 border-none rounded-md cursor-pointer transition-all font-medium hover:bg-amber-500"
597
+ },
598
+ "\u7528\u6237\u8D26\u53F7"
599
+ ))),
600
+ /* @__PURE__ */ React5__default.default.createElement("form", { onSubmit: handleSubmit, className: "p-6" }, /* @__PURE__ */ React5__default.default.createElement("div", { className: "mb-5" }, /* @__PURE__ */ React5__default.default.createElement("label", { htmlFor: "phone", className: "block mb-1.5 text-sm font-medium text-gray-700" }, "\u624B\u673A\u53F7"), /* @__PURE__ */ React5__default.default.createElement("div", { className: "relative flex items-center" }, /* @__PURE__ */ React5__default.default.createElement(lucideReact.User, { size: 18, className: "absolute left-4 text-gray-400 z-[1] pointer-events-none" }), /* @__PURE__ */ React5__default.default.createElement(
601
+ "input",
602
+ {
603
+ id: "phone",
604
+ name: "phone",
605
+ type: "tel",
606
+ autoComplete: "tel",
607
+ value: formData.phone,
608
+ onChange: handleInputChange,
609
+ placeholder: "\u8BF7\u8F93\u5165\u624B\u673A\u53F7",
610
+ className: "w-full py-3 px-4 pl-12 border-2 border-gray-200 rounded-lg text-base transition-all box-border min-h-12 bg-white focus:outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500/10 placeholder:text-gray-400 disabled:bg-gray-50 disabled:text-gray-500 disabled:cursor-not-allowed",
611
+ disabled: loading
612
+ }
613
+ ))), /* @__PURE__ */ React5__default.default.createElement("div", { className: "mb-5" }, /* @__PURE__ */ React5__default.default.createElement("label", { htmlFor: "password", className: "block mb-1.5 text-sm font-medium text-gray-700" }, "\u5BC6\u7801"), /* @__PURE__ */ React5__default.default.createElement("div", { className: "relative flex items-center" }, /* @__PURE__ */ React5__default.default.createElement(lucideReact.Lock, { size: 18, className: "absolute left-4 text-gray-400 z-[1] pointer-events-none" }), /* @__PURE__ */ React5__default.default.createElement(
614
+ "input",
615
+ {
616
+ id: "password",
617
+ name: "password",
618
+ type: showPassword ? "text" : "password",
619
+ autoComplete: "current-password",
620
+ value: formData.password,
621
+ onChange: handleInputChange,
622
+ placeholder: "\u8BF7\u8F93\u5165\u5BC6\u7801",
623
+ className: "w-full py-3 px-4 pl-12 pr-12 border-2 border-gray-200 rounded-lg text-base transition-all box-border min-h-12 bg-white focus:outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500/10 placeholder:text-gray-400 disabled:bg-gray-50 disabled:text-gray-500 disabled:cursor-not-allowed",
624
+ disabled: loading
625
+ }
626
+ ), /* @__PURE__ */ React5__default.default.createElement(
627
+ "button",
628
+ {
629
+ type: "button",
630
+ className: "absolute right-4 bg-transparent border-none text-gray-400 cursor-pointer p-1 rounded transition-all flex items-center justify-center min-w-6 min-h-6 hover:text-gray-600 hover:bg-gray-100 disabled:cursor-not-allowed disabled:opacity-50",
631
+ onClick: () => setShowPassword(!showPassword),
632
+ disabled: loading
633
+ },
634
+ showPassword ? /* @__PURE__ */ React5__default.default.createElement(lucideReact.EyeOff, { size: 18 }) : /* @__PURE__ */ React5__default.default.createElement(lucideReact.Eye, { size: 18 })
635
+ ))), /* @__PURE__ */ React5__default.default.createElement("div", { className: "text-right -mt-2 mb-4" }, /* @__PURE__ */ React5__default.default.createElement(
636
+ "button",
637
+ {
638
+ type: "button",
639
+ onClick: () => setShowForgotPassword(true),
640
+ className: "bg-transparent border-none text-blue-500 cursor-pointer text-sm font-medium underline px-1 py-0.5 rounded transition-all hover:text-blue-600 hover:bg-blue-50 hover:no-underline focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
641
+ },
642
+ "\u5FD8\u8BB0\u5BC6\u7801\uFF1F"
643
+ )), error && /* @__PURE__ */ React5__default.default.createElement("div", { className: "text-red-500 text-sm my-4 p-3 bg-red-50 border border-red-200 rounded-lg leading-relaxed" }, error), /* @__PURE__ */ React5__default.default.createElement(
644
+ "button",
645
+ {
646
+ type: "submit",
647
+ className: "w-full bg-blue-500 text-white border-none py-3.5 px-6 rounded-lg text-base font-medium cursor-pointer transition-all mt-2 min-h-[52px] hover:bg-blue-600 hover:-translate-y-0.5 hover:shadow-lg hover:shadow-blue-500/30 active:translate-y-0 disabled:bg-gray-400 disabled:cursor-not-allowed disabled:transform-none disabled:shadow-none focus:outline-none focus:ring-2 focus:ring-blue-700 focus:ring-offset-2",
648
+ disabled: loading
649
+ },
650
+ loading ? "\u767B\u5F55\u4E2D..." : "\u767B\u5F55"
651
+ ), onSwitchToRegister && /* @__PURE__ */ React5__default.default.createElement("div", { className: "text-center mt-5 pt-4 border-t border-gray-100" }, /* @__PURE__ */ React5__default.default.createElement("span", { className: "text-gray-500 text-sm mr-1" }, "\u8FD8\u6CA1\u6709\u8D26\u53F7\uFF1F"), /* @__PURE__ */ React5__default.default.createElement(
652
+ "button",
653
+ {
654
+ type: "button",
655
+ onClick: onSwitchToRegister,
656
+ className: "bg-transparent border-none text-blue-500 cursor-pointer text-sm font-medium underline px-1 py-0.5 rounded transition-all hover:text-blue-600 hover:bg-blue-50 hover:no-underline focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
657
+ },
658
+ "\u7ACB\u5373\u6CE8\u518C"
659
+ )))
660
+ )
661
+ ), /* @__PURE__ */ React5__default.default.createElement(
662
+ ForgotPasswordModal,
663
+ {
664
+ isOpen: showForgotPassword,
665
+ onClose: () => setShowForgotPassword(false),
666
+ onSuccess: () => {
667
+ setShowForgotPassword(false);
668
+ onSuccess();
669
+ }
670
+ }
671
+ ));
672
+ return reactDom.createPortal(modalContent, document.body);
673
+ }
674
+
675
+ // src/business/auth-legacy/components/AuthGuard.tsx
676
+ function AuthGuard({
677
+ children,
678
+ fallback,
679
+ requireAuth = true
680
+ }) {
681
+ const { isAuthenticated, loading, refreshUser } = useAuth();
682
+ const [showLoginModal, setShowLoginModal] = React5.useState(false);
683
+ React5.useEffect(() => {
684
+ if (!loading && requireAuth && !isAuthenticated) {
685
+ setShowLoginModal(true);
686
+ }
687
+ }, [loading, requireAuth, isAuthenticated]);
688
+ const handleLoginSuccess = () => {
689
+ refreshUser();
690
+ setShowLoginModal(false);
691
+ };
692
+ if (loading) {
693
+ return /* @__PURE__ */ React5__default.default.createElement("div", { style: {
694
+ display: "flex",
695
+ alignItems: "center",
696
+ justifyContent: "center",
697
+ minHeight: "200px",
698
+ color: "#6b7280",
699
+ flexDirection: "column",
700
+ gap: "12px"
701
+ } }, /* @__PURE__ */ React5__default.default.createElement("div", { style: {
702
+ width: "32px",
703
+ height: "32px",
704
+ border: "3px solid #e5e7eb",
705
+ borderTop: "3px solid #3b82f6",
706
+ borderRadius: "50%",
707
+ animation: "spin 1s linear infinite"
708
+ } }), /* @__PURE__ */ React5__default.default.createElement("div", null, "\u9A8C\u8BC1\u767B\u5F55\u72B6\u6001..."));
709
+ }
710
+ if (requireAuth && !isAuthenticated) {
711
+ return /* @__PURE__ */ React5__default.default.createElement(React5__default.default.Fragment, null, fallback || /* @__PURE__ */ React5__default.default.createElement("div", { style: {
712
+ display: "flex",
713
+ alignItems: "center",
714
+ justifyContent: "center",
715
+ minHeight: "200px",
716
+ color: "#6b7280",
717
+ flexDirection: "column",
718
+ gap: "16px",
719
+ padding: "24px",
720
+ textAlign: "center"
721
+ } }, /* @__PURE__ */ React5__default.default.createElement("div", { style: {
722
+ fontSize: "48px",
723
+ opacity: 0.5
724
+ } }, "\u{1F512}"), /* @__PURE__ */ React5__default.default.createElement("div", { style: {
725
+ fontSize: "18px",
726
+ fontWeight: "500"
727
+ } }, "\u8BF7\u5148\u767B\u5F55\u4EE5\u8BBF\u95EE\u6B64\u9875\u9762"), /* @__PURE__ */ React5__default.default.createElement("div", { style: {
728
+ fontSize: "14px",
729
+ opacity: 0.7
730
+ } }, "\u767B\u5F55\u540E\u5373\u53EF\u67E5\u770B\u76F8\u5173\u5185\u5BB9")), /* @__PURE__ */ React5__default.default.createElement(
731
+ LoginModal,
732
+ {
733
+ isOpen: showLoginModal,
734
+ onClose: () => setShowLoginModal(false),
735
+ onSuccess: handleLoginSuccess
736
+ }
737
+ ));
738
+ }
739
+ return /* @__PURE__ */ React5__default.default.createElement(React5__default.default.Fragment, null, children);
740
+ }
741
+ function RegisterModal({ isOpen, onClose, onSuccess, onSwitchToLogin }) {
742
+ const { register } = useAuth();
743
+ const [formData, setFormData] = React5.useState({
744
+ phone: "",
745
+ password: "",
746
+ confirmPassword: "",
747
+ name: ""
748
+ });
749
+ const [showPassword, setShowPassword] = React5.useState(false);
750
+ const [showConfirmPassword, setShowConfirmPassword] = React5.useState(false);
751
+ const [error, setError] = React5.useState("");
752
+ const [loading, setLoading] = React5.useState(false);
753
+ const [mounted, setMounted] = React5.useState(false);
754
+ React5.useEffect(() => {
755
+ setMounted(true);
756
+ }, []);
757
+ const handleInputChange = (e) => {
758
+ const { name, value } = e.target;
759
+ setFormData((prev) => ({ ...prev, [name]: value }));
760
+ if (error) setError("");
761
+ };
762
+ const validateForm = () => {
763
+ if (!formData.phone || !formData.password || !formData.confirmPassword) {
764
+ setError("\u8BF7\u586B\u5199\u5FC5\u8981\u4FE1\u606F");
765
+ return false;
766
+ }
767
+ if (!validatePhoneNumber(formData.phone)) {
768
+ setError("\u8BF7\u8F93\u5165\u6B63\u786E\u7684\u624B\u673A\u53F7\u683C\u5F0F");
769
+ return false;
770
+ }
771
+ const passwordValidation = validatePassword(formData.password);
772
+ if (!passwordValidation.valid) {
773
+ setError(passwordValidation.message || "\u5BC6\u7801\u683C\u5F0F\u9519\u8BEF");
774
+ return false;
775
+ }
776
+ if (formData.password !== formData.confirmPassword) {
777
+ setError("\u4E24\u6B21\u8F93\u5165\u7684\u5BC6\u7801\u4E0D\u4E00\u81F4");
778
+ return false;
779
+ }
780
+ return true;
781
+ };
782
+ const handleSubmit = async (e) => {
783
+ e.preventDefault();
784
+ setError("");
785
+ setLoading(true);
786
+ try {
787
+ if (!validateForm()) {
788
+ return;
789
+ }
790
+ console.log("\u{1F4DD} [RegisterModal] \u63D0\u4EA4\u6CE8\u518C\u8868\u5355:", {
791
+ phone: formData.phone,
792
+ name: formData.name || "\u672A\u8BBE\u7F6E",
793
+ password: "***"
794
+ });
795
+ const result = await register({
796
+ phone: formData.phone,
797
+ password: formData.password,
798
+ name: formData.name || void 0
799
+ });
800
+ if (result.success) {
801
+ console.log("\u2705 [RegisterModal] \u6CE8\u518C\u6210\u529F");
802
+ onSuccess();
803
+ } else {
804
+ console.log("\u274C [RegisterModal] \u6CE8\u518C\u5931\u8D25:", result.message);
805
+ setError(result.message || "\u6CE8\u518C\u5931\u8D25");
806
+ }
807
+ } catch (error2) {
808
+ console.error("\u{1F4A5} [RegisterModal] \u6CE8\u518C\u5F02\u5E38:", error2);
809
+ setError("\u6CE8\u518C\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5");
810
+ } finally {
811
+ setLoading(false);
812
+ }
813
+ };
814
+ const handleOverlayClick = (e) => {
815
+ if (e.target === e.currentTarget) {
816
+ onClose();
817
+ }
818
+ };
819
+ if (!isOpen || !mounted) return null;
820
+ const modalContent = /* @__PURE__ */ React5__default.default.createElement(
821
+ "div",
822
+ {
823
+ className: "fixed top-0 left-0 right-0 bottom-0 w-screen h-screen bg-black/50 backdrop-blur-sm flex items-center justify-center z-[9999] p-4",
824
+ style: { margin: 0 },
825
+ onClick: handleOverlayClick
826
+ },
827
+ /* @__PURE__ */ React5__default.default.createElement(
828
+ "div",
829
+ {
830
+ className: "bg-white rounded-2xl shadow-xl w-full max-w-[420px] max-h-[90vh] overflow-y-auto relative",
831
+ onClick: (e) => e.stopPropagation()
832
+ },
833
+ /* @__PURE__ */ React5__default.default.createElement(
834
+ "button",
835
+ {
836
+ className: "absolute top-5 right-5 bg-transparent border-none text-gray-500 cursor-pointer p-2 rounded-lg transition-all hover:bg-gray-100 hover:text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 min-w-9 min-h-9 flex items-center justify-center",
837
+ onClick: onClose
838
+ },
839
+ /* @__PURE__ */ React5__default.default.createElement(lucideReact.X, { size: 20 })
840
+ ),
841
+ /* @__PURE__ */ React5__default.default.createElement("div", { className: "px-6 pt-6 pb-4 text-center border-b border-gray-100" }, /* @__PURE__ */ React5__default.default.createElement("h2", { className: "text-2xl font-semibold text-gray-800 mb-2" }, "\u7528\u6237\u6CE8\u518C"), /* @__PURE__ */ React5__default.default.createElement("p", { className: "text-gray-500 text-sm" }, "\u8BF7\u586B\u5199\u4EE5\u4E0B\u4FE1\u606F\u521B\u5EFA\u8D26\u6237")),
842
+ /* @__PURE__ */ React5__default.default.createElement("form", { onSubmit: handleSubmit, className: "p-6" }, /* @__PURE__ */ React5__default.default.createElement("div", { className: "mb-5" }, /* @__PURE__ */ React5__default.default.createElement("label", { htmlFor: "phone", className: "block mb-1.5 text-sm font-medium text-gray-700" }, "\u624B\u673A\u53F7 *"), /* @__PURE__ */ React5__default.default.createElement("div", { className: "relative flex items-center" }, /* @__PURE__ */ React5__default.default.createElement(lucideReact.Phone, { size: 18, className: "absolute left-4 text-gray-400 z-[1] pointer-events-none" }), /* @__PURE__ */ React5__default.default.createElement(
843
+ "input",
844
+ {
845
+ id: "phone",
846
+ name: "phone",
847
+ type: "tel",
848
+ autoComplete: "tel",
849
+ value: formData.phone,
850
+ onChange: handleInputChange,
851
+ placeholder: "\u8BF7\u8F93\u5165\u624B\u673A\u53F7",
852
+ className: "w-full py-3 px-4 pl-12 border-2 border-gray-200 rounded-lg text-base transition-all box-border min-h-12 bg-white focus:outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500/10 placeholder:text-gray-400 disabled:bg-gray-50 disabled:text-gray-500 disabled:cursor-not-allowed",
853
+ disabled: loading
854
+ }
855
+ ))), /* @__PURE__ */ React5__default.default.createElement("div", { className: "mb-5" }, /* @__PURE__ */ React5__default.default.createElement("label", { htmlFor: "name", className: "block mb-1.5 text-sm font-medium text-gray-700" }, "\u59D3\u540D"), /* @__PURE__ */ React5__default.default.createElement("div", { className: "relative flex items-center" }, /* @__PURE__ */ React5__default.default.createElement(lucideReact.User, { size: 18, className: "absolute left-4 text-gray-400 z-[1] pointer-events-none" }), /* @__PURE__ */ React5__default.default.createElement(
856
+ "input",
857
+ {
858
+ id: "name",
859
+ name: "name",
860
+ type: "text",
861
+ autoComplete: "name",
862
+ value: formData.name,
863
+ onChange: handleInputChange,
864
+ placeholder: "\u8BF7\u8F93\u5165\u59D3\u540D\uFF08\u53EF\u9009\uFF09",
865
+ className: "w-full py-3 px-4 pl-12 border-2 border-gray-200 rounded-lg text-base transition-all box-border min-h-12 bg-white focus:outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500/10 placeholder:text-gray-400 disabled:bg-gray-50 disabled:text-gray-500 disabled:cursor-not-allowed",
866
+ disabled: loading
867
+ }
868
+ ))), /* @__PURE__ */ React5__default.default.createElement("div", { className: "mb-5" }, /* @__PURE__ */ React5__default.default.createElement("label", { htmlFor: "password", className: "block mb-1.5 text-sm font-medium text-gray-700" }, "\u5BC6\u7801 *"), /* @__PURE__ */ React5__default.default.createElement("div", { className: "relative flex items-center" }, /* @__PURE__ */ React5__default.default.createElement(lucideReact.Lock, { size: 18, className: "absolute left-4 text-gray-400 z-[1] pointer-events-none" }), /* @__PURE__ */ React5__default.default.createElement(
869
+ "input",
870
+ {
871
+ id: "password",
872
+ name: "password",
873
+ type: showPassword ? "text" : "password",
874
+ autoComplete: "new-password",
875
+ value: formData.password,
876
+ onChange: handleInputChange,
877
+ placeholder: "\u8BF7\u8F93\u5165\u5BC6\u7801\uFF08\u81F3\u5C116\u4F4D\uFF09",
878
+ className: "w-full py-3 px-4 pl-12 pr-12 border-2 border-gray-200 rounded-lg text-base transition-all box-border min-h-12 bg-white focus:outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500/10 placeholder:text-gray-400 disabled:bg-gray-50 disabled:text-gray-500 disabled:cursor-not-allowed",
879
+ disabled: loading
880
+ }
881
+ ), /* @__PURE__ */ React5__default.default.createElement(
882
+ "button",
883
+ {
884
+ type: "button",
885
+ className: "absolute right-4 bg-transparent border-none text-gray-400 cursor-pointer p-1 rounded transition-all flex items-center justify-center min-w-6 min-h-6 hover:text-gray-600 hover:bg-gray-100 disabled:cursor-not-allowed disabled:opacity-50",
886
+ onClick: () => setShowPassword(!showPassword),
887
+ disabled: loading
888
+ },
889
+ showPassword ? /* @__PURE__ */ React5__default.default.createElement(lucideReact.EyeOff, { size: 18 }) : /* @__PURE__ */ React5__default.default.createElement(lucideReact.Eye, { size: 18 })
890
+ ))), /* @__PURE__ */ React5__default.default.createElement("div", { className: "mb-5" }, /* @__PURE__ */ React5__default.default.createElement("label", { htmlFor: "confirmPassword", className: "block mb-1.5 text-sm font-medium text-gray-700" }, "\u786E\u8BA4\u5BC6\u7801 *"), /* @__PURE__ */ React5__default.default.createElement("div", { className: "relative flex items-center" }, /* @__PURE__ */ React5__default.default.createElement(lucideReact.Lock, { size: 18, className: "absolute left-4 text-gray-400 z-[1] pointer-events-none" }), /* @__PURE__ */ React5__default.default.createElement(
891
+ "input",
892
+ {
893
+ id: "confirmPassword",
894
+ name: "confirmPassword",
895
+ type: showConfirmPassword ? "text" : "password",
896
+ autoComplete: "new-password",
897
+ value: formData.confirmPassword,
898
+ onChange: handleInputChange,
899
+ placeholder: "\u8BF7\u518D\u6B21\u8F93\u5165\u5BC6\u7801",
900
+ className: "w-full py-3 px-4 pl-12 pr-12 border-2 border-gray-200 rounded-lg text-base transition-all box-border min-h-12 bg-white focus:outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-500/10 placeholder:text-gray-400 disabled:bg-gray-50 disabled:text-gray-500 disabled:cursor-not-allowed",
901
+ disabled: loading
902
+ }
903
+ ), /* @__PURE__ */ React5__default.default.createElement(
904
+ "button",
905
+ {
906
+ type: "button",
907
+ className: "absolute right-4 bg-transparent border-none text-gray-400 cursor-pointer p-1 rounded transition-all flex items-center justify-center min-w-6 min-h-6 hover:text-gray-600 hover:bg-gray-100 disabled:cursor-not-allowed disabled:opacity-50",
908
+ onClick: () => setShowConfirmPassword(!showConfirmPassword),
909
+ disabled: loading
910
+ },
911
+ showConfirmPassword ? /* @__PURE__ */ React5__default.default.createElement(lucideReact.EyeOff, { size: 18 }) : /* @__PURE__ */ React5__default.default.createElement(lucideReact.Eye, { size: 18 })
912
+ ))), error && /* @__PURE__ */ React5__default.default.createElement("div", { className: "text-red-500 text-sm my-4 p-3 bg-red-50 border border-red-200 rounded-lg leading-relaxed" }, error), /* @__PURE__ */ React5__default.default.createElement(
913
+ "button",
914
+ {
915
+ type: "submit",
916
+ className: "w-full bg-blue-500 text-white border-none py-3.5 px-6 rounded-lg text-base font-medium cursor-pointer transition-all mt-2 min-h-[52px] hover:bg-blue-600 hover:-translate-y-0.5 hover:shadow-lg hover:shadow-blue-500/30 active:translate-y-0 disabled:bg-gray-400 disabled:cursor-not-allowed disabled:transform-none disabled:shadow-none focus:outline-none focus:ring-2 focus:ring-blue-700 focus:ring-offset-2",
917
+ disabled: loading
918
+ },
919
+ loading ? "\u6CE8\u518C\u4E2D..." : "\u6CE8\u518C"
920
+ ), onSwitchToLogin && /* @__PURE__ */ React5__default.default.createElement("div", { className: "text-center mt-5 pt-4 border-t border-gray-100" }, /* @__PURE__ */ React5__default.default.createElement("span", { className: "text-gray-500 text-sm mr-1" }, "\u5DF2\u6709\u8D26\u53F7\uFF1F"), /* @__PURE__ */ React5__default.default.createElement(
921
+ "button",
922
+ {
923
+ type: "button",
924
+ onClick: onSwitchToLogin,
925
+ className: "bg-transparent border-none text-blue-500 cursor-pointer text-sm font-medium underline px-1 py-0.5 rounded transition-all hover:text-blue-600 hover:bg-blue-50 hover:no-underline focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
926
+ },
927
+ "\u7ACB\u5373\u767B\u5F55"
928
+ )))
929
+ )
930
+ );
931
+ return reactDom.createPortal(modalContent, document.body);
932
+ }
933
+
934
+ // src/business/auth-legacy/components/UserMenu.tsx
935
+ function UserMenu({ customMenuItems = [], className }) {
936
+ const { user, isAuthenticated, logout } = useAuth();
937
+ const [isOpen, setIsOpen] = React5.useState(false);
938
+ const [showLoginModal, setShowLoginModal] = React5.useState(false);
939
+ const [showRegisterModal, setShowRegisterModal] = React5.useState(false);
940
+ const toggleMenu = () => {
941
+ setIsOpen(!isOpen);
942
+ };
943
+ const handleMenuClick = (e) => {
944
+ e.stopPropagation();
945
+ };
946
+ const handleCustomMenuClick = (item) => {
947
+ console.log(`\u{1F527} [UserMenu] \u81EA\u5B9A\u4E49\u83DC\u5355\u9879\u88AB\u70B9\u51FB: ${item.label}`);
948
+ item.onClick();
949
+ setIsOpen(false);
950
+ };
951
+ const handleLogin = () => {
952
+ console.log("\u{1F511} [UserMenu] \u767B\u5F55\u6309\u94AE\u88AB\u70B9\u51FB");
953
+ setShowLoginModal(true);
954
+ setIsOpen(false);
955
+ };
956
+ const handleRegister = () => {
957
+ console.log("\u{1F4DD} [UserMenu] \u6CE8\u518C\u6309\u94AE\u88AB\u70B9\u51FB");
958
+ setShowRegisterModal(true);
959
+ setIsOpen(false);
960
+ };
961
+ const handleLogout = async () => {
962
+ console.log("\u{1F6AA} [UserMenu] \u9000\u51FA\u767B\u5F55\u6309\u94AE\u88AB\u70B9\u51FB");
963
+ try {
964
+ await logout();
965
+ console.log("\u2705 [UserMenu] \u9000\u51FA\u767B\u5F55\u6210\u529F");
966
+ } catch (error) {
967
+ console.error("\u9000\u51FA\u767B\u5F55\u5931\u8D25:", error);
968
+ }
969
+ setIsOpen(false);
970
+ };
971
+ const handleAuthSuccess = () => {
972
+ console.log("\u{1F389} [UserMenu] \u8BA4\u8BC1\u6210\u529F\u56DE\u8C03\u88AB\u8C03\u7528");
973
+ console.log("\u{1F464} [UserMenu] \u5F53\u524DuseAuth\u72B6\u6001:", {
974
+ user: user ? `${user.name || "\u672A\u8BBE\u7F6E"} (${user.phone})` : null,
975
+ isAuthenticated
976
+ });
977
+ setShowLoginModal(false);
978
+ setShowRegisterModal(false);
979
+ console.log("\u2705 [UserMenu] \u8BA4\u8BC1\u6210\u529F\u5904\u7406\u5B8C\u6210 - \u6A21\u6001\u6846\u5DF2\u5173\u95ED");
980
+ setTimeout(() => {
981
+ console.log("\u{1F50D} [UserMenu] \u5EF6\u8FDF\u72B6\u6001\u68C0\u67E5:", {
982
+ user: user ? `${user.name || "\u672A\u8BBE\u7F6E"} (${user.phone})` : null,
983
+ isAuthenticated
984
+ });
985
+ }, 500);
986
+ };
987
+ const handleSwitchToRegister = () => {
988
+ console.log("\u{1F504} [UserMenu] \u4ECE\u767B\u5F55\u5207\u6362\u5230\u6CE8\u518C");
989
+ setShowLoginModal(false);
990
+ setShowRegisterModal(true);
991
+ };
992
+ const handleSwitchToLogin = () => {
993
+ console.log("\u{1F504} [UserMenu] \u4ECE\u6CE8\u518C\u5207\u6362\u5230\u767B\u5F55");
994
+ setShowRegisterModal(false);
995
+ setShowLoginModal(true);
996
+ };
997
+ const getVisibleCustomMenuItems = () => {
998
+ return customMenuItems.filter((item) => {
999
+ if (item.requireAuth === true) {
1000
+ return isAuthenticated;
1001
+ }
1002
+ if (item.requireAuth === false) {
1003
+ return !isAuthenticated;
1004
+ }
1005
+ return true;
1006
+ });
1007
+ };
1008
+ React5__default.default.useEffect(() => {
1009
+ const handleGlobalClick = () => {
1010
+ setIsOpen(false);
1011
+ };
1012
+ if (isOpen) {
1013
+ document.addEventListener("click", handleGlobalClick);
1014
+ }
1015
+ return () => {
1016
+ document.removeEventListener("click", handleGlobalClick);
1017
+ };
1018
+ }, [isOpen]);
1019
+ const visibleCustomMenuItems = getVisibleCustomMenuItems();
1020
+ return /* @__PURE__ */ React5__default.default.createElement("div", { className: `relative inline-block ${className || ""}` }, /* @__PURE__ */ React5__default.default.createElement(
1021
+ "button",
1022
+ {
1023
+ className: "flex items-center gap-2 px-3 py-2 bg-slate-50 text-slate-500 border border-slate-200 rounded-lg cursor-pointer transition-all min-w-11 min-h-11 text-sm font-medium hover:bg-slate-200 hover:text-slate-800 focus:outline-none focus:ring-2 focus:ring-blue-500/10",
1024
+ onClick: toggleMenu
1025
+ },
1026
+ /* @__PURE__ */ React5__default.default.createElement(lucideReact.User, { size: 24 }),
1027
+ isAuthenticated && user && /* @__PURE__ */ React5__default.default.createElement("span", { className: "max-w-[120px] overflow-hidden text-ellipsis whitespace-nowrap" }, user.name || user.phone)
1028
+ ), isOpen && /* @__PURE__ */ React5__default.default.createElement(
1029
+ "div",
1030
+ {
1031
+ className: "absolute top-[calc(100%+0.5rem)] right-0 z-[1000] min-w-[200px] bg-white border border-slate-200 rounded-xl shadow-lg max-h-[90vh] overflow-y-auto p-2 animate-in slide-in-from-top-2 fade-in duration-200",
1032
+ onClick: handleMenuClick
1033
+ },
1034
+ isAuthenticated && user ? (
1035
+ // 已登录状态的菜单
1036
+ /* @__PURE__ */ React5__default.default.createElement(React5__default.default.Fragment, null, /* @__PURE__ */ React5__default.default.createElement("div", { className: "p-3 mb-2 bg-slate-50 rounded-lg" }, /* @__PURE__ */ React5__default.default.createElement("div", { className: "text-sm font-semibold text-slate-800 mb-1 overflow-hidden text-ellipsis whitespace-nowrap" }, user.name || "\u672A\u8BBE\u7F6E\u540D\u79F0"), /* @__PURE__ */ React5__default.default.createElement("div", { className: "text-xs text-slate-500 mb-1 overflow-hidden text-ellipsis whitespace-nowrap" }, user.phone), /* @__PURE__ */ React5__default.default.createElement("div", { className: "text-xs text-blue-500 font-medium" }, user.role === "admin" ? "\u7BA1\u7406\u5458" : "\u666E\u901A\u7528\u6237")), visibleCustomMenuItems.length > 0 && /* @__PURE__ */ React5__default.default.createElement(React5__default.default.Fragment, null, /* @__PURE__ */ React5__default.default.createElement("div", { className: "h-px bg-slate-200 my-2" }), visibleCustomMenuItems.map((item) => {
1037
+ const IconComponent = item.icon;
1038
+ return /* @__PURE__ */ React5__default.default.createElement(
1039
+ "button",
1040
+ {
1041
+ key: item.id,
1042
+ className: "flex items-center gap-3 w-full p-3 bg-transparent border-none rounded-lg text-gray-700 text-sm font-medium cursor-pointer transition-all text-left min-h-11 hover:bg-slate-100 hover:text-slate-800 focus:outline-none focus:bg-slate-100 focus:ring-2 focus:ring-blue-500/10",
1043
+ onClick: () => handleCustomMenuClick(item)
1044
+ },
1045
+ IconComponent && /* @__PURE__ */ React5__default.default.createElement(IconComponent, { size: 16 }),
1046
+ /* @__PURE__ */ React5__default.default.createElement("span", null, item.label)
1047
+ );
1048
+ })), /* @__PURE__ */ React5__default.default.createElement("div", { className: "h-px bg-slate-200 my-2" }), /* @__PURE__ */ React5__default.default.createElement(
1049
+ "button",
1050
+ {
1051
+ className: "flex items-center gap-3 w-full p-3 bg-transparent border-none rounded-lg text-gray-700 text-sm font-medium cursor-pointer transition-all text-left min-h-11 hover:bg-red-50 hover:text-red-600 focus:outline-none focus:bg-red-50 focus:text-red-600 focus:ring-2 focus:ring-red-500/10",
1052
+ onClick: handleLogout
1053
+ },
1054
+ /* @__PURE__ */ React5__default.default.createElement(lucideReact.LogOut, { size: 16 }),
1055
+ /* @__PURE__ */ React5__default.default.createElement("span", null, "\u9000\u51FA\u767B\u5F55")
1056
+ ))
1057
+ ) : (
1058
+ // 未登录状态的菜单
1059
+ /* @__PURE__ */ React5__default.default.createElement(React5__default.default.Fragment, null, /* @__PURE__ */ React5__default.default.createElement(
1060
+ "button",
1061
+ {
1062
+ className: "flex items-center gap-3 w-full p-3 bg-transparent border-none rounded-lg text-gray-700 text-sm font-medium cursor-pointer transition-all text-left min-h-11 hover:bg-slate-100 hover:text-slate-800 focus:outline-none focus:bg-slate-100 focus:ring-2 focus:ring-blue-500/10",
1063
+ onClick: handleLogin
1064
+ },
1065
+ /* @__PURE__ */ React5__default.default.createElement(lucideReact.LogIn, { size: 16 }),
1066
+ /* @__PURE__ */ React5__default.default.createElement("span", null, "\u767B\u5F55")
1067
+ ), /* @__PURE__ */ React5__default.default.createElement(
1068
+ "button",
1069
+ {
1070
+ className: "flex items-center gap-3 w-full p-3 bg-transparent border-none rounded-lg text-gray-700 text-sm font-medium cursor-pointer transition-all text-left min-h-11 hover:bg-slate-100 hover:text-slate-800 focus:outline-none focus:bg-slate-100 focus:ring-2 focus:ring-blue-500/10",
1071
+ onClick: handleRegister
1072
+ },
1073
+ /* @__PURE__ */ React5__default.default.createElement(lucideReact.User, { size: 16 }),
1074
+ /* @__PURE__ */ React5__default.default.createElement("span", null, "\u6CE8\u518C")
1075
+ ), visibleCustomMenuItems.length > 0 && /* @__PURE__ */ React5__default.default.createElement(React5__default.default.Fragment, null, /* @__PURE__ */ React5__default.default.createElement("div", { className: "h-px bg-slate-200 my-2" }), visibleCustomMenuItems.map((item) => {
1076
+ const IconComponent = item.icon;
1077
+ return /* @__PURE__ */ React5__default.default.createElement(
1078
+ "button",
1079
+ {
1080
+ key: item.id,
1081
+ className: "flex items-center gap-3 w-full p-3 bg-transparent border-none rounded-lg text-gray-700 text-sm font-medium cursor-pointer transition-all text-left min-h-11 hover:bg-slate-100 hover:text-slate-800 focus:outline-none focus:bg-slate-100 focus:ring-2 focus:ring-blue-500/10",
1082
+ onClick: () => handleCustomMenuClick(item)
1083
+ },
1084
+ IconComponent && /* @__PURE__ */ React5__default.default.createElement(IconComponent, { size: 16 }),
1085
+ /* @__PURE__ */ React5__default.default.createElement("span", null, item.label)
1086
+ );
1087
+ })))
1088
+ )
1089
+ ), /* @__PURE__ */ React5__default.default.createElement(
1090
+ LoginModal,
1091
+ {
1092
+ isOpen: showLoginModal,
1093
+ onClose: () => setShowLoginModal(false),
1094
+ onSuccess: handleAuthSuccess,
1095
+ onSwitchToRegister: handleSwitchToRegister
1096
+ }
1097
+ ), /* @__PURE__ */ React5__default.default.createElement(
1098
+ RegisterModal,
1099
+ {
1100
+ isOpen: showRegisterModal,
1101
+ onClose: () => setShowRegisterModal(false),
1102
+ onSuccess: handleAuthSuccess,
1103
+ onSwitchToLogin: handleSwitchToLogin
1104
+ }
1105
+ ));
1106
+ }
1107
+
1108
+ // src/business/auth-legacy/types/index.ts
1109
+ var UserRole = /* @__PURE__ */ ((UserRole2) => {
1110
+ UserRole2["USER"] = "user";
1111
+ UserRole2["ADMIN"] = "admin";
1112
+ return UserRole2;
1113
+ })(UserRole || {});
1114
+
1115
+ // src/business/bubbleShooter/index.ts
1116
+ var bubbleShooter_exports = {};
1117
+ __export(bubbleShooter_exports, {
1118
+ DEFAULT_BUBBLE_SHOOTER_CONFIG: () => DEFAULT_BUBBLE_SHOOTER_CONFIG,
1119
+ createEmptyGrid: () => createEmptyGrid,
1120
+ createInitialGrid: () => createInitialGrid,
1121
+ findAttachSlot: () => findAttachSlot,
1122
+ findCollisionSlot: () => findCollisionSlot,
1123
+ getBoardHeight: () => getBoardHeight,
1124
+ getBoardWidth: () => getBoardWidth,
1125
+ getBubbleDiameter: () => getBubbleDiameter,
1126
+ getBubblePosition: () => getBubblePosition,
1127
+ getNearestSlot: () => getNearestSlot,
1128
+ getNeighbors: () => getNeighbors,
1129
+ getRowStep: () => getRowStep,
1130
+ hasAnyBubble: () => hasAnyBubble,
1131
+ hasReachedDangerLine: () => hasReachedDangerLine,
1132
+ isValidSlot: () => isValidSlot,
1133
+ pickRandomColor: () => pickRandomColor,
1134
+ resolveMatches: () => resolveMatches,
1135
+ webUI: () => web_exports
1136
+ });
1137
+
1138
+ // src/business/bubbleShooter/logic/game.ts
1139
+ var slotKey = (row, col) => `${row}:${col}`;
1140
+ var cloneGrid = (grid) => grid.map((row) => [...row]);
1141
+ var DEFAULT_BUBBLE_SHOOTER_CONFIG = {
1142
+ rows: 12,
1143
+ cols: 8,
1144
+ initialRows: 5,
1145
+ bubbleRadius: 16,
1146
+ topOffset: 18,
1147
+ palette: ["#fb7185", "#60a5fa", "#34d399", "#fbbf24", "#a78bfa"],
1148
+ launchSpeed: 440,
1149
+ minMatchCount: 3
1150
+ };
1151
+ var getBubbleDiameter = (config) => config.bubbleRadius * 2;
1152
+ var getRowStep = (config) => Math.round(config.bubbleRadius * 1.73);
1153
+ var getBoardWidth = (config) => {
1154
+ return getBubbleDiameter(config) * config.cols + config.bubbleRadius;
1155
+ };
1156
+ var getBoardHeight = (config) => {
1157
+ return config.topOffset + getRowStep(config) * (config.rows + 1) + config.bubbleRadius;
1158
+ };
1159
+ var createEmptyGrid = (config) => {
1160
+ return Array.from({ length: config.rows }, () => Array.from({ length: config.cols }, () => null));
1161
+ };
1162
+ var createInitialGrid = (config) => {
1163
+ const grid = createEmptyGrid(config);
1164
+ for (let row = 0; row < config.initialRows; row += 1) {
1165
+ for (let col = 0; col < config.cols; col += 1) {
1166
+ const color = config.palette[Math.floor(Math.random() * config.palette.length)] || config.palette[0] || "#60a5fa";
1167
+ const targetRow = grid[row];
1168
+ if (targetRow) {
1169
+ targetRow[col] = color;
1170
+ }
1171
+ }
1172
+ }
1173
+ return grid;
1174
+ };
1175
+ var pickRandomColor = (palette) => {
1176
+ return palette[Math.floor(Math.random() * palette.length)] || "#60a5fa";
1177
+ };
1178
+ var getBubblePosition = (row, col, config) => {
1179
+ const diameter = getBubbleDiameter(config);
1180
+ const rowStep = getRowStep(config);
1181
+ const x = config.bubbleRadius + col * diameter + (row % 2 === 1 ? config.bubbleRadius : 0);
1182
+ const y = config.topOffset + config.bubbleRadius + row * rowStep;
1183
+ return { x, y };
1184
+ };
1185
+ var isValidSlot = (slot, config) => {
1186
+ return slot.row >= 0 && slot.row < config.rows && slot.col >= 0 && slot.col < config.cols;
1187
+ };
1188
+ var getNeighbors = (slot, config) => {
1189
+ const evenDirections = [
1190
+ { row: -1, col: -1 },
1191
+ { row: -1, col: 0 },
1192
+ { row: 0, col: -1 },
1193
+ { row: 0, col: 1 },
1194
+ { row: 1, col: -1 },
1195
+ { row: 1, col: 0 }
1196
+ ];
1197
+ const oddDirections = [
1198
+ { row: -1, col: 0 },
1199
+ { row: -1, col: 1 },
1200
+ { row: 0, col: -1 },
1201
+ { row: 0, col: 1 },
1202
+ { row: 1, col: 0 },
1203
+ { row: 1, col: 1 }
1204
+ ];
1205
+ const directions = slot.row % 2 === 0 ? evenDirections : oddDirections;
1206
+ return directions.map((direction) => ({ row: slot.row + direction.row, col: slot.col + direction.col })).filter((candidate) => isValidSlot(candidate, config));
1207
+ };
1208
+ var getNearestSlot = (x, y, config) => {
1209
+ const rowStep = getRowStep(config);
1210
+ const diameter = getBubbleDiameter(config);
1211
+ const row = Math.max(0, Math.min(config.rows - 1, Math.round((y - config.topOffset - config.bubbleRadius) / rowStep)));
1212
+ const rowOffset = row % 2 === 1 ? config.bubbleRadius : 0;
1213
+ const col = Math.max(0, Math.min(config.cols - 1, Math.round((x - config.bubbleRadius - rowOffset) / diameter)));
1214
+ return { row, col };
1215
+ };
1216
+ var hasAdjacentBubble = (row, col, grid, config) => {
1217
+ if (row === 0) {
1218
+ return true;
1219
+ }
1220
+ return getNeighbors({ row, col }, config).some((neighbor) => {
1221
+ const targetRow = grid[neighbor.row];
1222
+ return Boolean(targetRow?.[neighbor.col]);
1223
+ });
1224
+ };
1225
+ var findAttachSlot = (x, y, grid, config, preferred) => {
1226
+ const candidates = [];
1227
+ if (preferred && isValidSlot(preferred, config)) {
1228
+ candidates.push(preferred, ...getNeighbors(preferred, config));
1229
+ }
1230
+ const nearest = getNearestSlot(x, y, config);
1231
+ candidates.push(nearest, ...getNeighbors(nearest, config));
1232
+ const uniqueCandidates = Array.from(
1233
+ new Map(candidates.map((slot) => [slotKey(slot.row, slot.col), slot])).values()
1234
+ );
1235
+ let best = null;
1236
+ let bestDistance = Number.POSITIVE_INFINITY;
1237
+ uniqueCandidates.forEach((slot) => {
1238
+ const targetRow = grid[slot.row];
1239
+ if (!targetRow || targetRow[slot.col] || !hasAdjacentBubble(slot.row, slot.col, grid, config)) {
1240
+ return;
1241
+ }
1242
+ const position = getBubblePosition(slot.row, slot.col, config);
1243
+ const dx = position.x - x;
1244
+ const dy = position.y - y;
1245
+ const distance = dx * dx + dy * dy;
1246
+ if (distance < bestDistance) {
1247
+ bestDistance = distance;
1248
+ best = slot;
1249
+ }
1250
+ });
1251
+ return best;
1252
+ };
1253
+ var findCollisionSlot = (x, y, grid, config) => {
1254
+ const threshold = (config.bubbleRadius * 2 - 1) ** 2;
1255
+ for (let row = 0; row < config.rows; row += 1) {
1256
+ for (let col = 0; col < config.cols; col += 1) {
1257
+ const targetRow = grid[row];
1258
+ if (!targetRow?.[col]) {
1259
+ continue;
1260
+ }
1261
+ const position = getBubblePosition(row, col, config);
1262
+ const dx = position.x - x;
1263
+ const dy = position.y - y;
1264
+ if (dx * dx + dy * dy <= threshold) {
1265
+ return { row, col };
1266
+ }
1267
+ }
1268
+ }
1269
+ return null;
1270
+ };
1271
+ var collectGroup = (start, grid, config, color) => {
1272
+ const queue = [start];
1273
+ const visited = /* @__PURE__ */ new Set();
1274
+ const group = [];
1275
+ while (queue.length > 0) {
1276
+ const current = queue.shift();
1277
+ if (!current) {
1278
+ continue;
1279
+ }
1280
+ const key = slotKey(current.row, current.col);
1281
+ if (visited.has(key)) {
1282
+ continue;
1283
+ }
1284
+ visited.add(key);
1285
+ const currentColor = grid[current.row]?.[current.col];
1286
+ if (currentColor !== color) {
1287
+ continue;
1288
+ }
1289
+ group.push(current);
1290
+ getNeighbors(current, config).forEach((neighbor) => {
1291
+ const neighborKey = slotKey(neighbor.row, neighbor.col);
1292
+ if (!visited.has(neighborKey)) {
1293
+ queue.push(neighbor);
1294
+ }
1295
+ });
1296
+ }
1297
+ return group;
1298
+ };
1299
+ var collectTopConnected = (grid, config) => {
1300
+ const visited = /* @__PURE__ */ new Set();
1301
+ const queue = [];
1302
+ for (let col = 0; col < config.cols; col += 1) {
1303
+ if (grid[0]?.[col]) {
1304
+ queue.push({ row: 0, col });
1305
+ }
1306
+ }
1307
+ while (queue.length > 0) {
1308
+ const current = queue.shift();
1309
+ if (!current) {
1310
+ continue;
1311
+ }
1312
+ const key = slotKey(current.row, current.col);
1313
+ if (visited.has(key)) {
1314
+ continue;
1315
+ }
1316
+ if (!grid[current.row]?.[current.col]) {
1317
+ continue;
1318
+ }
1319
+ visited.add(key);
1320
+ getNeighbors(current, config).forEach((neighbor) => {
1321
+ const neighborKey = slotKey(neighbor.row, neighbor.col);
1322
+ if (!visited.has(neighborKey)) {
1323
+ queue.push(neighbor);
1324
+ }
1325
+ });
1326
+ }
1327
+ return visited;
1328
+ };
1329
+ var resolveMatches = (grid, placed, config) => {
1330
+ const nextGrid = cloneGrid(grid);
1331
+ const placedColor = nextGrid[placed.row]?.[placed.col];
1332
+ if (!placedColor) {
1333
+ return { grid: nextGrid, removed: 0, matched: 0, dropped: 0 };
1334
+ }
1335
+ const group = collectGroup(placed, nextGrid, config, placedColor);
1336
+ let matched = 0;
1337
+ if (group.length >= config.minMatchCount) {
1338
+ group.forEach((slot) => {
1339
+ const targetRow = nextGrid[slot.row];
1340
+ if (targetRow?.[slot.col]) {
1341
+ targetRow[slot.col] = null;
1342
+ matched += 1;
1343
+ }
1344
+ });
1345
+ }
1346
+ let dropped = 0;
1347
+ if (matched > 0) {
1348
+ const topConnected = collectTopConnected(nextGrid, config);
1349
+ for (let row = 0; row < config.rows; row += 1) {
1350
+ for (let col = 0; col < config.cols; col += 1) {
1351
+ const targetRow = nextGrid[row];
1352
+ if (targetRow?.[col] && !topConnected.has(slotKey(row, col))) {
1353
+ targetRow[col] = null;
1354
+ dropped += 1;
1355
+ }
1356
+ }
1357
+ }
1358
+ }
1359
+ return {
1360
+ grid: nextGrid,
1361
+ matched,
1362
+ dropped,
1363
+ removed: matched + dropped
1364
+ };
1365
+ };
1366
+ var hasAnyBubble = (grid) => {
1367
+ return grid.some((row) => row.some(Boolean));
1368
+ };
1369
+ var hasReachedDangerLine = (grid, config, dangerRowOffset = 2) => {
1370
+ const limitRow = Math.max(0, config.rows - dangerRowOffset);
1371
+ for (let row = limitRow; row < config.rows; row += 1) {
1372
+ const targetRow = grid[row];
1373
+ if (targetRow?.some(Boolean)) {
1374
+ return true;
1375
+ }
1376
+ }
1377
+ return false;
1378
+ };
1379
+
1380
+ // src/business/bubbleShooter/ui/web/index.ts
1381
+ var web_exports = {};
1382
+ __export(web_exports, {
1383
+ BubbleShooterBoard: () => BubbleShooterBoard_default,
1384
+ BubbleShooterGamePage: () => BubbleShooterGamePage_default
1385
+ });
1386
+ var clampAim = (angle) => {
1387
+ const minAngle = -Math.PI + 0.15;
1388
+ const maxAngle = -0.15;
1389
+ return Math.max(minAngle, Math.min(maxAngle, angle));
1390
+ };
1391
+ var drawCircle = (context, x, y, radius, color) => {
1392
+ context.beginPath();
1393
+ context.arc(x, y, radius, 0, Math.PI * 2);
1394
+ context.fillStyle = color;
1395
+ context.fill();
1396
+ context.strokeStyle = "rgba(15, 23, 42, 0.15)";
1397
+ context.lineWidth = 1;
1398
+ context.stroke();
1399
+ };
1400
+ var normalizeConfig = (input) => {
1401
+ return {
1402
+ ...DEFAULT_BUBBLE_SHOOTER_CONFIG,
1403
+ ...input,
1404
+ palette: input?.palette?.length ? input.palette : DEFAULT_BUBBLE_SHOOTER_CONFIG.palette
1405
+ };
1406
+ };
1407
+ var BubbleShooterBoard = ({ config: inputConfig, className }) => {
1408
+ const config = React5.useMemo(() => normalizeConfig(inputConfig), [inputConfig]);
1409
+ const boardWidth = React5.useMemo(() => getBoardWidth(config), [config]);
1410
+ const boardHeight = React5.useMemo(() => getBoardHeight(config), [config]);
1411
+ const shooterX = boardWidth / 2;
1412
+ const shooterY = boardHeight - config.bubbleRadius - 12;
1413
+ const canvasRef = React5.useRef(null);
1414
+ const rafRef = React5.useRef(null);
1415
+ const lastTsRef = React5.useRef(0);
1416
+ const [grid, setGrid] = React5.useState(() => createInitialGrid(config));
1417
+ const [status, setStatus] = React5.useState("ready");
1418
+ const [score, setScore] = React5.useState(0);
1419
+ const [shots, setShots] = React5.useState(0);
1420
+ const [aimAngle, setAimAngle] = React5.useState(-Math.PI / 2);
1421
+ const [currentColor, setCurrentColor] = React5.useState(() => pickRandomColor(config.palette));
1422
+ const [nextColor, setNextColor] = React5.useState(() => pickRandomColor(config.palette));
1423
+ const [projectile, setProjectile] = React5.useState(null);
1424
+ const gridRef = React5.useRef(grid);
1425
+ const currentColorRef = React5.useRef(currentColor);
1426
+ const nextColorRef = React5.useRef(nextColor);
1427
+ const statusRef = React5.useRef(status);
1428
+ React5.useEffect(() => {
1429
+ gridRef.current = grid;
1430
+ }, [grid]);
1431
+ React5.useEffect(() => {
1432
+ currentColorRef.current = currentColor;
1433
+ }, [currentColor]);
1434
+ React5.useEffect(() => {
1435
+ nextColorRef.current = nextColor;
1436
+ }, [nextColor]);
1437
+ React5.useEffect(() => {
1438
+ statusRef.current = status;
1439
+ }, [status]);
1440
+ const restart = React5.useCallback(() => {
1441
+ setGrid(createInitialGrid(config));
1442
+ setScore(0);
1443
+ setShots(0);
1444
+ setStatus("ready");
1445
+ setProjectile(null);
1446
+ setAimAngle(-Math.PI / 2);
1447
+ setCurrentColor(pickRandomColor(config.palette));
1448
+ setNextColor(pickRandomColor(config.palette));
1449
+ lastTsRef.current = 0;
1450
+ }, [config]);
1451
+ React5.useEffect(() => {
1452
+ restart();
1453
+ }, [restart]);
1454
+ const settleProjectile = React5.useCallback(
1455
+ (x, y, preferredSlot) => {
1456
+ const working = gridRef.current.map((row) => [...row]);
1457
+ const attach = findAttachSlot(x, y, working, config, preferredSlot);
1458
+ if (!attach) {
1459
+ setProjectile(null);
1460
+ setStatus("lost");
1461
+ return;
1462
+ }
1463
+ const rowData = working[attach.row];
1464
+ if (!rowData || rowData[attach.col]) {
1465
+ setProjectile(null);
1466
+ setStatus("lost");
1467
+ return;
1468
+ }
1469
+ rowData[attach.col] = currentColorRef.current;
1470
+ const resolved = resolveMatches(working, attach, config);
1471
+ setGrid(resolved.grid);
1472
+ setProjectile(null);
1473
+ setShots((prev) => prev + 1);
1474
+ if (resolved.removed > 0) {
1475
+ setScore((prev) => prev + resolved.matched * 10 + resolved.dropped * 15);
1476
+ }
1477
+ const hasBubbleLeft = hasAnyBubble(resolved.grid);
1478
+ if (!hasBubbleLeft) {
1479
+ setStatus("won");
1480
+ } else if (hasReachedDangerLine(resolved.grid, config)) {
1481
+ setStatus("lost");
1482
+ } else {
1483
+ setStatus("ready");
1484
+ }
1485
+ const nextCurrent = nextColorRef.current;
1486
+ const nextQueued = pickRandomColor(config.palette);
1487
+ setCurrentColor(nextCurrent);
1488
+ setNextColor(nextQueued);
1489
+ },
1490
+ [config]
1491
+ );
1492
+ React5.useEffect(() => {
1493
+ if (status !== "shooting") {
1494
+ return;
1495
+ }
1496
+ const step = (timestamp) => {
1497
+ if (statusRef.current !== "shooting") {
1498
+ lastTsRef.current = 0;
1499
+ return;
1500
+ }
1501
+ const previousTs = lastTsRef.current || timestamp;
1502
+ const deltaSec = Math.min((timestamp - previousTs) / 1e3, 0.05);
1503
+ lastTsRef.current = timestamp;
1504
+ let shouldContinue = true;
1505
+ setProjectile((prev) => {
1506
+ if (!prev || statusRef.current !== "shooting") {
1507
+ shouldContinue = false;
1508
+ return prev;
1509
+ }
1510
+ let nextX = prev.x + prev.vx * deltaSec;
1511
+ const nextY = prev.y + prev.vy * deltaSec;
1512
+ let nextVx = prev.vx;
1513
+ if (nextX <= config.bubbleRadius) {
1514
+ nextX = config.bubbleRadius;
1515
+ nextVx = Math.abs(nextVx);
1516
+ } else if (nextX >= boardWidth - config.bubbleRadius) {
1517
+ nextX = boardWidth - config.bubbleRadius;
1518
+ nextVx = -Math.abs(nextVx);
1519
+ }
1520
+ if (nextY <= config.topOffset + config.bubbleRadius) {
1521
+ shouldContinue = false;
1522
+ settleProjectile(nextX, config.topOffset + config.bubbleRadius, getNearestSlot(nextX, nextY, config));
1523
+ return null;
1524
+ }
1525
+ const collision = findCollisionSlot(nextX, nextY, gridRef.current, config);
1526
+ if (collision) {
1527
+ shouldContinue = false;
1528
+ settleProjectile(nextX, nextY, collision);
1529
+ return null;
1530
+ }
1531
+ return {
1532
+ ...prev,
1533
+ x: nextX,
1534
+ y: nextY,
1535
+ vx: nextVx
1536
+ };
1537
+ });
1538
+ if (shouldContinue) {
1539
+ rafRef.current = window.requestAnimationFrame(step);
1540
+ } else {
1541
+ rafRef.current = null;
1542
+ lastTsRef.current = 0;
1543
+ }
1544
+ };
1545
+ rafRef.current = window.requestAnimationFrame(step);
1546
+ return () => {
1547
+ if (rafRef.current) {
1548
+ window.cancelAnimationFrame(rafRef.current);
1549
+ rafRef.current = null;
1550
+ }
1551
+ lastTsRef.current = 0;
1552
+ };
1553
+ }, [boardWidth, config, settleProjectile, status]);
1554
+ const shoot = React5.useCallback(() => {
1555
+ if (status !== "ready") {
1556
+ return;
1557
+ }
1558
+ const vx = Math.cos(aimAngle) * config.launchSpeed;
1559
+ const vy = Math.sin(aimAngle) * config.launchSpeed;
1560
+ setProjectile({
1561
+ x: shooterX,
1562
+ y: shooterY - config.bubbleRadius,
1563
+ vx,
1564
+ vy,
1565
+ color: currentColor
1566
+ });
1567
+ setStatus("shooting");
1568
+ }, [aimAngle, config.bubbleRadius, config.launchSpeed, currentColor, shooterX, shooterY, status]);
1569
+ const updateAimFromPointer = React5.useCallback(
1570
+ (event) => {
1571
+ const canvas = canvasRef.current;
1572
+ if (!canvas || status !== "ready") {
1573
+ return;
1574
+ }
1575
+ const rect = canvas.getBoundingClientRect();
1576
+ const x = event.clientX - rect.left;
1577
+ const y = event.clientY - rect.top;
1578
+ const rawAngle = Math.atan2(y - shooterY, x - shooterX);
1579
+ setAimAngle(clampAim(rawAngle));
1580
+ },
1581
+ [shooterX, shooterY, status]
1582
+ );
1583
+ const handlePointerDown = (event) => {
1584
+ event.currentTarget.setPointerCapture(event.pointerId);
1585
+ updateAimFromPointer(event);
1586
+ };
1587
+ const handlePointerUp = (event) => {
1588
+ updateAimFromPointer(event);
1589
+ shoot();
1590
+ event.currentTarget.releasePointerCapture(event.pointerId);
1591
+ };
1592
+ React5.useEffect(() => {
1593
+ const canvas = canvasRef.current;
1594
+ if (!canvas) {
1595
+ return;
1596
+ }
1597
+ const context = canvas.getContext("2d");
1598
+ if (!context) {
1599
+ return;
1600
+ }
1601
+ const gradient = context.createLinearGradient(0, 0, 0, boardHeight);
1602
+ gradient.addColorStop(0, "#f8fafc");
1603
+ gradient.addColorStop(1, "#e2e8f0");
1604
+ context.fillStyle = gradient;
1605
+ context.fillRect(0, 0, boardWidth, boardHeight);
1606
+ context.fillStyle = "rgba(148, 163, 184, 0.2)";
1607
+ context.fillRect(0, shooterY - config.bubbleRadius * 1.3, boardWidth, 2);
1608
+ for (let row = 0; row < config.rows; row += 1) {
1609
+ for (let col = 0; col < config.cols; col += 1) {
1610
+ const color = grid[row]?.[col];
1611
+ if (!color) {
1612
+ continue;
1613
+ }
1614
+ const position = getBubblePosition(row, col, config);
1615
+ drawCircle(context, position.x, position.y, config.bubbleRadius - 0.5, color);
1616
+ }
1617
+ }
1618
+ const aimLength = config.bubbleRadius * 9;
1619
+ const aimX = shooterX + Math.cos(aimAngle) * aimLength;
1620
+ const aimY = shooterY + Math.sin(aimAngle) * aimLength;
1621
+ context.strokeStyle = "rgba(71, 85, 105, 0.75)";
1622
+ context.lineWidth = 2;
1623
+ context.setLineDash([4, 6]);
1624
+ context.beginPath();
1625
+ context.moveTo(shooterX, shooterY);
1626
+ context.lineTo(aimX, aimY);
1627
+ context.stroke();
1628
+ context.setLineDash([]);
1629
+ drawCircle(context, shooterX, shooterY, config.bubbleRadius - 0.5, currentColor);
1630
+ if (projectile) {
1631
+ drawCircle(context, projectile.x, projectile.y, config.bubbleRadius - 0.5, projectile.color);
1632
+ }
1633
+ drawCircle(context, boardWidth - config.bubbleRadius * 1.6, shooterY, config.bubbleRadius * 0.72, nextColor);
1634
+ context.fillStyle = "#334155";
1635
+ context.font = "12px sans-serif";
1636
+ context.textAlign = "right";
1637
+ context.fillText("Next", boardWidth - config.bubbleRadius * 0.5, shooterY - config.bubbleRadius * 1.05);
1638
+ if (status === "won" || status === "lost") {
1639
+ context.fillStyle = "rgba(15, 23, 42, 0.48)";
1640
+ context.fillRect(0, 0, boardWidth, boardHeight);
1641
+ context.fillStyle = "#ffffff";
1642
+ context.font = "bold 26px sans-serif";
1643
+ context.textAlign = "center";
1644
+ context.fillText(status === "won" ? "You Win!" : "Game Over", boardWidth / 2, boardHeight / 2);
1645
+ }
1646
+ }, [aimAngle, boardHeight, boardWidth, config, currentColor, grid, nextColor, projectile, shooterX, shooterY, status]);
1647
+ return /* @__PURE__ */ React5__default.default.createElement("div", { className }, /* @__PURE__ */ React5__default.default.createElement("div", { style: { display: "flex", gap: 16, marginBottom: 10, flexWrap: "wrap", color: "#334155" } }, /* @__PURE__ */ React5__default.default.createElement("span", null, "\u72B6\u6001\uFF1A", status === "ready" ? "\u5F85\u53D1\u5C04" : status === "shooting" ? "\u53D1\u5C04\u4E2D" : status === "won" ? "\u80DC\u5229" : "\u5931\u8D25"), /* @__PURE__ */ React5__default.default.createElement("span", null, "\u5206\u6570\uFF1A", score), /* @__PURE__ */ React5__default.default.createElement("span", null, "\u53D1\u5C04\u6B21\u6570\uFF1A", shots)), /* @__PURE__ */ React5__default.default.createElement(
1648
+ "canvas",
1649
+ {
1650
+ ref: canvasRef,
1651
+ width: boardWidth,
1652
+ height: boardHeight,
1653
+ onPointerMove: updateAimFromPointer,
1654
+ onPointerDown: handlePointerDown,
1655
+ onPointerUp: handlePointerUp,
1656
+ style: {
1657
+ width: "100%",
1658
+ maxWidth: boardWidth,
1659
+ borderRadius: 12,
1660
+ border: "1px solid #cbd5e1",
1661
+ boxShadow: "0 8px 28px rgba(15, 23, 42, 0.12)",
1662
+ touchAction: "none",
1663
+ cursor: status === "ready" ? "crosshair" : "default",
1664
+ display: "block"
1665
+ }
1666
+ }
1667
+ ), /* @__PURE__ */ React5__default.default.createElement("div", { style: { marginTop: 10, display: "flex", gap: 10 } }, /* @__PURE__ */ React5__default.default.createElement("button", { type: "button", onClick: restart, style: { padding: "8px 12px" } }, "\u91CD\u65B0\u5F00\u59CB")));
1668
+ };
1669
+ var BubbleShooterBoard_default = BubbleShooterBoard;
1670
+ var BubbleShooterGamePage = ({
1671
+ config,
1672
+ title = "\u6CE1\u6CE1\u9F99",
1673
+ description = "\u57FA\u7840\u7248\uFF1A\u7784\u51C6\u3001\u53D1\u5C04\u3001\u8FDE\u6D88\u3001\u60AC\u7A7A\u6389\u843D\u3002"
1674
+ }) => {
1675
+ return /* @__PURE__ */ React5__default.default.createElement("section", null, /* @__PURE__ */ React5__default.default.createElement("h2", null, title), /* @__PURE__ */ React5__default.default.createElement("p", { style: { color: "#64748b" } }, description), /* @__PURE__ */ React5__default.default.createElement(BubbleShooterBoard_default, { config }));
1676
+ };
1677
+ var BubbleShooterGamePage_default = BubbleShooterGamePage;
1678
+
1679
+ exports.authLegacy = auth_legacy_exports;
1680
+ exports.bubbleShooter = bubbleShooter_exports;
1681
+ //# sourceMappingURL=index.js.map
1682
+ //# sourceMappingURL=index.js.map