@seaverse/auth-sdk 0.2.6 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1192,12 +1192,17 @@ class SeaVerseBackendAPIClient {
1192
1192
  'X-App-ID': this.appId,
1193
1193
  ...options.headers,
1194
1194
  };
1195
+ // 设置重试配置,默认禁用重试(maxRetries: 0)
1196
+ const defaultRetryOptions = {
1197
+ maxRetries: 0,
1198
+ };
1195
1199
  const httpOptions = {
1196
1200
  baseURL: finalBaseURL,
1197
1201
  timeout: options.timeout,
1198
1202
  headers,
1199
1203
  auth: options.auth || this.getDefaultAuth(),
1200
1204
  hooks: options.hooks || this.getDefaultHooks(),
1205
+ retryOptions: options.retryOptions || defaultRetryOptions,
1201
1206
  };
1202
1207
  this.httpClient = new HttpClient(httpOptions);
1203
1208
  }
@@ -1306,10 +1311,12 @@ class SeaVerseBackendAPIClient {
1306
1311
  * Login with email and password
1307
1312
  */
1308
1313
  async login(data, options) {
1314
+ // 如果没有传 frontend_url,使用当前页面地址
1315
+ const frontend_url = data.frontend_url || (typeof window !== 'undefined' ? window.location.origin : '');
1309
1316
  const config = {
1310
1317
  method: 'POST',
1311
1318
  url: `/sdk/v1/auth/login`,
1312
- data,
1319
+ data: { ...data, frontend_url },
1313
1320
  headers: {
1314
1321
  'X-Operation-Id': 'login',
1315
1322
  ...options?.headers,
@@ -1405,6 +1412,34 @@ class SeaVerseBackendAPIClient {
1405
1412
  const response = await this.httpClient.request(config);
1406
1413
  return response.data;
1407
1414
  }
1415
+ /**
1416
+ * Verify email with token
1417
+ * Verify user email and return JWT tokens for auto-login
1418
+ *
1419
+ * @param verifyToken - Email verification token from email link
1420
+ * @param options - Additional axios request options
1421
+ * @returns Email verification response with user data and JWT tokens
1422
+ *
1423
+ * @example
1424
+ * const { data } = await client.verifyEmail('abc123def456...');
1425
+ * localStorage.setItem('token', data.token);
1426
+ * localStorage.setItem('refreshToken', data.refreshToken);
1427
+ * console.log('User:', data.user);
1428
+ */
1429
+ async verifyEmail(verifyToken, options) {
1430
+ const config = {
1431
+ method: 'GET',
1432
+ url: `/sdk/v1/auth/email/verify`,
1433
+ params: { verify_token: verifyToken },
1434
+ headers: {
1435
+ 'X-Operation-Id': 'verifyEmail',
1436
+ ...options?.headers,
1437
+ },
1438
+ ...options,
1439
+ };
1440
+ const response = await this.httpClient.request(config);
1441
+ return response.data;
1442
+ }
1408
1443
  /**
1409
1444
  * Get api-service token
1410
1445
  * Generate token for accessing api-service from sandbox
@@ -1423,6 +1458,155 @@ class SeaVerseBackendAPIClient {
1423
1458
  return response.data;
1424
1459
  }
1425
1460
  // ============================================================================
1461
+ // Invite Code Management APIs
1462
+ // ============================================================================
1463
+ /**
1464
+ * List my invite codes
1465
+ * Get all invite codes created by the current user
1466
+ *
1467
+ * @param params - Optional pagination and filtering parameters
1468
+ * @param options - Additional axios request options
1469
+ * @returns List of invite codes with pagination info
1470
+ *
1471
+ * @example
1472
+ * // List all active invite codes
1473
+ * const result = await client.listInvites({
1474
+ * status: 'active',
1475
+ * page: 1,
1476
+ * page_size: 20
1477
+ * });
1478
+ * console.log('Invites:', result.data.invites);
1479
+ */
1480
+ async listInvites(params, options) {
1481
+ const config = {
1482
+ method: 'GET',
1483
+ url: `/sdk/v1/auth/invites`,
1484
+ params,
1485
+ headers: {
1486
+ 'X-Operation-Id': 'listInvites',
1487
+ ...options?.headers,
1488
+ },
1489
+ ...options,
1490
+ };
1491
+ const response = await this.httpClient.request(config);
1492
+ return response.data;
1493
+ }
1494
+ /**
1495
+ * Get invite code statistics
1496
+ * Get statistics for invite codes created by the current user
1497
+ *
1498
+ * @param options - Additional axios request options
1499
+ * @returns Invite code statistics
1500
+ *
1501
+ * @example
1502
+ * const result = await client.getInviteStats();
1503
+ * console.log('Total codes:', result.data.total_codes);
1504
+ * console.log('Total uses:', result.data.total_uses);
1505
+ */
1506
+ async getInviteStats(options) {
1507
+ const config = {
1508
+ method: 'GET',
1509
+ url: `/sdk/v1/auth/invites/stats`,
1510
+ headers: {
1511
+ 'X-Operation-Id': 'getInviteStats',
1512
+ ...options?.headers,
1513
+ },
1514
+ ...options,
1515
+ };
1516
+ const response = await this.httpClient.request(config);
1517
+ return response.data;
1518
+ }
1519
+ /**
1520
+ * Get invite code details
1521
+ * Get detailed information for a specific invite code
1522
+ *
1523
+ * @param inviteId - Invite code ID
1524
+ * @param options - Additional axios request options
1525
+ * @returns Invite code details
1526
+ *
1527
+ * @example
1528
+ * const result = await client.getInvite('inv_abc123');
1529
+ * console.log('Code:', result.data.code);
1530
+ * console.log('Used:', result.data.used_count);
1531
+ */
1532
+ async getInvite(inviteId, options) {
1533
+ const config = {
1534
+ method: 'GET',
1535
+ url: `/sdk/v1/auth/invites/${inviteId}`,
1536
+ headers: {
1537
+ 'X-Operation-Id': 'getInvite',
1538
+ ...options?.headers,
1539
+ },
1540
+ ...options,
1541
+ };
1542
+ const response = await this.httpClient.request(config);
1543
+ return response.data;
1544
+ }
1545
+ /**
1546
+ * Get invite code usage records
1547
+ * Get all usage records for a specific invite code
1548
+ *
1549
+ * @param inviteId - Invite code ID
1550
+ * @param params - Optional pagination parameters
1551
+ * @param options - Additional axios request options
1552
+ * @returns List of usage records with pagination info
1553
+ *
1554
+ * @example
1555
+ * const result = await client.getInviteUsages('inv_abc123', {
1556
+ * page: 1,
1557
+ * page_size: 20
1558
+ * });
1559
+ * console.log('Usages:', result.data.usages);
1560
+ */
1561
+ async getInviteUsages(inviteId, params, options) {
1562
+ const config = {
1563
+ method: 'GET',
1564
+ url: `/sdk/v1/auth/invites/${inviteId}/usages`,
1565
+ params,
1566
+ headers: {
1567
+ 'X-Operation-Id': 'getInviteUsages',
1568
+ ...options?.headers,
1569
+ },
1570
+ ...options,
1571
+ };
1572
+ const response = await this.httpClient.request(config);
1573
+ return response.data;
1574
+ }
1575
+ /**
1576
+ * Bind invitation code to temporary account
1577
+ * Activate a temporary account by binding an invitation code
1578
+ *
1579
+ * @param data - Bind invite code request
1580
+ * @param options - Additional axios request options
1581
+ * @returns Activation response with JWT tokens and user info
1582
+ *
1583
+ * @example
1584
+ * // Bind invitation code to activate temporary account
1585
+ * const result = await client.bindInviteCode({
1586
+ * user_id: 'user_temp_xyz789',
1587
+ * invite_code: 'ABCD1234'
1588
+ * });
1589
+ *
1590
+ * // Auto-login with returned tokens
1591
+ * localStorage.setItem('token', result.data.token);
1592
+ * localStorage.setItem('refreshToken', result.data.refreshToken);
1593
+ * console.log('Account activated:', result.data.user);
1594
+ */
1595
+ async bindInviteCode(data, options) {
1596
+ const config = {
1597
+ method: 'POST',
1598
+ url: `/sdk/v1/auth/bind-invite-code`,
1599
+ data,
1600
+ headers: {
1601
+ 'X-Operation-Id': 'bindInviteCode',
1602
+ ...options?.headers,
1603
+ },
1604
+ ...options,
1605
+ };
1606
+ const response = await this.httpClient.request(config);
1607
+ return response.data;
1608
+ }
1609
+ // ============================================================================
1426
1610
  // OAuth APIs
1427
1611
  // ============================================================================
1428
1612
  /**
@@ -1810,15 +1994,559 @@ class SeaVerseBackendAPIClient {
1810
1994
  }
1811
1995
  }
1812
1996
 
1997
+ /**
1998
+ * Toast Notification System
1999
+ * A modern, glass-morphism inspired notification component
2000
+ */
2001
+ class Toast {
2002
+ /**
2003
+ * Show a toast notification
2004
+ */
2005
+ static show(options) {
2006
+ const { type, title, message, duration = 3000, onClose, } = options;
2007
+ // Ensure CSS is injected
2008
+ if (!this.cssInjected) {
2009
+ this.injectCSS();
2010
+ }
2011
+ // Ensure container exists
2012
+ if (!this.container) {
2013
+ this.createContainer();
2014
+ }
2015
+ // Create toast element
2016
+ const toast = this.createToast(type, title, message, onClose);
2017
+ // Add to container
2018
+ this.container.appendChild(toast);
2019
+ this.toasts.push(toast);
2020
+ // Trigger slide-in animation
2021
+ requestAnimationFrame(() => {
2022
+ toast.classList.add('toast-show');
2023
+ });
2024
+ // Auto-dismiss
2025
+ if (duration > 0) {
2026
+ setTimeout(() => {
2027
+ this.dismiss(toast, onClose);
2028
+ }, duration);
2029
+ }
2030
+ }
2031
+ /**
2032
+ * Convenience methods for different types
2033
+ */
2034
+ static success(title, message, duration) {
2035
+ this.show({ type: 'success', title, message, duration });
2036
+ }
2037
+ static error(title, message, duration) {
2038
+ this.show({ type: 'error', title, message, duration });
2039
+ }
2040
+ static warning(title, message, duration) {
2041
+ this.show({ type: 'warning', title, message, duration });
2042
+ }
2043
+ static info(title, message, duration) {
2044
+ this.show({ type: 'info', title, message, duration });
2045
+ }
2046
+ /**
2047
+ * Create the toast container
2048
+ */
2049
+ static createContainer() {
2050
+ this.container = document.createElement('div');
2051
+ this.container.className = 'toast-container';
2052
+ document.body.appendChild(this.container);
2053
+ }
2054
+ /**
2055
+ * Create a toast element using safe DOM methods
2056
+ */
2057
+ static createToast(type, title, message, onClose) {
2058
+ const id = `toast-${this.nextId++}`;
2059
+ const toast = document.createElement('div');
2060
+ toast.className = `toast toast-${type}`;
2061
+ toast.id = id;
2062
+ // Icon container
2063
+ const iconContainer = document.createElement('div');
2064
+ iconContainer.className = 'toast-icon';
2065
+ const iconSvg = this.createIconSVG(type);
2066
+ iconContainer.appendChild(iconSvg);
2067
+ toast.appendChild(iconContainer);
2068
+ // Content container
2069
+ const contentContainer = document.createElement('div');
2070
+ contentContainer.className = 'toast-content';
2071
+ const titleElement = document.createElement('div');
2072
+ titleElement.className = 'toast-title';
2073
+ titleElement.textContent = title;
2074
+ contentContainer.appendChild(titleElement);
2075
+ const messageElement = document.createElement('div');
2076
+ messageElement.className = 'toast-message';
2077
+ messageElement.textContent = message;
2078
+ contentContainer.appendChild(messageElement);
2079
+ toast.appendChild(contentContainer);
2080
+ // Close button
2081
+ const closeBtn = document.createElement('button');
2082
+ closeBtn.className = 'toast-close';
2083
+ closeBtn.setAttribute('aria-label', 'Close notification');
2084
+ closeBtn.addEventListener('click', () => {
2085
+ this.dismiss(toast, onClose);
2086
+ });
2087
+ // Close icon SVG
2088
+ const closeSvg = this.createCloseSVG();
2089
+ closeBtn.appendChild(closeSvg);
2090
+ toast.appendChild(closeBtn);
2091
+ return toast;
2092
+ }
2093
+ /**
2094
+ * Create icon SVG element for each type
2095
+ */
2096
+ static createIconSVG(type) {
2097
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
2098
+ svg.setAttribute('width', '24');
2099
+ svg.setAttribute('height', '24');
2100
+ svg.setAttribute('viewBox', '0 0 24 24');
2101
+ svg.setAttribute('fill', 'none');
2102
+ if (type === 'success') {
2103
+ // Background circle
2104
+ const bgCircle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
2105
+ bgCircle.setAttribute('cx', '12');
2106
+ bgCircle.setAttribute('cy', '12');
2107
+ bgCircle.setAttribute('r', '10');
2108
+ bgCircle.setAttribute('stroke', 'currentColor');
2109
+ bgCircle.setAttribute('stroke-width', '2');
2110
+ bgCircle.setAttribute('opacity', '0.2');
2111
+ svg.appendChild(bgCircle);
2112
+ // Animated circle
2113
+ const animCircle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
2114
+ animCircle.setAttribute('class', 'toast-icon-circle');
2115
+ animCircle.setAttribute('cx', '12');
2116
+ animCircle.setAttribute('cy', '12');
2117
+ animCircle.setAttribute('r', '10');
2118
+ animCircle.setAttribute('stroke', 'currentColor');
2119
+ animCircle.setAttribute('stroke-width', '2');
2120
+ animCircle.setAttribute('stroke-dasharray', '63');
2121
+ animCircle.setAttribute('stroke-dashoffset', '63');
2122
+ svg.appendChild(animCircle);
2123
+ // Check mark
2124
+ const check = document.createElementNS('http://www.w3.org/2000/svg', 'path');
2125
+ check.setAttribute('class', 'toast-icon-check');
2126
+ check.setAttribute('d', 'M8 12.5L10.5 15L16 9.5');
2127
+ check.setAttribute('stroke', 'currentColor');
2128
+ check.setAttribute('stroke-width', '2');
2129
+ check.setAttribute('stroke-linecap', 'round');
2130
+ check.setAttribute('stroke-linejoin', 'round');
2131
+ check.setAttribute('opacity', '0');
2132
+ svg.appendChild(check);
2133
+ }
2134
+ else if (type === 'error') {
2135
+ // Background circle
2136
+ const bgCircle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
2137
+ bgCircle.setAttribute('cx', '12');
2138
+ bgCircle.setAttribute('cy', '12');
2139
+ bgCircle.setAttribute('r', '10');
2140
+ bgCircle.setAttribute('stroke', 'currentColor');
2141
+ bgCircle.setAttribute('stroke-width', '2');
2142
+ bgCircle.setAttribute('opacity', '0.2');
2143
+ svg.appendChild(bgCircle);
2144
+ // Animated circle
2145
+ const animCircle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
2146
+ animCircle.setAttribute('class', 'toast-icon-circle');
2147
+ animCircle.setAttribute('cx', '12');
2148
+ animCircle.setAttribute('cy', '12');
2149
+ animCircle.setAttribute('r', '10');
2150
+ animCircle.setAttribute('stroke', 'currentColor');
2151
+ animCircle.setAttribute('stroke-width', '2');
2152
+ animCircle.setAttribute('stroke-dasharray', '63');
2153
+ animCircle.setAttribute('stroke-dashoffset', '63');
2154
+ svg.appendChild(animCircle);
2155
+ // X mark
2156
+ const xMark = document.createElementNS('http://www.w3.org/2000/svg', 'path');
2157
+ xMark.setAttribute('class', 'toast-icon-x');
2158
+ xMark.setAttribute('d', 'M9 9L15 15M15 9L9 15');
2159
+ xMark.setAttribute('stroke', 'currentColor');
2160
+ xMark.setAttribute('stroke-width', '2');
2161
+ xMark.setAttribute('stroke-linecap', 'round');
2162
+ xMark.setAttribute('opacity', '0');
2163
+ svg.appendChild(xMark);
2164
+ }
2165
+ else if (type === 'warning') {
2166
+ // Background triangle
2167
+ const bgTriangle = document.createElementNS('http://www.w3.org/2000/svg', 'path');
2168
+ bgTriangle.setAttribute('d', 'M12 2L2 20H22L12 2Z');
2169
+ bgTriangle.setAttribute('stroke', 'currentColor');
2170
+ bgTriangle.setAttribute('stroke-width', '2');
2171
+ bgTriangle.setAttribute('opacity', '0.2');
2172
+ svg.appendChild(bgTriangle);
2173
+ // Animated triangle
2174
+ const animTriangle = document.createElementNS('http://www.w3.org/2000/svg', 'path');
2175
+ animTriangle.setAttribute('class', 'toast-icon-triangle');
2176
+ animTriangle.setAttribute('d', 'M12 2L2 20H22L12 2Z');
2177
+ animTriangle.setAttribute('stroke', 'currentColor');
2178
+ animTriangle.setAttribute('stroke-width', '2');
2179
+ animTriangle.setAttribute('stroke-dasharray', '80');
2180
+ animTriangle.setAttribute('stroke-dashoffset', '80');
2181
+ svg.appendChild(animTriangle);
2182
+ // Exclamation mark
2183
+ const exclaim = document.createElementNS('http://www.w3.org/2000/svg', 'path');
2184
+ exclaim.setAttribute('class', 'toast-icon-exclaim');
2185
+ exclaim.setAttribute('d', 'M12 9V13M12 16V16.5');
2186
+ exclaim.setAttribute('stroke', 'currentColor');
2187
+ exclaim.setAttribute('stroke-width', '2');
2188
+ exclaim.setAttribute('stroke-linecap', 'round');
2189
+ exclaim.setAttribute('opacity', '0');
2190
+ svg.appendChild(exclaim);
2191
+ }
2192
+ else { // info
2193
+ // Background circle
2194
+ const bgCircle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
2195
+ bgCircle.setAttribute('cx', '12');
2196
+ bgCircle.setAttribute('cy', '12');
2197
+ bgCircle.setAttribute('r', '10');
2198
+ bgCircle.setAttribute('stroke', 'currentColor');
2199
+ bgCircle.setAttribute('stroke-width', '2');
2200
+ bgCircle.setAttribute('opacity', '0.2');
2201
+ svg.appendChild(bgCircle);
2202
+ // Animated circle
2203
+ const animCircle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
2204
+ animCircle.setAttribute('class', 'toast-icon-circle');
2205
+ animCircle.setAttribute('cx', '12');
2206
+ animCircle.setAttribute('cy', '12');
2207
+ animCircle.setAttribute('r', '10');
2208
+ animCircle.setAttribute('stroke', 'currentColor');
2209
+ animCircle.setAttribute('stroke-width', '2');
2210
+ animCircle.setAttribute('stroke-dasharray', '63');
2211
+ animCircle.setAttribute('stroke-dashoffset', '63');
2212
+ svg.appendChild(animCircle);
2213
+ // Info i
2214
+ const iMark = document.createElementNS('http://www.w3.org/2000/svg', 'path');
2215
+ iMark.setAttribute('class', 'toast-icon-i');
2216
+ iMark.setAttribute('d', 'M12 8V8.5M12 12V16');
2217
+ iMark.setAttribute('stroke', 'currentColor');
2218
+ iMark.setAttribute('stroke-width', '2');
2219
+ iMark.setAttribute('stroke-linecap', 'round');
2220
+ iMark.setAttribute('opacity', '0');
2221
+ svg.appendChild(iMark);
2222
+ }
2223
+ return svg;
2224
+ }
2225
+ /**
2226
+ * Create close button SVG
2227
+ */
2228
+ static createCloseSVG() {
2229
+ const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
2230
+ svg.setAttribute('width', '16');
2231
+ svg.setAttribute('height', '16');
2232
+ svg.setAttribute('viewBox', '0 0 16 16');
2233
+ svg.setAttribute('fill', 'none');
2234
+ const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
2235
+ path.setAttribute('d', 'M12 4L4 12M4 4L12 12');
2236
+ path.setAttribute('stroke', 'currentColor');
2237
+ path.setAttribute('stroke-width', '1.5');
2238
+ path.setAttribute('stroke-linecap', 'round');
2239
+ svg.appendChild(path);
2240
+ return svg;
2241
+ }
2242
+ /**
2243
+ * Dismiss a toast
2244
+ */
2245
+ static dismiss(toast, onClose) {
2246
+ // Trigger slide-out animation
2247
+ toast.classList.add('toast-hide');
2248
+ // Remove from DOM after animation
2249
+ setTimeout(() => {
2250
+ if (toast.parentNode) {
2251
+ toast.parentNode.removeChild(toast);
2252
+ }
2253
+ // Remove from array
2254
+ const index = this.toasts.indexOf(toast);
2255
+ if (index > -1) {
2256
+ this.toasts.splice(index, 1);
2257
+ }
2258
+ // Call onClose callback
2259
+ if (onClose) {
2260
+ onClose();
2261
+ }
2262
+ // Remove container if no toasts left
2263
+ if (this.toasts.length === 0 && this.container) {
2264
+ this.container.remove();
2265
+ this.container = null;
2266
+ }
2267
+ }, 300);
2268
+ }
2269
+ /**
2270
+ * Dismiss all toasts
2271
+ */
2272
+ static dismissAll() {
2273
+ this.toasts.forEach(toast => {
2274
+ this.dismiss(toast);
2275
+ });
2276
+ }
2277
+ /**
2278
+ * Inject Toast CSS into the page
2279
+ */
2280
+ static injectCSS() {
2281
+ if (this.cssInjected)
2282
+ return;
2283
+ const cssContent = `
2284
+ /* Toast Container */
2285
+ .toast-container {
2286
+ position: fixed;
2287
+ top: 24px;
2288
+ right: 24px;
2289
+ z-index: 10000;
2290
+ display: flex;
2291
+ flex-direction: column;
2292
+ gap: 12px;
2293
+ pointer-events: none;
2294
+ max-width: 420px;
2295
+ width: calc(100vw - 48px);
2296
+ }
2297
+
2298
+ @media (max-width: 640px) {
2299
+ .toast-container {
2300
+ top: 16px;
2301
+ right: 16px;
2302
+ left: 16px;
2303
+ width: auto;
2304
+ max-width: none;
2305
+ }
2306
+ }
2307
+
2308
+ /* Toast Base */
2309
+ .toast {
2310
+ position: relative;
2311
+ display: flex;
2312
+ align-items: flex-start;
2313
+ gap: 14px;
2314
+ padding: 18px 20px;
2315
+ border-radius: 16px;
2316
+ background: rgba(20, 20, 24, 0.92);
2317
+ backdrop-filter: blur(20px) saturate(180%);
2318
+ -webkit-backdrop-filter: blur(20px) saturate(180%);
2319
+ border: 1px solid rgba(255, 255, 255, 0.08);
2320
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4), 0 8px 32px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.05);
2321
+ pointer-events: auto;
2322
+ transform: translateY(-120%) translateZ(0);
2323
+ opacity: 0;
2324
+ transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
2325
+ will-change: transform, opacity;
2326
+ }
2327
+
2328
+ .toast-show {
2329
+ transform: translateY(0) translateZ(0);
2330
+ opacity: 1;
2331
+ }
2332
+
2333
+ .toast-hide {
2334
+ transform: translateY(-120%) translateZ(0);
2335
+ opacity: 0;
2336
+ transition: all 0.25s cubic-bezier(0.4, 0, 1, 1);
2337
+ }
2338
+
2339
+ .toast-icon {
2340
+ flex-shrink: 0;
2341
+ width: 24px;
2342
+ height: 24px;
2343
+ margin-top: 2px;
2344
+ position: relative;
2345
+ }
2346
+
2347
+ @keyframes drawCircle {
2348
+ to { stroke-dashoffset: 0; }
2349
+ }
2350
+
2351
+ @keyframes drawTriangle {
2352
+ to { stroke-dashoffset: 0; }
2353
+ }
2354
+
2355
+ @keyframes fadeInIcon {
2356
+ to { opacity: 1; }
2357
+ }
2358
+
2359
+ .toast-show .toast-icon-circle {
2360
+ animation: drawCircle 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.1s forwards;
2361
+ }
2362
+
2363
+ .toast-show .toast-icon-triangle {
2364
+ animation: drawTriangle 0.5s cubic-bezier(0.4, 0, 0.2, 1) 0.1s forwards;
2365
+ }
2366
+
2367
+ .toast-show .toast-icon-check,
2368
+ .toast-show .toast-icon-x,
2369
+ .toast-show .toast-icon-exclaim,
2370
+ .toast-show .toast-icon-i {
2371
+ animation: fadeInIcon 0.3s ease-out 0.4s forwards;
2372
+ }
2373
+
2374
+ .toast-content {
2375
+ flex: 1;
2376
+ min-width: 0;
2377
+ padding-top: 1px;
2378
+ }
2379
+
2380
+ .toast-title {
2381
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
2382
+ font-size: 15px;
2383
+ font-weight: 700;
2384
+ line-height: 1.4;
2385
+ letter-spacing: -0.01em;
2386
+ color: rgba(255, 255, 255, 0.95);
2387
+ margin-bottom: 4px;
2388
+ }
2389
+
2390
+ .toast-message {
2391
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
2392
+ font-size: 13.5px;
2393
+ font-weight: 400;
2394
+ line-height: 1.5;
2395
+ letter-spacing: -0.005em;
2396
+ color: rgba(255, 255, 255, 0.65);
2397
+ }
2398
+
2399
+ .toast-close {
2400
+ flex-shrink: 0;
2401
+ width: 28px;
2402
+ height: 28px;
2403
+ display: flex;
2404
+ align-items: center;
2405
+ justify-content: center;
2406
+ border: none;
2407
+ background: transparent;
2408
+ color: rgba(255, 255, 255, 0.4);
2409
+ cursor: pointer;
2410
+ border-radius: 8px;
2411
+ transition: all 0.2s ease;
2412
+ margin: -4px -6px 0 0;
2413
+ }
2414
+
2415
+ .toast-close:hover {
2416
+ background: rgba(255, 255, 255, 0.08);
2417
+ color: rgba(255, 255, 255, 0.7);
2418
+ }
2419
+
2420
+ .toast-close:active {
2421
+ transform: scale(0.92);
2422
+ }
2423
+
2424
+ .toast-success {
2425
+ border-color: rgba(52, 211, 153, 0.2);
2426
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4), 0 8px 32px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.05), 0 0 0 1px rgba(52, 211, 153, 0.15), 0 0 24px rgba(52, 211, 153, 0.12);
2427
+ }
2428
+
2429
+ .toast-success .toast-icon {
2430
+ color: #34d399;
2431
+ filter: drop-shadow(0 0 8px rgba(52, 211, 153, 0.4));
2432
+ }
2433
+
2434
+ @keyframes successGlow {
2435
+ 0%, 100% { box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4), 0 8px 32px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.05), 0 0 0 1px rgba(52, 211, 153, 0.15), 0 0 24px rgba(52, 211, 153, 0.12); }
2436
+ 50% { box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4), 0 8px 32px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.05), 0 0 0 1px rgba(52, 211, 153, 0.25), 0 0 32px rgba(52, 211, 153, 0.2); }
2437
+ }
2438
+
2439
+ .toast-success.toast-show {
2440
+ animation: successGlow 2s ease-in-out infinite;
2441
+ }
2442
+
2443
+ .toast-error {
2444
+ border-color: rgba(248, 113, 113, 0.2);
2445
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4), 0 8px 32px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.05), 0 0 0 1px rgba(248, 113, 113, 0.15), 0 0 24px rgba(248, 113, 113, 0.12);
2446
+ }
2447
+
2448
+ .toast-error .toast-icon {
2449
+ color: #f87171;
2450
+ filter: drop-shadow(0 0 8px rgba(248, 113, 113, 0.4));
2451
+ }
2452
+
2453
+ @keyframes errorGlow {
2454
+ 0%, 100% { box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4), 0 8px 32px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.05), 0 0 0 1px rgba(248, 113, 113, 0.15), 0 0 24px rgba(248, 113, 113, 0.12); }
2455
+ 50% { box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4), 0 8px 32px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.05), 0 0 0 1px rgba(248, 113, 113, 0.25), 0 0 32px rgba(248, 113, 113, 0.2); }
2456
+ }
2457
+
2458
+ .toast-error.toast-show {
2459
+ animation: errorGlow 2s ease-in-out infinite;
2460
+ }
2461
+
2462
+ .toast-warning {
2463
+ border-color: rgba(251, 191, 36, 0.2);
2464
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4), 0 8px 32px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.05), 0 0 0 1px rgba(251, 191, 36, 0.15), 0 0 24px rgba(251, 191, 36, 0.12);
2465
+ }
2466
+
2467
+ .toast-warning .toast-icon {
2468
+ color: #fbbf24;
2469
+ filter: drop-shadow(0 0 8px rgba(251, 191, 36, 0.4));
2470
+ }
2471
+
2472
+ @keyframes warningGlow {
2473
+ 0%, 100% { box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4), 0 8px 32px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.05), 0 0 0 1px rgba(251, 191, 36, 0.15), 0 0 24px rgba(251, 191, 36, 0.12); }
2474
+ 50% { box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4), 0 8px 32px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.05), 0 0 0 1px rgba(251, 191, 36, 0.25), 0 0 32px rgba(251, 191, 36, 0.2); }
2475
+ }
2476
+
2477
+ .toast-warning.toast-show {
2478
+ animation: warningGlow 2s ease-in-out infinite;
2479
+ }
2480
+
2481
+ .toast-info {
2482
+ border-color: rgba(96, 165, 250, 0.2);
2483
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4), 0 8px 32px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.05), 0 0 0 1px rgba(96, 165, 250, 0.15), 0 0 24px rgba(96, 165, 250, 0.12);
2484
+ }
2485
+
2486
+ .toast-info .toast-icon {
2487
+ color: #60a5fa;
2488
+ filter: drop-shadow(0 0 8px rgba(96, 165, 250, 0.4));
2489
+ }
2490
+
2491
+ @keyframes infoGlow {
2492
+ 0%, 100% { box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4), 0 8px 32px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.05), 0 0 0 1px rgba(96, 165, 250, 0.15), 0 0 24px rgba(96, 165, 250, 0.12); }
2493
+ 50% { box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4), 0 8px 32px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.05), 0 0 0 1px rgba(96, 165, 250, 0.25), 0 0 32px rgba(96, 165, 250, 0.2); }
2494
+ }
2495
+
2496
+ .toast-info.toast-show {
2497
+ animation: infoGlow 2s ease-in-out infinite;
2498
+ }
2499
+
2500
+ @media (prefers-reduced-motion: reduce) {
2501
+ .toast {
2502
+ transition: opacity 0.2s ease;
2503
+ animation: none !important;
2504
+ }
2505
+ .toast-show {
2506
+ transform: none;
2507
+ }
2508
+ .toast-hide {
2509
+ transform: none;
2510
+ }
2511
+ .toast-icon-circle,
2512
+ .toast-icon-triangle,
2513
+ .toast-icon-check,
2514
+ .toast-icon-x,
2515
+ .toast-icon-exclaim,
2516
+ .toast-icon-i {
2517
+ animation: none !important;
2518
+ opacity: 1 !important;
2519
+ stroke-dashoffset: 0 !important;
2520
+ }
2521
+ }
2522
+ `;
2523
+ const style = document.createElement('style');
2524
+ style.id = 'toast-styles';
2525
+ style.textContent = cssContent;
2526
+ document.head.appendChild(style);
2527
+ this.cssInjected = true;
2528
+ }
2529
+ }
2530
+ Toast.container = null;
2531
+ Toast.toasts = [];
2532
+ Toast.nextId = 0;
2533
+ Toast.cssInjected = false;
2534
+
1813
2535
  class AuthModal {
1814
2536
  constructor(options) {
1815
2537
  this.modal = null;
1816
2538
  this.currentView = 'login';
1817
2539
  this.resetToken = null;
2540
+ this.tempUserId = null; // 临时用户ID(需要激活)
2541
+ this.tempUserEmail = null; // 临时用户邮箱
1818
2542
  this.client = options.client;
1819
2543
  this.options = options;
2544
+ // Auto-detect email verification token in URL
2545
+ this.checkForEmailVerification();
1820
2546
  // Auto-detect reset token in URL
1821
2547
  this.checkForResetToken();
2548
+ // Auto-detect invite code required error
2549
+ this.checkForInviteCodeRequired();
1822
2550
  }
1823
2551
  /**
1824
2552
  * Show the authentication modal
@@ -1929,6 +2657,9 @@ class AuthModal {
1929
2657
  // Reset password form
1930
2658
  const resetForm = this.createResetPasswordForm();
1931
2659
  rightPanel.appendChild(resetForm);
2660
+ // Invite code form
2661
+ const inviteCodeForm = this.createInviteCodeForm();
2662
+ rightPanel.appendChild(inviteCodeForm);
1932
2663
  // Success message
1933
2664
  const successMessage = this.createSuccessMessage();
1934
2665
  rightPanel.appendChild(successMessage);
@@ -2262,6 +2993,60 @@ class AuthModal {
2262
2993
  container.appendChild(footer);
2263
2994
  return container;
2264
2995
  }
2996
+ createInviteCodeForm() {
2997
+ const container = document.createElement('div');
2998
+ container.id = 'inviteCodeForm';
2999
+ container.className = 'auth-form-view hidden';
3000
+ // Icon
3001
+ const icon = document.createElement('div');
3002
+ icon.className = 'forgot-password-icon';
3003
+ icon.innerHTML = '<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="5" y="11" width="14" height="10" rx="2" /><path d="M12 11V7a3 3 0 0 1 3-3h0a3 3 0 0 1 3 3v4" /><circle cx="12" cy="15" r="1" /></svg>';
3004
+ container.appendChild(icon);
3005
+ // Header
3006
+ const header = document.createElement('div');
3007
+ header.className = 'auth-form-header';
3008
+ const title = document.createElement('h2');
3009
+ title.className = 'auth-form-title';
3010
+ title.textContent = 'Activation Required';
3011
+ const subtitle = document.createElement('p');
3012
+ subtitle.className = 'auth-form-subtitle';
3013
+ subtitle.id = 'inviteCodeSubtitle';
3014
+ subtitle.textContent = 'Enter your invitation code to activate your account';
3015
+ header.appendChild(title);
3016
+ header.appendChild(subtitle);
3017
+ container.appendChild(header);
3018
+ // Form
3019
+ const form = document.createElement('form');
3020
+ form.id = 'inviteCodeFormElement';
3021
+ form.className = 'auth-form';
3022
+ const codeGroup = this.createFormGroup('inviteCodeInput', 'Invitation Code', 'text', 'Enter invitation code');
3023
+ form.appendChild(codeGroup);
3024
+ const submitBtn = document.createElement('button');
3025
+ submitBtn.type = 'submit';
3026
+ submitBtn.id = 'bindInviteCodeButton';
3027
+ submitBtn.className = 'btn-auth-primary';
3028
+ const btnText = document.createElement('span');
3029
+ btnText.className = 'btn-text';
3030
+ btnText.textContent = 'Activate Account';
3031
+ const btnLoader = document.createElement('span');
3032
+ btnLoader.className = 'btn-loader hidden';
3033
+ btnLoader.innerHTML = '<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>';
3034
+ submitBtn.appendChild(btnText);
3035
+ submitBtn.appendChild(btnLoader);
3036
+ form.appendChild(submitBtn);
3037
+ container.appendChild(form);
3038
+ // Footer
3039
+ const footer = document.createElement('div');
3040
+ footer.className = 'auth-footer forgot-footer';
3041
+ const backLink = document.createElement('a');
3042
+ backLink.href = '#';
3043
+ backLink.id = 'backToLoginFromInvite';
3044
+ backLink.className = 'back-to-login';
3045
+ backLink.innerHTML = '<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>';
3046
+ footer.appendChild(backLink);
3047
+ container.appendChild(footer);
3048
+ return container;
3049
+ }
2265
3050
  createSuccessMessage() {
2266
3051
  const container = document.createElement('div');
2267
3052
  container.id = 'authMessage';
@@ -2351,12 +3136,59 @@ class AuthModal {
2351
3136
  button.appendChild(span);
2352
3137
  return button;
2353
3138
  }
3139
+ /**
3140
+ * Check if URL contains email verification token and auto-verify
3141
+ */
3142
+ async checkForEmailVerification() {
3143
+ const urlParams = new URLSearchParams(window.location.search);
3144
+ const verifyToken = urlParams.get('verify_token');
3145
+ if (!verifyToken) {
3146
+ return;
3147
+ }
3148
+ console.log('[AuthModal] Detected verify_token, starting email verification...');
3149
+ try {
3150
+ // Call email verification API
3151
+ const result = await this.client.verifyEmail(verifyToken);
3152
+ if (result.success && result.data.token) {
3153
+ console.log('[AuthModal] Email verification successful:', result.data.user);
3154
+ // Auto-login: set token in client
3155
+ this.client.setToken(result.data.token);
3156
+ // Clean up URL parameters
3157
+ urlParams.delete('verify_token');
3158
+ const newUrl = window.location.pathname + (urlParams.toString() ? '?' + urlParams.toString() : '');
3159
+ window.history.replaceState({}, '', newUrl);
3160
+ // Trigger onLoginSuccess callback
3161
+ if (this.options.onLoginSuccess) {
3162
+ this.options.onLoginSuccess(result.data.token, result.data.user);
3163
+ }
3164
+ // Show success message
3165
+ this.showSuccess('Email Verified!', 'Your email has been verified successfully. You are now logged in.');
3166
+ }
3167
+ else {
3168
+ throw new Error(result.data?.message || 'Email verification failed');
3169
+ }
3170
+ }
3171
+ catch (error) {
3172
+ console.error('[AuthModal] Email verification failed:', error);
3173
+ // Clean up URL on error too
3174
+ urlParams.delete('verify_token');
3175
+ const newUrl = window.location.pathname + (urlParams.toString() ? '?' + urlParams.toString() : '');
3176
+ window.history.replaceState({}, '', newUrl);
3177
+ // Show error message
3178
+ const errorMessage = error.response?.data?.error || error.message || 'Email verification failed';
3179
+ this.showError('Verification Failed', errorMessage + '. Please try registering again or contact support.');
3180
+ // Trigger error callback
3181
+ if (this.options.onError) {
3182
+ this.options.onError(error);
3183
+ }
3184
+ }
3185
+ }
2354
3186
  /**
2355
3187
  * Check if URL contains reset token and auto-show reset password form
2356
3188
  */
2357
3189
  checkForResetToken() {
2358
3190
  const urlParams = new URLSearchParams(window.location.search);
2359
- const verifyToken = urlParams.get('verify_token');
3191
+ const verifyToken = urlParams.get('reset_token');
2360
3192
  if (verifyToken) {
2361
3193
  this.resetToken = verifyToken;
2362
3194
  // Auto-show modal with reset password form
@@ -2365,6 +3197,58 @@ class AuthModal {
2365
3197
  }, 100);
2366
3198
  }
2367
3199
  }
3200
+ /**
3201
+ * Check if URL contains invite code required error and auto-show invite code form
3202
+ */
3203
+ checkForInviteCodeRequired() {
3204
+ const urlParams = new URLSearchParams(window.location.search);
3205
+ const errorCode = urlParams.get('error_code');
3206
+ const userId = urlParams.get('user_id');
3207
+ const tempUserId = urlParams.get('temp_user_id'); // Support alternative parameter name
3208
+ const email = urlParams.get('email');
3209
+ // Log for debugging
3210
+ if (errorCode === 'INVITE_CODE_REQUIRED') {
3211
+ console.log('[AuthModal] Detected INVITE_CODE_REQUIRED:', {
3212
+ errorCode,
3213
+ userId,
3214
+ tempUserId,
3215
+ email,
3216
+ fullURL: window.location.href
3217
+ });
3218
+ }
3219
+ if (errorCode === 'INVITE_CODE_REQUIRED') {
3220
+ // Use user_id or temp_user_id
3221
+ const finalUserId = userId || tempUserId;
3222
+ if (!finalUserId) {
3223
+ // Missing user_id - show error
3224
+ console.error('[AuthModal] Missing user_id in URL parameters. Backend should include user_id when redirecting with INVITE_CODE_REQUIRED.');
3225
+ alert('错误:后端重定向缺少 user_id 参数。请联系技术支持。\n\n完整URL: ' + window.location.href);
3226
+ // Clean up URL anyway
3227
+ const url = new URL(window.location.href);
3228
+ url.searchParams.delete('error_code');
3229
+ window.history.replaceState({}, document.title, url.toString());
3230
+ return;
3231
+ }
3232
+ this.tempUserId = finalUserId;
3233
+ this.tempUserEmail = email || '';
3234
+ // Clean up URL
3235
+ const url = new URL(window.location.href);
3236
+ url.searchParams.delete('error_code');
3237
+ url.searchParams.delete('user_id');
3238
+ url.searchParams.delete('temp_user_id');
3239
+ url.searchParams.delete('email');
3240
+ window.history.replaceState({}, document.title, url.toString());
3241
+ // Trigger callback if provided
3242
+ if (this.options.onInviteCodeRequired) {
3243
+ this.options.onInviteCodeRequired(finalUserId, email || '');
3244
+ }
3245
+ // Auto-show modal with invite code form
3246
+ console.log('[AuthModal] Auto-showing invite code form');
3247
+ setTimeout(() => {
3248
+ this.show('invite-code');
3249
+ }, 100);
3250
+ }
3251
+ }
2368
3252
  bindEventListeners() {
2369
3253
  if (!this.modal)
2370
3254
  return;
@@ -2400,6 +3284,11 @@ class AuthModal {
2400
3284
  e.preventDefault();
2401
3285
  this.switchView('login');
2402
3286
  });
3287
+ const backToLoginFromInvite = this.modal.querySelector('#backToLoginFromInvite');
3288
+ backToLoginFromInvite?.addEventListener('click', (e) => {
3289
+ e.preventDefault();
3290
+ this.switchView('login');
3291
+ });
2403
3292
  // Password toggle
2404
3293
  const passwordToggles = this.modal.querySelectorAll('.toggle-password');
2405
3294
  passwordToggles.forEach((toggle) => {
@@ -2432,6 +3321,8 @@ class AuthModal {
2432
3321
  forgotForm?.addEventListener('submit', (e) => this.handleForgotPassword(e));
2433
3322
  const resetPasswordForm = this.modal.querySelector('#resetPasswordFormElement');
2434
3323
  resetPasswordForm?.addEventListener('submit', (e) => this.handleResetPassword(e));
3324
+ const inviteCodeForm = this.modal.querySelector('#inviteCodeFormElement');
3325
+ inviteCodeForm?.addEventListener('submit', (e) => this.handleBindInviteCode(e));
2435
3326
  // OAuth social login buttons
2436
3327
  this.bindSocialLoginButtons();
2437
3328
  }
@@ -2478,7 +3369,7 @@ class AuthModal {
2478
3369
  switchView(view) {
2479
3370
  if (!this.modal)
2480
3371
  return;
2481
- const views = ['loginForm', 'signupForm', 'forgotPasswordForm', 'resetPasswordForm', 'authMessage'];
3372
+ const views = ['loginForm', 'signupForm', 'forgotPasswordForm', 'resetPasswordForm', 'inviteCodeForm', 'authMessage'];
2482
3373
  views.forEach((viewId) => {
2483
3374
  const element = this.modal.querySelector(`#${viewId}`);
2484
3375
  element?.classList.add('hidden');
@@ -2488,6 +3379,7 @@ class AuthModal {
2488
3379
  signup: 'signupForm',
2489
3380
  forgot: 'forgotPasswordForm',
2490
3381
  'reset-password': 'resetPasswordForm',
3382
+ 'invite-code': 'inviteCodeForm',
2491
3383
  message: 'authMessage',
2492
3384
  };
2493
3385
  const targetView = this.modal.querySelector(`#${viewMap[view]}`);
@@ -2520,7 +3412,7 @@ class AuthModal {
2520
3412
  if (this.options.onLoginSuccess) {
2521
3413
  this.options.onLoginSuccess(response.token, response.user);
2522
3414
  }
2523
- this.showMessage('Login Successful', 'Welcome back!');
3415
+ this.showSuccess('Login Successful', 'Welcome back!');
2524
3416
  }
2525
3417
  else {
2526
3418
  throw new Error('Invalid response from server');
@@ -2529,7 +3421,7 @@ class AuthModal {
2529
3421
  catch (error) {
2530
3422
  // Handle error
2531
3423
  const errorMessage = error instanceof Error ? error.message : 'Login failed';
2532
- this.showError(errorMessage);
3424
+ this.showError('Login Failed', errorMessage);
2533
3425
  if (this.options.onError) {
2534
3426
  this.options.onError(error);
2535
3427
  }
@@ -2556,7 +3448,7 @@ class AuthModal {
2556
3448
  const passwordConfirm = passwordConfirmInput.value;
2557
3449
  // Validate passwords match
2558
3450
  if (password !== passwordConfirm) {
2559
- this.showError('Passwords do not match');
3451
+ this.showError('Password Mismatch', 'Passwords do not match');
2560
3452
  return;
2561
3453
  }
2562
3454
  try {
@@ -2571,18 +3463,31 @@ class AuthModal {
2571
3463
  });
2572
3464
  // Handle success - Note: register returns different response format
2573
3465
  if (response.success) {
2574
- // After successful registration, perform login to get token
3466
+ // Check if email verification is required
3467
+ if (response.requiresEmailVerification) {
3468
+ this.showInfo('Email Verification Required', 'Please check your email and click the verification link to activate your account.');
3469
+ return; // Don't auto-login, user needs to verify email first
3470
+ }
3471
+ // Check if invitation code is required
3472
+ if (response.requiresInvitationCode && response.tempUserId) {
3473
+ this.tempUserId = response.tempUserId;
3474
+ this.tempUserEmail = email;
3475
+ this.showInfo('Activation Required', 'Please enter your invitation code to activate your account.');
3476
+ this.show('invite-code'); // Show invite code activation form
3477
+ return; // Don't auto-login, user needs to activate with invite code first
3478
+ }
3479
+ // Only auto-login if no verification or activation is needed
2575
3480
  const loginResponse = await this.client.login({ email, password });
2576
3481
  if (loginResponse.token) {
2577
3482
  if (this.options.onSignupSuccess) {
2578
3483
  this.options.onSignupSuccess(loginResponse.token, loginResponse.user);
2579
3484
  }
2580
- this.showMessage('Account Created', response.message || 'Your account has been created successfully!');
3485
+ this.showSuccess('Account Created', response.message || 'Your account has been created successfully!');
2581
3486
  }
2582
3487
  }
2583
3488
  else if (response.code === ErrorCode.ACCOUNT_EXISTS) {
2584
3489
  // Handle account already exists error
2585
- this.showMessage('Account Already Exists', 'This email is already registered. Please login instead.');
3490
+ this.showWarning('Account Already Exists', 'This email is already registered. Please login instead.');
2586
3491
  }
2587
3492
  else {
2588
3493
  throw new Error(response.error || 'Registration failed');
@@ -2591,12 +3496,12 @@ class AuthModal {
2591
3496
  catch (error) {
2592
3497
  // Handle HTTP errors
2593
3498
  if (error.response?.data?.code === ErrorCode.ACCOUNT_EXISTS) {
2594
- this.showMessage('Account Already Exists', 'This email is already registered. Please login instead.');
3499
+ this.showWarning('Account Already Exists', 'This email is already registered. Please login instead.');
2595
3500
  return;
2596
3501
  }
2597
3502
  // Handle other errors
2598
3503
  const errorMessage = error.response?.data?.error || error.message || 'Signup failed';
2599
- this.showError(errorMessage);
3504
+ this.showError('Registration Failed', errorMessage);
2600
3505
  if (this.options.onError) {
2601
3506
  this.options.onError(error);
2602
3507
  }
@@ -2621,12 +3526,12 @@ class AuthModal {
2621
3526
  // Call forgot password API
2622
3527
  await this.client.forgotPassword({ email });
2623
3528
  // Show success message
2624
- this.showMessage('Reset Link Sent', `We've sent a password reset link to ${email}`);
3529
+ this.showSuccess('Reset Link Sent', `We've sent a password reset link to ${email}`);
2625
3530
  }
2626
3531
  catch (error) {
2627
3532
  // Handle error
2628
3533
  const errorMessage = error instanceof Error ? error.message : 'Failed to send reset link';
2629
- this.showError(errorMessage);
3534
+ this.showError('Failed to Send Link', errorMessage);
2630
3535
  if (this.options.onError) {
2631
3536
  this.options.onError(error);
2632
3537
  }
@@ -2649,12 +3554,12 @@ class AuthModal {
2649
3554
  const confirmPassword = passwordConfirmInput.value;
2650
3555
  // Validate passwords match
2651
3556
  if (newPassword !== confirmPassword) {
2652
- this.showError('Passwords do not match');
3557
+ this.showError('Password Mismatch', 'Passwords do not match');
2653
3558
  return;
2654
3559
  }
2655
3560
  // Validate token exists
2656
3561
  if (!this.resetToken) {
2657
- this.showError('Reset token is missing. Please use the link from your email.');
3562
+ this.showError('Invalid Token', 'Reset token is missing. Please use the link from your email.');
2658
3563
  return;
2659
3564
  }
2660
3565
  try {
@@ -2667,19 +3572,19 @@ class AuthModal {
2667
3572
  token: this.resetToken,
2668
3573
  new_password: newPassword,
2669
3574
  });
2670
- // Clean up URL (remove verify_token from query string)
3575
+ // Clean up URL (remove reset_token from query string)
2671
3576
  const url = new URL(window.location.href);
2672
- url.searchParams.delete('verify_token');
3577
+ url.searchParams.delete('reset_token');
2673
3578
  window.history.replaceState({}, document.title, url.toString());
2674
3579
  // Clear reset token
2675
3580
  this.resetToken = null;
2676
3581
  // Show success message
2677
- this.showMessage('Password Reset Successful', 'Your password has been updated successfully. You can now log in with your new password.');
3582
+ this.showSuccess('Password Reset Successful', 'Your password has been updated successfully. You can now log in with your new password.');
2678
3583
  }
2679
3584
  catch (error) {
2680
3585
  // Handle error
2681
3586
  const errorMessage = error instanceof Error ? error.message : 'Failed to reset password';
2682
- this.showError(errorMessage);
3587
+ this.showError('Reset Failed', errorMessage);
2683
3588
  if (this.options.onError) {
2684
3589
  this.options.onError(error);
2685
3590
  }
@@ -2691,24 +3596,89 @@ class AuthModal {
2691
3596
  btnLoader?.classList.add('hidden');
2692
3597
  }
2693
3598
  }
2694
- showMessage(title, message) {
2695
- if (!this.modal)
3599
+ async handleBindInviteCode(e) {
3600
+ e.preventDefault();
3601
+ const codeInput = this.modal?.querySelector('#inviteCodeInput');
3602
+ const submitBtn = this.modal?.querySelector('#bindInviteCodeButton');
3603
+ const btnText = submitBtn?.querySelector('.btn-text');
3604
+ const btnLoader = submitBtn?.querySelector('.btn-loader');
3605
+ if (!codeInput || !submitBtn)
3606
+ return;
3607
+ const inviteCode = codeInput.value.trim();
3608
+ // Validate invite code
3609
+ if (!inviteCode) {
3610
+ this.showError('Invalid Input', 'Please enter an invitation code');
3611
+ return;
3612
+ }
3613
+ // Validate temp user ID exists
3614
+ if (!this.tempUserId) {
3615
+ this.showError('Session Error', 'User ID is missing. Please try logging in again.');
2696
3616
  return;
2697
- const messageTitle = this.modal.querySelector('#messageTitle');
2698
- const messageText = this.modal.querySelector('#messageText');
2699
- const messageButton = this.modal.querySelector('#messageButton');
2700
- if (messageTitle)
2701
- messageTitle.textContent = title;
2702
- if (messageText)
2703
- messageText.textContent = message;
2704
- messageButton?.addEventListener('click', () => {
2705
- this.hide();
2706
- }, { once: true });
2707
- this.switchView('message');
2708
- }
2709
- showError(message) {
2710
- // Simple error display - you can enhance this
2711
- alert(message);
3617
+ }
3618
+ try {
3619
+ // Show loading state
3620
+ submitBtn.disabled = true;
3621
+ btnText?.classList.add('hidden');
3622
+ btnLoader?.classList.remove('hidden');
3623
+ // Call bind invite code API
3624
+ const response = await this.client.bindInviteCode({
3625
+ user_id: this.tempUserId,
3626
+ invite_code: inviteCode,
3627
+ });
3628
+ // Handle success - auto login with returned token
3629
+ if (response.success && response.data.token) {
3630
+ const { token, refreshToken, user } = response.data;
3631
+ // Store tokens
3632
+ if (this.options.onLoginSuccess) {
3633
+ this.options.onLoginSuccess(token, user);
3634
+ }
3635
+ // Auto-set token in client
3636
+ this.client.setToken(token);
3637
+ // Show success message
3638
+ this.showSuccess('Account Activated!', 'Your account has been successfully activated. Welcome!');
3639
+ // Clear temp user data
3640
+ this.tempUserId = null;
3641
+ this.tempUserEmail = null;
3642
+ }
3643
+ else {
3644
+ throw new Error('Invalid response from server');
3645
+ }
3646
+ }
3647
+ catch (error) {
3648
+ // Handle errors
3649
+ const errorMessage = error.response?.data?.error || error.message || 'Failed to activate account';
3650
+ this.showError('Activation Failed', errorMessage);
3651
+ if (this.options.onError) {
3652
+ this.options.onError(error);
3653
+ }
3654
+ }
3655
+ finally {
3656
+ // Reset loading state
3657
+ submitBtn.disabled = false;
3658
+ btnText?.classList.remove('hidden');
3659
+ btnLoader?.classList.add('hidden');
3660
+ }
3661
+ }
3662
+ showSuccess(title, message) {
3663
+ Toast.success(title, message);
3664
+ // Also hide modal after short delay for better UX
3665
+ setTimeout(() => this.hide(), 1500);
3666
+ }
3667
+ showError(title, message) {
3668
+ Toast.error(title, message);
3669
+ }
3670
+ showWarning(title, message) {
3671
+ Toast.warning(title, message);
3672
+ }
3673
+ showInfo(title, message) {
3674
+ Toast.info(title, message);
3675
+ }
3676
+ /**
3677
+ * @deprecated Use showSuccess, showError, showWarning, or showInfo instead
3678
+ */
3679
+ showMessage(title, message) {
3680
+ // Fallback for backward compatibility
3681
+ Toast.info(title, message);
2712
3682
  }
2713
3683
  // ============================================================================
2714
3684
  // OAuth Methods
@@ -2750,7 +3720,7 @@ class AuthModal {
2750
3720
  }
2751
3721
  catch (error) {
2752
3722
  const err = error instanceof Error ? error : new Error(`${provider} OAuth failed`);
2753
- this.showError(err.message);
3723
+ this.showError('OAuth Failed', err.message);
2754
3724
  if (this.options.onError) {
2755
3725
  this.options.onError(err);
2756
3726
  }
@@ -2783,6 +3753,62 @@ class AuthModal {
2783
3753
  token,
2784
3754
  };
2785
3755
  }
3756
+ /**
3757
+ * Handle invite code required scenario (static method for custom UI)
3758
+ *
3759
+ * When backend redirects to return_url?error_code=INVITE_CODE_REQUIRED&user_id=xxx&email=xxx,
3760
+ * call this method to show the invite code form or handle it custom way.
3761
+ *
3762
+ * @param options - Auth modal options
3763
+ * @param customHandler - Optional custom handler for invite code requirement
3764
+ * @returns Object with userId and email if invite code is required, null otherwise
3765
+ *
3766
+ * @example
3767
+ * // Auto-show AuthModal invite code form
3768
+ * const modal = new AuthModal(options);
3769
+ * AuthModal.handleInviteCodeRequired(options);
3770
+ *
3771
+ * @example
3772
+ * // Custom UI handling
3773
+ * const result = AuthModal.handleInviteCodeRequired(options, (userId, email) => {
3774
+ * // Show your custom invite code input UI
3775
+ * const code = prompt('Enter invitation code:');
3776
+ * if (code) {
3777
+ * options.client.bindInviteCode({ user_id: userId, invite_code: code })
3778
+ * .then(res => {
3779
+ * localStorage.setItem('token', res.data.token);
3780
+ * window.location.reload();
3781
+ * });
3782
+ * }
3783
+ * });
3784
+ */
3785
+ static handleInviteCodeRequired(options, customHandler) {
3786
+ const urlParams = new URLSearchParams(window.location.search);
3787
+ const errorCode = urlParams.get('error_code');
3788
+ const userId = urlParams.get('user_id');
3789
+ const email = urlParams.get('email');
3790
+ if (errorCode !== 'INVITE_CODE_REQUIRED' || !userId) {
3791
+ return null; // Not an invite code required scenario
3792
+ }
3793
+ // Clean up URL
3794
+ const url = new URL(window.location.href);
3795
+ url.searchParams.delete('error_code');
3796
+ url.searchParams.delete('user_id');
3797
+ url.searchParams.delete('email');
3798
+ window.history.replaceState({}, document.title, url.toString());
3799
+ // Call custom handler if provided
3800
+ if (customHandler) {
3801
+ customHandler(userId, email || '');
3802
+ }
3803
+ else if (options.onInviteCodeRequired) {
3804
+ // Call the callback option
3805
+ options.onInviteCodeRequired(userId, email || '');
3806
+ }
3807
+ return {
3808
+ userId,
3809
+ email: email || '',
3810
+ };
3811
+ }
2786
3812
  }
2787
3813
  /**
2788
3814
  * Create and show auth modal
@@ -2791,5 +3817,5 @@ function createAuthModal(options) {
2791
3817
  return new AuthModal(options);
2792
3818
  }
2793
3819
 
2794
- export { AuthFactory, AuthModal, AuthProvider, BuiltInHooks, ENVIRONMENT_CONFIGS, ErrorCode, SeaVerseBackendAPIClient, createAuthModal, detectEnvironment, getEnvironmentConfig, models };
3820
+ export { AuthFactory, AuthModal, AuthProvider, BuiltInHooks, ENVIRONMENT_CONFIGS, ErrorCode, SeaVerseBackendAPIClient, Toast, createAuthModal, detectEnvironment, getEnvironmentConfig, models };
2795
3821
  //# sourceMappingURL=index.js.map