Phase 1 CLINK integration (recognize & display only): - types.ts: optional noffer field + isValidNoffer guard on SkateCoach, SkateFilmer, SkateEditor, SkatePhotographer, SkateShop; parsers read it - NofferField: reusable opt-in input with live validation, added to all 4 Become-* Lab forms and both Create/Edit shop forms - ClinkPayButton: secondary "Pay via CLINK" action (QR + copy + nostr: deep link), rendered on CoachDetailPage and ShopDetailPage only when a valid noffer is present; never replaces the existing zap / Lightning Address flow - useQRCodeScanner: classify scanned noffer1/ndebit1 CLINK codes - NIP.md: document the noffer tag on Lab kinds + shops and a CLINK section Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
62 lines
2.3 KiB
TypeScript
62 lines
2.3 KiB
TypeScript
import { Zap, ShieldCheck, AlertCircle } from 'lucide-react';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Label } from '@/components/ui/label';
|
|
import { isValidNoffer } from '@/lib/types';
|
|
|
|
interface NofferFieldProps {
|
|
value: string;
|
|
onChange: (value: string) => void;
|
|
}
|
|
|
|
/**
|
|
* Optional CLINK Offer (noffer1...) input for provider / shop forms.
|
|
* Self-custodial, Nostr-native static payment code — see https://clinkme.dev.
|
|
* Purely additive: leaving it blank changes nothing.
|
|
*/
|
|
export function NofferField({ value, onChange }: NofferFieldProps) {
|
|
const trimmed = value.trim();
|
|
const showError = trimmed.length > 0 && !isValidNoffer(trimmed);
|
|
const showValid = isValidNoffer(trimmed);
|
|
|
|
return (
|
|
<div className="space-y-2 rounded-lg border border-indigo-500/30 bg-indigo-500/5 p-4">
|
|
<Label htmlFor="noffer" className="flex items-center gap-2 text-indigo-400">
|
|
<Zap className="h-4 w-4" />
|
|
CLINK Offer Code <span className="text-xs font-normal text-gray-500">(optional, advanced)</span>
|
|
</Label>
|
|
<Input
|
|
id="noffer"
|
|
placeholder="noffer1..."
|
|
value={value}
|
|
onChange={(e) => onChange(e.target.value)}
|
|
spellCheck={false}
|
|
autoCapitalize="none"
|
|
className="bg-gray-800 border-gray-700 font-mono text-sm"
|
|
/>
|
|
{showError ? (
|
|
<p className="flex items-center gap-1.5 text-xs text-red-400">
|
|
<AlertCircle className="h-3.5 w-3.5 shrink-0" />
|
|
That doesn't look like a valid <code className="mx-1">noffer1…</code> code.
|
|
</p>
|
|
) : showValid ? (
|
|
<p className="flex items-center gap-1.5 text-xs text-green-400">
|
|
<ShieldCheck className="h-3.5 w-3.5 shrink-0" />
|
|
Valid CLINK code — a "Pay via CLINK" button will show on your profile.
|
|
</p>
|
|
) : (
|
|
<p className="text-xs text-gray-500">
|
|
Paste a self-custodial CLINK payment code (e.g. from ShockWallet / Lightning.Pub) to accept
|
|
Nostr-native Lightning payments — no web server or custodian.{' '}
|
|
<a
|
|
href="https://clinkme.dev"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="text-indigo-400 underline hover:text-indigo-300"
|
|
>
|
|
Learn more
|
|
</a>
|
|
</p>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|