Files
zeekayandHanzo Dev 35f27c9bd4 feat(dash): investor-grade Vite SPA — true-black @hanzo/gui tokens, Geist, mobile-first
- Full redesign on @hanzo/gui design tokens (true-black, Geist, single accent, hairline
  borders, calm motion) via Tailwind v4 @theme + self-hosted Geist (no CDN)
- Flows: signup → IAM (lux.id) → sandbox KYC onboarding → dashboard → accounts → cards
  (virtual card visual, freeze/unfreeze, issue w/ one-time CVV) → send + beneficiaries +
  receive → exchange (unified FX + crypto buy/sell/convert) → crypto wallet (testnet addr,
  holdings) → activity
- Mobile-first shell: desktop sidebar + mobile bottom-tab bar; designed empty/loading states
- Reusable UI kit (Button/Card/Money/StatusBadge/Modal/AssetAvatar/Skeleton/EmptyState/Icon)
- API client + OverviewProvider mapped to the new bankd contract
- dashd: tiny stdlib static+proxy server (SPA fallback + /v1 → bankd) — no nginx/caddy;
  same-origin so IAM PKCE + bank API share the app origin
- Dockerfiles: app/dash multi-stage (Vite build + dashd); bankd GOSUMDB=off for re-tagged luxfi deps
- add GET /v1/bank/transactions (full activity feed with decimals)

tsc --strict + --noUnusedLocals clean; pnpm build green; landing/login verified via headless render.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-17 17:12:53 -07:00

72 lines
1.9 KiB
Go

// Command dashd serves the Lux Financial SPA and proxies /v1/* to bankd.
//
// No nginx/caddy: a ~60-line stdlib static server with SPA fallback plus a
// reverse proxy so the app is same-origin for both the bank API and the IAM
// PKCE flow (bankd mounts /v1/iam as a transparent proxy to lux.id).
package main
import (
"log"
"net/http"
"net/http/httputil"
"net/url"
"os"
"path/filepath"
"strings"
)
func env(k, def string) string {
if v := os.Getenv(k); v != "" {
return v
}
return def
}
func main() {
dist := env("DIST_DIR", "/app/dist")
addr := ":" + env("PORT", "3000")
upstream := env("BANK_UPSTREAM", "http://bankd.lux-bank.svc.cluster.local")
target, err := url.Parse(upstream)
if err != nil {
log.Fatalf("invalid BANK_UPSTREAM %q: %v", upstream, err)
}
proxy := httputil.NewSingleHostReverseProxy(target)
proxy.FlushInterval = -1 // stream responses (SSE-safe)
index := filepath.Join(dist, "index.html")
fileServer := http.FileServer(http.Dir(dist))
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// API + IAM: forward to bankd, preserving the Host it expects.
if strings.HasPrefix(r.URL.Path, "/v1/") {
r.Host = target.Host
proxy.ServeHTTP(w, r)
return
}
// Static assets: serve if the file exists, else SPA fallback.
clean := filepath.Clean(r.URL.Path)
if clean != "/" {
if fp := filepath.Join(dist, clean); fileExists(fp) {
if strings.HasPrefix(clean, "/assets/") {
w.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
}
fileServer.ServeHTTP(w, r)
return
}
}
w.Header().Set("Cache-Control", "no-cache")
http.ServeFile(w, r, index)
})
log.Printf("dashd: serving %s on %s, proxying /v1 -> %s", dist, addr, upstream)
srv := &http.Server{Addr: addr, Handler: mux}
log.Fatal(srv.ListenAndServe())
}
func fileExists(p string) bool {
info, err := os.Stat(p)
return err == nil && !info.IsDir()
}