initial commit
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
# heroui-agents-md
|
||||
.heroui-docs/
|
||||
CLAUDE.md
|
||||
.yarn
|
||||
45
app/.gitignore
vendored
Normal file
45
app/.gitignore
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
|
||||
|
||||
# Dependencies
|
||||
node_modules/
|
||||
.yarn/
|
||||
|
||||
# Expo
|
||||
.expo/
|
||||
dist/
|
||||
web-build/
|
||||
expo-env.d.ts
|
||||
|
||||
# Uniwind (generated on first `expo start`)
|
||||
uniwind-types.d.ts
|
||||
|
||||
# Native
|
||||
.kotlin/
|
||||
*.orig.*
|
||||
*.jks
|
||||
*.p8
|
||||
*.p12
|
||||
*.key
|
||||
*.mobileprovision
|
||||
|
||||
# Metro
|
||||
.metro-health-check*
|
||||
|
||||
# Debug
|
||||
npm-debug.*
|
||||
yarn-debug.*
|
||||
yarn-error.*
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# Local env files
|
||||
.env*.local
|
||||
|
||||
# TypeScript
|
||||
*.tsbuildinfo
|
||||
|
||||
# Generated native folders
|
||||
/ios
|
||||
/android
|
||||
19581
app/.pnp.cjs
generated
Executable file
19581
app/.pnp.cjs
generated
Executable file
File diff suppressed because one or more lines are too long
2129
app/.pnp.loader.mjs
generated
Normal file
2129
app/.pnp.loader.mjs
generated
Normal file
File diff suppressed because it is too large
Load Diff
13
app/.prettierignore
Normal file
13
app/.prettierignore
Normal file
@@ -0,0 +1,13 @@
|
||||
node_modules
|
||||
.expo
|
||||
dist
|
||||
ios
|
||||
android
|
||||
*.lock
|
||||
pnpm-lock.yaml
|
||||
yarn.lock
|
||||
package-lock.json
|
||||
bun.lockb
|
||||
expo-env.d.ts
|
||||
uniwind-types.d.ts
|
||||
.DS_Store
|
||||
9
app/.prettierrc.json
Normal file
9
app/.prettierrc.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 100,
|
||||
"tabWidth": 2,
|
||||
"arrowParens": "always",
|
||||
"endOfLine": "lf"
|
||||
}
|
||||
46
app/README.md
Normal file
46
app/README.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Welcome to your HeroUI Native app 👋
|
||||
|
||||
This is an [Expo](https://expo.dev) project preconfigured with
|
||||
[HeroUI Native](https://heroui.com/docs/native), [Uniwind](https://docs.uniwind.dev)
|
||||
(Tailwind CSS for React Native), and [Expo Router](https://docs.expo.dev/router/introduction)
|
||||
with a bottom-tab layout.
|
||||
|
||||
## Get started
|
||||
|
||||
1. Install dependencies
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
2. Start the app
|
||||
|
||||
```bash
|
||||
npx expo start
|
||||
```
|
||||
|
||||
In the output, you'll find options to open the app in a
|
||||
|
||||
- [development build](https://docs.expo.dev/develop/development-builds/introduction/)
|
||||
- [Android emulator](https://docs.expo.dev/workflow/android-studio-emulator/)
|
||||
- [iOS simulator](https://docs.expo.dev/workflow/ios-simulator/)
|
||||
- [Expo Go](https://expo.dev/go), a limited sandbox for trying out app development with Expo
|
||||
|
||||
You can start developing by editing the files inside the **src/app** directory. The tabs themselves live under `src/app/(tabs)/`. This project uses [file-based routing](https://docs.expo.dev/router/introduction).
|
||||
|
||||
## What's preconfigured
|
||||
|
||||
- **HeroUI Native** (`heroui-native`) wrapped in `HeroUINativeProvider` and `GestureHandlerRootView` in `src/app/_layout.tsx`
|
||||
- **Uniwind** + **Tailwind CSS** wired through `metro.config.js` and `src/global.css`
|
||||
- All HeroUI Native mandatory peer dependencies: `react-native-reanimated`, `react-native-gesture-handler`, `react-native-worklets`, `react-native-safe-area-context`, `react-native-svg`, `react-native-screens`
|
||||
- `@gorhom/bottom-sheet` for bottom-sheet UIs
|
||||
- `@expo/vector-icons` (Ionicons) for tab bar icons
|
||||
- TypeScript with `strict: true` and `@/*` path alias to `./src/*`
|
||||
- React Compiler enabled
|
||||
|
||||
## Learn more
|
||||
|
||||
- [HeroUI Native components](https://heroui.com/docs/native) — full component reference
|
||||
- [Expo documentation](https://docs.expo.dev/) — Expo fundamentals and guides
|
||||
- [Uniwind documentation](https://docs.uniwind.dev) — Tailwind for React Native
|
||||
- [Expo Router](https://docs.expo.dev/router/introduction) — file-based routing
|
||||
37
app/app.json
Normal file
37
app/app.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"expo": {
|
||||
"name": "volana",
|
||||
"slug": "volana",
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/images/icon.png",
|
||||
"scheme": "heroui-native-app",
|
||||
"userInterfaceStyle": "automatic",
|
||||
"ios": {
|
||||
"supportsTablet": true
|
||||
},
|
||||
"android": {
|
||||
"adaptiveIcon": {
|
||||
"foregroundImage": "./assets/images/adaptive-icon.png",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"predictiveBackGestureEnabled": false
|
||||
},
|
||||
"plugins": [
|
||||
"expo-router",
|
||||
"expo-font",
|
||||
[
|
||||
"expo-splash-screen",
|
||||
{
|
||||
"image": "./assets/images/splash-icon.png",
|
||||
"resizeMode": "contain",
|
||||
"backgroundColor": "#ffffff"
|
||||
}
|
||||
]
|
||||
],
|
||||
"experiments": {
|
||||
"typedRoutes": true,
|
||||
"reactCompiler": true
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
app/assets/images/adaptive-icon.png
Normal file
BIN
app/assets/images/adaptive-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
BIN
app/assets/images/favicon.png
Normal file
BIN
app/assets/images/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
BIN
app/assets/images/icon.png
Normal file
BIN
app/assets/images/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
BIN
app/assets/images/splash-icon.png
Normal file
BIN
app/assets/images/splash-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
8
app/eslint.config.js
Normal file
8
app/eslint.config.js
Normal file
@@ -0,0 +1,8 @@
|
||||
const expoConfig = require("eslint-config-expo/flat");
|
||||
|
||||
module.exports = [
|
||||
...expoConfig,
|
||||
{
|
||||
ignores: ["dist/*", ".expo/*", "node_modules/*", "expo-env.d.ts", "uniwind-types.d.ts"],
|
||||
},
|
||||
];
|
||||
8
app/metro.config.js
Normal file
8
app/metro.config.js
Normal file
@@ -0,0 +1,8 @@
|
||||
const { getDefaultConfig } = require("expo/metro-config");
|
||||
const { withUniwindConfig } = require("uniwind/metro");
|
||||
|
||||
const config = getDefaultConfig(__dirname);
|
||||
|
||||
module.exports = withUniwindConfig(config, {
|
||||
cssEntryFile: "./src/global.css",
|
||||
});
|
||||
49
app/package.json
Normal file
49
app/package.json
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "volana",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"main": "expo-router/entry",
|
||||
"scripts": {
|
||||
"start": "expo start",
|
||||
"android": "expo start --android",
|
||||
"ios": "expo start --ios",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"format": "prettier --write .",
|
||||
"format:check": "prettier --check .",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/metro-runtime": "~56.0.13",
|
||||
"@expo/vector-icons": "^15.0.3",
|
||||
"@gorhom/bottom-sheet": "^5.2.14",
|
||||
"expo": "~56.0.5",
|
||||
"expo-constants": "~56.0.16",
|
||||
"expo-font": "~56.0.5",
|
||||
"expo-linking": "~56.0.12",
|
||||
"expo-router": "~56.2.7",
|
||||
"expo-splash-screen": "~56.0.10",
|
||||
"expo-status-bar": "~56.0.4",
|
||||
"heroui-native": "^1.0.2",
|
||||
"react": "19.2.3",
|
||||
"react-dom": "19.2.3",
|
||||
"react-native": "0.85.3",
|
||||
"react-native-gesture-handler": "~2.31.2",
|
||||
"react-native-reanimated": "~4.3.1",
|
||||
"react-native-safe-area-context": "~5.7.0",
|
||||
"react-native-screens": "~4.25.2",
|
||||
"react-native-svg": "15.15.4",
|
||||
"react-native-worklets": "0.8.3",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"tailwind-variants": "^3.2.2",
|
||||
"tailwindcss": "^4.1.17",
|
||||
"uniwind": "^1.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "~19.2.15",
|
||||
"eslint": "^9.39.4",
|
||||
"eslint-config-expo": "^56.0.4",
|
||||
"prettier": "^3.8.3",
|
||||
"typescript": "~6.0.3"
|
||||
}
|
||||
}
|
||||
31
app/src/app/(tabs)/_layout.tsx
Normal file
31
app/src/app/(tabs)/_layout.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { Tabs } from "expo-router";
|
||||
import type { ComponentProps, JSX } from "react";
|
||||
import type { ColorValue } from "react-native";
|
||||
|
||||
type IoniconName = ComponentProps<typeof Ionicons>["name"];
|
||||
|
||||
function TabIcon({ name, color }: { name: IoniconName; color: ColorValue }): JSX.Element {
|
||||
return <Ionicons name={name} size={24} color={color} />;
|
||||
}
|
||||
|
||||
export default function TabsLayout(): JSX.Element {
|
||||
return (
|
||||
<Tabs screenOptions={{ headerShown: false }}>
|
||||
<Tabs.Screen
|
||||
name="index"
|
||||
options={{
|
||||
title: "Home",
|
||||
tabBarIcon: ({ color }) => <TabIcon name="home-outline" color={color} />,
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="explore"
|
||||
options={{
|
||||
title: "Explore",
|
||||
tabBarIcon: ({ color }) => <TabIcon name="compass-outline" color={color} />,
|
||||
}}
|
||||
/>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
6
app/src/app/(tabs)/explore.tsx
Normal file
6
app/src/app/(tabs)/explore.tsx
Normal file
@@ -0,0 +1,6 @@
|
||||
import type { JSX } from "react";
|
||||
import { View } from "react-native";
|
||||
|
||||
export default function ExploreTab(): JSX.Element {
|
||||
return <View className="flex-1 bg-background" />;
|
||||
}
|
||||
52
app/src/app/(tabs)/index.tsx
Normal file
52
app/src/app/(tabs)/index.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import { Button, Card, Typography, useThemeColor } from "heroui-native";
|
||||
import type { JSX } from "react";
|
||||
import { View } from "react-native";
|
||||
import Svg, { Path } from "react-native-svg";
|
||||
|
||||
function HeroUILogo({ tintColor }: { tintColor: string }): JSX.Element {
|
||||
return (
|
||||
<Svg width={90} height={30} viewBox="0 0 140 44" fill="none">
|
||||
<Path
|
||||
d="M0.677734 11.3847V24.0405C0.677734 24.6387 0.985209 25.1946 1.49107 25.5109L10.1195 30.9067C11.2693 31.6257 12.7586 30.796 12.7586 29.4363V18.7981C12.7586 18.186 13.0803 17.6194 13.605 17.3074L18.8683 14.1785V41.4437C18.8683 42.7988 20.3486 43.6293 21.4988 42.9195L30.4044 37.4229C30.9152 37.1076 31.2264 36.549 31.2264 35.9471V9.76484C31.2264 8.41634 29.759 7.58483 28.6085 8.28139L18.8683 14.1785V2.55643C18.8683 1.21158 17.408 0.379537 16.2574 1.06878L1.51927 9.89703C0.997365 10.2097 0.677734 10.7747 0.677734 11.3847Z"
|
||||
fill={tintColor}
|
||||
/>
|
||||
<Path
|
||||
d="M63.8763 24.0707C63.8763 20.4817 62.4078 18.8253 59.4709 18.8253C56.1076 18.8253 53.7391 21.0799 53.7391 26.1412V37.7363H47.6756V5.52769H53.7391V17.3069C55.2075 14.9142 57.6234 13.7179 60.9394 13.7179C66.5764 13.7179 69.8924 17.1688 69.8924 22.9664V37.7363H63.8763V24.0707Z"
|
||||
fill={tintColor}
|
||||
/>
|
||||
<Path
|
||||
d="M84.8996 38.4725C77.3677 38.4725 72.5832 33.5952 72.5832 26.0952C72.5832 18.6872 77.3203 13.7179 84.8996 13.7179C93.0947 13.7179 97.5475 19.5154 96.3158 27.6596H78.6467C78.9783 31.5247 81.252 33.7333 84.8996 33.7333C87.8839 33.7333 89.684 32.2149 90.1577 30.6964H96.1737C95.2263 35.2057 91.0577 38.4725 84.8996 38.4725ZM78.7888 23.6566H90.4419C90.3945 20.4817 88.3102 18.3191 84.7574 18.3191C81.5836 18.3191 79.3572 20.1596 78.7888 23.6566Z"
|
||||
fill={tintColor}
|
||||
/>
|
||||
<Path
|
||||
d="M99.6225 20.3437C99.6225 16.5246 101.754 14.4541 105.828 14.4541H113.597V19.4234H105.686V37.7363H99.6225V20.3437Z"
|
||||
fill={tintColor}
|
||||
/>
|
||||
<Path
|
||||
d="M126.863 38.4725C119.189 38.4725 114.31 33.5492 114.31 26.0952C114.31 18.6412 119.189 13.7179 126.863 13.7179C134.442 13.7179 139.322 18.6412 139.322 26.0952C139.322 33.5492 134.442 38.4725 126.863 38.4725ZM126.863 33.4572C130.653 33.4572 133.163 30.5584 133.163 26.0952C133.163 21.632 130.653 18.6872 126.863 18.6872C123.026 18.6872 120.515 21.632 120.515 26.0952C120.515 30.5584 123.026 33.4572 126.863 33.4572Z"
|
||||
fill={tintColor}
|
||||
/>
|
||||
</Svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default function HomeTab(): JSX.Element {
|
||||
const themeColorForeground = useThemeColor("foreground");
|
||||
|
||||
return (
|
||||
<View className="flex-1 bg-background justify-center px-6">
|
||||
<Card className="items-center gap-8">
|
||||
<HeroUILogo tintColor={themeColorForeground} />
|
||||
<Typography.Paragraph className="text-center">
|
||||
A modern starter for React Native, preconfigured with HeroUI Native, Uniwind, and Expo
|
||||
Router. Edit{" "}
|
||||
<Typography.Paragraph className="font-semibold">
|
||||
app/(tabs)/index.tsx
|
||||
</Typography.Paragraph>{" "}
|
||||
and watch it reload.
|
||||
</Typography.Paragraph>
|
||||
<Button className="w-full">Get started</Button>
|
||||
</Card>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
20
app/src/app/_layout.tsx
Normal file
20
app/src/app/_layout.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { JSX } from "react";
|
||||
import { Stack } from "expo-router";
|
||||
import { StatusBar } from "expo-status-bar";
|
||||
import { HeroUINativeProvider } from "heroui-native";
|
||||
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
||||
|
||||
import "../global.css";
|
||||
|
||||
export default function RootLayout(): JSX.Element {
|
||||
return (
|
||||
<GestureHandlerRootView style={{ flex: 1 }}>
|
||||
<HeroUINativeProvider>
|
||||
<Stack screenOptions={{ headerShown: false }}>
|
||||
<Stack.Screen name="(tabs)" />
|
||||
</Stack>
|
||||
<StatusBar style="auto" />
|
||||
</HeroUINativeProvider>
|
||||
</GestureHandlerRootView>
|
||||
);
|
||||
}
|
||||
8
app/src/global.css
Normal file
8
app/src/global.css
Normal file
@@ -0,0 +1,8 @@
|
||||
@import "tailwindcss";
|
||||
@import "uniwind";
|
||||
|
||||
@import "heroui-native/styles";
|
||||
|
||||
/* End-user path: heroui-native lives in the project's node_modules, one
|
||||
level up from this src/global.css file. */
|
||||
@source '../node_modules/heroui-native/lib';
|
||||
11
app/tsconfig.json
Normal file
11
app/tsconfig.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "expo/tsconfig.base",
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"paths": {
|
||||
"@/*": ["./src/*"],
|
||||
"@/assets/*": ["./assets/*"]
|
||||
}
|
||||
},
|
||||
"include": ["**/*.ts", "**/*.tsx", ".expo/types/**/*.ts", "expo-env.d.ts", "uniwind-env.d.ts"]
|
||||
}
|
||||
1
app/uniwind-env.d.ts
vendored
Normal file
1
app/uniwind-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="uniwind/types" />
|
||||
8976
app/yarn.lock
Normal file
8976
app/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
558
docs/volana-design-brief.md
Normal file
558
docs/volana-design-brief.md
Normal file
@@ -0,0 +1,558 @@
|
||||
# Volana — Consumer Frontend Design Brief
|
||||
|
||||
> This document is the sole source of truth for the Claude Design session. It contains everything needed to design the Volana consumer marketplace frontend: brand context, product vision, UX goals, data models, page inventory, interaction patterns, and content guidance. Visual design decisions (color, typography, spacing, motion) are left to the designer.
|
||||
|
||||
---
|
||||
|
||||
## 1. What is Volana?
|
||||
|
||||
**Volana is a decentralized, censorship-resistant marketplace** — think eBay, but on Solana. The primary inspiration is eBay's consumer UX (browse listings, view a product, buy it), with two fundamental differences:
|
||||
|
||||
1. **Nobody can censor it.** No government, no company, no intermediary can remove a listing or block a transaction. The marketplace is governed entirely by open-source smart contracts on Solana. Anyone can list anything, and no third party can intervene.
|
||||
|
||||
2. **Payments are final and predictable.** Unlike PayPal (which can reverse a payment weeks later) or traditional card payments (chargebacks), payments on Volana go through on-chain escrow contracts. Once the buyer releases the funds, they are final. The entire payment flow is encoded in open smart contracts — anyone can read exactly what will happen before they sign anything. There are no hidden fees, no unexpected reversals, and no intermediary who can freeze your funds.
|
||||
|
||||
**Target audience for this design:** Regular online shoppers who are crypto-curious or already have a Solana wallet (Phantom, Solflare, Backpack, etc.). They are used to eBay or Amazon. They do not need to understand blockchain to use Volana — but they should feel like they are on something more trustworthy and modern.
|
||||
|
||||
**Scope of this design sprint:** Consumer-facing frontend only. This means:
|
||||
- Homepage / listing browse (like eBay's search results page)
|
||||
- Listing detail page (like eBay's product page)
|
||||
- The "place order" / checkout flow (connect wallet → confirm purchase)
|
||||
- Wallet connect / account state in the nav
|
||||
- My Orders / Order status
|
||||
|
||||
**Out of scope for now:** seller dashboard, dispute resolution UI, resolver management, technical on-chain explorer views.
|
||||
|
||||
---
|
||||
|
||||
## 2. Why Volana Exists — The Value Propositions (Use in Copy & Tone)
|
||||
|
||||
These are the core benefits. The design should make them feel real, not like marketing:
|
||||
|
||||
### 2a. True Freedom of Commerce
|
||||
No government, no corporation, no payment processor can remove a listing or block a purchase. The marketplace runs on open-source contracts on a public blockchain. Even if the Volana website went offline, someone could spin up a new frontend that reads the same on-chain data and resume trading.
|
||||
|
||||
### 2b. Final, Predictable Payments
|
||||
- **PayPal/credit card problem:** A buyer can initiate a chargeback 6 months after a sale. Sellers live in constant fear of payment reversals.
|
||||
- **Volana's solution:** Payment flows through a smart contract escrow. The buyer locks funds in the escrow when placing an order. The seller confirms the order. The buyer releases funds when they receive the goods. Once released, the transfer is final — no third party can reverse it.
|
||||
- **Predictability:** The exact payment flow (including dispute resolution) is encoded in open contracts. Anyone can audit them before transacting.
|
||||
|
||||
### 2c. Ultra-Low Fees
|
||||
Solana transactions cost fractions of a cent. Volana charges no percentage fee — the only costs are Solana network fees (< $0.001 per transaction) and, if a dispute arises, the resolver's fee (typically 0.5–2% of the transaction, only charged if the dispute is actually resolved). Compare this to eBay (10–13%) or PayPal (3–4%).
|
||||
|
||||
### 2d. Permissionless Dispute Resolution
|
||||
If something goes wrong, a neutral third-party resolver (chosen by the buyer at checkout, from a list the seller accepts) adjudicates the dispute. The resolver's ruling is enforced on-chain — neither buyer nor seller can override it. The resolver earns a small fee only when they rule on a case.
|
||||
|
||||
---
|
||||
|
||||
## 3. Brand Identity
|
||||
|
||||
### Name
|
||||
**Volana** — the consumer-facing brand. The underlying protocol is "Solisting/Descro" but regular users never need to know that. The brand is "Volana."
|
||||
|
||||
### Personality
|
||||
Modern, clean, trustworthy, slightly edgy (freedom angle), fast. Not "crypto bro" — approachable. Not corporate — honest. Not cheap — premium but accessible.
|
||||
|
||||
### Tagline options (for reference)
|
||||
- *"Trade freely. Pay finally."*
|
||||
- *"The marketplace nobody controls."*
|
||||
- *"Buy anything. Pay once. For real."*
|
||||
|
||||
### Visual Mood
|
||||
The design should feel like a **dark-mode-first modern marketplace** with a premium, slightly futuristic feel. Reference points: a modern SaaS product, not a financial terminal. Solana's brand identity (purple/green) can be an influence but doesn't have to dominate. The primary CTA ("Buy Now") should feel like the most important thing on screen wherever it appears.
|
||||
|
||||
---
|
||||
|
||||
## 4. Technical Context — What Data Exists On-Chain
|
||||
|
||||
The frontend reads two Solana programs:
|
||||
|
||||
### 4a. `solisting` program — Listings and Orders
|
||||
|
||||
**ListingAccount** (one per product listing):
|
||||
- `seller: Pubkey` — the seller's wallet address (32-byte Solana public key)
|
||||
- `name: String` — product name (max 64 chars)
|
||||
- `description: String` — product description (max 256 chars)
|
||||
- `metadata_uri: String` — URL to a JSON file with additional metadata (image, more details) (max 256 chars)
|
||||
- `price: u64` — price in the canonical currency's smallest unit
|
||||
- `canonical_currency` — either `Sol` (native SOL, 9 decimals) or `Spl { mint, decimals }` (an SPL token)
|
||||
- `alt_currencies: Vec<AltCurrencyConfig>` — other currencies the seller accepts (max 3). Price is auto-converted via Pyth oracle at order time.
|
||||
- `quantity: u32` — total units available
|
||||
- `quantity_reserved: u32` — units currently held by pending orders. Available = quantity − quantity_reserved.
|
||||
- `is_active: bool` — whether the listing accepts new orders
|
||||
- `accepted_resolvers: Vec<Pubkey>` — resolvers this seller trusts. Empty = any registered resolver accepted.
|
||||
- `listing_id: u64` — unique identifier per seller
|
||||
|
||||
**OrderAccount** (one per purchase):
|
||||
- `listing: Pubkey` — which listing this order is for
|
||||
- `buyer: Pubkey` — the buyer's wallet
|
||||
- `seller: Pubkey` — the seller's wallet
|
||||
- `resolver: Option<Pubkey>` — the dispute resolver chosen at checkout
|
||||
- `payment_currency` — the currency the buyer chose to pay in
|
||||
- `amount: u64` — the actual amount paid (oracle-computed if not canonical currency)
|
||||
- `escrow_account: Pubkey` — the linked escrow on the `descro` program
|
||||
- `created_at: i64` — Unix timestamp
|
||||
|
||||
### 4b. `descro` program — Escrow State Machine
|
||||
|
||||
Each order has a corresponding **EscrowAccount** with a state machine:
|
||||
|
||||
```
|
||||
AwaitingSellerConfirm → Active → Complete
|
||||
↘ ↘ Cancelled
|
||||
↘ Disputed → Resolved (Complete or Cancelled)
|
||||
```
|
||||
|
||||
- **AwaitingSellerConfirm:** Buyer has locked funds. Waiting for the seller to confirm the order. Buyer can cancel for a full refund in this phase.
|
||||
- **Active:** Seller confirmed. Funds locked. Buyer should receive the goods. Buyer can confirm receipt (→ Complete) or raise a dispute.
|
||||
- **Complete:** Buyer confirmed receipt. Seller receives funds. Final.
|
||||
- **Cancelled:** Either the buyer cancelled before the seller confirmed, the seller rejected, or a dispute was resolved in the buyer's favor. Buyer gets refunded.
|
||||
- **Disputed:** A dispute was raised. A resolver will adjudicate.
|
||||
|
||||
**For the consumer frontend, the relevant states are:**
|
||||
- When browsing: show available quantity (quantity − quantity_reserved)
|
||||
- At checkout: the buyer places an order which goes immediately to `AwaitingSellerConfirm`
|
||||
- In "My Orders": show order state + available actions
|
||||
|
||||
### 4c. Resolvers (dispute arbitrators)
|
||||
|
||||
**ResolverEntry** — registered dispute resolvers:
|
||||
- `name: String` — resolver's display name
|
||||
- `description: String` — what they handle / their policy
|
||||
- `fee_bps: u16` — fee in basis points (100 bps = 1%). Charged only if they rule on a dispute.
|
||||
- `resolver_type` — e.g., Automated / Human / DAO
|
||||
- `acceptance_policy` — AutoAccept (takes all cases) or ManualAccept (can decline)
|
||||
- `ruled_for_buyer: u32` — historical rulings for the buyer
|
||||
- `ruled_for_seller: u32` — historical rulings for the seller
|
||||
|
||||
At checkout, the buyer selects a resolver from the list the seller accepts.
|
||||
|
||||
### 4d. Currency / Price Display Rules
|
||||
- **SOL:** price in lamports (divide by 1,000,000,000 to get SOL). Display as "X SOL".
|
||||
- **SPL tokens:** price in smallest unit (divide by 10^decimals). Display with token symbol.
|
||||
- **Stablecoins** (USDC, USDT, etc.): oracle = None, $1.00 peg assumed.
|
||||
- **Alt currency prices** are computed at order time by the on-chain oracle. The buyer passes an `expected_amount` and `max_slippage_bps` to protect against price movement.
|
||||
|
||||
---
|
||||
|
||||
## 5. Page Inventory & UX Specification
|
||||
|
||||
### Priority: Consumer Frontend Only
|
||||
The design session should focus exclusively on the **buyer experience**:
|
||||
|
||||
1. **Homepage / Browse Listings** (highest priority)
|
||||
2. **Listing Detail / Product Page** (highest priority)
|
||||
3. **Checkout / Place Order flow** (highest priority)
|
||||
4. **Wallet Connect** (integrated into nav)
|
||||
5. **My Orders / Order Status** (lower priority, can be a simple list)
|
||||
|
||||
The following are **out of scope** for this design sprint:
|
||||
- Seller dashboard (create/manage listings)
|
||||
- Dispute resolution UI
|
||||
- Resolver management
|
||||
- Technical on-chain explorer views
|
||||
- Admin/protocol views
|
||||
|
||||
---
|
||||
|
||||
### Page 1: Homepage / Browse Listings
|
||||
|
||||
**URL:** `/` or `/browse`
|
||||
|
||||
**Purpose:** The product catalog. Like eBay's search results or Amazon's browse page. The primary conversion surface.
|
||||
|
||||
**UX Goals:**
|
||||
- Buyer should immediately see products and understand what Volana is
|
||||
- Scanning products should feel fast — like a physical store, not a terminal
|
||||
- Price, image, availability should be instantly readable
|
||||
- Category/search/filter should be accessible without needing to know how blockchain works
|
||||
|
||||
**Layout concept:**
|
||||
- Hero / value prop section at the top (first visit only, or collapsible): headline + 2-3 key benefit chips (e.g. "Final payments", "No censorship", "< $0.001 fees")
|
||||
- Below hero: search + filter bar
|
||||
- Product grid (primary): card-based layout like eBay. Each card has:
|
||||
- Product image (from metadata_uri → JSON → image field; fallback: placeholder)
|
||||
- Product name (from `listing.name`)
|
||||
- Price prominently displayed (formatted: "1.5 SOL" or "12.00 USDC")
|
||||
- Availability indicator: "In Stock" / "Low Stock" (when ≤ 3 available) / "Out of Stock"
|
||||
- Seller abbreviation (first 4 + last 4 chars of wallet address)
|
||||
- "Buy" button (opens checkout flow)
|
||||
|
||||
**Filters / Search:**
|
||||
- Search bar: searches listing names and descriptions
|
||||
- Filter by currency: All / SOL / USDC / Other
|
||||
- Filter by price range
|
||||
- Filter by availability: In Stock only (default on)
|
||||
- Sort: Newest / Price: Low to High / Price: High to Low
|
||||
|
||||
**Card states:**
|
||||
- Active + in stock: fully interactive
|
||||
- Active + out of stock: visible but "Out of Stock" label, buy button disabled
|
||||
- Inactive: hidden from browse view by default
|
||||
|
||||
**Technical data per card:**
|
||||
- `listing.name` (display title)
|
||||
- `listing.description` (shown on hover / card subtitle)
|
||||
- `listing.price` + `listing.canonical_currency` (formatted price)
|
||||
- `listing.quantity - listing.quantity_reserved` (available count)
|
||||
- `listing.metadata_uri` (fetch the JSON at this URL to get the product image)
|
||||
- `listing.seller` (abbreviated wallet address)
|
||||
- `listing.is_active` (show/hide logic)
|
||||
|
||||
---
|
||||
|
||||
### Page 2: Listing Detail / Product Page
|
||||
|
||||
**URL:** `/listing/[address]`
|
||||
|
||||
**Purpose:** The product detail page. Like eBay's item page. Where the buyer decides to purchase.
|
||||
|
||||
**UX Goals:**
|
||||
- The buyer should feel informed and confident
|
||||
- Price and "Buy Now" should be obvious within 2 seconds of loading
|
||||
- Trust signals should be visible (on-chain verification, resolver info)
|
||||
- Blockchain complexity should be completely hidden — it just feels like a trustworthy product page
|
||||
|
||||
**Layout concept (desktop: 2-column):**
|
||||
|
||||
**Left column (~60%):**
|
||||
- Large product image (from metadata JSON, or placeholder)
|
||||
- Image gallery thumbnails if multiple images exist in metadata
|
||||
- Product name (primary heading)
|
||||
- Description (full text, with "show more" if long)
|
||||
- Seller info row: abbreviated address + copy button + "Verified on Solana" label
|
||||
- Technical details expandable section (for advanced users): listing ID, on-chain address
|
||||
|
||||
**Right column (~40%, sticky while scrolling):**
|
||||
- Price (large, prominent) — show canonical price + note if other currencies accepted
|
||||
- Availability: "X in stock" with a visual indicator when low
|
||||
- Currency selector: if the listing accepts multiple currencies, let the buyer pick which to pay with
|
||||
- Resolver selector: shows accepted resolvers with their fee %. Pre-select a recommended one. Fee is labeled as "only charged if dispute is resolved."
|
||||
- **"Buy Now" button** — the primary CTA, most prominent element on the page
|
||||
- Below CTA: 3 trust signals:
|
||||
- "Final Payment" — payment cannot be reversed
|
||||
- "Dispute Protection" — a resolver mediates if something goes wrong
|
||||
- "On-chain Verified" — all terms are in open smart contracts
|
||||
- Note: "Funds held in escrow until you confirm receipt"
|
||||
|
||||
**Seller section** (below the grid):
|
||||
- Abbreviated seller address + copy
|
||||
- Number of active listings by this seller (optional, if easily fetchable)
|
||||
|
||||
---
|
||||
|
||||
### Page 3: Checkout / Place Order
|
||||
|
||||
**Design as a modal or side panel** — keep the user on the product page, don't navigate away.
|
||||
|
||||
**Trigger:** Clicking "Buy Now" on the listing detail page.
|
||||
|
||||
**Pre-condition — wallet not connected:**
|
||||
- "Buy Now" opens a wallet connect modal first
|
||||
- After connecting, resume the checkout flow automatically
|
||||
|
||||
**Step 1: Order Review**
|
||||
|
||||
The user sees a summary before committing:
|
||||
|
||||
```
|
||||
Buying: [Product Name]
|
||||
Seller: [abbreviated address]
|
||||
|
||||
Payment
|
||||
Currency: [SOL ▾] ← dropdown if multiple currencies accepted
|
||||
Amount: 1.5 SOL ≈ $180 USD ← show USD estimate alongside
|
||||
|
||||
[ℹ] This amount may vary slightly due to real-time price conversion.
|
||||
Slippage tolerance: 0.5% ← shown, but pre-set to sensible default
|
||||
|
||||
Dispute Protection
|
||||
[Resolver Name — 0.5% fee if dispute resolved] [▾ Change]
|
||||
← First/recommended resolver pre-selected
|
||||
"Resolver fee is only charged if a dispute is actually adjudicated."
|
||||
|
||||
[Cancel] [Confirm & Place Order →]
|
||||
```
|
||||
|
||||
**Step 2: Transaction pending**
|
||||
- The wallet extension popup appears (handled by browser)
|
||||
- The modal shows a loading/pending state: "Confirming on Solana…"
|
||||
|
||||
**Step 3: Confirmation (after transaction confirmed)**
|
||||
|
||||
```
|
||||
✓ Order placed successfully!
|
||||
|
||||
Your payment of 1.5 SOL is now held in secure escrow.
|
||||
The seller has been notified and will confirm your order.
|
||||
|
||||
What happens next:
|
||||
1. Seller confirms → your order becomes active
|
||||
2. You receive the goods
|
||||
3. You confirm receipt → seller gets paid
|
||||
4. If something goes wrong, [Resolver Name] will mediate
|
||||
|
||||
[View My Orders] [Continue Shopping]
|
||||
```
|
||||
|
||||
**Error states:**
|
||||
- Wallet rejected: "Purchase cancelled."
|
||||
- Out of stock (race condition): "Sorry, this item just sold out."
|
||||
- Slippage exceeded: "The price changed too much before your transaction went through. Please try again."
|
||||
- Network error: "Connection issue. Please try again."
|
||||
|
||||
**What happens under the hood (invisible to the user):**
|
||||
- Frontend calls `solisting::create_order` on-chain
|
||||
- This atomically: validates the listing, computes the oracle-adjusted price if paying in an alt currency, increments `quantity_reserved`, and creates a `descro` escrow (moves funds from buyer's wallet into the escrow vault)
|
||||
- After the transaction confirms, an `OrderAccount` exists on-chain linking the listing to the escrow
|
||||
|
||||
---
|
||||
|
||||
### Page 4: Wallet Connect (in Nav)
|
||||
|
||||
**Disconnected state:**
|
||||
- Prominent "Connect Wallet" button on the right of the nav
|
||||
|
||||
**On click:** Opens wallet selection modal listing installed wallets (Phantom, Solflare, Backpack, etc.) via `@solana/connector`.
|
||||
|
||||
**Connected state:**
|
||||
- Shows an abbreviated wallet address (first 4 + last 4 chars) with a connected indicator dot and a dropdown arrow
|
||||
|
||||
**On click — dropdown:**
|
||||
- Wallet name + icon
|
||||
- Full address (copyable)
|
||||
- [Copy Address] button
|
||||
- [Disconnect] button
|
||||
|
||||
---
|
||||
|
||||
### Page 5: My Orders (Order Status)
|
||||
|
||||
**URL:** `/orders` — visible in nav only when wallet is connected
|
||||
|
||||
**Content:** A list of the connected wallet's OrderAccounts:
|
||||
- Product name (linked to listing detail)
|
||||
- Amount paid
|
||||
- Current status (Awaiting Confirm / Active / Complete / Cancelled / Disputed)
|
||||
- Date placed
|
||||
- Link to full order detail
|
||||
|
||||
**Order Detail page** (`/order/[address]`):
|
||||
- Order ID and date
|
||||
- Linked listing (clickable)
|
||||
- Amount in escrow
|
||||
- Current escrow state visualized as a linear progress:
|
||||
```
|
||||
[1. Awaiting Confirm] → [2. Active] → [3. Done]
|
||||
```
|
||||
Current step highlighted; terminal outcome (Complete / Cancelled) shown separately
|
||||
- Status-appropriate actions:
|
||||
- `AwaitingSellerConfirm` state → [Cancel Order] (full refund)
|
||||
- `Active` state → [Confirm Receipt] (releases payment to seller)
|
||||
- Terminal states → informational only, no actions
|
||||
|
||||
---
|
||||
|
||||
## 6. Navigation Structure
|
||||
|
||||
```
|
||||
[Volana Logo] [Search bar] [Browse] [My Orders] [Network ▾] [Connect Wallet]
|
||||
```
|
||||
|
||||
- **Logo** → homepage/browse
|
||||
- **Browse** → `/browse`
|
||||
- **My Orders** → `/orders` (only shown when wallet connected)
|
||||
- **Network picker** → dropdown to switch between Mainnet-beta, Devnet, Localnet (indicated by a colored dot per network)
|
||||
- **Wallet button** → connect/disconnect
|
||||
|
||||
**Mobile nav:** collapsed behind a hamburger; wallet connect accessible from the top of the menu.
|
||||
|
||||
---
|
||||
|
||||
## 7. UI Component Inventory
|
||||
|
||||
These are the components that need to exist. Visual design is up to the designer.
|
||||
|
||||
### Product Card (Browse Grid)
|
||||
Required information hierarchy:
|
||||
1. Product image (top, full width of card)
|
||||
2. Product name (bold, prominent)
|
||||
3. Seller abbreviation (small, secondary)
|
||||
4. Price (prominent, clear)
|
||||
5. Availability label (In Stock / Low Stock / Out of Stock)
|
||||
6. Buy button (at bottom or on hover)
|
||||
|
||||
### Status Labels
|
||||
The following statuses need distinct visual treatment (not just color — also shape/icon/text so they're accessible):
|
||||
- **In Stock** — positive/active
|
||||
- **Low Stock** — urgency/warning (≤ 3 available)
|
||||
- **Out of Stock** — disabled/unavailable
|
||||
- **Awaiting Confirm** — pending/waiting
|
||||
- **Active** (order) — in progress
|
||||
- **Complete** — success/terminal
|
||||
- **Cancelled** — neutral/terminal
|
||||
- **Disputed** — alert/problem
|
||||
|
||||
### Trust Badge Strip
|
||||
Three small badges used on the listing detail page and in the checkout flow:
|
||||
- "Final Payment"
|
||||
- "Dispute Protection"
|
||||
- "On-chain Verified"
|
||||
|
||||
### Checkout Modal / Sheet
|
||||
- Overlay/modal on desktop
|
||||
- Full-screen bottom sheet on mobile
|
||||
- Contains: order summary, currency picker, resolver picker, CTA button, pending/success states
|
||||
|
||||
### Toast / Notification
|
||||
- Transient notification for feedback: "Address copied", "Order placed!", errors
|
||||
- Should appear without interrupting the user flow
|
||||
|
||||
### Loading / Skeleton States
|
||||
- Product grid: skeleton cards while listings load from the blockchain (can take 1–3 seconds)
|
||||
- Listing detail: skeleton for image and fields
|
||||
- Button: loading spinner while transaction is pending on-chain
|
||||
|
||||
---
|
||||
|
||||
## 8. Key UX Principles
|
||||
|
||||
### 8a. Hide the Blockchain
|
||||
- Never show raw 44-character public keys as primary information — abbreviate (first 4 + "…" + last 4 chars), and offer a copy button or "view on explorer" link
|
||||
- Price is shown in human-readable format: "1.5 SOL", not "1500000000 lamports"
|
||||
- Escrow states should be explained in plain English (see copy guidance), not contract terminology
|
||||
- The transaction flow should feel like clicking a "Confirm" button — the wallet popup is the only visible blockchain interaction
|
||||
|
||||
### 8b. Trust Through Transparency (for those who want it)
|
||||
- Provide optional "see on-chain" links for technically-minded users — don't hide it, just don't foreground it
|
||||
- The "How it works" section (in footer or dedicated page) explains the escrow flow in plain language with a simple step diagram
|
||||
- Resolver selection should feel like choosing a "dispute protection plan" — not a cryptographic concept
|
||||
|
||||
### 8c. Conversion Optimization
|
||||
- The primary "Buy Now" CTA must be above the fold on the listing detail page (sticky in the right column as user scrolls)
|
||||
- Urgency messaging: "Only 2 left!" when available ≤ 3
|
||||
- No account creation — connecting a wallet is the account
|
||||
- Checkout is 1 step (review + confirm) — not a multi-page funnel
|
||||
- Post-purchase: immediately show order status with clear "what happens next" steps — reduce post-purchase anxiety
|
||||
|
||||
### 8d. Responsive / Mobile
|
||||
- Browse grid: 1 column on small mobile, 2 on large mobile/tablet, 3–4 on desktop
|
||||
- Listing detail: single column on mobile (image → price/buy panel → description)
|
||||
- Checkout: full-screen bottom sheet on mobile (not a floating modal)
|
||||
|
||||
### 8e. Performance Perception
|
||||
- Product images load lazily with a placeholder shown immediately (blur-up or skeleton)
|
||||
- Show previously cached listing data while re-fetching — never show a blank screen if stale data exists
|
||||
- The page should feel fast even when waiting on blockchain RPC responses
|
||||
|
||||
---
|
||||
|
||||
## 9. Content & Copy Guidance
|
||||
|
||||
### Terminology to Use vs. Avoid
|
||||
|
||||
| ❌ Avoid (crypto jargon) | ✅ Use instead |
|
||||
|-----------------------------------|---------------------------------------|
|
||||
| "Sign a transaction" | "Confirm your purchase" |
|
||||
| "Escrow account" | "Secure payment hold" |
|
||||
| "PDA / program derived address" | (hide entirely from user) |
|
||||
| "Lamports" | "SOL" (always convert and display) |
|
||||
| "CPI" / "on-chain instruction" | (hide entirely from user) |
|
||||
| "Resolver" (without context) | "Dispute mediator" or "Protection" |
|
||||
| "Canonical currency" | "Primary currency" |
|
||||
| "Pubkey" | "Wallet address" |
|
||||
| "Quantity reserved" | "Pending orders" |
|
||||
| "is_active: false" | "Listing closed" / "Not available" |
|
||||
|
||||
### Error Messages (User-Facing)
|
||||
- Wallet declined: "Purchase cancelled."
|
||||
- Network error: "Connection issue. Please try again."
|
||||
- Out of stock (race): "Someone else just bought the last one. Sorry!"
|
||||
- Slippage exceeded: "The price changed too much before your transaction went through. Please try again."
|
||||
- Wallet not connected: "Connect your wallet to buy."
|
||||
|
||||
### How It Works (for footer / explainer page)
|
||||
|
||||
Plain-language 4-step flow for curious users:
|
||||
1. **Browse & pick** — find something you want to buy
|
||||
2. **Place order** — connect your wallet and confirm. Your payment is locked in a secure escrow (a smart contract nobody can tamper with).
|
||||
3. **Receive & confirm** — when you receive your goods, confirm receipt. The payment is released to the seller automatically.
|
||||
4. **Protected** — if something goes wrong, your chosen dispute mediator steps in. Their ruling is final and enforced on-chain.
|
||||
|
||||
---
|
||||
|
||||
## 10. Technical Stack Reference
|
||||
|
||||
For context on what data is available and how the frontend fetches it:
|
||||
|
||||
- **Frontend:** Next.js 16, React 19, App Router
|
||||
- **Wallet:** `@solana/connector` — standard Solana wallet adapter; provides `useWallet()`, `useCluster()`, transaction signing
|
||||
- **Data fetching:** React Query — polls Solana RPC for on-chain account data
|
||||
- **SDK:** `@solisting/sdk` — typed fetchers for `ListingAccount`, `OrderAccount`, etc.
|
||||
- **Networks:** Mainnet-beta, Devnet, Localnet (switchable in the nav)
|
||||
- **Payments currently supported:** SOL (native). SPL tokens (USDC etc.) are planned — the contract infrastructure supports them but the frontend doesn't wire them up yet.
|
||||
- **Product images:** stored off-chain. The seller provides a `metadata_uri` (URL to a JSON file) which contains the image URL. The JSON format follows the standard Solana NFT metadata schema: `{ name, description, image, ... }`.
|
||||
|
||||
---
|
||||
|
||||
## 11. Flows to Design (Priority Order)
|
||||
|
||||
1. **Browse → Product Detail → Buy Now (connected wallet)** — the primary happy path
|
||||
2. **Browse → Product Detail → Buy Now (wallet not connected)** → connect wallet → complete checkout
|
||||
3. **Post-purchase confirmation** — order summary + "what happens next"
|
||||
4. **My Orders list** — status overview of buyer's orders
|
||||
5. **Order Detail** — current state + available actions (confirm receipt / cancel)
|
||||
|
||||
---
|
||||
|
||||
## 12. What Makes Volana Different from Traditional Marketplaces
|
||||
|
||||
Use in hero copy, onboarding tooltips, and "How it works":
|
||||
|
||||
| Feature | eBay / Amazon / PayPal | Volana |
|
||||
|-------------------------|------------------------------------------|------------------------------------------------|
|
||||
| Censorship resistance | Any listing can be removed | No one can remove a listing |
|
||||
| Payment reversal | Chargebacks possible for months | Payments are final once the buyer confirms |
|
||||
| Fees | 10–13% platform + 3–4% payment processor | < $0.001 network fee only |
|
||||
| Dispute resolution | Black-box, provider-controlled | On-chain, auditable, mediator of your choice |
|
||||
| Account required | Email + card + KYC | Solana wallet only |
|
||||
| Transparency | Terms can change at any time | Rules are open-source smart contracts |
|
||||
|
||||
---
|
||||
|
||||
## 13. Accessibility Requirements
|
||||
|
||||
- All interactive elements must have visible focus states
|
||||
- Status labels must never rely on color alone — pair color with a text label or icon
|
||||
- Product cards must be fully keyboard-navigable
|
||||
- Wallet connection modal must be screenreader-compatible (ARIA roles, focus trap)
|
||||
- Minimum contrast ratio for body text: 4.5:1 (WCAG AA)
|
||||
|
||||
---
|
||||
|
||||
## 14. Screen States to Cover
|
||||
|
||||
### Browse Page
|
||||
- **Loading:** placeholder/skeleton cards while blockchain data is fetched
|
||||
- **Loaded:** product grid with filters active
|
||||
- **Empty:** no listings on the selected network — suggest switching to Mainnet
|
||||
- **No results:** search/filter combination returns nothing
|
||||
|
||||
### Listing Detail
|
||||
- **Loading:** skeleton for image, title, price panel
|
||||
- **Loaded:** full detail with all fields
|
||||
- **Out of Stock:** detail still visible, "Buy Now" replaced with "Out of Stock" (disabled)
|
||||
- **Wallet not connected:** "Buy Now" says "Connect Wallet to Buy" — click opens wallet modal
|
||||
|
||||
### Checkout Modal
|
||||
- **Review state:** order summary, currency/resolver selectors, confirm button
|
||||
- **Pending state:** wallet popup has appeared, modal shows loading state
|
||||
- **Success state:** confirmation with "what happens next"
|
||||
- **Error state:** failed transaction with clear message and retry option
|
||||
|
||||
### My Orders
|
||||
- **Empty:** no orders yet — prompt to browse
|
||||
- **List with orders:** orders grouped by status or sorted by date
|
||||
- **Order detail — awaiting confirm:** Cancel button available
|
||||
- **Order detail — active:** Confirm Receipt button available
|
||||
- **Order detail — terminal:** read-only, shows outcome
|
||||
Reference in New Issue
Block a user