@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/README.md +403 -9
- package/dist/index.cjs +1064 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +389 -9
- package/dist/index.js +1064 -38
- package/dist/index.js.map +1 -1
- package/dist/toast.css +1 -0
- package/package.json +1 -1
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('
|
|
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.
|
|
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
|
-
//
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
3575
|
+
// Clean up URL (remove reset_token from query string)
|
|
2671
3576
|
const url = new URL(window.location.href);
|
|
2672
|
-
url.searchParams.delete('
|
|
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.
|
|
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
|
-
|
|
2695
|
-
|
|
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
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
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
|