"Payload drop" scan experience — hand over a link or code on scan
Experience #8: a published card can carry a payload that's shown prominently on scan — a primary link CTA, or a discount/access code with tap-to-copy. - Backend: demo publish accepts an optional payload {kind, label, value}, stored in definition.profile.payload (flows through the public profile). Test extended to assert it. Gate green (120 tests). - Web ([handle].astro): renders a "link" payload as a big CTA button and a "code" payload as a dashed chip with copy-on-tap. astro check clean. - Mobile (publish.tsx): payload kind chips (None/Link/Code) + label + value, sent with publishToWeb. Verified live: publish with a code payload -> profile shows a copyable CARD20 chip; link payload -> CTA button. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
895ca4993a
commit
a61d0221d1
@@ -28,6 +28,9 @@ export default function PublishScreen() {
|
||||
|
||||
const [welcomePrompt, setWelcomePrompt] = useState("");
|
||||
const [welcomeMessage, setWelcomeMessage] = useState("Great to meet you");
|
||||
const [payloadKind, setPayloadKind] = useState<"none" | "link" | "code">("none");
|
||||
const [payloadLabel, setPayloadLabel] = useState("");
|
||||
const [payloadValue, setPayloadValue] = useState("");
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [result, setResult] = useState<string | null>(card?.publishedUrl ?? null);
|
||||
|
||||
@@ -49,6 +52,10 @@ export default function PublishScreen() {
|
||||
links: card.links.filter((l) => l.url.trim()).map((l) => ({ label: l.label, url: l.url })),
|
||||
welcomePrompt: welcomePrompt.trim() || undefined,
|
||||
welcomeMessage: welcomePrompt.trim() ? welcomeMessage : undefined,
|
||||
payload:
|
||||
payloadKind !== "none" && payloadValue.trim()
|
||||
? { kind: payloadKind, label: payloadLabel.trim(), value: payloadValue.trim() }
|
||||
: undefined,
|
||||
});
|
||||
// Point the card's QR at the real profile + remember it.
|
||||
upsert({ ...card, url: profileUrl, publishedUrl: profileUrl, updatedAt: Date.now() });
|
||||
@@ -94,6 +101,42 @@ export default function PublishScreen() {
|
||||
</>
|
||||
)}
|
||||
|
||||
<Text style={styles.label}>Payload drop (optional)</Text>
|
||||
<View style={styles.kindRow}>
|
||||
{(["none", "link", "code"] as const).map((k) => (
|
||||
<Pressable
|
||||
key={k}
|
||||
onPress={() => setPayloadKind(k)}
|
||||
style={[styles.kindChip, payloadKind === k && styles.kindOn]}
|
||||
>
|
||||
<Text style={[styles.kindText, payloadKind === k && styles.kindTextOn]}>
|
||||
{k === "none" ? "None" : k === "link" ? "Link" : "Code"}
|
||||
</Text>
|
||||
</Pressable>
|
||||
))}
|
||||
</View>
|
||||
{payloadKind !== "none" && (
|
||||
<>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
editable={!busy}
|
||||
placeholder={payloadKind === "code" ? "Label (e.g. 20% off)" : "Button label (e.g. Get the deck)"}
|
||||
placeholderTextColor="#6b6b70"
|
||||
value={payloadLabel}
|
||||
onChangeText={setPayloadLabel}
|
||||
/>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
editable={!busy}
|
||||
autoCapitalize={payloadKind === "code" ? "characters" : "none"}
|
||||
placeholder={payloadKind === "code" ? "CODE123" : "https://…"}
|
||||
placeholderTextColor="#6b6b70"
|
||||
value={payloadValue}
|
||||
onChangeText={setPayloadValue}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Pressable style={[styles.cta, busy && styles.ctaBusy]} disabled={busy} onPress={publish}>
|
||||
{busy ? (
|
||||
<ActivityIndicator color="#fff" />
|
||||
@@ -149,6 +192,17 @@ const styles = StyleSheet.create({
|
||||
},
|
||||
ctaBusy: { opacity: 0.8 },
|
||||
ctaText: { color: "#fff", fontWeight: "700", fontSize: 17 },
|
||||
kindRow: { flexDirection: "row", gap: 8 },
|
||||
kindChip: {
|
||||
flex: 1,
|
||||
paddingVertical: 10,
|
||||
borderRadius: 10,
|
||||
backgroundColor: "#222228",
|
||||
alignItems: "center",
|
||||
},
|
||||
kindOn: { backgroundColor: "#ff3b30" },
|
||||
kindText: { color: "#9a9aa0", fontWeight: "600" },
|
||||
kindTextOn: { color: "#fff" },
|
||||
qrWrap: {
|
||||
backgroundColor: "#ffffff",
|
||||
borderRadius: 16,
|
||||
|
||||
Reference in New Issue
Block a user