Files
volana/docs/superpowers/specs/2026-07-02-expo-app-ui-design.md
thesn10 0186a2bf1c Add design spec for translating the HTML prototype into the Expo app
Captures navigation structure, theming, mock data, state, and
responsive (mobile + web) layout decisions agreed on during
brainstorming before implementation planning starts.
2026-07-02 21:48:23 +02:00

11 KiB
Raw Blame History

Volana Expo App — UI-Umsetzung des Prototyps

Kontext

prototype/Volana.dc.html ist ein interaktiver HTML-Prototyp (DC-Format mit Template-Bindings à la {{ x }}, sc-if, sc-for) der Volana-Konsumenten-Oberfläche: ein dezentraler, zensurresistenter Marktplatz auf Solana (siehe docs/volana-design-brief.md für den vollständigen Produkt-Kontext). Der Prototyp enthält Homepage, Browse (Liste/Grid), Listing-Detail, My Orders, Order-Detail, Wallet-Connect-Modal, Checkout-Modal (Review/Pending/Success) und Toasts, inklusive vollständiger Mock-Daten (12 Produkte, 3 Dispute-Resolver, 3 Orders) und Farb-/Theming-Variablen für Light/Dark.

Die Expo-App (app/) ist aktuell ein leeres Grundgerüst: Expo Router mit (tabs)-Gruppe (Home/Explore), HeroUI Native, Uniwind (Tailwind v4 für React Native), React 19 / RN 0.86 / Expo SDK 57.

Ziel dieser Iteration: Das Design des Prototyps 1:1 in Seiten/Komponenten der Expo-App übertragen — mobil UND auf Web (react-native-web, in den Dependencies vorhanden) nahe am Prototyp. Keine echte Funktionalität (kein echtes Wallet, kein Backend) — Mock-Daten reichen. Styling bleibt so nah wie möglich an den HeroUI-Native-Defaults; nur Akzentfarbe, Statusfarben und Radius werden angepasst (Theming), komplexere Komponenten/Seiten bekommen zusätzlich Custom-Styles via className (Uniwind/Tailwind) bzw. StyleSheet wo nötig.

Nicht-Ziele

  • Keine echte Solana-Wallet-Integration (@solana/connector o.ä.) — Connect/Disconnect ist gemockt wie im Prototyp.
  • Kein Backend/On-Chain-Datenfetching — statische Mock-Daten aus dem Prototyp übernommen.
  • Keine Persistenz von Bestellungen (gekaufte Listings erscheinen nicht dynamisch in "My Orders" — der Prototyp macht das ebenfalls nicht).
  • Kein manueller Light/Dark-Toggle — die App folgt dem System-Theme (Uniwind unterstützt das nativ).
  • Kein Footer (im Prototyp vorhanden, in der App nicht nötig).
  • Keine Pixel-genaue 1:1-Übernahme der Inline-Styles des Prototyps — HeroUI-Native-Komponenten und ihre Standard-Abstände/Formen bleiben so weit wie möglich erhalten.

Theming

Anpassung ausschließlich über CSS-Variablen-Overrides in src/global.css (@layer theme { @variant light {...} @variant dark {...} }), wie in der HeroUI-Native-Theming-Doku beschrieben. Kein Erstellen neuer Custom-Colors — bestehende semantische Slots werden mit den Prototyp-Werten belegt:

Variable Dark (Prototyp --c-*) Light (Prototyp --c-*) Verwendung
--accent / --accent-foreground #b060ff / weiß #7a34d4 / weiß Primär-CTA, Preis-Hervorhebung, aktive Filter
--success #14f195 #059669 "In Stock", "Verified", "Complete", "Final Payment"
--warning #f5a623 #d97706 "Low Stock", "Awaiting Confirm"
--danger #ff4d4f #dc2626 "Out of Stock", "Disputed", "Cancel Order"
--background / --surface / --surface-secondary leicht lila-stichige Dunkeltöne (#0e0e1c/#171730/#1e1e3c) helle lila-stichige Töne (#f7f6fb/#ffffff/#f0eef8) Basis-/Karten-Hintergründe
--radius Basiswert so gewählt, dass abgeleitete --radius-lg/--radius-xl in etwa den 814px-Radii des Prototyps entsprechen gleich Cards, Buttons, Inputs

Die im Prototyp verwendete "Solana-Mainnet"-Grün-Punkt/"Verified"-Optik nutzt direkt success, keine eigene Farbe nötig.

Datenmodell & Mock-Daten

Neue Datei src/data/mock.ts mit TS-Typen und den Werten 1:1 aus dem Prototyp (_L, _R, _O Arrays in Volana.dc.html):

type Currency = 'SOL' | 'USDC';

interface Product {
  id: number; name: string; desc: string;
  price: string; priceNum: number; cur: Currency; usd: string;
  qty: number; seller: string; bg: string; emoji: string;
  alt: Currency[]; cat: string;
}

interface Resolver {
  id: number; name: string; desc: string;
  fee: string; type: 'Human' | 'DAO' | 'Automated';
  wins: number; total: number;
}

type OrderStatus = 'AwaitingConfirm' | 'Active' | 'Complete' | 'Cancelled' | 'Disputed';

interface Order {
  id: number; lid: number; name: string; amt: string;
  status: OrderStatus; date: string; seller: string; emoji: string;
}

Emojis dienen weiterhin als Platzhalter-"Produktbild" (wie im Prototyp) statt echter Bilder — kein Asset-Aufwand nötig. Helper-Funktionen analog zu _si (Status → Label/Farbe) und _av (Verfügbarkeit → Label/Farbe) werden als reine Funktionen in src/data/mock.ts oder src/lib/status.ts nachgebaut.

State

  • WalletProvider (src/state/wallet.tsx, React Context): connected: boolean, address: string | null, connect(wallet: 'phantom' | 'solflare' | 'backpack') (setzt eine fixe Mock-Adresse je Wallet, identisch zum Prototyp), disconnect().
  • ModalProvider (src/state/modals.tsx, React Context): zentraler Zustand für die beiden globalen Overlays:
    • Wallet-Connect-Sheet: open, pendingListingId (falls "Buy Now" ohne verbundenes Wallet ausgelöst wurde → nach Connect automatisch Checkout öffnen, wie im Prototyp)
    • Checkout-Sheet: open, listingId, step: 'review' | 'pending' | 'success', inkl. simuliertem setTimeout-Übergang pending → success wie im Prototyp (2.2s)
  • Bestellungen bleiben statische Mock-Daten (siehe Nicht-Ziele) — kein zusätzlicher Order-State nötig.
  • Toasts über HeroUI Natives eingebauten useToast/ToastProvider (Teil von HeroUINativeProvider), verwendet für: Wallet verbunden/getrennt, Adresse kopiert, Order storniert/bestätigt.

Beide Provider werden in app/_layout.tsx um den bestehenden HeroUINativeProvider ergänzt.

Navigation & Screens

app/_layout.tsx                 Root Stack + WalletProvider + ModalProvider; rendert zusätzlich
                                 <TopNav /> (nur ab `md:`, s.u.) + globale Sheets (WalletConnectSheet, CheckoutSheet)
app/(tabs)/_layout.tsx          Bottom Tabs: Home | Browse | Orders (Tab-Bar ab `md:` ausgeblendet)
app/(tabs)/index.tsx            Home
app/(tabs)/browse.tsx           Browse (ersetzt bisheriges explore.tsx)
app/(tabs)/orders.tsx           My Orders (Liste)
app/listing/[id].tsx            Listing-Detail — Stack-Push über den Tabs, eigener Header mit Zurück-Button
app/orders/[id].tsx             Order-Detail — Stack-Push über den Tabs, eigener Header mit Zurück-Button

Wallet-Connect- und Checkout-Overlay sind bewusst keine Routen, sondern globale, über ModalProvider gesteuerte BottomSheet-Komponenten im Root-Layout — "Buy Now" ist von Home, Browse und Listing-Detail auslösbar und muss überall denselben zentralen State treffen (entspricht dem zentralen Component-State im Prototyp).

Responsive Shell (Mobile vs. Web/Desktop)

Umschaltung über Uniwind-Responsive-Utilities (Breakpoint md:, ~768px) — funktioniert sowohl nativ (z. B. Tablet-Breite) als auch im Web-Build:

  • TopNav (src/components/TopNav.tsx): Logo, Suchfeld, "Browse"-/"My Orders"-Links, Wallet-Button/-Pill — analog zur Prototyp-Nav. Klasse hidden md:flex, liegt im Root-Layout über allen Screens (auch Listing-/Order-Detail), damit Web-Nutzer durchgehend navigieren können.
  • Bottom-Tab-Bar: in (tabs)/_layout.tsx per useWindowDimensions (React-Navigation-tabBarStyle ist keine Tailwind-Klasse, daher JS-Bedingung statt CSS) ab md:-Breite auf display: 'none' gesetzt.
  • Ergebnis: Mobile = Bottom-Tabs + Stack-Pushes für Details. Web/Desktop = Top-Nav wie Prototyp, keine Tab-Bar, Navigation über router.push.

Grid-Layouts: React Native kennt kein CSS-Grid (auch nicht im Web-Build, da react-native-web View-Primitives nutzt) — alle "Grid"-Bereiche (Browse-Grid, Featured Listings, Customers-also-bought) werden über flex-row flex-wrap + Breiten-Utilities (w-1/2 md:w-1/3 lg:w-1/4 o. ä.) gebaut, nicht über display: grid.

Listing-Detail: flex-col md:flex-row (mobil gestapelt, ab md: zweispaltig wie im Prototyp). Die rechte Kauf-Box nutzt auf Web position: 'sticky' (von react-native-web unterstützt); nativ scrollt sie regulär mit.

Screens im Detail

Home (app/(tabs)/index.tsx)

Kurzer Hero (Headline + Subline + "Browse Listings"-Button, kein Stats-/"Warum Volana"-/"How it works"-Block), darunter Featured-Listings (4 Karten, ListingCard Grid-Variante).

Browse (app/(tabs)/browse.tsx)

SearchField (Name/Beschreibung), Filter-Reihe (Chip-Toggles für Currency All/SOL/USDC und Sort Newest/Price↑/Price↓), Switch "In stock only", List/Grid-View-Toggle (Icon-Buttons). Darunter Produktliste aus den gefilterten/sortierten Mock-Daten:

  • List-View: horizontale Card (Bild-Platzhalter · Preis · Name+Beschreibung · Buy-Button), analog Prototyp-Zeilen-Layout.
  • Grid-View: 2-spaltige (mobil) / mehr-spaltige (md:/lg:) vertikale Cards.

Listing-Detail (app/listing/[id].tsx)

Bild-Platzhalter (Emoji auf Farbfläche), Titel, Beschreibung, "Verified on Solana"-Zeile mit Copy-Button (Toast "Address copied!"), Info-Grid (Kategorie/Verfügbar/Währung/ID), "Customers also bought" (horizontale/Flex-Wrap-Liste verwandter Produkte). Kauf-Sektion (sticky ab md:): Preis, Currency-Umschalter (falls alt.length > 0), Resolver-Auswahl (3 selektierbare Cards aus Mock-Resolvern), "Buy Now"-Button (öffnet Wallet-Sheet falls nicht verbunden, sonst Checkout-Sheet), Trust-Badges (Final Payment / Dispute Protection / On-chain Verified).

Orders (app/(tabs)/orders.tsx)

Liste von OrderRow (Emoji, Name, Seller-Kürzel, Betrag, Datum, Status-Chip), Tap → app/orders/[id].tsx.

Order-Detail (app/orders/[id].tsx)

Header (Datum, Name, Seller, Betrag, Status), 3-Schritt-Fortschrittsanzeige (Payment Held → Seller Confirms → You Confirm Receipt) mit Status-abhängiger Einfärbung wie im Prototyp (_si-Logik), Escrow-Info-Box, Aktions-Buttons je Status: AwaitingConfirm → Cancel Order (mock, zeigt Toast + navigiert zurück), Active → Confirm Receipt (mock, zeigt Toast), immer sichtbar → "View Listing".

Gemeinsame Komponenten (src/components/)

  • TopNav — s. o.
  • AppHeader — einfacher mobiler Screen-Header für Stack-Screens (Titel + Zurück-Button), auf md: ggf. ausgeblendet da TopNav übernimmt
  • ListingCard — Varianten list und grid
  • StatusChip — mappt OrderStatus/Verfügbarkeit auf Label + Farbe (nutzt Chip von HeroUI)
  • ResolverCard — selektierbare Resolver-Option (Listing-Detail + Checkout-Review)
  • OrderRow — Zeile in der Orders-Liste
  • WalletConnectSheet — globales BottomSheet mit den drei Mock-Wallets (Phantom/Solflare/Backpack)
  • CheckoutSheet — globales BottomSheet, rendert intern je nach step Review-/Pending-(Spinner)-/Success-Inhalt

Verifikation

Da keine echte Funktionalität besteht, erfolgt Verifikation visuell: App per expo start (Web + iOS/Android-Simulator soweit verfügbar) starten, jede Seite und beide Breakpoints (schmal/mobil, breit/md:+) durchklicken, Kernflows prüfen (Home → Browse → Listing → Buy Now ohne Wallet → Connect → Checkout Review → Pending → Success → My Orders; Order-Detail Cancel/Confirm-Aktionen; Such-/Filter-/Sortier-/List-Grid-Umschaltung in Browse).