@seaverse/auth-sdk 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -94,8 +94,17 @@ const registerResult = await client.register({
94
94
  password: 'SecurePassword123',
95
95
  username: 'johndoe', // 可选,未提供则从邮箱自动生成
96
96
  invitation_code: 'INVITE123', // 可选
97
+ frontend_url: 'https://mygame.com/verify', // 可选,邮箱验证链接的前端URL,默认为 window.location.origin
97
98
  });
98
99
 
100
+ // 检查注册结果
101
+ if (registerResult.success) {
102
+ console.log('注册成功:', registerResult);
103
+ } else if (registerResult.code === 'ACCOUNT_EXISTS') {
104
+ console.log('账户已存在,请直接登录');
105
+ console.log('错误详情:', registerResult.details);
106
+ }
107
+
99
108
  // 登录
100
109
  const loginResult = await client.login({
101
110
  email: 'user@example.com',
@@ -117,6 +126,7 @@ await client.logout();
117
126
  // 忘记密码
118
127
  await client.forgotPassword({
119
128
  email: 'user@example.com',
129
+ frontend_url: 'https://mygame.com/', // 可选,默认为 window.location.origin
120
130
  });
121
131
 
122
132
  // 重置密码
@@ -262,6 +272,34 @@ authModal.hide();
262
272
  authModal.destroy();
263
273
  ```
264
274
 
275
+ #### 重置密码流程
276
+
277
+ AuthModal 支持完整的密码重置流程:
278
+
279
+ 1. **用户触发忘记密码**:在登录界面点击 "Forgot Password?" 链接
280
+ 2. **发送重置邮件**:输入邮箱后,系统发送带有 `verify_token` 的重置链接
281
+ 3. **自动唤起重置弹窗**:用户点击邮件中的链接返回网站时,AuthModal 会自动检测 URL 中的 `verify_token` 参数并显示重置密码表单
282
+ 4. **设置新密码**:用户输入并确认新密码后提交
283
+ 5. **自动清理 URL**:重置成功后自动清除 URL 中的 `verify_token` 参数
284
+
285
+ 整个流程无需额外代码,AuthModal 会自动处理:
286
+
287
+ ```typescript
288
+ // 1. 初始化 AuthModal(只需一次)
289
+ const authModal = new AuthModal({
290
+ client,
291
+ // ... 其他配置
292
+ });
293
+
294
+ // 2. 用户点击邮件中的重置链接后,AuthModal 会自动:
295
+ // - 检测 URL 中的 ?verify_token=xxx 参数
296
+ // - 显示重置密码表单
297
+ // - 用户提交新密码
298
+ // - 调用 client.resetPassword() API
299
+ // - 清理 URL 中的 verify_token 参数
300
+ // - 显示成功消息
301
+ ```
302
+
265
303
  #### OAuth 配置说明
266
304
 
267
305
  `enableOAuth` 参数是**完全可选的**:
@@ -511,11 +549,11 @@ SDK支持以下环境:
511
549
 
512
550
  | 方法 | 参数 | 返回值 | 说明 |
513
551
  |------|------|--------|------|
514
- | `register()` | `{ email, password, username?, invitation_code? }` | `RegisterResponse` | 注册新用户 |
552
+ | `register()` | `{ email, password, username?, invitation_code?, frontend_url? }` | `RegisterResponse` | 注册新用户,frontend_url 为邮箱验证链接的前端URL,默认为 window.location.origin |
515
553
  | `login()` | `{ email, password }` | `LoginResponse` | 用户登录 |
516
554
  | `getCurrentUser()` | - | `User` | 获取当前用户 |
517
555
  | `logout()` | - | `SuccessResponse` | 登出 |
518
- | `forgotPassword()` | `{ email }` | `SuccessResponse` | 忘记密码 |
556
+ | `forgotPassword()` | `{ email, frontend_url? }` | `SuccessResponse` | 忘记密码,frontend_url 默认为 window.location.origin |
519
557
  | `resetPassword()` | `{ token, new_password }` | `SuccessResponse` | 重置密码 |
520
558
  | `setToken()` | `token: string` | `void` | 设置认证 token(OAuth 登录后使用) |
521
559
  | `getApiServiceToken()` | - | `ApiServiceTokenResponse` | 获取API Token |
@@ -560,6 +598,222 @@ SDK支持以下环境:
560
598
  | `getConversationStatus()` | `{ conversationId }` | `ConversationStatusResponse` | 获取对话状态 |
561
599
  | `getSpeechToken()` | - | `SpeechTokenResponse` | 获取语音Token |
562
600
 
601
+ ## 错误处理
602
+
603
+ ### 错误响应格式
604
+
605
+ 所有 API 错误都遵循统一的响应格式:
606
+
607
+ ```typescript
608
+ interface ApiError {
609
+ success: false; // 错误时始终为 false
610
+ error: string; // 人类可读的错误消息
611
+ code?: string; // 机器可读的错误码
612
+ details?: any; // 额外的错误详情
613
+ }
614
+ ```
615
+
616
+ ### 错误码
617
+
618
+ SDK 提供了标准的错误码枚举:
619
+
620
+ ```typescript
621
+ import { ErrorCode } from '@seaverse/auth-sdk';
622
+
623
+ // 账户相关错误
624
+ ErrorCode.ACCOUNT_EXISTS // 账户已存在
625
+ ErrorCode.ACCOUNT_NOT_FOUND // 账户不存在
626
+ ErrorCode.ACCOUNT_SUSPENDED // 账户已被暂停
627
+ ErrorCode.INVALID_CREDENTIALS // 登录凭证无效
628
+ ErrorCode.EMAIL_NOT_VERIFIED // 邮箱未验证
629
+
630
+ // 验证相关错误
631
+ ErrorCode.INVALID_EMAIL // 无效的邮箱地址
632
+ ErrorCode.INVALID_PASSWORD // 无效的密码
633
+ ErrorCode.PASSWORD_TOO_WEAK // 密码强度不够
634
+
635
+ // 邀请码相关错误
636
+ ErrorCode.INVALID_INVITATION_CODE // 无效的邀请码
637
+ ErrorCode.INVITATION_REQUIRED // 需要邀请码
638
+
639
+ // Token 相关错误
640
+ ErrorCode.INVALID_TOKEN // 无效的 token
641
+ ErrorCode.TOKEN_EXPIRED // Token 已过期
642
+
643
+ // 内部错误
644
+ ErrorCode.INTERNAL_ERROR // 服务器内部错误
645
+ ```
646
+
647
+ ### 错误处理最佳实践
648
+
649
+ #### 1. 注册时处理重复用户
650
+
651
+ 由于后端在账户已存在时返回 200 OK(成功响应),所以前端不会抛出异常,而是在响应体中包含错误信息。
652
+
653
+ ```typescript
654
+ import { ErrorCode } from '@seaverse/auth-sdk';
655
+
656
+ const result = await client.register({
657
+ email: 'user@example.com',
658
+ password: 'password123',
659
+ });
660
+
661
+ // 检查响应中的 success 字段
662
+ if (result.success) {
663
+ console.log('注册成功:', result);
664
+ // 进行后续操作,如自动登录
665
+ } else if (result.code === ErrorCode.ACCOUNT_EXISTS) {
666
+ // 账户已存在,提示用户
667
+ const { email, app_id } = result.details;
668
+ console.log(`账户 ${email} 已存在于应用 ${app_id} 中`);
669
+
670
+ // 显示友好的提示信息
671
+ alert('This email is already registered. Please login instead.');
672
+
673
+ // 或者引导用户去登录
674
+ showLoginModal();
675
+ } else {
676
+ // 处理其他错误
677
+ console.error('注册失败:', result.error);
678
+ }
679
+ ```
680
+
681
+ #### 2. 登录时处理各种错误
682
+
683
+ ```typescript
684
+ try {
685
+ const result = await client.login({
686
+ email: 'user@example.com',
687
+ password: 'wrong-password',
688
+ });
689
+ console.log('登录成功:', result);
690
+ } catch (error) {
691
+ const errorCode = error.response?.data?.code;
692
+
693
+ switch (errorCode) {
694
+ case ErrorCode.INVALID_CREDENTIALS:
695
+ showError('用户名或密码错误');
696
+ break;
697
+ case ErrorCode.EMAIL_NOT_VERIFIED:
698
+ showError('请先验证您的邮箱');
699
+ showResendVerificationButton();
700
+ break;
701
+ case ErrorCode.ACCOUNT_SUSPENDED:
702
+ showError('您的账户已被暂停,请联系管理员');
703
+ break;
704
+ default:
705
+ showError('登录失败,请稍后重试');
706
+ }
707
+ }
708
+ ```
709
+
710
+ #### 3. 通用错误处理函数
711
+
712
+ ```typescript
713
+ import { ErrorCode } from '@seaverse/auth-sdk';
714
+
715
+ function handleApiError(error: any) {
716
+ const apiError = error.response?.data;
717
+
718
+ if (!apiError) {
719
+ // 网络错误或其他未知错误
720
+ return {
721
+ title: '网络错误',
722
+ message: '请检查您的网络连接',
723
+ };
724
+ }
725
+
726
+ // 根据错误码返回用户友好的消息
727
+ const errorMessages: Record<string, { title: string; message: string }> = {
728
+ [ErrorCode.ACCOUNT_EXISTS]: {
729
+ title: '账户已存在',
730
+ message: '该邮箱已注册,请直接登录',
731
+ },
732
+ [ErrorCode.INVALID_CREDENTIALS]: {
733
+ title: '登录失败',
734
+ message: '用户名或密码错误',
735
+ },
736
+ [ErrorCode.EMAIL_NOT_VERIFIED]: {
737
+ title: '邮箱未验证',
738
+ message: '请先验证您的邮箱地址',
739
+ },
740
+ [ErrorCode.INVALID_INVITATION_CODE]: {
741
+ title: '邀请码无效',
742
+ message: '请检查您的邀请码是否正确',
743
+ },
744
+ [ErrorCode.TOKEN_EXPIRED]: {
745
+ title: '登录已过期',
746
+ message: '请重新登录',
747
+ },
748
+ };
749
+
750
+ return errorMessages[apiError.code] || {
751
+ title: '操作失败',
752
+ message: apiError.error || '发生未知错误',
753
+ };
754
+ }
755
+
756
+ // 使用示例
757
+ try {
758
+ await client.register({ email, password });
759
+ } catch (error) {
760
+ const { title, message } = handleApiError(error);
761
+ showNotification(title, message);
762
+ }
763
+ ```
764
+
765
+ #### 4. TypeScript 类型安全的错误处理
766
+
767
+ ```typescript
768
+ import { ErrorCode, models } from '@seaverse/auth-sdk';
769
+
770
+ async function safeRegister(email: string, password: string) {
771
+ // Register API now returns 200 OK for both success and account exists cases
772
+ const result = await client.register({ email, password });
773
+
774
+ if (result.success) {
775
+ return {
776
+ success: true,
777
+ data: result,
778
+ };
779
+ } else if (result.code === ErrorCode.ACCOUNT_EXISTS) {
780
+ // TypeScript knows the type of details
781
+ const details = result.details as models.AccountExistsErrorDetails;
782
+ return {
783
+ success: false,
784
+ error: 'ACCOUNT_EXISTS',
785
+ email: details.email,
786
+ appId: details.app_id,
787
+ };
788
+ } else {
789
+ return {
790
+ success: false,
791
+ error: result.error || 'Unknown error',
792
+ };
793
+ }
794
+ }
795
+
796
+ // Usage
797
+ const registerResult = await safeRegister('user@example.com', 'password123');
798
+ if (registerResult.success) {
799
+ console.log('Registration successful');
800
+ } else if (registerResult.error === 'ACCOUNT_EXISTS') {
801
+ console.log(`Account already exists: ${registerResult.email}`);
802
+ }
803
+ ```
804
+
805
+ ### HTTP 状态码对照
806
+
807
+ | HTTP状态码 | 错误码示例 | 说明 |
808
+ |-----------|----------|------|
809
+ | 200 OK | `ACCOUNT_EXISTS` | 账户已存在(业务错误但返回成功响应) |
810
+ | 400 Bad Request | `INVALID_EMAIL`, `PASSWORD_TOO_WEAK` | 请求参数无效 |
811
+ | 401 Unauthorized | `INVALID_CREDENTIALS`, `TOKEN_EXPIRED` | 认证失败 |
812
+ | 403 Forbidden | `EMAIL_NOT_VERIFIED`, `ACCOUNT_SUSPENDED` | 权限不足 |
813
+ | 500 Internal Server Error | `INTERNAL_ERROR` | 服务器内部错误 |
814
+
815
+ **注意**:对于注册接口,账户已存在的情况会返回 200 OK,但在响应体中 `success: false` 和 `code: "ACCOUNT_EXISTS"`。这样设计是为了前端能够更容易地处理这种常见的业务场景。
816
+
563
817
  ## 类型定义
564
818
 
565
819
  ```typescript
package/dist/index.cjs CHANGED
@@ -1136,9 +1136,37 @@ function resolveBaseURL(options) {
1136
1136
  * Type definitions for SeaVerse Dispatcher API
1137
1137
  * Generated from auth.yaml OpenAPI specification
1138
1138
  */
1139
+ // ============================================================================
1140
+ // Error Types
1141
+ // ============================================================================
1142
+ /**
1143
+ * Error codes for authentication system
1144
+ */
1145
+ exports.ErrorCode = void 0;
1146
+ (function (ErrorCode) {
1147
+ // Account errors
1148
+ ErrorCode["ACCOUNT_EXISTS"] = "ACCOUNT_EXISTS";
1149
+ ErrorCode["ACCOUNT_NOT_FOUND"] = "ACCOUNT_NOT_FOUND";
1150
+ ErrorCode["ACCOUNT_SUSPENDED"] = "ACCOUNT_SUSPENDED";
1151
+ ErrorCode["INVALID_CREDENTIALS"] = "INVALID_CREDENTIALS";
1152
+ ErrorCode["EMAIL_NOT_VERIFIED"] = "EMAIL_NOT_VERIFIED";
1153
+ // Validation errors
1154
+ ErrorCode["INVALID_EMAIL"] = "INVALID_EMAIL";
1155
+ ErrorCode["INVALID_PASSWORD"] = "INVALID_PASSWORD";
1156
+ ErrorCode["PASSWORD_TOO_WEAK"] = "PASSWORD_TOO_WEAK";
1157
+ // Invitation errors
1158
+ ErrorCode["INVALID_INVITATION_CODE"] = "INVALID_INVITATION_CODE";
1159
+ ErrorCode["INVITATION_REQUIRED"] = "INVITATION_REQUIRED";
1160
+ // Token errors
1161
+ ErrorCode["INVALID_TOKEN"] = "INVALID_TOKEN";
1162
+ ErrorCode["TOKEN_EXPIRED"] = "TOKEN_EXPIRED";
1163
+ // Internal errors
1164
+ ErrorCode["INTERNAL_ERROR"] = "INTERNAL_ERROR";
1165
+ })(exports.ErrorCode || (exports.ErrorCode = {}));
1139
1166
 
1140
1167
  var models = /*#__PURE__*/Object.freeze({
1141
- __proto__: null
1168
+ __proto__: null,
1169
+ get ErrorCode () { return exports.ErrorCode; }
1142
1170
  });
1143
1171
 
1144
1172
  /**
@@ -1276,10 +1304,12 @@ class SeaVerseBackendAPIClient {
1276
1304
  * Register a new user with email verification
1277
1305
  */
1278
1306
  async register(data, options) {
1307
+ // 如果没有传 frontend_url,使用当前页面地址
1308
+ const frontend_url = data.frontend_url || (typeof window !== 'undefined' ? window.location.origin : '');
1279
1309
  const config = {
1280
1310
  method: 'POST',
1281
1311
  url: `/sdk/v1/auth/register`,
1282
- data,
1312
+ data: { ...data, frontend_url },
1283
1313
  headers: {
1284
1314
  'X-Operation-Id': 'register',
1285
1315
  ...options?.headers,
@@ -1346,10 +1376,12 @@ class SeaVerseBackendAPIClient {
1346
1376
  * Send password reset email
1347
1377
  */
1348
1378
  async forgotPassword(data, options) {
1379
+ // 如果没有传 frontend_url,使用当前页面地址
1380
+ const frontend_url = data.frontend_url || (typeof window !== 'undefined' ? window.location.origin : '');
1349
1381
  const config = {
1350
1382
  method: 'POST',
1351
1383
  url: `/sdk/v1/auth/forgot-password`,
1352
- data,
1384
+ data: { ...data, frontend_url },
1353
1385
  headers: {
1354
1386
  'X-Operation-Id': 'forgotPassword',
1355
1387
  ...options?.headers,
@@ -1786,8 +1818,11 @@ class AuthModal {
1786
1818
  constructor(options) {
1787
1819
  this.modal = null;
1788
1820
  this.currentView = 'login';
1821
+ this.resetToken = null;
1789
1822
  this.client = options.client;
1790
1823
  this.options = options;
1824
+ // Auto-detect reset token in URL
1825
+ this.checkForResetToken();
1791
1826
  }
1792
1827
  /**
1793
1828
  * Show the authentication modal
@@ -1895,6 +1930,9 @@ class AuthModal {
1895
1930
  // Forgot password form
1896
1931
  const forgotForm = this.createForgotPasswordForm();
1897
1932
  rightPanel.appendChild(forgotForm);
1933
+ // Reset password form
1934
+ const resetForm = this.createResetPasswordForm();
1935
+ rightPanel.appendChild(resetForm);
1898
1936
  // Success message
1899
1937
  const successMessage = this.createSuccessMessage();
1900
1938
  rightPanel.appendChild(successMessage);
@@ -2148,6 +2186,86 @@ class AuthModal {
2148
2186
  container.appendChild(footer);
2149
2187
  return container;
2150
2188
  }
2189
+ createResetPasswordForm() {
2190
+ const container = document.createElement('div');
2191
+ container.id = 'resetPasswordForm';
2192
+ container.className = 'auth-form-view hidden';
2193
+ // Icon
2194
+ const icon = document.createElement('div');
2195
+ icon.className = 'forgot-password-icon';
2196
+ const iconContent = '<div class="icon-glow"></div><svg class="icon-lock" width="56" height="56" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path class="lock-shackle" d="M7 11V7a5 5 0 0 1 10 0v4"/><circle class="lock-keyhole" cx="12" cy="16" r="1.5"/></svg>';
2197
+ icon.innerHTML = iconContent;
2198
+ container.appendChild(icon);
2199
+ // Header
2200
+ const header = document.createElement('div');
2201
+ header.className = 'auth-form-header';
2202
+ const title = document.createElement('h2');
2203
+ title.className = 'auth-form-title';
2204
+ title.textContent = 'Set New Password';
2205
+ const subtitle = document.createElement('p');
2206
+ subtitle.className = 'auth-form-subtitle';
2207
+ subtitle.textContent = 'Enter your new password below';
2208
+ header.appendChild(title);
2209
+ header.appendChild(subtitle);
2210
+ container.appendChild(header);
2211
+ // Form
2212
+ const form = document.createElement('form');
2213
+ form.id = 'resetPasswordFormElement';
2214
+ form.className = 'auth-form';
2215
+ // New password field
2216
+ const passwordGroup = document.createElement('div');
2217
+ passwordGroup.className = 'form-group';
2218
+ const passwordLabel = document.createElement('label');
2219
+ passwordLabel.htmlFor = 'newPassword';
2220
+ passwordLabel.className = 'form-label';
2221
+ passwordLabel.textContent = 'New Password';
2222
+ passwordGroup.appendChild(passwordLabel);
2223
+ const passwordInputWrapper = this.createPasswordInput('newPassword', 'Enter new password');
2224
+ passwordGroup.appendChild(passwordInputWrapper);
2225
+ const strengthText = document.createElement('p');
2226
+ strengthText.className = 'strength-text';
2227
+ strengthText.textContent = 'Use 8+ characters with mix of letters, numbers & symbols';
2228
+ passwordGroup.appendChild(strengthText);
2229
+ form.appendChild(passwordGroup);
2230
+ // Confirm password field
2231
+ const confirmGroup = document.createElement('div');
2232
+ confirmGroup.className = 'form-group';
2233
+ const confirmLabel = document.createElement('label');
2234
+ confirmLabel.htmlFor = 'newPasswordConfirm';
2235
+ confirmLabel.className = 'form-label';
2236
+ confirmLabel.textContent = 'Confirm New Password';
2237
+ confirmGroup.appendChild(confirmLabel);
2238
+ const confirmInputWrapper = this.createPasswordInput('newPasswordConfirm', 'Confirm new password');
2239
+ confirmGroup.appendChild(confirmInputWrapper);
2240
+ form.appendChild(confirmGroup);
2241
+ const submitBtn = document.createElement('button');
2242
+ submitBtn.type = 'submit';
2243
+ submitBtn.id = 'resetPasswordButton';
2244
+ submitBtn.className = 'btn-auth-primary btn-forgot-password';
2245
+ const btnText = document.createElement('span');
2246
+ btnText.className = 'btn-text';
2247
+ btnText.textContent = 'Reset Password';
2248
+ const btnLoader = document.createElement('span');
2249
+ btnLoader.className = 'btn-loader hidden';
2250
+ const spinnerSVG = '<svg class="spinner" viewBox="0 0 24 24"><circle class="spinner-track" cx="12" cy="12" r="10"></circle><circle class="spinner-circle" cx="12" cy="12" r="10"></circle></svg>';
2251
+ btnLoader.innerHTML = spinnerSVG;
2252
+ submitBtn.appendChild(btnText);
2253
+ submitBtn.appendChild(btnLoader);
2254
+ form.appendChild(submitBtn);
2255
+ container.appendChild(form);
2256
+ // Footer
2257
+ const footer = document.createElement('div');
2258
+ footer.className = 'auth-footer forgot-footer';
2259
+ const backLink = document.createElement('a');
2260
+ backLink.href = '#';
2261
+ backLink.id = 'backToLoginFromReset';
2262
+ backLink.className = 'back-to-login';
2263
+ const backLinkHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5M12 19l-7-7 7-7"/></svg><span>Back to Sign In</span>';
2264
+ backLink.innerHTML = backLinkHTML;
2265
+ footer.appendChild(backLink);
2266
+ container.appendChild(footer);
2267
+ return container;
2268
+ }
2151
2269
  createSuccessMessage() {
2152
2270
  const container = document.createElement('div');
2153
2271
  container.id = 'authMessage';
@@ -2237,6 +2355,20 @@ class AuthModal {
2237
2355
  button.appendChild(span);
2238
2356
  return button;
2239
2357
  }
2358
+ /**
2359
+ * Check if URL contains reset token and auto-show reset password form
2360
+ */
2361
+ checkForResetToken() {
2362
+ const urlParams = new URLSearchParams(window.location.search);
2363
+ const verifyToken = urlParams.get('verify_token');
2364
+ if (verifyToken) {
2365
+ this.resetToken = verifyToken;
2366
+ // Auto-show modal with reset password form
2367
+ setTimeout(() => {
2368
+ this.show('reset-password');
2369
+ }, 100);
2370
+ }
2371
+ }
2240
2372
  bindEventListeners() {
2241
2373
  if (!this.modal)
2242
2374
  return;
@@ -2267,6 +2399,11 @@ class AuthModal {
2267
2399
  e.preventDefault();
2268
2400
  this.switchView('login');
2269
2401
  });
2402
+ const backToLoginFromReset = this.modal.querySelector('#backToLoginFromReset');
2403
+ backToLoginFromReset?.addEventListener('click', (e) => {
2404
+ e.preventDefault();
2405
+ this.switchView('login');
2406
+ });
2270
2407
  // Password toggle
2271
2408
  const passwordToggles = this.modal.querySelectorAll('.toggle-password');
2272
2409
  passwordToggles.forEach((toggle) => {
@@ -2297,6 +2434,8 @@ class AuthModal {
2297
2434
  signupForm?.addEventListener('submit', (e) => this.handleSignup(e));
2298
2435
  const forgotForm = this.modal.querySelector('#forgotPasswordFormElement');
2299
2436
  forgotForm?.addEventListener('submit', (e) => this.handleForgotPassword(e));
2437
+ const resetPasswordForm = this.modal.querySelector('#resetPasswordFormElement');
2438
+ resetPasswordForm?.addEventListener('submit', (e) => this.handleResetPassword(e));
2300
2439
  // OAuth social login buttons
2301
2440
  this.bindSocialLoginButtons();
2302
2441
  }
@@ -2343,7 +2482,7 @@ class AuthModal {
2343
2482
  switchView(view) {
2344
2483
  if (!this.modal)
2345
2484
  return;
2346
- const views = ['loginForm', 'signupForm', 'forgotPasswordForm', 'authMessage'];
2485
+ const views = ['loginForm', 'signupForm', 'forgotPasswordForm', 'resetPasswordForm', 'authMessage'];
2347
2486
  views.forEach((viewId) => {
2348
2487
  const element = this.modal.querySelector(`#${viewId}`);
2349
2488
  element?.classList.add('hidden');
@@ -2352,6 +2491,7 @@ class AuthModal {
2352
2491
  login: 'loginForm',
2353
2492
  signup: 'signupForm',
2354
2493
  forgot: 'forgotPasswordForm',
2494
+ 'reset-password': 'resetPasswordForm',
2355
2495
  message: 'authMessage',
2356
2496
  };
2357
2497
  const targetView = this.modal.querySelector(`#${viewMap[view]}`);
@@ -2444,13 +2584,22 @@ class AuthModal {
2444
2584
  this.showMessage('Account Created', response.message || 'Your account has been created successfully!');
2445
2585
  }
2446
2586
  }
2587
+ else if (response.code === exports.ErrorCode.ACCOUNT_EXISTS) {
2588
+ // Handle account already exists error
2589
+ this.showMessage('Account Already Exists', 'This email is already registered. Please login instead.');
2590
+ }
2447
2591
  else {
2448
- throw new Error('Registration failed');
2592
+ throw new Error(response.error || 'Registration failed');
2449
2593
  }
2450
2594
  }
2451
2595
  catch (error) {
2452
- // Handle error
2453
- const errorMessage = error instanceof Error ? error.message : 'Signup failed';
2596
+ // Handle HTTP errors
2597
+ if (error.response?.data?.code === exports.ErrorCode.ACCOUNT_EXISTS) {
2598
+ this.showMessage('Account Already Exists', 'This email is already registered. Please login instead.');
2599
+ return;
2600
+ }
2601
+ // Handle other errors
2602
+ const errorMessage = error.response?.data?.error || error.message || 'Signup failed';
2454
2603
  this.showError(errorMessage);
2455
2604
  if (this.options.onError) {
2456
2605
  this.options.onError(error);
@@ -2473,8 +2622,8 @@ class AuthModal {
2473
2622
  try {
2474
2623
  // Show loading state
2475
2624
  submitBtn.disabled = true;
2476
- // TODO: Call forgot password API when available
2477
- // await this.client.postapiauthforgotpassword({ email });
2625
+ // Call forgot password API
2626
+ await this.client.forgotPassword({ email });
2478
2627
  // Show success message
2479
2628
  this.showMessage('Reset Link Sent', `We've sent a password reset link to ${email}`);
2480
2629
  }
@@ -2491,6 +2640,61 @@ class AuthModal {
2491
2640
  submitBtn.disabled = false;
2492
2641
  }
2493
2642
  }
2643
+ async handleResetPassword(e) {
2644
+ e.preventDefault();
2645
+ const passwordInput = this.modal?.querySelector('#newPassword');
2646
+ const passwordConfirmInput = this.modal?.querySelector('#newPasswordConfirm');
2647
+ const submitBtn = this.modal?.querySelector('#resetPasswordButton');
2648
+ const btnText = submitBtn?.querySelector('.btn-text');
2649
+ const btnLoader = submitBtn?.querySelector('.btn-loader');
2650
+ if (!passwordInput || !passwordConfirmInput || !submitBtn)
2651
+ return;
2652
+ const newPassword = passwordInput.value;
2653
+ const confirmPassword = passwordConfirmInput.value;
2654
+ // Validate passwords match
2655
+ if (newPassword !== confirmPassword) {
2656
+ this.showError('Passwords do not match');
2657
+ return;
2658
+ }
2659
+ // Validate token exists
2660
+ if (!this.resetToken) {
2661
+ this.showError('Reset token is missing. Please use the link from your email.');
2662
+ return;
2663
+ }
2664
+ try {
2665
+ // Show loading state
2666
+ submitBtn.disabled = true;
2667
+ btnText?.classList.add('hidden');
2668
+ btnLoader?.classList.remove('hidden');
2669
+ // Call reset password API
2670
+ await this.client.resetPassword({
2671
+ token: this.resetToken,
2672
+ new_password: newPassword,
2673
+ });
2674
+ // Clean up URL (remove verify_token from query string)
2675
+ const url = new URL(window.location.href);
2676
+ url.searchParams.delete('verify_token');
2677
+ window.history.replaceState({}, document.title, url.toString());
2678
+ // Clear reset token
2679
+ this.resetToken = null;
2680
+ // Show success message
2681
+ this.showMessage('Password Reset Successful', 'Your password has been updated successfully. You can now log in with your new password.');
2682
+ }
2683
+ catch (error) {
2684
+ // Handle error
2685
+ const errorMessage = error instanceof Error ? error.message : 'Failed to reset password';
2686
+ this.showError(errorMessage);
2687
+ if (this.options.onError) {
2688
+ this.options.onError(error);
2689
+ }
2690
+ }
2691
+ finally {
2692
+ // Reset loading state
2693
+ submitBtn.disabled = false;
2694
+ btnText?.classList.remove('hidden');
2695
+ btnLoader?.classList.add('hidden');
2696
+ }
2697
+ }
2494
2698
  showMessage(title, message) {
2495
2699
  if (!this.modal)
2496
2700
  return;