From eed26c2d5def60abe240d28257a24ed39748d2ee Mon Sep 17 00:00:00 2001 From: "nekomimi.pet" Date: Wed, 18 Feb 2026 14:51:54 -0500 Subject: [PATCH] contrast fixes --- .../src/lib/cache-manager.test.ts | 32 ++--- apps/main-app/public/components/ui/dialog.tsx | 33 ++++++ apps/main-app/public/components/ui/tabs.tsx | 2 +- apps/main-app/public/editor/editor.tsx | 72 +++++++++--- .../public/editor/tabs/DomainsTab.tsx | 7 +- apps/main-app/public/editor/tabs/SitesTab.tsx | 12 +- apps/main-app/public/styles/global.css | 110 +++++++++++++++++- apps/main-app/src/routes/wisp.ts | 18 +-- 8 files changed, 233 insertions(+), 53 deletions(-) diff --git a/apps/hosting-service/src/lib/cache-manager.test.ts b/apps/hosting-service/src/lib/cache-manager.test.ts index 18e45c3..8497492 100644 --- a/apps/hosting-service/src/lib/cache-manager.test.ts +++ b/apps/hosting-service/src/lib/cache-manager.test.ts @@ -22,22 +22,22 @@ describe('CacheManager', () => { test('stores and retrieves a value', () => { const c = createTestCache() c.set('ttl', 'k', 42) - expect(c.get('ttl', 'k')).toBe(42) + expect(c.get('ttl', 'k')).toBe(42) }) test('namespaces are isolated', () => { const c = createTestCache() c.set('ttl', 'k', 'from-ttl') c.set('lru', 'k', 'from-lru') - expect(c.get('ttl', 'k')).toBe('from-ttl') - expect(c.get('lru', 'k')).toBe('from-lru') + expect(c.get('ttl', 'k')).toBe('from-ttl') + expect(c.get('lru', 'k')).toBe('from-lru') }) test('overwrites existing key', () => { const c = createTestCache() c.set('lru', 'k', 'v1') c.set('lru', 'k', 'v2') - expect(c.get('lru', 'k')).toBe('v2') + expect(c.get('lru', 'k')).toBe('v2') }) }) @@ -45,7 +45,7 @@ describe('CacheManager', () => { test('returns value within TTL', () => { const c = createTestCache() c.set('ttl', 'k', 'fresh') - expect(c.get('ttl', 'k')).toBe('fresh') + expect(c.get('ttl', 'k')).toBe('fresh') }) test('expires value after TTL', async () => { @@ -64,9 +64,9 @@ describe('CacheManager', () => { c.set('lru', 'c', 3) // At capacity (3). Adding a 4th should evict 'a' (oldest). c.set('lru', 'd', 4) - expect(c.get('lru', 'a')).toBeUndefined() - expect(c.get('lru', 'b')).toBe(2) - expect(c.get('lru', 'd')).toBe(4) + expect(c.get('lru', 'a')).toBeUndefined() + expect(c.get('lru', 'b')).toBe(2) + expect(c.get('lru', 'd')).toBe(4) }) test('accessing a key refreshes its LRU position', () => { @@ -78,8 +78,8 @@ describe('CacheManager', () => { c.get('lru', 'a') // Now 'b' is the oldest c.set('lru', 'd', 4) - expect(c.get('lru', 'b')).toBeUndefined() - expect(c.get('lru', 'a')).toBe(1) + expect(c.get('lru', 'b')).toBeUndefined() + expect(c.get('lru', 'a')).toBe(1) }) }) @@ -118,10 +118,10 @@ describe('CacheManager', () => { c.set('lru', 'b', 2) c.set('ttl', 'x', 3) c.clear('lru') - expect(c.get('lru', 'a')).toBeUndefined() - expect(c.get('lru', 'b')).toBeUndefined() + expect(c.get('lru', 'a')).toBeUndefined() + expect(c.get('lru', 'b')).toBeUndefined() // Other namespace untouched - expect(c.get('ttl', 'x')).toBe(3) + expect(c.get('ttl', 'x')).toBe(3) }) }) @@ -151,7 +151,7 @@ describe('CacheManager', () => { }) expect(val).toBe('async-result') // Second call should be from cache - expect(c.get('lru', 'k')).toBe('async-result') + expect(c.get('lru', 'k')).toBe('async-result') }) test('cacheIf: false skips caching', async () => { @@ -170,7 +170,7 @@ describe('CacheManager', () => { cacheIf: (v) => v !== null, }) expect(val).toBe('good') - expect(c.get('lru', 'k')).toBe('good') + expect(c.get('lru', 'k')).toBe('good') }) }) @@ -248,7 +248,7 @@ describe('CacheManager', () => { c.startCleanup(50) await Bun.sleep(200) c.stopCleanup() - expect(c.get('lru', 'k')).toBe('val') + expect(c.get('lru', 'k')).toBe('val') }) }) }) diff --git a/apps/main-app/public/components/ui/dialog.tsx b/apps/main-app/public/components/ui/dialog.tsx index 9640858..ab8f8c1 100644 --- a/apps/main-app/public/components/ui/dialog.tsx +++ b/apps/main-app/public/components/ui/dialog.tsx @@ -89,8 +89,41 @@ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { } function DialogFooter({ className, ...props }: React.ComponentProps<"div">) { + const footerRef = React.useRef(null) + + React.useEffect(() => { + const footer = footerRef.current + if (!footer) return + + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') return + + const buttons = Array.from( + footer.querySelectorAll('button:not([disabled])') + ) as HTMLElement[] + const currentIndex = buttons.indexOf(document.activeElement as HTMLElement) + + if (currentIndex === -1) return + + e.preventDefault() + let nextIndex = currentIndex + + if (e.key === 'ArrowRight') { + nextIndex = (currentIndex + 1) % buttons.length + } else if (e.key === 'ArrowLeft') { + nextIndex = (currentIndex - 1 + buttons.length) % buttons.length + } + + buttons[nextIndex]?.focus() + } + + footer.addEventListener('keydown', handleKeyDown) + return () => footer.removeEventListener('keydown', handleKeyDown) + }, []) + return (
>(new Set()) const [isSavingConfig, setIsSavingConfig] = useState(false) const [isDeletingSite, setIsDeletingSite] = useState(false) + const [deleteConfirmSite, setDeleteConfirmSite] = useState(null) // Site settings state type RoutingMode = 'default' | 'spa' | 'directory' | 'custom404' @@ -303,19 +304,21 @@ function Dashboard() { } } - const handleDeleteSite = async () => { - if (!configuringSite) return + const handleDeleteConfirmSite = async (site: SiteWithDomains) => { + setDeleteConfirmSite(site) + } - if (!confirm(`Are you sure you want to delete "${configuringSite.display_name || configuringSite.rkey}"? This action cannot be undone.`)) { - return - } + const handleDeleteSite = async () => { + const site = configuringSite || deleteConfirmSite + if (!site) return setIsDeletingSite(true) - const success = await deleteSite(configuringSite.rkey) + const success = await deleteSite(site.rkey) if (success) { // Refresh domains in case this site was mapped await fetchDomains() setConfiguringSite(null) + setDeleteConfirmSite(null) } setIsDeletingSite(false) } @@ -441,28 +444,28 @@ function Dashboard() {
- + Sites Domains Upload Cli @@ -475,6 +478,7 @@ function Dashboard() { sitesLoading={sitesLoading} userInfo={userInfo} onConfigureSite={handleConfigureSite} + onDeleteSite={handleDeleteConfirmSite} /> @@ -580,9 +584,9 @@ function Dashboard() { - - Domains - Settings + + Domains + Settings {/* Domains Tab */} @@ -910,6 +914,46 @@ function Dashboard() { + + {/* Delete Site Confirmation Modal */} + !open && setDeleteConfirmSite(null)} + > + + + Delete Site + + Are you sure you want to delete "{deleteConfirmSite?.display_name || deleteConfirmSite?.rkey}"? This action cannot be undone. + + + + + + + + ) } diff --git a/apps/main-app/public/editor/tabs/DomainsTab.tsx b/apps/main-app/public/editor/tabs/DomainsTab.tsx index 7018a08..038f112 100644 --- a/apps/main-app/public/editor/tabs/DomainsTab.tsx +++ b/apps/main-app/public/editor/tabs/DomainsTab.tsx @@ -31,9 +31,10 @@ import type { UserInfo } from '../hooks/useUserInfo' // Hosting node IP addresses for A record fallback const HOSTING_NODES = [ - { region: 'US East (Virginia)', ip: '192.0.2.1' }, - { region: 'US West (California)', ip: '192.0.2.2' }, - { region: 'Europe (Frankfurt)', ip: '192.0.2.3' }, + { region: 'US East (Virginia)', ip: '129.213.110.75' }, + { region: 'US West (California)', ip: '152.44.44.138' }, + { region: 'Europe (Netherlands)', ip: '152.53.121.97' }, + { region: 'Asia (Singapore)', ip: '213.163.207.16' }, ] as const interface DomainsTabProps { diff --git a/apps/main-app/public/editor/tabs/SitesTab.tsx b/apps/main-app/public/editor/tabs/SitesTab.tsx index b8174e0..3911067 100644 --- a/apps/main-app/public/editor/tabs/SitesTab.tsx +++ b/apps/main-app/public/editor/tabs/SitesTab.tsx @@ -18,6 +18,7 @@ interface SitesTabProps { sitesLoading: boolean userInfo: UserInfo | null onConfigureSite: (site: SiteWithDomains) => void + onDeleteSite: (site: SiteWithDomains) => void } // Helper to generate unique site key @@ -42,7 +43,8 @@ export function SitesTab({ sites, sitesLoading, userInfo, - onConfigureSite + onConfigureSite, + onDeleteSite }: SitesTabProps) { // State: only one site can be expanded at a time (null = none expanded) const [expandedSiteKey, setExpandedSiteKey] = useState(null) @@ -166,7 +168,7 @@ export function SitesTab({ case 'd': if (isExpanded && currentSite) { e.preventDefault() - onConfigureSite(currentSite) + onDeleteSite(currentSite) } break } @@ -174,7 +176,7 @@ export function SitesTab({ window.addEventListener('keydown', handleKeyDown) return () => window.removeEventListener('keydown', handleKeyDown) - }, [sites, focusedIndex, expandedSiteKey, toggleExpanded, getSiteUrl, onConfigureSite]) + }, [sites, focusedIndex, expandedSiteKey, toggleExpanded, getSiteUrl, onConfigureSite, onDeleteSite]) // Loading state if (sitesLoading) { @@ -255,7 +257,7 @@ export function SitesTab({ key={siteKey} ref={el => { siteRefs.current[index] = el }} className={`border transition-colors ${ - isFocused ? 'border-accent bg-accent/10' : 'border-border/30 hover:bg-muted/20' + isFocused ? 'border-accent bg-accent/10' : 'border-border/30 bg-card hover:bg-muted/10' }`} > {/* Site header */} @@ -365,7 +367,7 @@ export function SitesTab({ variant="outline" size="sm" className="font-mono text-xs text-red-400 hover:text-red-500 hover:border-red-400/50" - onClick={() => onConfigureSite(site)} + onClick={() => onDeleteSite(site)} > Delete diff --git a/apps/main-app/public/styles/global.css b/apps/main-app/public/styles/global.css index 296f42a..a35843f 100644 --- a/apps/main-app/public/styles/global.css +++ b/apps/main-app/public/styles/global.css @@ -22,8 +22,8 @@ --primary: oklch(0.30 0.025 35); --primary-foreground: oklch(0.96 0.008 35); - /* Deeper pink accent for better visibility */ - --accent: oklch(0.65 0.18 345); + /* Bubblegum pink accent */ + --accent: #ff5c8a; --accent-foreground: oklch(0.15 0.015 30); /* Darker taupe secondary for better contrast */ @@ -38,12 +38,12 @@ --border: oklch(0.65 0.02 30); /* Input backgrounds lighter than cards */ --input: oklch(0.97 0.005 35); - --ring: oklch(0.55 0.12 345); + --ring: #ff5c8a; --destructive: oklch(0.50 0.20 25); --destructive-foreground: oklch(0.98 0 0); - --chart-1: oklch(0.65 0.18 345); + --chart-1: #ff5c8a; --chart-2: oklch(0.32 0.04 285); --chart-3: oklch(0.50 0.10 220); --chart-4: oklch(0.70 0.08 130); @@ -57,7 +57,7 @@ --sidebar-accent: oklch(0.90 0.01 35); --sidebar-accent-foreground: oklch(0.20 0.02 30); --sidebar-border: oklch(0.65 0.02 30); - --sidebar-ring: oklch(0.55 0.12 345); + --sidebar-ring: #ff5c8a; } .dark { @@ -156,6 +156,106 @@ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); --color-sidebar-border: var(--sidebar-border); --color-sidebar-ring: var(--sidebar-ring); + --color-hot-fuchsia: #ff0a54; + --color-hot-fuchsia-100: #350010; + --color-hot-fuchsia-200: #6a0020; + --color-hot-fuchsia-300: #9f0030; + --color-hot-fuchsia-400: #d40040; + --color-hot-fuchsia-500: #ff0a54; + --color-hot-fuchsia-600: #ff3b76; + --color-hot-fuchsia-700: #ff6c98; + --color-hot-fuchsia-800: #ff9dba; + --color-hot-fuchsia-900: #ffcedd; + --color-wild-strawberry: #ff477e; + --color-wild-strawberry-100: #410014; + --color-wild-strawberry-200: #830027; + --color-wild-strawberry-300: #c4003b; + --color-wild-strawberry-400: #ff0651; + --color-wild-strawberry-500: #ff477e; + --color-wild-strawberry-600: #ff6c98; + --color-wild-strawberry-700: #ff91b2; + --color-wild-strawberry-800: #ffb6cc; + --color-wild-strawberry-900: #ffdae5; + --color-bubblegum-pink: #ff5c8a; + --color-bubblegum-pink-100: #450014; + --color-bubblegum-pink-200: #8b0027; + --color-bubblegum-pink-300: #d0003b; + --color-bubblegum-pink-400: #ff1658; + --color-bubblegum-pink-500: #ff5c8a; + --color-bubblegum-pink-600: #ff7ca1; + --color-bubblegum-pink-700: #ff9db9; + --color-bubblegum-pink-800: #ffbed0; + --color-bubblegum-pink-900: #ffdee8; + --color-petal-rouge: #ff7096; + --color-petal-rouge-100: #490014; + --color-petal-rouge-200: #930027; + --color-petal-rouge-300: #dc003b; + --color-petal-rouge-400: #ff2760; + --color-petal-rouge-500: #ff7096; + --color-petal-rouge-600: #ff8dab; + --color-petal-rouge-700: #ffa9c0; + --color-petal-rouge-800: #ffc6d5; + --color-petal-rouge-900: #ffe2ea; + --color-bubblegum-tint: #ff85a1; + --color-bubblegum-tint-100: #4e0012; + --color-bubblegum-tint-200: #9b0024; + --color-bubblegum-tint-300: #e90036; + --color-bubblegum-tint-400: #ff3766; + --color-bubblegum-tint-500: #ff85a1; + --color-bubblegum-tint-600: #ff9db4; + --color-bubblegum-tint-700: #ffb6c7; + --color-bubblegum-tint-800: #ffced9; + --color-bubblegum-tint-900: #ffe7ec; + --color-cotton-candy: #ff99ac; + --color-cotton-candy-100: #52000f; + --color-cotton-candy-200: #a3001e; + --color-cotton-candy-300: #f5002d; + --color-cotton-candy-400: #ff4769; + --color-cotton-candy-500: #ff99ac; + --color-cotton-candy-600: #ffadbc; + --color-cotton-candy-700: #ffc2cd; + --color-cotton-candy-800: #ffd6de; + --color-cotton-candy-900: #ffebee; + --color-cherry-blossom: #fbb1bd; + --color-cherry-blossom-100: #510411; + --color-cherry-blossom-200: #a30922; + --color-cherry-blossom-300: #f20f35; + --color-cherry-blossom-400: #f76079; + --color-cherry-blossom-500: #fbb1bd; + --color-cherry-blossom-600: #fcc1cb; + --color-cherry-blossom-700: #fdd0d8; + --color-cherry-blossom-800: #fde0e5; + --color-cherry-blossom-900: #feeff2; + --color-pastel-pink: #f9bec7; + --color-pastel-pink-100: #500712; + --color-pastel-pink-200: #a10f25; + --color-pastel-pink-300: #ea1d3c; + --color-pastel-pink-400: #f16d81; + --color-pastel-pink-500: #f9bec7; + --color-pastel-pink-600: #facbd2; + --color-pastel-pink-700: #fbd8dd; + --color-pastel-pink-800: #fde5e8; + --color-pastel-pink-900: #fef2f4; + --color-cotton-rose: #f7cad0; + --color-cotton-rose-100: #4e0c15; + --color-cotton-rose-200: #9c1729; + --color-cotton-rose-300: #e02e45; + --color-cotton-rose-400: #eb7c8b; + --color-cotton-rose-500: #f7cad0; + --color-cotton-rose-600: #f9d4d9; + --color-cotton-rose-700: #fadfe3; + --color-cotton-rose-800: #fceaec; + --color-cotton-rose-900: #fdf4f6; + --color-soft-blush: #fae0e4; + --color-soft-blush-100: #520d18; + --color-soft-blush-200: #a31b2f; + --color-soft-blush-300: #df3d55; + --color-soft-blush-400: #ed8f9d; + --color-soft-blush-500: #fae0e4; + --color-soft-blush-600: #fbe6ea; + --color-soft-blush-700: #fcedef; + --color-soft-blush-800: #fdf3f4; + --color-soft-blush-900: #fef9fa; } @layer base { diff --git a/apps/main-app/src/routes/wisp.ts b/apps/main-app/src/routes/wisp.ts index 4a514a0..d0249eb 100644 --- a/apps/main-app/src/routes/wisp.ts +++ b/apps/main-app/src/routes/wisp.ts @@ -317,7 +317,7 @@ async function processUploadInBackground( await upsertSiteCache(did, rkey, record.data.cid, {}); } catch (err) { // Don't fail the upload if caching fails - logger.warn('Failed to cache site', err); + logger.warn('Failed to cache site', err as any); } // Auto-map claimed domain to this site if user has a claimed domain with no rkey @@ -331,10 +331,10 @@ async function processUploadInBackground( logger.info(`Auto-mapped domain ${existingDomain} to new site ${siteName}`); } } - } catch (err) { - // Don't fail the upload if domain mapping fails - logger.warn('Failed to auto-map domain to new site', err); - } + } catch (err) { + // Don't fail the upload if domain mapping fails + logger.warn('Failed to auto-map domain to new site', err as any); + } completeUploadJob(jobId, { success: true, @@ -888,7 +888,7 @@ async function processUploadInBackground( await upsertSiteCache(did, rkey, record.data.cid, fileCids); } catch (err) { // Don't fail the upload if caching fails - logger.warn('Failed to cache site files', err); + logger.warn('Failed to cache site files', err as any); } // Auto-map claimed domain to this site if user has a claimed domain with no rkey @@ -904,7 +904,7 @@ async function processUploadInBackground( } } catch (err) { // Don't fail the upload if domain mapping fails - logger.warn('Failed to auto-map domain to new site', err); + logger.warn('Failed to auto-map domain to new site', err as any); } // Clean up old subfs records if we had any @@ -1135,7 +1135,7 @@ export const wispRoutes = (client: NodeOAuthClient, cookieSecret: string) => await upsertSiteCache(auth.did, rkey, record.data.cid, {}); } catch (err) { // Don't fail the upload if caching fails - logger.warn('Failed to cache site', err); + logger.warn('Failed to cache site', err as any); } // Auto-map claimed domain to this site if user has a claimed domain with no rkey @@ -1151,7 +1151,7 @@ export const wispRoutes = (client: NodeOAuthClient, cookieSecret: string) => } } catch (err) { // Don't fail the upload if domain mapping fails - logger.warn('Failed to auto-map domain to new site', err); + logger.warn('Failed to auto-map domain to new site', err as any); } return { -- 2.54.0