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.
This commit is contained in:
thesn10
2026-07-02 21:48:23 +02:00
parent 16b2ce2623
commit 0186a2bf1c

View File

@@ -0,0 +1,135 @@
# 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`):
```ts
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).