- Add 4 duration tiers (7d/30d/90d/365d) with per-day price discrimination to usePlatformFee - PlatformFeePayment: tier selector UI + attempt NIP-57 zap request (a/p tags), graceful fallback to plain LNURL invoice - Add getZapProviderPubkey + getZapInvoiceFromLightningAddress helpers - Add useFeaturedVerification hook (verified/claimed/none) querying kind 9735 zap receipts - ProBadge: optional entityCoord to show green Verified state on detail pages - Wire entityCoord through all 5 Become* Lab pages + CoachDetailPage - Add featuredTour fee type; FeaturedUpgrade generalized for events + tours - CreateTourPage: featured tag on publish + FeaturedUpgrade UI - TourCard/TourDetailPage: render ProBadge; sort featured tours first - Document tiers + zap-receipt verification scheme in NIP.md Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
108 lines
3.6 KiB
TypeScript
108 lines
3.6 KiB
TypeScript
import { Sparkles, Check } from 'lucide-react';
|
|
import { Switch } from '@/components/ui/switch';
|
|
import { PlatformFeePayment } from '@/components/PlatformFeePayment';
|
|
import { usePlatformFee, type FeeType } from '@/hooks/usePlatformFee';
|
|
import { cn } from '@/lib/utils';
|
|
|
|
interface FeaturedUpgradeProps {
|
|
/** Which platform fee this upgrade drives. */
|
|
type: Extract<FeeType, 'featuredEvent' | 'featuredTour'>;
|
|
/** Noun shown to the user, e.g. "event" or "tour". */
|
|
noun: string;
|
|
/** Where featured items appear, e.g. "events page" or "tours". */
|
|
placement: string;
|
|
enabled: boolean;
|
|
onEnabledChange: (enabled: boolean) => void;
|
|
className?: string;
|
|
}
|
|
|
|
/**
|
|
* Opt-in "Feature this <noun>" upgrade.
|
|
*
|
|
* Every listing posts for free. Toggling this on reveals the Lightning fee and,
|
|
* once paid (or for platform owners), the item is published with a `featured`
|
|
* tag that gives it a badge + top placement.
|
|
*/
|
|
export function FeaturedUpgrade({
|
|
type,
|
|
noun,
|
|
placement,
|
|
enabled,
|
|
onEnabledChange,
|
|
className,
|
|
}: FeaturedUpgradeProps) {
|
|
const { isActive, isPlatformOwner, daysRemaining } = usePlatformFee(type);
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
'rounded-2xl border border-[#ccff00]/30 bg-gradient-to-br from-[#ccff00]/8 to-black/40 overflow-hidden',
|
|
className,
|
|
)}
|
|
>
|
|
<div className="p-4">
|
|
<div className="flex items-start justify-between gap-3">
|
|
<div className="flex items-start gap-3 min-w-0">
|
|
<div className="w-9 h-9 rounded-xl bg-gradient-to-br from-[#ccff00] to-lime-500 flex items-center justify-center flex-shrink-0 shadow-lg shadow-[#ccff00]/25">
|
|
<Sparkles className="h-4 w-4 text-black" />
|
|
</div>
|
|
<div className="min-w-0">
|
|
<p className="font-black uppercase tracking-wide text-sm text-[#ccff00]">
|
|
Feature this {noun} <span className="text-gray-500 font-normal normal-case">· optional</span>
|
|
</p>
|
|
<p className="text-xs text-gray-400 mt-0.5">
|
|
Free to post. Featured {noun}s get a <strong className="text-[#ccff00]">badge</strong> and sit
|
|
{' '}<strong className="text-[#ccff00]">at the top</strong> of {placement}.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
{isActive ? (
|
|
<span className="flex items-center gap-1 text-[11px] font-bold text-green-400 flex-shrink-0">
|
|
<Check className="h-3.5 w-3.5" /> Active
|
|
</span>
|
|
) : (
|
|
<Switch
|
|
checked={enabled}
|
|
onCheckedChange={onEnabledChange}
|
|
className="flex-shrink-0 data-[state=checked]:bg-[#ccff00]"
|
|
/>
|
|
)}
|
|
</div>
|
|
|
|
{isActive && (
|
|
<p className="mt-2 text-xs text-gray-500">
|
|
{isPlatformOwner
|
|
? 'Platform owner — featured is always on.'
|
|
: `Featured active · ${daysRemaining} day${daysRemaining !== 1 ? 's' : ''} remaining.`}
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
{enabled && !isActive && (
|
|
<div className="px-4 pb-4">
|
|
<PlatformFeePayment
|
|
type={type}
|
|
onSuccess={() => { /* hook auto-updates isActive */ }}
|
|
context={`Your ${noun} stays listed for free either way — featured just adds the badge & boost.`}
|
|
/>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/** Convenience wrapper for events. */
|
|
export function FeaturedEventUpgrade(props: {
|
|
enabled: boolean;
|
|
onEnabledChange: (enabled: boolean) => void;
|
|
className?: string;
|
|
}) {
|
|
return (
|
|
<FeaturedUpgrade
|
|
type="featuredEvent"
|
|
noun="event"
|
|
placement="the events page"
|
|
{...props}
|
|
/>
|
|
);
|
|
}
|