mirror of
https://github.com/hanzo-templates/expo-shop.git
synced 2026-10-30 20:53:01 +00:00
The catalogue shipped 71 web templates and one EMPTY `mobile` repo, so "build an app" had no starting point on the platform people actually ship to. This is the iOS lane — Expo/React Native and Flutter for cross-platform, SwiftUI for native. The constraint that shaped every file here: an iOS archive needs macOS + Xcode + a signing identity, and our arc fleet has none. hanzoai has 519 registered runners and every one reports `os: linux`; luxfi has 151, same story; zooai has none. The only macOS builds in the org today (hanzoai/world build-desktop.yml) run on GitHub-HOSTED macos-14, which is exactly what the house CI rule says not to do. So `hanzo.yml` declares only what a Linux arc runner can genuinely execute — typecheck + web bundle for Expo, analyze + test + web for Flutter, `swiftc -parse` for SwiftUI — and the macOS archive lives in a separate `workflow_dispatch`-only `ios.yml`. A job pinned to a label no runner advertises would sit queued forever if it fired on push; parking it is honest, a permanently-yellow check is not. The README of every template states the gap and the one command that closes it. Same reasoning on the demos. Expo and Flutter both have a real web target, so `<slug>.hanzo.app` serves the SAME source the simulator runs. SwiftUI has no web target, so those two templates ship with no demo link rather than a screenshot pretending to be one. Upstreams are permissive only and recorded per template: expo/expo templates (MIT), flutter/samples (BSD-3-Clause), XcodeGen (MIT). Second measured gap, found while wiring this up: `hanzo-templates` is not in arcd's `orgs:` list either (hanzoai, luxfi, zooai, parsdao, zenlm), so the Linux jobs would queue with no runner too. Both gaps are one installer re-run, no code change; both are spelled out in every README rather than left to be discovered on the first red build. Third gap, found by probing rather than assuming, and FIXED here: every run in this org died as `startup_failure` with no logs and no annotation. A bare `runs-on: [self-hosted, linux]` probe queued fine while the same job importing hanzoai/ci failed identically at @v1 and at a pinned SHA — so it was never the tag. The org defaulted GITHUB_TOKEN to `read`, and the reusable declares `permissions: packages: write`; a caller cannot exceed the org maximum, so GitHub kills the run before creating a job. hanzo-templates now defaults to `write` like hanzoai/luxfi/zooai, and runs reach `queued` — the honest state, waiting on gap 2.
75 lines
3.1 KiB
TypeScript
75 lines
3.1 KiB
TypeScript
import { FlatList, Pressable, StyleSheet, Text, View } from 'react-native';
|
||
import { add, byId, money, total, useCart } from '../store';
|
||
import { t } from '../theme';
|
||
|
||
export default function Cart() {
|
||
const cart = useCart();
|
||
const lines = Object.entries(cart);
|
||
|
||
if (!lines.length) {
|
||
return <View style={s.empty}><Text style={s.dim}>Your cart is empty.</Text></View>;
|
||
}
|
||
|
||
return (
|
||
<View style={s.wrap}>
|
||
<FlatList
|
||
data={lines}
|
||
keyExtractor={([id]) => id}
|
||
contentContainerStyle={{ padding: t.pad, gap: 12 }}
|
||
renderItem={({ item: [id, qty] }) => {
|
||
const p = byId(id)!;
|
||
return (
|
||
<View style={s.row}>
|
||
<View style={[s.swatch, { backgroundColor: `hsl(${p.hue} 45% 22%)` }]}>
|
||
<Text style={[s.glyph, { color: `hsl(${p.hue} 70% 68%)` }]}>{p.name[0]}</Text>
|
||
</View>
|
||
<View style={{ flex: 1 }}>
|
||
<Text style={s.name}>{p.name}</Text>
|
||
<Text style={s.dim}>{money(p.price)} each</Text>
|
||
</View>
|
||
<View style={s.stepper}>
|
||
<Step label="−" onPress={() => add(id, -1)} />
|
||
<Text style={s.qty}>{qty}</Text>
|
||
<Step label="+" onPress={() => add(id, 1)} />
|
||
</View>
|
||
</View>
|
||
);
|
||
}}
|
||
/>
|
||
<View style={s.footer}>
|
||
<View style={s.totalRow}>
|
||
<Text style={s.name}>Total</Text>
|
||
<Text style={s.total}>{money(total(cart))}</Text>
|
||
</View>
|
||
{/* Wire this to your payment provider — /v1/commerce/checkout in Hanzo. */}
|
||
<Pressable style={s.btn}><Text style={s.btnText}>Checkout</Text></Pressable>
|
||
</View>
|
||
</View>
|
||
);
|
||
}
|
||
|
||
const Step = ({ label, onPress }: { label: string; onPress: () => void }) => (
|
||
<Pressable onPress={onPress} style={s.step} hitSlop={8}>
|
||
<Text style={s.stepText}>{label}</Text>
|
||
</Pressable>
|
||
);
|
||
|
||
const s = StyleSheet.create({
|
||
wrap: { flex: 1, backgroundColor: t.bg },
|
||
empty: { flex: 1, backgroundColor: t.bg, alignItems: 'center', justifyContent: 'center' },
|
||
row: { flexDirection: 'row', alignItems: 'center', gap: 12 },
|
||
swatch: { width: 56, height: 56, borderRadius: 10, alignItems: 'center', justifyContent: 'center' },
|
||
glyph: { fontSize: 24, fontWeight: '800' },
|
||
name: { color: t.text, fontSize: 16, fontWeight: '600' },
|
||
dim: { color: t.dim, fontSize: 14 },
|
||
stepper: { flexDirection: 'row', alignItems: 'center', gap: 12 },
|
||
step: { width: 30, height: 30, borderRadius: 15, backgroundColor: t.card, borderWidth: 1, borderColor: t.line, alignItems: 'center', justifyContent: 'center' },
|
||
stepText: { color: t.text, fontSize: 17, lineHeight: 19 },
|
||
qty: { color: t.text, fontSize: 16, minWidth: 18, textAlign: 'center' },
|
||
footer: { borderTopWidth: 1, borderTopColor: t.line, padding: t.pad, gap: 12 },
|
||
totalRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' },
|
||
total: { color: t.text, fontSize: 22, fontWeight: '700' },
|
||
btn: { backgroundColor: t.accent, borderRadius: t.radius, paddingVertical: 15, alignItems: 'center' },
|
||
btnText: { color: '#fff', fontSize: 16, fontWeight: '700' },
|
||
});
|