SkateSpots/src/components/ProSupportScreen.tsx
SkateSpots 4cad201716 Add a Settings → Pro & Support hub
One discoverable home for every paid extra, so the pricing model is legible
instead of scattered across the Sasquatch panel and each offer form:

- components/ProSupportScreen.tsx (Settings ?section=pro + a menu row): honest
  "prepaid, not a subscription" framing; Sasquatch Pro status (days left) +
  PRO_TIERS with per-day sats and a Go Pro / Extend button; Featured placement
  (PLATFORM_FEES x DURATION_TIERS with per-day + best-value); your currently
  featured Lab listings (featuredUntil); and the free paths (PoW listing, free
  AI FAQ).
- Distinct naming in copy: Sasquatch Pro (AI) vs Featured placement (visibility).
- Smoke test now walks /settings?section=pro.
2026-10-04 14:28:13 +07:00

158 lines
8.4 KiB
TypeScript

import { useState } from 'react';
import { Link } from 'react-router-dom';
import { ChevronLeft, Crown, CheckCircle2, Sparkles, Cpu } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { useTranslation } from '@/contexts/language';
import { useSasquatchPro } from '@/hooks/useSasquatchPro';
import { PRO_TIERS } from '@/lib/sasquatchPro';
import { DURATION_TIERS, PLATFORM_FEES, FEE_META, tierPriceSats, tierPerDaySats, type FeeType } from '@/hooks/usePlatformFee';
import { SasquatchProDialog } from '@/components/SasquatchProDialog';
import { useLabProfiles } from '@/hooks/useLabProfiles';
import { useCurrentUser } from '@/hooks/useCurrentUser';
import { labProfilePath } from '@/lib/labRoutes';
import { dateLocaleCode } from '@/lib/dateFnsI18n';
import { cn } from '@/lib/utils';
/**
* Settings → Pro & Support.
*
* One honest home for every paid extra: what it does, the duration tiers and
* effective per-day price, your current status, and the free alternatives.
* Nothing here holds funds — each purchase is a one-off Lightning payment.
*/
export function ProSupportScreen({ onBack }: { onBack: () => void }) {
const { t } = useTranslation();
const { isPro, expiresAt } = useSasquatchPro();
const { user } = useCurrentUser();
const { data: profiles } = useLabProfiles(100);
const [proOpen, setProOpen] = useState(false);
const now = Math.floor(Date.now() / 1000);
const daysLeft = isPro && expiresAt ? Math.max(0, Math.ceil((expiresAt - now) / 86400)) : 0;
const fmt = (secs: number) => new Date(secs * 1000).toLocaleDateString(dateLocaleCode(), { month: 'short', day: 'numeric', year: 'numeric' });
const featured = (profiles ?? []).filter((p) => p.pubkey === user?.pubkey && p.featuredUntil && p.featuredUntil > now);
return (
<div>
<div className="flex items-center gap-3 mb-5">
<button
onClick={onBack}
aria-label={t('common.back')}
className="flex items-center justify-center w-8 h-8 rounded-xl bg-white/5 hover:bg-white/10 active:scale-95 transition-all"
>
<ChevronLeft className="h-4 w-4 text-white" />
</button>
<h2 className="text-lg font-black uppercase tracking-wide text-white">{t('settingsUi.proSupport.title')}</h2>
</div>
<div className="space-y-4">
{/* Honest framing */}
<div className="rounded-2xl border border-[#ccff00]/20 bg-[#ccff00]/5 p-4">
<p className="flex items-center gap-2 text-sm font-bold text-[#ccff00]">
<CheckCircle2 className="h-4 w-4" /> {t('settingsUi.proSupport.prepaidTitle')}
</p>
<p className="mt-1 text-xs text-muted-foreground leading-relaxed">{t('settingsUi.proSupport.prepaidBody')}</p>
</div>
{/* Sasquatch Pro */}
<div className="rounded-2xl border border-white/8 p-4 bg-white/2">
<div className="flex items-start gap-3">
<div className="flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-xl bg-emerald-500/15">
<Sparkles className="h-4 w-4 text-emerald-400" />
</div>
<div className="min-w-0 flex-1">
<p className="text-sm font-black uppercase tracking-wide text-white">{t('settingsUi.proSupport.sasquatchTitle')}</p>
<p className="mt-0.5 text-xs text-muted-foreground leading-relaxed">{t('settingsUi.proSupport.sasquatchBenefit')}</p>
<p className={cn('mt-1 text-xs font-bold', isPro ? 'text-emerald-400' : 'text-gray-500')}>
{isPro
? expiresAt
? `${t('settingsUi.proSupport.activeUntil', { date: fmt(expiresAt) })} · ${t('settingsUi.proSupport.daysLeft', { count: daysLeft })}`
: t('settingsUi.proSupport.active')
: t('settingsUi.proSupport.notActive')}
</p>
</div>
</div>
<div className="mt-3 grid grid-cols-2 gap-2 sm:grid-cols-4">
{PRO_TIERS.map((tier) => (
<div key={tier.id} className={cn('rounded-xl border p-2.5 text-center', tier.popular ? 'border-emerald-500/50 bg-emerald-500/10' : 'border-white/10')}>
<p className="text-xs font-bold text-white">{t(tier.labelKey)}</p>
<p className="mt-1 text-sm font-black text-white">{tier.sats.toLocaleString()}</p>
<p className="text-[10px] text-muted-foreground">{t('common.sats')} · {Math.round(tier.sats / tier.days)}/{t('settingsUi.proSupport.day')}</p>
{tier.badgeKey && <p className="mt-1 text-[10px] font-bold text-emerald-400">{t(tier.badgeKey)}</p>}
</div>
))}
</div>
<Button onClick={() => setProOpen(true)} className="mt-3 w-full gap-2 bg-[#ccff00] font-black uppercase text-black hover:bg-[#ccff00]/90">
<Crown className="h-4 w-4" /> {isPro ? t('settingsUi.proSupport.extend') : t('settingsUi.proSupport.goPro')}
</Button>
</div>
{/* Featured placement */}
<div className="rounded-2xl border border-white/8 p-4 bg-white/2">
<div className="flex items-start gap-3">
<div className="flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-xl bg-amber-500/15">
<Cpu className="h-4 w-4 text-amber-400" />
</div>
<div className="min-w-0 flex-1">
<p className="text-sm font-black uppercase tracking-wide text-white">{t('settingsUi.proSupport.featuredTitle')}</p>
<p className="mt-0.5 text-xs text-muted-foreground leading-relaxed">{t('settingsUi.proSupport.featuredBenefit')}</p>
</div>
</div>
<div className="mt-3 space-y-3">
{(Object.keys(PLATFORM_FEES) as FeeType[]).map((type) => (
<div key={type}>
<p className="text-xs font-bold text-gray-300">
<span className="mr-1">{FEE_META[type].emoji}</span>{FEE_META[type].label}
<span className="ml-2 font-normal text-muted-foreground">{FEE_META[type].benefit}</span>
</p>
<div className="mt-1.5 grid grid-cols-2 gap-1.5 sm:grid-cols-4">
{DURATION_TIERS.map((tier) => (
<div key={tier.id} className={cn('rounded-lg border p-2 text-center', tier.bestValue ? 'border-[#ccff00]/40 bg-[#ccff00]/5' : 'border-white/10')}>
<p className="text-[11px] font-bold text-white">{tier.label}</p>
<p className="text-xs font-black text-white">{tierPriceSats(type, tier).toLocaleString()}</p>
<p className="text-[10px] text-muted-foreground">{tierPerDaySats(type, tier).toLocaleString()}/{t('settingsUi.proSupport.day')}</p>
</div>
))}
</div>
</div>
))}
</div>
<p className="mt-3 text-xs text-muted-foreground">{t('settingsUi.proSupport.featuredHow')}</p>
</div>
{/* Your featured listings */}
<div className="rounded-2xl border border-white/8 p-4 bg-white/2">
<p className="text-sm font-black uppercase tracking-wide text-white">{t('settingsUi.proSupport.yourFeatured')}</p>
{featured.length === 0 ? (
<p className="mt-1 text-xs text-muted-foreground">{t('settingsUi.proSupport.noneFeatured')}</p>
) : (
<div className="mt-2 space-y-1.5">
{featured.map((p) => (
<Link
key={p.id}
to={labProfilePath(p.profileType, p.pubkey, p.identifier)}
className="flex items-center justify-between gap-2 rounded-lg border border-white/10 bg-white/5 px-3 py-2 text-xs font-bold text-gray-200 hover:bg-white/10"
>
<span className="truncate">{p.title}</span>
<span className="flex-shrink-0 text-amber-400">{t('settingsUi.proSupport.featuredUntil', { date: fmt(p.featuredUntil!) })}</span>
</Link>
))}
</div>
)}
</div>
{/* Free paths */}
<div className="rounded-2xl border border-white/8 p-4 bg-white/2">
<p className="text-sm font-black uppercase tracking-wide text-white">{t('settingsUi.proSupport.freeTitle')}</p>
<p className="mt-1 text-xs text-muted-foreground leading-relaxed">{t('settingsUi.proSupport.freeBody')}</p>
</div>
</div>
<SasquatchProDialog open={proOpen} onOpenChange={setProOpen} />
</div>
);
}