From ce2986fd2a599ba2e8f17499273b59163c3f723e Mon Sep 17 00:00:00 2001 From: Dorian Date: Sat, 14 Mar 2026 19:08:09 +0000 Subject: [PATCH] fix: indeedhub staging API, nginx caching, nostr identity and UI improvements Switch IndeedHub to staging API, add _next asset caching in nginx, simplify NostrIdentityPicker component, and update Apps/Web5/Marketplace views. Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/indeedhub/Dockerfile | 8 +- image-recipe/configs/nginx-archipelago.conf | 12 +- .../archipelago-https-app-proxies.conf | 12 +- .../src/components/NostrIdentityPicker.vue | 290 +++++------------- neode-ui/src/locales/en.json | 1 + neode-ui/src/locales/es.json | 1 + neode-ui/src/views/Apps.vue | 29 +- neode-ui/src/views/Marketplace.vue | 16 +- neode-ui/src/views/Web5.vue | 34 +- 9 files changed, 155 insertions(+), 248 deletions(-) diff --git a/apps/indeedhub/Dockerfile b/apps/indeedhub/Dockerfile index 266914d4..f7afb278 100644 --- a/apps/indeedhub/Dockerfile +++ b/apps/indeedhub/Dockerfile @@ -20,7 +20,7 @@ RUN sed -i 's/reactStrictMode: true,/reactStrictMode: true, output: "standalone" # Build-time environment — connects to Indeehub production services ENV NEXT_PUBLIC_APP_ENVIRONMENT=production ENV NEXT_PUBLIC_APP_URL=http://localhost:8190 -ENV NEXT_PUBLIC_API_URL=https://api.indeehub.studio +ENV NEXT_PUBLIC_API_URL=https://staging-api.indeehub.studio ENV NEXT_PUBLIC_S3_PRIVATE_BUCKET=indeehub-private ENV NEXT_PUBLIC_S3_PUBLIC_BUCKET=indeehub-public ENV NEXT_PUBLIC_ENABLE_APPROVAL_FLOW=false @@ -28,6 +28,12 @@ ENV NEXT_TELEMETRY_DISABLED=1 # Remove shaka-player .d.ts files that break the build (per package.json build script) RUN rm -f ./node_modules/shaka-player/dist/*.d.ts + +# Patch: make the home page static (no SSR revalidation) so it uses the +# pre-rendered Webflow HTML from build time instead of re-fetching every request. +# Also add error handling so SSR failures don't crash the page. +RUN sed -i '1s|^|export const revalidate = false;\nexport const dynamic = "force-static";\n|' src/app/page.tsx + RUN npm run build # ── Stage 3: Runner ── diff --git a/image-recipe/configs/nginx-archipelago.conf b/image-recipe/configs/nginx-archipelago.conf index 22bef01a..a1f3e2da 100644 --- a/image-recipe/configs/nginx-archipelago.conf +++ b/image-recipe/configs/nginx-archipelago.conf @@ -331,6 +331,13 @@ server { sub_filter_once on; sub_filter '' ''; } + location /app/indeedhub/_next/ { + proxy_pass http://127.0.0.1:8190/_next/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_cache_valid 200 30d; + add_header Cache-Control "public, max-age=2592000, immutable"; + } location /app/indeedhub/ { proxy_pass http://127.0.0.1:8190/; proxy_http_version 1.1; @@ -342,7 +349,10 @@ server { proxy_hide_header Content-Security-Policy; add_header X-Content-Type-Options "nosniff" always; proxy_set_header Accept-Encoding ""; - sub_filter_once on; + sub_filter_types text/html; + sub_filter_once off; + sub_filter '/_next/' '/app/indeedhub/_next/'; + sub_filter '/favicon.ico' '/app/indeedhub/favicon.ico'; sub_filter '' ''; } location /app/lnd/ { diff --git a/image-recipe/configs/snippets/archipelago-https-app-proxies.conf b/image-recipe/configs/snippets/archipelago-https-app-proxies.conf index 5a519f09..853b8fc0 100644 --- a/image-recipe/configs/snippets/archipelago-https-app-proxies.conf +++ b/image-recipe/configs/snippets/archipelago-https-app-proxies.conf @@ -231,6 +231,13 @@ location /app/electrs/ { sub_filter_once on; sub_filter '' ''; } +location /app/indeedhub/_next/ { + proxy_pass http://127.0.0.1:8190/_next/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_cache_valid 200 30d; + add_header Cache-Control "public, max-age=2592000, immutable"; +} location /app/indeedhub/ { proxy_pass http://127.0.0.1:8190/; proxy_http_version 1.1; @@ -241,7 +248,10 @@ location /app/indeedhub/ { proxy_hide_header X-Frame-Options; proxy_hide_header Content-Security-Policy; proxy_set_header Accept-Encoding ""; - sub_filter_once on; + sub_filter_types text/html; + sub_filter_once off; + sub_filter '/_next/' '/app/indeedhub/_next/'; + sub_filter '/favicon.ico' '/app/indeedhub/favicon.ico'; sub_filter '' ''; } location /app/nginx-proxy-manager/ { diff --git a/neode-ui/src/components/NostrIdentityPicker.vue b/neode-ui/src/components/NostrIdentityPicker.vue index c3a12aa9..9904b74a 100644 --- a/neode-ui/src/components/NostrIdentityPicker.vue +++ b/neode-ui/src/components/NostrIdentityPicker.vue @@ -6,15 +6,8 @@ class="fixed inset-0 z-[3100] flex items-center justify-center p-4" @click="$emit('cancel')" > - -
- - -
-
- {{ particleChar(i) }} -
-
+ +
- +
- -
- - - - +
+ + + + - - - - - + + + + - - - - - - - - - - - - + + + + + + + + + +
-

SELECT IDENTITY

-

Nostr Authentication Protocol

+

Select Identity

+

Nostr authentication protocol

-
- +
+
- Loading identities... + Loading identities...
@@ -85,28 +74,33 @@ v-for="identity in identities" :key="identity.id" type="button" - class="cyber-identity-card w-full text-left" - :class="{ 'cyber-identity-selected': selectedId === identity.id }" + class="w-full text-left p-3 rounded-lg border transition-all duration-200" + :class="selectedId === identity.id + ? 'bg-orange-500/10 border-orange-500/30' + : 'bg-white/5 border-white/10 hover:bg-white/8 hover:border-white/15'" @click="selectedId = identity.id" >
- -
+ +
{{ identity.name.charAt(0).toUpperCase() }}
{{ identity.name }} - DEFAULT + default
-
+
{{ truncateNpub(identity.nostr_npub) }} - NO NOSTR KEY + No Nostr key
- +
@@ -117,26 +111,28 @@
- +
-
- -

- NIP-07 · SECP256K1 · SIGNED LOCALLY +

+ NIP-07 · SECP256K1 · Signed locally

@@ -197,7 +193,6 @@ async function loadIdentities() { try { const res = await rpcClient.call<{ identities: Identity[] }>({ method: 'identity.list' }) identities.value = res.identities || [] - // Auto-select the default identity or first one with a Nostr key const defaultId = identities.value.find(i => i.is_default && i.nostr_pubkey) || identities.value.find(i => i.nostr_pubkey) if (defaultId) { @@ -222,97 +217,41 @@ function truncateNpub(npub: string): string { return npub.slice(0, 12) + '...' + npub.slice(-6) } -function purposeColor(purpose: string): string { +function avatarClasses(purpose: string): string { switch (purpose) { - case 'business': return 'cyber-avatar-blue' - case 'anonymous': return 'cyber-avatar-purple' - default: return 'cyber-avatar-orange' + case 'business': return 'bg-blue-500/15 text-blue-400 border-blue-500/25' + case 'anonymous': return 'bg-purple-500/15 text-purple-400 border-purple-500/25' + default: return 'bg-orange-500/15 text-orange-400 border-orange-500/25' } } - -function particleStyle(i: number): Record { - const left = ((i * 37 + 13) % 100) - const delay = ((i * 1.3) % 8).toFixed(1) - const duration = (6 + (i % 5) * 2).toFixed(1) - const size = 10 + (i % 3) * 2 - return { - left: `${left}%`, - animationDelay: `${delay}s`, - animationDuration: `${duration}s`, - fontSize: `${size}px`, - } -} - -function particleChar(i: number): string { - const chars = '01アイウエオカキクケコ暗号鍵身元' - return chars[i % chars.length] ?? '0' -}