/** * useSasquatchActions — publish the actions Sasquatch drafts (Pro). * ───────────────────────────────────────────────────────────────────────────── * Sasquatch never publishes on its own. The AI proposes a structured DRAFT; the * user reviews it in a confirmation card; only on confirm do we sign & publish * here. All actions reuse the app's existing event schemas. * * Supported drafts: * • post_status — NIP-38 skating status (kind 30315), optional expiry * • list_gear — NIP-99 marketplace listing (kind 30402), skateboarding-tagged */ import { useCallback } from 'react'; import { useCurrentUser } from '@/hooks/useCurrentUser'; import { useNostrPublish } from '@/hooks/useNostrPublish'; import { MARKETPLACE_ITEM_KIND } from '@/lib/types'; import { USER_STATUS_KIND } from '@/hooks/useUserStatus'; // ─── Draft shapes ───────────────────────────────────────────────────────────── export interface PostStatusDraft { type: 'post_status'; /** Freeform status text, e.g. "🛹 Skating Southbank". */ text: string; /** Minutes until it auto-expires (default 120). */ expiresInMinutes?: number; } export interface ListGearDraft { type: 'list_gear'; title: string; description?: string; /** Price in the given currency (default sats). Omit for "make offer". */ price?: number; currency?: string; /** deck | trucks | wheels | bearings | shoes | clothing | other */ category?: string; condition?: string; size?: string; } export type SasquatchDraft = PostStatusDraft | ListGearDraft; // ─── Human-readable summary for the confirmation card ──────────────────────── export function summarizeDraft(d: SasquatchDraft): { title: string; lines: string[]; cta: string } { if (d.type === 'post_status') { const mins = d.expiresInMinutes ?? 120; return { title: 'Post a skating status', lines: [`"${d.text}"`, `Auto-expires in ${mins} min`], cta: 'Post status', }; } const lines: string[] = []; if (d.category) lines.push(`Category: ${d.category}`); if (d.condition) lines.push(`Condition: ${d.condition}`); if (d.size) lines.push(`Size: ${d.size}`); lines.push(d.price ? `Price: ${d.price.toLocaleString()} ${d.currency ?? 'sats'}` : 'Price: make offer'); if (d.description) lines.push(d.description); return { title: `List "${d.title}" for sale`, lines, cta: 'Publish listing' }; } // ─── The hook ───────────────────────────────────────────────────────────────── export function useSasquatchActions() { const { user } = useCurrentUser(); const { mutateAsync: publishEvent } = useNostrPublish(); const publishDraft = useCallback( async (draft: SasquatchDraft): Promise<{ ok: true; label: string }> => { if (!user) throw new Error('You must be logged in.'); if (draft.type === 'post_status') { const tags: string[][] = [['d', 'general']]; const mins = draft.expiresInMinutes ?? 120; tags.push(['expiration', String(Math.floor(Date.now() / 1000) + mins * 60)]); await publishEvent({ kind: USER_STATUS_KIND, content: draft.text.slice(0, 140), tags }); return { ok: true, label: 'Status posted 🛹' }; } // list_gear const now = Math.floor(Date.now() / 1000); const identifier = `gear-${now}-${Math.random().toString(36).slice(2, 8)}`; const currency = draft.currency ?? 'sats'; const tags: string[][] = [ ['d', identifier], ['title', draft.title.slice(0, 120)], ['t', 'skateboarding'], ['listing_type', 'sell'], ['status', 'active'], ['published_at', String(now)], ['alt', `Gear listing: ${draft.title}`], ]; if (draft.category) tags.push(['t', draft.category]); if (typeof draft.price === 'number' && draft.price > 0) { tags.push(['price_type', 'fixed']); tags.push(['price', String(draft.price), currency]); } else { tags.push(['price_type', 'offer']); } if (draft.condition) tags.push(['condition', draft.condition]); if (draft.size) tags.push(['size', draft.size]); await publishEvent({ kind: MARKETPLACE_ITEM_KIND, content: (draft.description ?? '').slice(0, 2000), tags, }); return { ok: true, label: 'Listing published 💸' }; }, [user, publishEvent], ); return { publishDraft }; }