import { useState } from 'react'; import { useLocalStorage } from '@/hooks/useLocalStorage'; import { useCurrentUser } from '@/hooks/useCurrentUser'; import { PLATFORM_OWNER_PUBKEYS, PLATFORM_LIGHTNING_ADDRESS } from '@/hooks/useListingRestrictions'; // ─── Base monthly (30-day) fee per type, in sats ───────────────────────────── // Actual charge = base × the selected duration tier's multiplier. export const PLATFORM_FEES = { /** Featured event listing */ featuredEvent: 5000, /** Lab Pro profile (coach / filmer / editor / photographer / builder) */ labProfile: 5000, /** Featured shop placement on map */ featuredShop: 10000, /** Featured tour listing */ featuredTour: 5000, } as const; export type FeeType = keyof typeof PLATFORM_FEES; // ─── Duration tiers ─────────────────────────────────────────────────────────── // Price discrimination: the longer the commitment, the cheaper the effective // per-day rate. `multiplier` is applied to a type's base monthly price. // 7d → 0.4× (short "try it" window, priced at a premium per-day) // 30d → 1.0× (baseline monthly) // 90d → 2.4× (season — ~20% per-day discount) ← best value // 365d→ 8.0× (year — ~33% per-day discount) export type DurationTierId = '7d' | '30d' | '90d' | '365d'; export interface DurationTier { id: DurationTierId; label: string; days: number; multiplier: number; /** Cosmetic "best value" flag for UI. */ bestValue?: boolean; } export const DURATION_TIERS: DurationTier[] = [ { id: '7d', label: '7 days', days: 7, multiplier: 0.4 }, { id: '30d', label: '1 month', days: 30, multiplier: 1 }, { id: '90d', label: '3 months', days: 90, multiplier: 2.4, bestValue: true }, { id: '365d', label: '12 months', days: 365, multiplier: 8 }, ]; export const DEFAULT_TIER_ID: DurationTierId = '30d'; /** Compute the sats price for a fee type at a given duration tier. */ export function tierPriceSats(type: FeeType, tier: DurationTier): number { // Round to the nearest 100 sats for tidy amounts. return Math.round((PLATFORM_FEES[type] * tier.multiplier) / 100) * 100; } /** Effective per-day sats (for "best value" display). */ export function tierPerDaySats(type: FeeType, tier: DurationTier): number { return Math.round(tierPriceSats(type, tier) / tier.days); } // ─── Display metadata per fee type ─────────────────────────────────────────── export const FEE_META: Record = { featuredEvent: { label: 'Featured Event', description: 'Feature your event on SkateSpots', benefit: 'Your event appears at the top of the events page', emoji: '📅', }, labProfile: { label: 'Lab Pro Profile', description: 'Feature your services in The Lab', benefit: 'Pro badge + featured placement when skaters browse The Lab', emoji: '🎓', }, featuredShop: { label: 'Featured Shop', description: 'Feature your skate shop on the map and directory', benefit: 'Verified badge + featured pin on the map', emoji: '🏪', }, featuredTour: { label: 'Featured Tour', description: 'Feature your tour on SkateSpots', benefit: 'Pro badge + top placement when skaters browse tours', emoji: '🗺️', }, }; // ─── localStorage key per fee type + account ───────────────────────────────── // Scoped by pubkey so one account on a shared device can't inherit another's // paid status (a device-global key let any later login skip the fee). function storageKey(type: FeeType, pubkey: string): string { return `platform-fee-paid-${type}-${pubkey}`; } interface FeeRecord { paidAt: number; // unix ms expiresAt: number; // unix ms } // ─── Hook ───────────────────────────────────────────────────────────────────── export function usePlatformFee(type: FeeType) { const { user } = useCurrentUser(); const [record, setRecord] = useLocalStorage(storageKey(type, user?.pubkey ?? 'anon'), null); // Currently selected duration tier (UI state). const [tierId, setTierId] = useState(DEFAULT_TIER_ID); const tier = DURATION_TIERS.find((t) => t.id === tierId) ?? DURATION_TIERS[1]; // Platform owners never pay const isPlatformOwner = !!user?.pubkey && PLATFORM_OWNER_PUBKEYS.includes(user.pubkey); // Is the fee currently active (paid and not expired)? const isActive = isPlatformOwner || ( record !== null && Date.now() < record.expiresAt ); // Days remaining const daysRemaining = isActive && record ? Math.ceil((record.expiresAt - Date.now()) / (24 * 60 * 60 * 1000)) : 0; // Expiry as unix SECONDS, for writing into a `featured` event tag. // Platform owners get a long-lived (1 year) featured window. const featuredUntilSeconds = (() => { if (isPlatformOwner) { return Math.floor((Date.now() + 365 * 24 * 60 * 60 * 1000) / 1000); } if (record) return Math.floor(record.expiresAt / 1000); return undefined; })(); // Price for the currently selected tier. const amountSats = tierPriceSats(type, tier); // Record a successful payment for the selected tier's duration. const recordPayment = () => { const now = Date.now(); setRecord({ paidAt: now, expiresAt: now + tier.days * 24 * 60 * 60 * 1000, }); }; return { /** Amount in sats for the selected tier */ amountSats, /** Lightning address to pay */ recipient: PLATFORM_LIGHTNING_ADDRESS, recipientName: 'SkateSpots', /** Display metadata */ meta: FEE_META[type], /** Available duration tiers */ tiers: DURATION_TIERS, /** Currently selected tier */ tier, /** Change the selected tier */ setTierId, /** Is fee currently paid and active? */ isActive, /** Days remaining on active fee */ daysRemaining, /** Expiry in unix seconds — write into a `featured` tag when publishing */ featuredUntilSeconds, /** Platform owners bypass everything */ isPlatformOwner, /** Call this after successful payment */ recordPayment, /** Price/per-day helpers for UI */ tierPriceSats: (t: DurationTier) => tierPriceSats(type, t), tierPerDaySats: (t: DurationTier) => tierPerDaySats(type, t), }; }