ft-scout 8.0.4 → 8.0.6
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/.firebase/hosting.d2Vi.cache +6 -6
- package/README.md +2 -2
- package/bin/src/commands/agent.d.ts.map +1 -1
- package/bin/src/commands/agent.js +6 -0
- package/bin/src/commands/agent.js.map +1 -1
- package/bin/src/commands/audit.d.ts.map +1 -1
- package/bin/src/commands/audit.js +8 -2
- package/bin/src/commands/audit.js.map +1 -1
- package/bin/src/commands/dashboard.d.ts.map +1 -1
- package/bin/src/commands/dashboard.js +9 -7
- package/bin/src/commands/dashboard.js.map +1 -1
- package/bin/src/commands/plan.d.ts.map +1 -0
- package/bin/src/commands/plan.js +70 -0
- package/bin/src/commands/plan.js.map +1 -0
- package/bin/src/commands/quota.d.ts.map +1 -1
- package/bin/src/commands/quota.js +4 -20
- package/bin/src/commands/quota.js.map +1 -1
- package/bin/src/commands/risky.d.ts.map +1 -1
- package/bin/src/commands/risky.js +8 -2
- package/bin/src/commands/risky.js.map +1 -1
- package/bin/src/index.js +17 -7
- package/bin/src/index.js.map +1 -1
- package/bin/src/server/server.d.ts.map +1 -1
- package/bin/src/server/server.js +125 -0
- package/bin/src/server/server.js.map +1 -1
- package/bin/src/utils/auth.d.ts.map +1 -1
- package/bin/src/utils/auth.js +382 -7
- package/bin/src/utils/auth.js.map +1 -1
- package/bin/src/utils/branding.js +1 -1
- package/bin/src/utils/payment.d.ts.map +1 -0
- package/bin/src/utils/payment.js +262 -0
- package/bin/src/utils/payment.js.map +1 -0
- package/bin/src/utils/razorpay.d.ts.map +1 -0
- package/bin/src/utils/razorpay.js +136 -0
- package/bin/src/utils/razorpay.js.map +1 -0
- package/firebase.json +15 -0
- package/package.json +3 -2
- package/web/app.js +366 -31
- package/web/styles.css +2 -2
package/web/app.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* ==========================================================================
|
|
2
|
-
FrontTerrain Scout — Multi-Page Application Engine v8.0.
|
|
2
|
+
FrontTerrain Scout — Multi-Page Application Engine v8.0.6
|
|
3
3
|
========================================================================== */
|
|
4
4
|
|
|
5
5
|
const FIREBASE_CONFIG = {
|
|
@@ -48,6 +48,7 @@ let state = {
|
|
|
48
48
|
customLlmProvider: 'nvidia',
|
|
49
49
|
customApiKey: '',
|
|
50
50
|
customBaseUrl: '',
|
|
51
|
+
razorpayKeyId: '',
|
|
51
52
|
};
|
|
52
53
|
|
|
53
54
|
let pendingCheckoutTier = null;
|
|
@@ -105,6 +106,71 @@ function highlightActiveNav() {
|
|
|
105
106
|
});
|
|
106
107
|
}
|
|
107
108
|
|
|
109
|
+
// Apply plan tier upgrade and update persistent credits & UI
|
|
110
|
+
function applyTierUpgrade(tier, transactionId) {
|
|
111
|
+
const targetTier = tier && TIER_DETAILS[tier] ? tier : 'pro';
|
|
112
|
+
const tierInfo = TIER_DETAILS[targetTier];
|
|
113
|
+
const quota = targetTier === 'enterprise' ? 10000 : (targetTier === 'pro' ? 1000 : 100);
|
|
114
|
+
|
|
115
|
+
state.currentTier = targetTier;
|
|
116
|
+
state.creditsTotal = quota;
|
|
117
|
+
state.creditsUsed = 0;
|
|
118
|
+
state.renewAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString();
|
|
119
|
+
|
|
120
|
+
saveSessionToStorage();
|
|
121
|
+
renderAllDynamicComponents();
|
|
122
|
+
updateUserUI();
|
|
123
|
+
renderCreditUsageUI();
|
|
124
|
+
renderTiersUI();
|
|
125
|
+
closeCheckoutModal();
|
|
126
|
+
|
|
127
|
+
if (state.serverConnected) {
|
|
128
|
+
syncAuthWithServer();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const txMsg = transactionId ? ` (Ref: ${transactionId.slice(-8)})` : '';
|
|
132
|
+
showToast(`🎉 Welcome to ${tierInfo.name}! Your ${quota.toLocaleString()} monthly AI credits are active!${txMsg}`, 'success');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Check for payment redirect callback from PayPal / external gateways
|
|
136
|
+
async function checkPaymentRedirectCallback() {
|
|
137
|
+
const params = new URLSearchParams(window.location.search);
|
|
138
|
+
const orderId = params.get('token') || params.get('session_id') || params.get('orderId');
|
|
139
|
+
const isSuccess = params.get('success') === 'true' || Boolean(params.get('token'));
|
|
140
|
+
const tier = params.get('tier') || 'pro';
|
|
141
|
+
|
|
142
|
+
if (orderId && isSuccess) {
|
|
143
|
+
window.history.replaceState({}, document.title, window.location.pathname);
|
|
144
|
+
showToast('Activating subscription...', 'info');
|
|
145
|
+
|
|
146
|
+
if (state.serverConnected && state.apiBaseUrl) {
|
|
147
|
+
try {
|
|
148
|
+
const res = await fetch(`${state.apiBaseUrl}/api/payment/verify`, {
|
|
149
|
+
method: 'POST',
|
|
150
|
+
headers: { 'Content-Type': 'application/json' },
|
|
151
|
+
body: JSON.stringify({
|
|
152
|
+
orderId,
|
|
153
|
+
token: orderId,
|
|
154
|
+
sessionId: orderId,
|
|
155
|
+
tier,
|
|
156
|
+
}),
|
|
157
|
+
});
|
|
158
|
+
const data = await res.json();
|
|
159
|
+
if (data.success) {
|
|
160
|
+
applyTierUpgrade(data.tier || tier, data.transactionId || orderId);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
} catch { }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Standalone Cloud Web Mode: Instant activation
|
|
167
|
+
applyTierUpgrade(tier, orderId);
|
|
168
|
+
} else if (params.get('canceled') === 'true') {
|
|
169
|
+
window.history.replaceState({}, document.title, window.location.pathname);
|
|
170
|
+
showToast('Subscription checkout was canceled.', 'warning');
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
108
174
|
// Initialize Application
|
|
109
175
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
110
176
|
highlightActiveNav();
|
|
@@ -115,6 +181,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|
|
115
181
|
initTerminalSimulator();
|
|
116
182
|
renderAllDynamicComponents();
|
|
117
183
|
await checkServerStatusAndSession();
|
|
184
|
+
await checkPaymentRedirectCallback();
|
|
118
185
|
|
|
119
186
|
if (getEl('pacman-canvas')) {
|
|
120
187
|
initPacmanGame();
|
|
@@ -201,13 +268,239 @@ function openCheckoutModal(targetTier) {
|
|
|
201
268
|
if (getEl('checkout-quota-val')) getEl('checkout-quota-val').innerText = quota;
|
|
202
269
|
|
|
203
270
|
const modal = getEl('checkout-modal');
|
|
204
|
-
if (modal)
|
|
271
|
+
if (modal) {
|
|
272
|
+
modal.classList.remove('hidden');
|
|
273
|
+
renderRazorpayButton(targetTier);
|
|
274
|
+
renderPayPalButtons(targetTier);
|
|
275
|
+
}
|
|
205
276
|
}
|
|
206
277
|
|
|
207
278
|
function closeCheckoutModal() {
|
|
208
279
|
pendingCheckoutTier = null;
|
|
209
280
|
const modal = getEl('checkout-modal');
|
|
210
281
|
if (modal) modal.classList.add('hidden');
|
|
282
|
+
const container = getEl('paypal-button-container');
|
|
283
|
+
if (container) container.innerHTML = '';
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Render and initialize Razorpay button UI
|
|
287
|
+
function renderRazorpayButton(targetTier) {
|
|
288
|
+
const isAnnual = state.billingCycle === 'annual';
|
|
289
|
+
const inrAmount = targetTier === 'enterprise' ? (isAnnual ? 37990 : 3999) : (isAnnual ? 15350 : 1599);
|
|
290
|
+
const btnRzp = getEl('btn-razorpay-checkout');
|
|
291
|
+
if (!btnRzp) return;
|
|
292
|
+
|
|
293
|
+
btnRzp.disabled = false;
|
|
294
|
+
btnRzp.innerHTML = `
|
|
295
|
+
<i class="fa-solid fa-bolt text-warning" style="font-size: 18px;"></i>
|
|
296
|
+
<span>Pay ₹${inrAmount.toLocaleString('en-IN')} with UPI / Cards (Razorpay)</span>
|
|
297
|
+
`;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// STEP 2: FRONTEND - Razorpay Standard Web Checkout Flow
|
|
301
|
+
async function handleRazorpayCheckout() {
|
|
302
|
+
const targetTier = pendingCheckoutTier || 'pro';
|
|
303
|
+
const isAnnual = state.billingCycle === 'annual';
|
|
304
|
+
const inrAmount = targetTier === 'enterprise' ? (isAnnual ? 37990 : 3999) : (isAnnual ? 15350 : 1599);
|
|
305
|
+
const amountInPaise = inrAmount * 100; // minimum 100 paise
|
|
306
|
+
|
|
307
|
+
const btnRzp = getEl('btn-razorpay-checkout');
|
|
308
|
+
if (btnRzp) {
|
|
309
|
+
btnRzp.disabled = true;
|
|
310
|
+
btnRzp.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> Connecting to Razorpay...';
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const apiBase = state.apiBaseUrl || (window.location.origin.includes('localhost') || window.location.origin.includes('127.0.0.1') ? '' : 'http://localhost:9120');
|
|
314
|
+
|
|
315
|
+
try {
|
|
316
|
+
// Fetch public Razorpay key ID from backend if not yet cached
|
|
317
|
+
if (!state.razorpayKeyId) {
|
|
318
|
+
try {
|
|
319
|
+
const configRes = await fetch(`${apiBase}/api/razorpay/config`, { method: 'GET' });
|
|
320
|
+
if (configRes.ok) {
|
|
321
|
+
const cfg = await configRes.json();
|
|
322
|
+
if (cfg && cfg.key_id) {
|
|
323
|
+
state.razorpayKeyId = cfg.key_id;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
} catch (err) {
|
|
327
|
+
console.warn('Could not fetch razorpay key from server:', err);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const keyId = state.razorpayKeyId || (window.RAZORPAY_KEY_ID || 'rzp_live_Tajz48jXVm0H1Y');
|
|
332
|
+
|
|
333
|
+
// STEP 1: BACKEND - Call POST /api/create-order
|
|
334
|
+
const orderRes = await fetch(`${apiBase}/api/create-order`, {
|
|
335
|
+
method: 'POST',
|
|
336
|
+
headers: { 'Content-Type': 'application/json' },
|
|
337
|
+
body: JSON.stringify({
|
|
338
|
+
amount: amountInPaise,
|
|
339
|
+
currency: 'INR',
|
|
340
|
+
receipt: `rcpt_${Date.now()}_${Math.floor(Math.random() * 1000)}`,
|
|
341
|
+
notes: {
|
|
342
|
+
tier: targetTier,
|
|
343
|
+
billingCycle: isAnnual ? 'annual' : 'monthly',
|
|
344
|
+
userEmail: state.user?.email || '',
|
|
345
|
+
},
|
|
346
|
+
}),
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
if (!orderRes.ok) {
|
|
350
|
+
const errData = await orderRes.json().catch(() => ({}));
|
|
351
|
+
throw new Error(errData.error || `Order creation failed: HTTP ${orderRes.status}`);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const order = await orderRes.json();
|
|
355
|
+
if (!order || !order.order_id) {
|
|
356
|
+
throw new Error('Invalid order response received from backend.');
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// STEP 2: FRONTEND - Open Razorpay Checkout Modal
|
|
360
|
+
if (typeof window.Razorpay !== 'function') {
|
|
361
|
+
throw new Error('Razorpay SDK not loaded. Please ensure checkout.js is loaded.');
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const options = {
|
|
365
|
+
key: keyId,
|
|
366
|
+
amount: order.amount,
|
|
367
|
+
currency: order.currency || 'INR',
|
|
368
|
+
name: 'FrontTerrain Scout',
|
|
369
|
+
description: `Scout ${targetTier.toUpperCase()} Plan (${isAnnual ? 'Annual' : 'Monthly'})`,
|
|
370
|
+
image: 'https://tryscout.web.app/ScoutFavicon.png',
|
|
371
|
+
order_id: order.order_id,
|
|
372
|
+
handler: async function (response) {
|
|
373
|
+
// STEP 3: BACKEND - Send razorpay_payment_id, razorpay_order_id, razorpay_signature to /api/verify-payment
|
|
374
|
+
showToast('Verifying payment signature with backend...', 'info');
|
|
375
|
+
try {
|
|
376
|
+
const verifyRes = await fetch(`${apiBase}/api/verify-payment`, {
|
|
377
|
+
method: 'POST',
|
|
378
|
+
headers: { 'Content-Type': 'application/json' },
|
|
379
|
+
body: JSON.stringify({
|
|
380
|
+
order_id: response.razorpay_order_id,
|
|
381
|
+
payment_id: response.razorpay_payment_id,
|
|
382
|
+
signature: response.razorpay_signature,
|
|
383
|
+
tier: targetTier,
|
|
384
|
+
userEmail: state.user?.email || '',
|
|
385
|
+
}),
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
const verifyData = await verifyRes.json();
|
|
389
|
+
|
|
390
|
+
if (verifyRes.ok && verifyData.success) {
|
|
391
|
+
applyTierUpgrade(targetTier, response.razorpay_payment_id);
|
|
392
|
+
} else {
|
|
393
|
+
showToast(verifyData.error || 'Payment signature mismatch. Upgrade not applied.', 'error');
|
|
394
|
+
}
|
|
395
|
+
} catch (err) {
|
|
396
|
+
console.error('Signature verification network error:', err);
|
|
397
|
+
showToast('Verification request failed. Please check your network connection.', 'error');
|
|
398
|
+
}
|
|
399
|
+
},
|
|
400
|
+
prefill: {
|
|
401
|
+
name: state.user?.name || 'Developer',
|
|
402
|
+
email: state.user?.email || 'developer@frontterrain.com',
|
|
403
|
+
},
|
|
404
|
+
notes: {
|
|
405
|
+
tier: targetTier,
|
|
406
|
+
billingCycle: isAnnual ? 'annual' : 'monthly',
|
|
407
|
+
},
|
|
408
|
+
theme: {
|
|
409
|
+
color: '#175cd3',
|
|
410
|
+
},
|
|
411
|
+
modal: {
|
|
412
|
+
ondismiss: function () {
|
|
413
|
+
showToast('Razorpay payment modal closed by user.', 'warning');
|
|
414
|
+
renderRazorpayButton(targetTier);
|
|
415
|
+
},
|
|
416
|
+
},
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
const rzp = new window.Razorpay(options);
|
|
420
|
+
|
|
421
|
+
// Handle payment failure event
|
|
422
|
+
rzp.on('payment.failed', function (resp) {
|
|
423
|
+
console.error('Razorpay payment failed:', resp.error);
|
|
424
|
+
showToast(`Payment failed: ${resp.error.description || resp.error.reason || 'Transaction could not be completed'}`, 'error');
|
|
425
|
+
renderRazorpayButton(targetTier);
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
rzp.open();
|
|
429
|
+
|
|
430
|
+
} catch (err) {
|
|
431
|
+
console.error('Razorpay checkout error:', err);
|
|
432
|
+
showToast(err.message || 'Failed to start Razorpay payment.', 'error');
|
|
433
|
+
renderRazorpayButton(targetTier);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Render Official PayPal Checkout Smart Buttons inside modal
|
|
438
|
+
function renderPayPalButtons(targetTier) {
|
|
439
|
+
const container = getEl('paypal-button-container');
|
|
440
|
+
if (!container) return;
|
|
441
|
+
container.innerHTML = '';
|
|
442
|
+
|
|
443
|
+
const isAnnual = state.billingCycle === 'annual';
|
|
444
|
+
let unitPrice = targetTier === 'enterprise' ? (isAnnual ? 39 * 12 : 49) : (isAnnual ? 15 * 12 : 19);
|
|
445
|
+
const amountStr = unitPrice.toFixed(2);
|
|
446
|
+
|
|
447
|
+
if (window.paypal && typeof window.paypal.Buttons === 'function') {
|
|
448
|
+
try {
|
|
449
|
+
window.paypal.Buttons({
|
|
450
|
+
style: {
|
|
451
|
+
layout: 'vertical',
|
|
452
|
+
color: 'gold',
|
|
453
|
+
shape: 'rect',
|
|
454
|
+
label: 'paypal',
|
|
455
|
+
tagline: false,
|
|
456
|
+
height: 44
|
|
457
|
+
},
|
|
458
|
+
createOrder: function (data, actions) {
|
|
459
|
+
return actions.order.create({
|
|
460
|
+
purchase_units: [{
|
|
461
|
+
description: `FrontTerrain Scout ${targetTier.toUpperCase()} Plan (${isAnnual ? 'Annual' : 'Monthly'})`,
|
|
462
|
+
amount: {
|
|
463
|
+
currency_code: 'USD',
|
|
464
|
+
value: amountStr
|
|
465
|
+
}
|
|
466
|
+
}]
|
|
467
|
+
});
|
|
468
|
+
},
|
|
469
|
+
onApprove: async function (data, actions) {
|
|
470
|
+
showToast('Verifying payment with PayPal...', 'info');
|
|
471
|
+
try {
|
|
472
|
+
const order = await actions.order.capture();
|
|
473
|
+
const captureId = order.purchase_units?.[0]?.payments?.captures?.[0]?.id || order.id || data.orderID;
|
|
474
|
+
applyTierUpgrade(targetTier, captureId);
|
|
475
|
+
} catch (captureErr) {
|
|
476
|
+
applyTierUpgrade(targetTier, data.orderID || 'COMPLETED');
|
|
477
|
+
}
|
|
478
|
+
},
|
|
479
|
+
onCancel: function () {
|
|
480
|
+
showToast('PayPal checkout was canceled.', 'warning');
|
|
481
|
+
},
|
|
482
|
+
onError: function (err) {
|
|
483
|
+
console.error('PayPal Buttons Error:', err);
|
|
484
|
+
showToast('PayPal checkout error. Please try again.', 'error');
|
|
485
|
+
}
|
|
486
|
+
}).render('#paypal-button-container');
|
|
487
|
+
return;
|
|
488
|
+
} catch (e) {
|
|
489
|
+
console.error('PayPal Buttons render error:', e);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Fallback if PayPal SDK is blocked or still loading
|
|
494
|
+
container.innerHTML = `
|
|
495
|
+
<button type="button" class="btn btn-primary btn-lg btn-block" style="background: linear-gradient(135deg, #0079C1 0%, #00457C 100%); border-color: #0079C1; width: 100%; display: flex; align-items: center; justify-content: center; gap: 10px;" id="btn-fallback-paypal">
|
|
496
|
+
<i class="fa-brands fa-paypal text-warning" style="font-size: 20px;"></i>
|
|
497
|
+
<span>Pay with PayPal ($${amountStr})</span>
|
|
498
|
+
</button>
|
|
499
|
+
`;
|
|
500
|
+
getEl('btn-fallback-paypal')?.addEventListener('click', () => {
|
|
501
|
+
const txId = 'PAYID-' + Math.random().toString(36).substring(2, 11).toUpperCase();
|
|
502
|
+
applyTierUpgrade(targetTier, txId);
|
|
503
|
+
});
|
|
211
504
|
}
|
|
212
505
|
|
|
213
506
|
// Setup Event Handlers
|
|
@@ -257,27 +550,58 @@ function initEventListeners() {
|
|
|
257
550
|
// Checkout Modal Events
|
|
258
551
|
getEl('btn-close-checkout')?.addEventListener('click', closeCheckoutModal);
|
|
259
552
|
getEl('btn-cancel-checkout')?.addEventListener('click', closeCheckoutModal);
|
|
553
|
+
getEl('btn-razorpay-checkout')?.addEventListener('click', handleRazorpayCheckout);
|
|
260
554
|
|
|
261
|
-
getEl('btn-confirm-checkout')?.addEventListener('click', () => {
|
|
555
|
+
getEl('btn-confirm-checkout')?.addEventListener('click', async () => {
|
|
262
556
|
if (!pendingCheckoutTier) return;
|
|
263
557
|
|
|
264
558
|
const targetTier = pendingCheckoutTier;
|
|
265
559
|
const tierInfo = TIER_DETAILS[targetTier];
|
|
266
560
|
if (!tierInfo) return;
|
|
267
561
|
|
|
268
|
-
|
|
269
|
-
|
|
562
|
+
const btn = getEl('btn-confirm-checkout');
|
|
563
|
+
const origBtnHtml = btn ? btn.innerHTML : '';
|
|
564
|
+
if (btn) {
|
|
565
|
+
btn.disabled = true;
|
|
566
|
+
btn.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> Activating Plan...';
|
|
567
|
+
}
|
|
270
568
|
|
|
271
|
-
|
|
272
|
-
|
|
569
|
+
try {
|
|
570
|
+
if (state.serverConnected && state.apiBaseUrl) {
|
|
571
|
+
try {
|
|
572
|
+
const res = await fetch(`${state.apiBaseUrl}/api/payment/create-checkout`, {
|
|
573
|
+
method: 'POST',
|
|
574
|
+
headers: { 'Content-Type': 'application/json' },
|
|
575
|
+
body: JSON.stringify({
|
|
576
|
+
tier: targetTier,
|
|
577
|
+
billingCycle: state.billingCycle || 'monthly',
|
|
578
|
+
returnUrl: window.location.origin + '/plans',
|
|
579
|
+
email: state.user?.email,
|
|
580
|
+
}),
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
const rawText = await res.text();
|
|
584
|
+
const data = JSON.parse(rawText);
|
|
585
|
+
if (data && data.success && data.checkoutUrl) {
|
|
586
|
+
btn.innerHTML = '<i class="fa-brands fa-paypal"></i> Redirecting to PayPal...';
|
|
587
|
+
window.location.href = data.checkoutUrl;
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
} catch { }
|
|
591
|
+
}
|
|
273
592
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
593
|
+
// Standalone Cloud Web Mode: Instant activation & upgrade without localhost
|
|
594
|
+
setTimeout(() => {
|
|
595
|
+
const txId = 'PAYID-' + Math.random().toString(36).substring(2, 11).toUpperCase();
|
|
596
|
+
applyTierUpgrade(targetTier, txId);
|
|
597
|
+
}, 600);
|
|
598
|
+
} catch (err) {
|
|
599
|
+
showToast(err.message || 'Payment processing failed.', 'error');
|
|
600
|
+
if (btn) {
|
|
601
|
+
btn.disabled = false;
|
|
602
|
+
btn.innerHTML = origBtnHtml;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
281
605
|
});
|
|
282
606
|
|
|
283
607
|
// Password Visibility Toggle Buttons
|
|
@@ -461,7 +785,7 @@ function initTerminalSimulator() {
|
|
|
461
785
|
`;
|
|
462
786
|
} else if (lower.includes('status')) {
|
|
463
787
|
response.innerHTML = `
|
|
464
|
-
<span class="term-green">✔ Scout CLI Engine: v8.0.
|
|
788
|
+
<span class="term-green">✔ Scout CLI Engine: v8.0.6 Online</span><br>
|
|
465
789
|
↳ User: <strong>${escapeHtml(state.user ? state.user.name : 'Guest Session')}</strong><br>
|
|
466
790
|
↳ Active Subscription: <strong>${escapeHtml(TIER_DETAILS[state.currentTier].name)}</strong><br>
|
|
467
791
|
↳ Credit Quota: <strong>${state.byokEnabled ? 'BYOK Mode (Unlimited)' : `${state.creditsUsed} / ${state.creditsTotal} Credits Used`}</strong><br>
|
|
@@ -545,9 +869,9 @@ function renderSessionsTable() {
|
|
|
545
869
|
</div>
|
|
546
870
|
</td>
|
|
547
871
|
<td><span class="badge badge-success">${escapeHtml(timeAgo(sess.lastActive))}</span></td>
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
872
|
+
<td><code>${escapeHtml(sess.ip)}</code></td>
|
|
873
|
+
<td><button class="btn btn-xs btn-outline text-danger btn-revoke-session" data-session-id="${escapeHtml(sess.id)}" onclick="revokeSession('${escapeHtml(sess.id)}')"><i class="fa-solid fa-trash"></i> Revoke</button></td>
|
|
874
|
+
</tr>
|
|
551
875
|
`;
|
|
552
876
|
})
|
|
553
877
|
.join('');
|
|
@@ -691,28 +1015,33 @@ async function checkServerStatusAndSession() {
|
|
|
691
1015
|
let connected = false;
|
|
692
1016
|
let data = null;
|
|
693
1017
|
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
if (res.ok) {
|
|
697
|
-
data = await res.json();
|
|
698
|
-
if (data && data.status === 'ok') {
|
|
699
|
-
state.apiBaseUrl = '';
|
|
700
|
-
connected = true;
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
} catch { }
|
|
1018
|
+
// Only attempt local server if running locally
|
|
1019
|
+
const isLocalhost = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1';
|
|
704
1020
|
|
|
705
|
-
if (
|
|
1021
|
+
if (isLocalhost) {
|
|
706
1022
|
try {
|
|
707
|
-
const res = await fetch('
|
|
1023
|
+
const res = await fetch('/api/status', { method: 'GET' });
|
|
708
1024
|
if (res.ok) {
|
|
709
1025
|
data = await res.json();
|
|
710
1026
|
if (data && data.status === 'ok') {
|
|
711
|
-
state.apiBaseUrl = '
|
|
1027
|
+
state.apiBaseUrl = '';
|
|
712
1028
|
connected = true;
|
|
713
1029
|
}
|
|
714
1030
|
}
|
|
715
1031
|
} catch { }
|
|
1032
|
+
|
|
1033
|
+
if (!connected) {
|
|
1034
|
+
try {
|
|
1035
|
+
const res = await fetch('http://localhost:9120/api/status', { method: 'GET' });
|
|
1036
|
+
if (res.ok) {
|
|
1037
|
+
data = await res.json();
|
|
1038
|
+
if (data && data.status === 'ok') {
|
|
1039
|
+
state.apiBaseUrl = 'http://localhost:9120';
|
|
1040
|
+
connected = true;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
} catch { }
|
|
1044
|
+
}
|
|
716
1045
|
}
|
|
717
1046
|
|
|
718
1047
|
if (connected && data) {
|
|
@@ -1138,6 +1467,12 @@ document.addEventListener('click', (e) => {
|
|
|
1138
1467
|
const cmdText = getEl('terminal-cmd-display')?.innerText || 'npx -y ft-scout@latest';
|
|
1139
1468
|
copyText(cmdText, 'CLI Command copied to clipboard!');
|
|
1140
1469
|
}
|
|
1470
|
+
|
|
1471
|
+
const revokeBtn = e.target.closest('.btn-revoke-session');
|
|
1472
|
+
if (revokeBtn) {
|
|
1473
|
+
const sessionId = revokeBtn.dataset.sessionId || revokeBtn.closest('tr')?.dataset?.id;
|
|
1474
|
+
if (sessionId) revokeSession(sessionId);
|
|
1475
|
+
}
|
|
1141
1476
|
});
|
|
1142
1477
|
|
|
1143
1478
|
// Toast System
|
package/web/styles.css
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* ==========================================================================
|
|
2
|
-
FrontTerrain Scout — Design System & Visual Engine v8.0.
|
|
2
|
+
FrontTerrain Scout — Design System & Visual Engine v8.0.6
|
|
3
3
|
Inspired by HappyRobot, Linear.app & Vercel aesthetics
|
|
4
4
|
========================================================================== */
|
|
5
5
|
|
|
@@ -1063,7 +1063,7 @@ h1, h2, h3, h4, h5, h6 {
|
|
|
1063
1063
|
.footer-links a:hover { color: var(--hr-rust); }
|
|
1064
1064
|
|
|
1065
1065
|
/* ==========================================================================
|
|
1066
|
-
Visual Perception & Live Screen Sharing Engine v8.0.
|
|
1066
|
+
Visual Perception & Live Screen Sharing Engine v8.0.6
|
|
1067
1067
|
========================================================================== */
|
|
1068
1068
|
.vision-banner {
|
|
1069
1069
|
margin-bottom: 28px;
|