From 494ebdb104d627b883cdebfe47f5d61138658ebc Mon Sep 17 00:00:00 2001 From: thesn10 <38666407+thesn10@users.noreply.github.com> Date: Thu, 2 Jul 2026 23:01:00 +0200 Subject: [PATCH] Add listing detail screen --- app/src/app/listing/[id].tsx | 207 +++++++++++++++++++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 app/src/app/listing/[id].tsx diff --git a/app/src/app/listing/[id].tsx b/app/src/app/listing/[id].tsx new file mode 100644 index 0000000..38515f7 --- /dev/null +++ b/app/src/app/listing/[id].tsx @@ -0,0 +1,207 @@ +import type { JSX } from "react"; +import type { ViewStyle } from "react-native"; +import { useMemo, useState } from "react"; +import { Platform, ScrollView, View } from "react-native"; +import { useLocalSearchParams } from "expo-router"; +import { Button, Chip, Separator, Typography, useToast } from "heroui-native"; +import type { Currency } from "@/data/mock"; +import { PRODUCTS, RESOLVERS } from "@/data/mock"; +import { availabilityInfo } from "@/lib/status"; +import { AppHeader } from "@/components/AppHeader"; +import { StatusChip } from "@/components/StatusChip"; +import { ResolverCard } from "@/components/ResolverCard"; +import { ListingCard } from "@/components/ListingCard"; +import { useModals } from "@/state/modals"; + +// react-native-web supports CSS `position: sticky`, but React Native's own +// ViewStyle type doesn't include it — cast is required to use it web-only. +const stickyBoxStyle: ViewStyle | undefined = + Platform.OS === "web" ? ({ position: "sticky", top: 84 } as ViewStyle) : undefined; + +export default function ListingDetailScreen(): JSX.Element { + const { id } = useLocalSearchParams<{ id: string }>(); + const { toast } = useToast(); + const { requestBuy } = useModals(); + const [resolverId, setResolverId] = useState(0); + + const listing = useMemo( + () => PRODUCTS.find((product) => product.id === Number(id)) ?? PRODUCTS[0], + [id], + ); + const [currency, setCurrency] = useState(listing.cur); + const availability = availabilityInfo(listing.qty); + const isOut = listing.qty === 0; + const relatedProducts = PRODUCTS.filter((p) => p.id !== listing.id && p.qty > 0).slice(0, 6); + + return ( + + + + + + {listing.emoji} + + + + {listing.name} + + {listing.desc} + + + + + + + Verified on Solana + + + {listing.seller} + + + + + + + + About this listing + + + + + Category + + + {listing.cat} + + + + + Available + + + {listing.qty} units + + + + + Currency + + + {listing.cur} + + + + + Listing ID + + + {`#${listing.id}${listing.id}${listing.id}${listing.id}`} + + + + + + {relatedProducts.length > 0 && ( + + Customers also bought + + {relatedProducts.map((product) => ( + + ))} + + + )} + + + + + {listing.price} + + ≈ {listing.usd} USD + + + + + + + + + {listing.alt.length > 0 && ( + <> + + + Pay with + + + setCurrency(listing.cur)} + > + {listing.cur} + + setCurrency(listing.alt[0])} + > + {listing.alt[0]} + + + + + + )} + + + + Dispute Protection + + {RESOLVERS.map((resolver) => ( + setResolverId(resolver.id)} + /> + ))} + + Fee only charged if dispute is adjudicated. + + + + + + + + + + ✓ Final Payment + + + 🛡 Dispute Cover + + + ⬡ On-chain + + + + + Funds held in secure escrow until you confirm receipt. + + + + + ); +}