Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ library AppStorage {
mapping(uint256 => address) allPkpIds; // mapping from a counter to a pkp id, allowing us to get a list of all pkp ids ever generated
mapping(uint256 => uint256) pricing; // mapping from a pricing item id to it's price
EnumerableSet.AddressSet api_payers; // set of accounts that pays for state mutation made by api calls, optionally mutates state on behalf of an api key holder.
address configOperator; // account that can make operational changes.
address configOperator; // operator that can perform config management alongside the diamond owner
address pricingOperator; // account that can mutate certain state for operational purposes ( like pricing ).
uint256 pkpCount; // counter for creating unique pkp ids
uint256 accountCount; // counter for creating unique account ids
Expand Down
66 changes: 63 additions & 3 deletions lit-static/dapps/dashboard/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ function getApiKey() {
function setApiKey(v) {
if (v) sessionStorage.setItem(STORAGE_KEY_API, v);
else sessionStorage.removeItem(STORAGE_KEY_API);
_billingAvailable = null;
_billingCheckedAt = 0;
if (_billingRetryTimer) { clearTimeout(_billingRetryTimer); _billingRetryTimer = null; }
updateAuthUI();
}

Expand All @@ -26,21 +29,77 @@ function getBaseUrl() {
return '__LIT_API_BASE_URL__';
}

// Track whether billing (Stripe) is available for this environment
let _billingAvailable = null; // null = not yet checked, true/false after check
let _billingCheckedAt = 0;
let _billingRetryTimer = null; // pending retry timer ID (deduplicates stacked retries)
const BILLING_RETRY_MS = 30000; // retry failed checks after 30s

async function checkBillingAvailable() {
// If billing is confirmed available, return immediately (permanent cache)
if (_billingAvailable === true) return true;
// If a previous check failed, retry after the TTL expires
if (_billingAvailable === false && (Date.now() - _billingCheckedAt) < BILLING_RETRY_MS) {
return false;
Comment thread
GTC6244 marked this conversation as resolved.
}
try {
const client = await getClient();
await client.getStripeConfig();
_billingAvailable = true;
} catch (_) {
_billingAvailable = false;
}
_billingCheckedAt = Date.now();
return _billingAvailable;
}

function updateAuthUI() {
const hasKey = !!getApiKey();
document.body.classList.toggle('has-api-key', hasKey);
// Clear any pending billing retry to prevent timer stacking
if (_billingRetryTimer) { clearTimeout(_billingRetryTimer); _billingRetryTimer = null; }
const balanceEl = document.getElementById('billing-balance-display');
const addFundsBtn = document.getElementById('btn-add-funds');
if (balanceEl) balanceEl.style.display = hasKey ? '' : 'none';
if (addFundsBtn) addFundsBtn.style.display = hasKey ? '' : 'none';
const notRequiredEl = document.getElementById('billing-not-required');
const billingBanner = document.getElementById('billing-disabled-banner');
if (balanceEl) balanceEl.style.display = 'none';
if (addFundsBtn) addFundsBtn.style.display = 'none';
if (notRequiredEl) notRequiredEl.style.display = 'none';
if (billingBanner) billingBanner.style.display = 'none';
if (hasKey) {
const capturedKey = getApiKey();
refreshOverviewAccount();
updateStatCards();
preloadAllTables();
loadBillingBalance();
// Check billing availability then show appropriate UI
refreshBillingUI(capturedKey, balanceEl, addFundsBtn, notRequiredEl, billingBanner);
}
}

// Separated from updateAuthUI so the retry timer can re-check billing
// without re-running preloadAllTables / refreshOverviewAccount every 30s.
function refreshBillingUI(capturedKey, balanceEl, addFundsBtn, notRequiredEl, billingBanner) {
checkBillingAvailable().then(available => {
// Guard: if the user logged out or switched accounts while the
// async check was in flight, do not mutate the UI.
if (getApiKey() !== capturedKey) return;
if (available) {
if (balanceEl) balanceEl.style.display = '';
if (addFundsBtn) addFundsBtn.style.display = '';
loadBillingBalance();
} else {
if (notRequiredEl) notRequiredEl.style.display = '';
if (billingBanner) billingBanner.style.display = '';
// Schedule a retry so transient failures recover without a reload.
if (_billingRetryTimer) clearTimeout(_billingRetryTimer);
_billingRetryTimer = setTimeout(() => {
_billingRetryTimer = null;
refreshBillingUI(capturedKey, balanceEl, addFundsBtn, notRequiredEl, billingBanner);
}, BILLING_RETRY_MS);
}
}).catch(e => console.error('billing check failed', e));
}

// Preload groups, wallets, usage keys, and actions (for default group) when dashboard is shown
async function preloadAllTables() {
const apiKey = getApiKey();
Expand Down Expand Up @@ -223,6 +282,7 @@ let _stripe = null;
let _stripeCard = null;

async function openAddFundsModal() {
if (_billingAvailable === false) return;
const overlay = document.getElementById('billing-modal-overlay');
if (!overlay) return;
overlay.classList.add('is-open');
Expand Down
7 changes: 7 additions & 0 deletions lit-static/dapps/dashboard/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ <h1 class="topbar-title"> </h1>
<button type="button" class="btn btn-topbar btn-theme-toggle" id="theme-toggle" aria-label="Switch to dark mode"><span class="theme-icon" aria-hidden="true"></span></button>
<span id="billing-balance-display" class="billing-balance" style="display:none;"></span>
<button type="button" class="btn btn-primary btn-sm" id="btn-add-funds" style="display:none;">Add Funds</button>
<span id="billing-not-required" class="billing-not-required" style="display:none;">Payment Not Required</span>
<div class="account-dropdown" id="account-dropdown">
<button type="button" class="btn btn-topbar account-dropdown-trigger" id="account-dropdown-trigger" aria-expanded="false" aria-haspopup="true" aria-label="Account menu">
Account
Expand All @@ -123,6 +124,12 @@ <h1 class="topbar-title"> </h1>
<button type="button" id="new-account-dismiss-btn" class="new-account-dismiss" aria-label="Dismiss">&#x2715;</button>
</div>

<!-- Billing disabled notice (shown when Stripe is not configured) -->
<div id="billing-disabled-banner" class="billing-disabled-banner" style="display:none;">
<strong>Payment Not Required</strong>
<p>This environment is not configured to accept payments. You should migrate towards using the production system as there are no uptime or state guarantees available for test systems.</p>
</div>

<!-- Stat cards -->
<div class="stats-row">
<div class="stat-card">
Expand Down
33 changes: 33 additions & 0 deletions lit-static/dapps/dashboard/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
--primary-hover: #5a67d8;
--danger: #dc2626;
--success: #16a34a;
--bg-muted: #f3f4f6;
--shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1);
--radius: 8px;
--radius-lg: 12px;
Expand All @@ -38,6 +39,7 @@
--primary-hover: #818cf8;
--danger: #f87171;
--success: #4ade80;
--bg-muted: #1e293b;
--shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.3);
}

Expand Down Expand Up @@ -293,6 +295,37 @@ body.has-api-key .dashboard-wrap {
white-space: nowrap;
}

/* Payment not required badge (topbar) */
.billing-not-required {
font-size: 0.75rem;
font-weight: 500;
color: var(--text-muted);
background: var(--bg-muted, #f3f4f6);
padding: 0.25rem 0.625rem;
border-radius: 0.375rem;
white-space: nowrap;
}

/* Billing disabled banner (dashboard body) */
.billing-disabled-banner {
background: var(--bg-muted, #f3f4f6);
border: 1px solid var(--border, #e5e7eb);
border-radius: 0.5rem;
padding: 1rem 1.25rem;
margin-bottom: 1.5rem;
font-size: 0.875rem;
color: var(--text-muted);
}
.billing-disabled-banner strong {
display: block;
margin-bottom: 0.25rem;
color: var(--text, #111827);
}
.billing-disabled-banner p {
margin: 0;
line-height: 1.5;
}

/* Account dropdown */
.account-dropdown {
position: relative;
Expand Down
Loading