diff --git a/nostr/commit-signatures.jsonl b/nostr/commit-signatures.jsonl index c554d9d..b464fe8 100644 --- a/nostr/commit-signatures.jsonl +++ b/nostr/commit-signatures.jsonl @@ -59,3 +59,4 @@ {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1771829031,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","fix file management and refactor"]],"content":"Signed commit: fix file management and refactor","id":"626196cdbf9eab28b44990706281878083d66983b503e8a81df7421054ed6caf","sig":"516c0001a800083411a1e04340e82116a82c975f38b984e92ebe021b61271ba7d6f645466ddba3594320c228193e708675a5d7a144b2f3d5e9bfbc65c4c7372b"} {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1771836045,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","fix repo management and refactor\nimplement more GRASP support"]],"content":"Signed commit: fix repo management and refactor\nimplement more GRASP support","id":"6ae016621b13e22809e7bcebe34e5250fd6e0767d2b12ca634104def4ca78a29","sig":"99c34f66a8a67d352622621536545b7dee11cfd9d14a007ec0550d138109116a2f24483c6836fea59b94b9e96066fba548bcb7600bc55adbe0562d999c3c651d"} {"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1771838236,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","refactor repo manager"]],"content":"Signed commit: refactor repo manager","id":"d134c35516991f27e47ed8a4aa0d3f1d6e6be41c46c9cf3f6c982c1442b09b4b","sig":"cb699fae6a8e44a3b9123f215749f6fec0470c75a0401a94c37dfb8e572c07281b3941862e704b868663f943c573ab2ee9fec217e87f7be567cc6bb3514cacdb"} +{"kind":1640,"pubkey":"573634b648634cbad10f2451776089ea21090d9407f715e83c577b4611ae6edc","created_at":1771840654,"tags":[["author","Silberengel","silberengel7@protonmail.com"],["message","bug-fixes"]],"content":"Signed commit: bug-fixes","id":"0580e0df8000275817f040bbd6c04dfdfbff08a366df7a1686f227d8b7310053","sig":"9a238266f989c0664dc5c9743675907477e2fcb5311e8edeb505dec97027f619f6dc6742ee5f3887ff6a864274b45005fc7dd4432f8e2772dfe0bb7e2d8a449c"} diff --git a/src/app.css b/src/app.css index 9db0cbe..3dfd118 100644 --- a/src/app.css +++ b/src/app.css @@ -1214,6 +1214,8 @@ button.theme-option.active img.theme-icon-option, color: var(--text-primary); transition: all 0.2s ease; font-size: 0.9rem; + /* Ensure good contrast in all themes */ + min-height: 2.5rem; } .repo-badge:hover { @@ -1222,6 +1224,7 @@ button.theme-option.active img.theme-icon-option, transform: translateY(-1px); box-shadow: 0 2px 4px var(--shadow-color-light); font-size: 0.9rem; /* Preserve font size on hover */ + color: var(--text-primary); } .repo-badge-image { @@ -1230,6 +1233,9 @@ button.theme-option.active img.theme-icon-option, object-fit: cover; border-radius: 50%; flex-shrink: 0; + /* Ensure image is visible in all themes */ + border: 1px solid var(--border-color); + background: var(--bg-secondary); } .repo-badge-icon { @@ -1238,8 +1244,23 @@ button.theme-option.active img.theme-icon-option, display: flex; align-items: center; justify-content: center; - font-size: 1.2rem; flex-shrink: 0; + /* Ensure icon is visible in all themes */ + color: var(--text-primary); +} + +.repo-badge-icon svg { + width: 100%; + height: 100%; + stroke: var(--text-primary); + fill: none; +} + +.repo-badge-icon img { + width: 100%; + height: 100%; + object-fit: contain; + filter: var(--icon-filter, none); } .repo-badge-name { @@ -1248,6 +1269,9 @@ button.theme-option.active img.theme-icon-option, overflow: hidden; text-overflow: ellipsis; max-width: 200px; + color: var(--text-primary); + /* Ensure text is readable */ + line-height: 1.4; } .repo-badge.transferred { diff --git a/src/lib/components/RepoHeaderEnhanced.svelte b/src/lib/components/RepoHeaderEnhanced.svelte index 333c77c..980e8d0 100644 --- a/src/lib/components/RepoHeaderEnhanced.svelte +++ b/src/lib/components/RepoHeaderEnhanced.svelte @@ -207,11 +207,17 @@ Create Patch {/if} - {#if hasUnlimitedAccess && (isRepoCloned === false || isRepoCloned === null) && onCloneToServer} + {#if (isRepoCloned === false || isRepoCloned === null) && onCloneToServer} diff --git a/src/lib/components/TransferNotification.svelte b/src/lib/components/TransferNotification.svelte index 3d54bd0..ad0d37c 100644 --- a/src/lib/components/TransferNotification.svelte +++ b/src/lib/components/TransferNotification.svelte @@ -73,10 +73,7 @@

Repository Ownership Transfer

diff --git a/src/lib/styles/repo.css b/src/lib/styles/repo.css index e1db6e8..fab4c4d 100644 --- a/src/lib/styles/repo.css +++ b/src/lib/styles/repo.css @@ -7,6 +7,116 @@ overflow: hidden; } +.repo-not-cloned-message { + display: flex; + align-items: center; + justify-content: center; + min-height: 400px; + padding: 2rem; +} + +.repo-not-cloned-message .message-content { + max-width: 600px; + text-align: center; + padding: 2rem; + background: var(--card-bg); + border: 1px solid var(--border-color); + border-radius: 0.5rem; + box-shadow: 0 2px 8px var(--shadow-color-light); +} + +.repo-not-cloned-message .message-content h2 { + margin: 0 0 1rem 0; + color: var(--text-primary); + font-size: 1.5rem; +} + +.repo-not-cloned-message .message-content p { + margin: 0.75rem 0; + color: var(--text-secondary); + line-height: 1.6; +} + +.repo-not-cloned-message .message-content p:first-of-type { + margin-top: 0; +} + +.repo-not-cloned-message .message-content p:last-of-type { + margin-bottom: 0; +} + +.read-only-badge { + display: inline-block; + padding: 0.125rem 0.5rem; + margin-left: 0.5rem; + font-size: 0.75rem; + font-weight: 500; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 0.25rem; + color: var(--text-secondary); + text-transform: uppercase; + letter-spacing: 0.5px; + vertical-align: middle; +} + +.read-only-banner { + background: var(--bg-secondary); + border-bottom: 1px solid var(--border-color); + padding: 0.75rem 1rem; + margin: 0; +} + +.read-only-banner .banner-content { + display: flex; + align-items: center; + gap: 0.75rem; + max-width: 1400px; + margin: 0 auto; + font-size: 0.875rem; + color: var(--text-secondary); +} + +.read-only-banner .banner-icon { + width: 20px; + height: 20px; + flex-shrink: 0; + color: var(--text-secondary); +} + +.read-only-banner .banner-content span { + flex: 1; + line-height: 1.5; +} + +.read-only-banner .banner-content strong { + color: var(--text-primary); + font-weight: 600; +} + +.read-only-banner .clone-button-banner { + padding: 0.375rem 0.75rem; + font-size: 0.875rem; + background: var(--accent); + color: var(--accent-text, #fff); + border: 1px solid var(--accent); + border-radius: 0.375rem; + cursor: pointer; + transition: opacity 0.2s, background-color 0.2s; + font-weight: 500; + white-space: nowrap; +} + +.read-only-banner .clone-button-banner:hover:not(:disabled) { + opacity: 0.9; + background: var(--accent-hover, var(--accent)); +} + +.read-only-banner .clone-button-banner:disabled { + opacity: 0.6; + cursor: not-allowed; +} + .repo-metadata-section { padding: 0.75rem 1rem; background: var(--card-bg, #ffffff); @@ -339,6 +449,39 @@ border: 1px solid var(--error-text); } +.error .error-message { + margin-bottom: 0.75rem; +} + +.error .error-actions { + margin-top: 0.75rem; + padding-top: 0.75rem; + border-top: 1px solid var(--error-text); + opacity: 0.7; +} + +.error .clone-button-inline { + padding: 0.5rem 1rem; + font-size: 0.875rem; + background: var(--accent); + color: var(--accent-text, #fff); + border: 1px solid var(--accent); + border-radius: 0.375rem; + cursor: pointer; + transition: opacity 0.2s, background-color 0.2s; + font-weight: 500; +} + +.error .clone-button-inline:hover:not(:disabled) { + opacity: 0.9; + background: var(--accent-hover, var(--accent)); +} + +.error .clone-button-inline:disabled { + opacity: 0.6; + cursor: not-allowed; +} + .readme-section { display: flex; flex-direction: column; @@ -1553,17 +1696,23 @@ span.clone-more { } .reachability-refresh-button { + display: flex; + align-items: center; + gap: 0.375rem; padding: 0.25rem 0.5rem; font-size: 0.875rem; - background: var(--bg-secondary, #e8e8e8); - border: 1px solid var(--border-color, #ccc); + background: var(--bg-secondary); + border: 1px solid var(--border-color); border-radius: 4px; cursor: pointer; - transition: opacity 0.2s; + transition: opacity 0.2s, background-color 0.2s, border-color 0.2s; + color: var(--text-primary); } .reachability-refresh-button:hover:not(:disabled) { opacity: 0.8; + background: var(--bg-tertiary); + border-color: var(--accent); } .reachability-refresh-button:disabled { @@ -1571,6 +1720,13 @@ span.clone-more { cursor: not-allowed; } +.reachability-refresh-button .refresh-icon { + width: 16px; + height: 16px; + flex-shrink: 0; + color: var(--text-primary); +} + .server-type-badge { display: inline-flex; align-items: center; diff --git a/src/routes/dashboard/+page.svelte b/src/routes/dashboard/+page.svelte index e1a83fd..4c6bf72 100644 --- a/src/routes/dashboard/+page.svelte +++ b/src/routes/dashboard/+page.svelte @@ -313,7 +313,7 @@ {item.state} {#if isPR && 'merged_at' in item && item.merged_at} - ✓ Merged + Merged Merged {/if}
diff --git a/src/routes/repos/+page.svelte b/src/routes/repos/+page.svelte index 4fb534b..7e418bc 100644 --- a/src/routes/repos/+page.svelte +++ b/src/routes/repos/+page.svelte @@ -381,7 +381,7 @@ } async function deleteLocalRepo(npub: string, repo: string) { - if (!confirm(`⚠️ Are you sure you want to delete the local clone of "${repo}"?\n\nThis will permanently remove the repository from this server. The announcement on Nostr will NOT be deleted.\n\nThis action cannot be undone.\n\nClick OK to delete, or Cancel to abort.`)) { + if (!confirm(`Are you sure you want to delete the local clone of "${repo}"?\n\nThis will permanently remove the repository from this server. The announcement on Nostr will NOT be deleted.\n\nThis action cannot be undone.\n\nClick OK to delete, or Cancel to abort.`)) { return; } diff --git a/src/routes/repos/[npub]/[repo]/+page.svelte b/src/routes/repos/[npub]/[repo]/+page.svelte index 2874991..2f84ec7 100644 --- a/src/routes/repos/[npub]/[repo]/+page.svelte +++ b/src/routes/repos/[npub]/[repo]/+page.svelte @@ -248,9 +248,14 @@ } } let copyingCloneUrl = $state(false); + let apiFallbackAvailable = $state(null); // null = unknown, true = API fallback works, false = doesn't work // Helper: Check if repo needs to be cloned for write operations const needsClone = $derived(isRepoCloned === false); + // Helper: Check if we can use API fallback for read-only operations + const canUseApiFallback = $derived(apiFallbackAvailable === true); + // Helper: Check if we have any way to view the repo (cloned or API fallback) + const canViewRepo = $derived(isRepoCloned === true || canUseApiFallback); const cloneTooltip = 'Please clone this repo to use this feature.'; // Copy clone URL to clipboard @@ -330,16 +335,39 @@ // Tabs menu - defined after issues and prs // Order: Files, Issues, PRs, Patches, Discussion, History, Tags, Docs - const tabs = $derived([ - { id: 'files', label: 'Files', icon: '/icons/file-text.svg' }, - { id: 'issues', label: 'Issues', icon: '/icons/alert-circle.svg' }, - { id: 'prs', label: 'Pull Requests', icon: '/icons/git-pull-request.svg' }, - { id: 'patches', label: 'Patches', icon: '/icons/clipboard-list.svg' }, - { id: 'discussions', label: 'Discussions', icon: '/icons/message-circle.svg' }, - { id: 'history', label: 'Commit History', icon: '/icons/git-commit.svg' }, - { id: 'tags', label: 'Tags', icon: '/icons/tag.svg' }, - { id: 'docs', label: 'Docs', icon: '/icons/book.svg' } - ]); + // Show tabs that require cloned repo when repo is cloned OR API fallback is available + const tabs = $derived.by(() => { + const allTabs = [ + { id: 'files', label: 'Files', icon: '/icons/file-text.svg', requiresClone: true }, + { id: 'issues', label: 'Issues', icon: '/icons/alert-circle.svg', requiresClone: false }, + { id: 'prs', label: 'Pull Requests', icon: '/icons/git-pull-request.svg', requiresClone: false }, + { id: 'patches', label: 'Patches', icon: '/icons/clipboard-list.svg', requiresClone: false }, + { id: 'discussions', label: 'Discussions', icon: '/icons/message-circle.svg', requiresClone: false }, + { id: 'history', label: 'Commit History', icon: '/icons/git-commit.svg', requiresClone: true }, + { id: 'tags', label: 'Tags', icon: '/icons/tag.svg', requiresClone: true }, + { id: 'docs', label: 'Docs', icon: '/icons/book.svg', requiresClone: false } + ]; + + // Show all tabs if repo is cloned OR API fallback is available + // Otherwise, only show tabs that don't require cloning + if (isRepoCloned === false && !canUseApiFallback) { + return allTabs.filter(tab => !tab.requiresClone).map(({ requiresClone, ...tab }) => tab); + } + + // Return all tabs when repo is cloned, API fallback is available, or status is unknown (remove requiresClone property) + return allTabs.map(({ requiresClone, ...tab }) => tab); + }); + + // Redirect to a valid tab if current tab requires cloning but repo isn't cloned and API fallback isn't available + $effect(() => { + if (isRepoCloned === false && !canUseApiFallback && tabs.length > 0) { + const currentTab = tabs.find(t => t.id === activeTab); + if (!currentTab) { + // Current tab requires cloning, switch to first available tab + activeTab = tabs[0].id as typeof activeTab; + } + } + }); // Patches let patches = $state>([]); @@ -822,14 +850,15 @@ if (response.ok) { const data = await response.json(); - const newMap = new Map(); + const newMap = new Map(); if (data.results && Array.isArray(data.results)) { for (const result of data.results) { newMap.set(result.url, { reachable: result.reachable, error: result.error, - checkedAt: result.checkedAt + checkedAt: result.checkedAt, + serverType: result.serverType || 'unknown' }); } } @@ -1386,11 +1415,27 @@ // If response is 200, repo exists and is accessible (cloned) const wasCloned = response.status !== 404; isRepoCloned = wasCloned; - console.log(`[Clone Status] Repo ${wasCloned ? 'is cloned' : 'is not cloned'} (status: ${response.status})`); + + // If repo is not cloned, check if API fallback is available + if (!wasCloned) { + // Try to detect API fallback by checking if we have clone URLs + if (pageData.repoCloneUrls && pageData.repoCloneUrls.length > 0) { + // We have clone URLs, so API fallback might work - will be detected when loadBranches() runs + apiFallbackAvailable = null; // Will be set to true if a subsequent request succeeds + } else { + apiFallbackAvailable = false; + } + } else { + // Repo is cloned, API fallback not needed + apiFallbackAvailable = false; + } + + console.log(`[Clone Status] Repo ${wasCloned ? 'is cloned' : 'is not cloned'} (status: ${response.status}), API fallback: ${apiFallbackAvailable}`); } catch (err) { // On error, assume not cloned console.warn('[Clone Status] Error checking clone status:', err); isRepoCloned = false; + apiFallbackAvailable = false; } finally { checkingCloneStatus = false; } @@ -1424,6 +1469,8 @@ alert('Repository cloned successfully! The repository is now available on this server.'); // Force refresh clone status await checkCloneStatus(true); + // Reset API fallback status since repo is now cloned + apiFallbackAvailable = false; // Reload data to use the cloned repo instead of API await Promise.all([ loadBranches(), @@ -1475,7 +1522,7 @@ console.log(`[Fork UI] - Announcement ID: ${data.fork.announcementId}`); console.log(`[Fork UI] - Ownership Transfer ID: ${data.fork.ownershipTransferId}`); - alert(`✓ ${message}\n\nRedirecting to your fork...`); + alert(`${message}\n\nRedirecting to your fork...`); goto(`/repos/${data.fork.npub}/${data.fork.repo}`); } else { const errorMessage = data.error || 'Failed to fork repository'; @@ -1491,13 +1538,13 @@ } error = fullError; - alert(`✗ Fork failed!\n\n${fullError}`); + alert(`Fork failed!\n\n${fullError}`); } } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Failed to fork repository'; console.error(`[Fork UI] ✗ Unexpected error: ${errorMessage}`, err); error = errorMessage; - alert(`✗ Fork failed!\n\n${errorMessage}`); + alert(`Fork failed!\n\n${errorMessage}`); } finally { forking = false; } @@ -2610,12 +2657,12 @@ } // First confirmation - if (!confirm('⚠️ WARNING: Are you sure you want to delete this repository announcement?\n\nThis will permanently delete the repository announcement from Nostr relays. This action CANNOT be undone.\n\nClick OK to continue, or Cancel to abort.')) { + if (!confirm('WARNING: Are you sure you want to delete this repository announcement?\n\nThis will permanently delete the repository announcement from Nostr relays. This action CANNOT be undone.\n\nClick OK to continue, or Cancel to abort.')) { return; } // Second confirmation for critical operation - if (!confirm('⚠️ FINAL CONFIRMATION: This will permanently delete the repository announcement.\n\nAre you absolutely certain you want to proceed?\n\nThis action CANNOT be undone.')) { + if (!confirm('FINAL CONFIRMATION: This will permanently delete the repository announcement.\n\nAre you absolutely certain you want to proceed?\n\nThis action CANNOT be undone.')) { return; } @@ -2714,6 +2761,11 @@ }); if (response.ok) { branches = await response.json(); + + // If repo is not cloned but we got branches, API fallback is available + if (isRepoCloned === false && branches.length > 0) { + apiFallbackAvailable = true; + } if (branches.length > 0) { // Branches can be an array of objects with .name property or array of strings const branchNames = branches.map((b: any) => typeof b === 'string' ? b : b.name); @@ -2762,9 +2814,24 @@ } } } else if (response.status === 404) { - // Repository not provisioned yet - set error message and flag - repoNotFound = true; - error = `Repository not found. This repository exists in Nostr but hasn't been provisioned on this server yet. The server will automatically provision it soon, or you can contact the server administrator.`; + // Check if this is a "not cloned" error with API fallback suggestion + const errorText = await response.text().catch(() => ''); + if (errorText.includes('not cloned locally') && errorText.includes('API')) { + // API fallback might be available, but this specific request failed + // Try to detect if API fallback works by checking if we have clone URLs + if (pageData.repoCloneUrls && pageData.repoCloneUrls.length > 0) { + // We have clone URLs, so API fallback might work - mark as unknown for now + // It will be set to true if a subsequent request succeeds + apiFallbackAvailable = null; + } else { + apiFallbackAvailable = false; + } + } else { + // Repository not provisioned yet - set error message and flag + repoNotFound = true; + error = `Repository not found. This repository exists in Nostr but hasn't been provisioned on this server yet. The server will automatically provision it soon, or you can contact the server administrator.`; + apiFallbackAvailable = false; + } } else if (response.status === 403) { // Access denied - don't set repoNotFound, allow retry after login const errorText = await response.text().catch(() => response.statusText); @@ -2807,7 +2874,19 @@ if (!response.ok) { if (response.status === 404) { - repoNotFound = true; + // Check if this is a "not cloned" error with API fallback suggestion + const errorText = await response.text().catch(() => ''); + if (errorText.includes('not cloned locally') && errorText.includes('API')) { + // API fallback might be available, but this specific request failed + if (pageData.repoCloneUrls && pageData.repoCloneUrls.length > 0) { + apiFallbackAvailable = null; // Unknown, will be set if a request succeeds + } else { + apiFallbackAvailable = false; + } + } else { + repoNotFound = true; + apiFallbackAvailable = false; + } throw new Error(`Repository not found. This repository exists in Nostr but hasn't been provisioned on this server yet. The server will automatically provision it soon, or you can contact the server administrator.`); } else if (response.status === 403) { // 403 means access denied - don't set repoNotFound, just show error @@ -2823,6 +2902,11 @@ files = await response.json(); currentPath = path; + // If repo is not cloned but we got files, API fallback is available + if (isRepoCloned === false && files.length > 0) { + apiFallbackAvailable = true; + } + // Auto-load README if we're in the root directory and no file is currently selected // Only attempt once per path to prevent loops if (path === '' && !currentFile && !readmeAutoLoadAttempted) { @@ -3613,7 +3697,7 @@ } async function deleteFile(filePath: string) { - if (!confirm(`⚠️ Are you sure you want to delete "${filePath}"?\n\nThis will permanently delete the file from the repository. This action cannot be undone.\n\nClick OK to delete, or Cancel to abort.`)) { + if (!confirm(`Are you sure you want to delete "${filePath}"?\n\nThis will permanently delete the file from the repository. This action cannot be undone.\n\nClick OK to delete, or Cancel to abort.`)) { return; } @@ -3741,7 +3825,7 @@ } async function deleteBranch(branchName: string) { - if (!confirm(`⚠️ Are you sure you want to delete the branch "${branchName}"?\n\nThis will permanently delete the branch from the repository. This action CANNOT be undone.\n\nClick OK to delete, or Cancel to abort.`)) { + if (!confirm(`Are you sure you want to delete the branch "${branchName}"?\n\nThis will permanently delete the branch from the repository. This action CANNOT be undone.\n\nClick OK to delete, or Cancel to abort.`)) { return; } @@ -4393,18 +4477,20 @@ aria-expanded={cloneUrlsExpanded} > Clone URLs: - - - +
@@ -4505,17 +4591,60 @@ {/if}
+ {#if isRepoCloned === false && canUseApiFallback} +
+ +
+ {/if} {#if error}
- Error: {error} +
+ Error: {error} +
+ {#if error.includes('not cloned locally') && hasUnlimitedAccess($userStore.userLevel)} +
+ +
+ {/if}
{/if} - + + {#if isRepoCloned === false && !canUseApiFallback && tabs.length === 0} +
+
+

Repository Not Cloned

+

This repository has not been cloned to the server yet, and read-only access via external clone URLs is not available.

+ {#if hasUnlimitedAccess($userStore.userLevel)} +

Use the "Clone to Server" option in the repository menu to clone this repository.

+ {:else} +

Contact a server administrator with unlimited access to clone this repository.

+ {/if} +
+
+ {:else}
- {#if activeTab === 'files'} + {#if activeTab === 'files' && canViewRepo}
diff --git a/src/routes/signup/+page.svelte b/src/routes/signup/+page.svelte index 2d49045..b856226 100644 --- a/src/routes/signup/+page.svelte +++ b/src/routes/signup/+page.svelte @@ -2221,10 +2221,7 @@ {#if lookupLoading['repo-existingRepoRef']} Loading... {:else} - - - - + Search {/if} {#if maintainers.length > 1} @@ -2733,7 +2727,7 @@ {/if} {#if lookupResults[`doc-${index}`]}
- ✓ Documentation address converted successfully + Success Documentation address converted successfully
{/if} {/each} @@ -2789,10 +2783,7 @@ {#if lookupLoading['repo-forkOriginalRepo']} Loading... {:else} - - - - + Search {/if}
diff --git a/src/routes/users/[npub]/+page.svelte b/src/routes/users/[npub]/+page.svelte index 6c4cc49..62e1040 100644 --- a/src/routes/users/[npub]/+page.svelte +++ b/src/routes/users/[npub]/+page.svelte @@ -1664,7 +1664,7 @@ i *
{#if hasUnlimitedAccess(userLevel)} - ✓ Unlimited Access + Unlimited Access Unlimited Access {:else if userLevel === 'rate_limited'} diff --git a/static/icons/check.svg b/static/icons/check.svg new file mode 100644 index 0000000..858ea6f --- /dev/null +++ b/static/icons/check.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/icons/chevron-down.svg b/static/icons/chevron-down.svg new file mode 100644 index 0000000..3e17f6e --- /dev/null +++ b/static/icons/chevron-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/icons/search.svg b/static/icons/search.svg new file mode 100644 index 0000000..3eb16b3 --- /dev/null +++ b/static/icons/search.svg @@ -0,0 +1,4 @@ + + + + diff --git a/static/icons/x-circle.svg b/static/icons/x-circle.svg new file mode 100644 index 0000000..48b4954 --- /dev/null +++ b/static/icons/x-circle.svg @@ -0,0 +1,5 @@ + + + + +