feat: Add city detail panel with weather visualizations and integrate Pro feature paywall components.
This commit is contained in:
@@ -14,8 +14,6 @@ import {
|
|||||||
getRiskBadgeLabel,
|
getRiskBadgeLabel,
|
||||||
getTemperatureChartData,
|
getTemperatureChartData,
|
||||||
} from "@/lib/dashboard-utils";
|
} from "@/lib/dashboard-utils";
|
||||||
import { ChevronRight, Crown } from "lucide-react";
|
|
||||||
import { useRouter } from "next/navigation";
|
|
||||||
|
|
||||||
function DetailMiniTemperatureChart({ detail }: { detail: CityDetail }) {
|
function DetailMiniTemperatureChart({ detail }: { detail: CityDetail }) {
|
||||||
const { locale, t } = useI18n();
|
const { locale, t } = useI18n();
|
||||||
@@ -125,7 +123,6 @@ function DetailMiniTemperatureChart({ detail }: { detail: CityDetail }) {
|
|||||||
|
|
||||||
export function DetailPanel() {
|
export function DetailPanel() {
|
||||||
const store = useDashboardStore();
|
const store = useDashboardStore();
|
||||||
const router = useRouter();
|
|
||||||
const { locale, t } = useI18n();
|
const { locale, t } = useI18n();
|
||||||
const detail = store.selectedDetail;
|
const detail = store.selectedDetail;
|
||||||
const isPro = store.proAccess.subscriptionActive;
|
const isPro = store.proAccess.subscriptionActive;
|
||||||
@@ -294,33 +291,9 @@ export function DetailPanel() {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="detail-section relative overflow-hidden rounded-2xl">
|
<section className="detail-section rounded-2xl">
|
||||||
<h3>{t("detail.todayMiniTrend")}</h3>
|
<h3>{t("detail.todayMiniTrend")}</h3>
|
||||||
<div
|
<DetailMiniTemperatureChart detail={detail} />
|
||||||
className={clsx(
|
|
||||||
"transition-all duration-500",
|
|
||||||
!isPro &&
|
|
||||||
"blur-md opacity-30 select-none pointer-events-none",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<DetailMiniTemperatureChart detail={detail} />
|
|
||||||
</div>
|
|
||||||
{!isPro && (
|
|
||||||
<div className="absolute inset-x-0 bottom-0 top-[3rem] z-10 flex flex-col items-center justify-center p-4 bg-gradient-to-t from-[#0b0f1a] via-transparent to-transparent">
|
|
||||||
<div className="bg-blue-600/90 text-white text-[10px] font-bold px-3 py-1.5 rounded-full shadow-lg shadow-blue-600/40 flex items-center gap-1.5 mb-2">
|
|
||||||
<Crown size={10} fill="currentColor" /> Pro Feature
|
|
||||||
</div>
|
|
||||||
<p className="text-[10px] text-slate-400 text-center mb-3">
|
|
||||||
解锁日内高精趋势图
|
|
||||||
</p>
|
|
||||||
<button
|
|
||||||
onClick={() => router.push("/account")}
|
|
||||||
className="text-[10px] font-bold text-blue-400 hover:text-blue-300 transition-colors flex items-center gap-1"
|
|
||||||
>
|
|
||||||
立即升级 <ChevronRight size={10} />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<ForecastTable />
|
<ForecastTable />
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export function ProFeaturePaywall({ feature, onClose }: ProFeaturePaywallProps)
|
|||||||
const [usePoints, setUsePoints] = useState(true);
|
const [usePoints, setUsePoints] = useState(true);
|
||||||
|
|
||||||
const isEn = locale === "en-US";
|
const isEn = locale === "en-US";
|
||||||
|
const isAuthenticated = proAccess.authenticated;
|
||||||
const pointsAvailable = Number(proAccess.points || 0);
|
const pointsAvailable = Number(proAccess.points || 0);
|
||||||
|
|
||||||
const PRO_PRICE_USD = 5;
|
const PRO_PRICE_USD = 5;
|
||||||
@@ -45,17 +46,13 @@ export function ProFeaturePaywall({ feature, onClose }: ProFeaturePaywallProps)
|
|||||||
};
|
};
|
||||||
}, [pointsAvailable, usePoints]);
|
}, [pointsAvailable, usePoints]);
|
||||||
|
|
||||||
const payLabel = isEn
|
const payLabel = isAuthenticated
|
||||||
? feature === "history"
|
? isEn
|
||||||
? "Unlock History in Account"
|
? "Open Pro in Account"
|
||||||
: feature === "future"
|
: "去账户中心开通 Pro"
|
||||||
? "Unlock Forecast in Account"
|
: isEn
|
||||||
: "Unlock Intraday in Account"
|
? "Sign In to Unlock Pro"
|
||||||
: feature === "history"
|
: "先登录再开通 Pro";
|
||||||
? "去账户中心解锁历史功能"
|
|
||||||
: feature === "future"
|
|
||||||
? "去账户中心解锁未来分析"
|
|
||||||
: "去账户中心解锁今日日内";
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full flex-col items-center justify-center py-6 md:py-10 z-30 p-4">
|
<div className="flex w-full flex-col items-center justify-center py-6 md:py-10 z-30 p-4">
|
||||||
@@ -67,7 +64,13 @@ export function ProFeaturePaywall({ feature, onClose }: ProFeaturePaywallProps)
|
|||||||
onToggleUsePoints={() => setUsePoints((prev) => !prev)}
|
onToggleUsePoints={() => setUsePoints((prev) => !prev)}
|
||||||
billing={billing}
|
billing={billing}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
onPay={() => router.push("/account")}
|
onPay={() => {
|
||||||
|
if (!isAuthenticated) {
|
||||||
|
router.push("/auth/login?next=%2Faccount");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
router.push("/account");
|
||||||
|
}}
|
||||||
payLabel={payLabel}
|
payLabel={payLabel}
|
||||||
faqHref="/account"
|
faqHref="/account"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -69,209 +69,217 @@ export function UnlockProOverlay({
|
|||||||
payLabel || (isEn ? "Subscribe & Activate" : "立即订阅并激活服务");
|
payLabel || (isEn ? "Subscribe & Activate" : "立即订阅并激活服务");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative w-full max-w-2xl bg-gradient-to-b from-[#1e293b] to-[#0f172a] border border-white/10 rounded-[2.5rem] p-8 md:p-12 shadow-[0_0_100px_-20px_rgba(59,130,246,0.3)] overflow-hidden">
|
<div className="relative w-full max-w-[860px] overflow-hidden rounded-[28px] border border-white/10 bg-gradient-to-b from-[#1a2740] via-[#14233b] to-[#0f172a] p-6 md:p-10 shadow-[0_0_90px_-24px_rgba(59,130,246,0.35)]">
|
||||||
|
<div className="pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_20%_0%,rgba(59,130,246,0.2),transparent_40%),radial-gradient(circle_at_80%_100%,rgba(99,102,241,0.18),transparent_45%)]" />
|
||||||
{onClose && (
|
{onClose && (
|
||||||
<button
|
<button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="absolute top-8 right-8 p-2 text-slate-500 hover:text-white bg-white/5 hover:bg-white/10 rounded-full transition-all active:scale-90"
|
className="absolute right-5 top-5 z-20 rounded-full bg-white/8 p-2 text-slate-500 transition-all hover:bg-white/15 hover:text-white active:scale-90"
|
||||||
title={isEn ? "Close" : "关闭"}
|
title={isEn ? "Close" : "关闭"}
|
||||||
>
|
>
|
||||||
<X size={20} />
|
<X size={18} />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex flex-col items-center mb-8">
|
<div className="relative z-10">
|
||||||
<div className="w-20 h-20 bg-gradient-to-tr from-yellow-500 via-amber-400 to-orange-500 rounded-[2rem] flex items-center justify-center shadow-2xl shadow-yellow-500/20 rotate-12 mb-6">
|
<div className="mx-auto mb-7 flex max-w-[580px] flex-col items-center text-center">
|
||||||
<Crown className="text-white w-10 h-10 drop-shadow-lg" />
|
<div className="mb-5 flex h-16 w-16 rotate-12 items-center justify-center rounded-3xl bg-gradient-to-tr from-yellow-500 via-amber-400 to-orange-500 shadow-2xl shadow-yellow-500/25">
|
||||||
</div>
|
<Crown className="h-8 w-8 text-white drop-shadow-lg" />
|
||||||
<h2 className="text-3xl font-black text-white tracking-tight text-center">
|
|
||||||
{title}
|
|
||||||
</h2>
|
|
||||||
<p className="text-slate-400 text-sm mt-2 text-center">{subtitle}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-10 text-left">
|
|
||||||
<div className="p-6 bg-blue-600/10 border-2 border-blue-500/40 rounded-3xl relative overflow-hidden">
|
|
||||||
<div className="absolute top-0 right-0 w-24 h-24 bg-blue-500/5 blur-2xl rounded-full translate-x-1/2 -translate-y-1/2" />
|
|
||||||
<div className="relative z-10">
|
|
||||||
<span className="text-[10px] font-bold text-blue-400 uppercase tracking-widest block mb-1">
|
|
||||||
STANDARD PRO
|
|
||||||
</span>
|
|
||||||
<div className="flex items-baseline gap-1">
|
|
||||||
<span className="text-4xl font-black text-white">
|
|
||||||
${planPriceUsd.toFixed(2)}
|
|
||||||
</span>
|
|
||||||
<span className="text-slate-500 text-sm font-medium">
|
|
||||||
/ {isEn ? "mo" : "月"}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<ul className="mt-4 space-y-2">
|
|
||||||
{(isEn
|
|
||||||
? [
|
|
||||||
"15-day high precision trend",
|
|
||||||
"Realtime radar panel",
|
|
||||||
"Cross-platform alert push",
|
|
||||||
]
|
|
||||||
: ["15天趋势预报", "实时雷达图", "全平台推送"]
|
|
||||||
).map((item, i) => (
|
|
||||||
<li
|
|
||||||
key={i}
|
|
||||||
className="flex items-center gap-2 text-[11px] text-slate-400"
|
|
||||||
>
|
|
||||||
<CheckCircle2 size={12} className="text-blue-500" /> {item}
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
|
<h2 className="text-4xl font-black tracking-tight text-white md:text-5xl">
|
||||||
|
{title}
|
||||||
|
</h2>
|
||||||
|
<p className="mt-2 max-w-[520px] text-sm leading-6 text-slate-300 md:text-base">
|
||||||
|
{subtitle}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="grid grid-cols-1 gap-4 text-left md:grid-cols-2">
|
||||||
|
<div className="relative min-h-[208px] rounded-3xl border border-blue-500/45 bg-blue-600/10 p-6">
|
||||||
|
<div className="pointer-events-none absolute right-0 top-0 h-24 w-24 translate-x-1/3 -translate-y-1/3 rounded-full bg-blue-500/15 blur-2xl" />
|
||||||
|
<div className="relative z-10">
|
||||||
|
<span className="block text-[11px] font-bold uppercase tracking-[0.12em] text-blue-300">
|
||||||
|
STANDARD PRO
|
||||||
|
</span>
|
||||||
|
<div className="mt-2 flex items-baseline gap-1">
|
||||||
|
<span className="text-5xl font-black leading-none text-white">
|
||||||
|
${planPriceUsd.toFixed(2)}
|
||||||
|
</span>
|
||||||
|
<span className="text-sm font-semibold text-slate-400">
|
||||||
|
/ {isEn ? "mo" : "月"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<ul className="mt-5 space-y-2">
|
||||||
|
{(isEn
|
||||||
|
? [
|
||||||
|
"15-day high precision trend",
|
||||||
|
"Realtime radar panel",
|
||||||
|
"Cross-platform alert push",
|
||||||
|
]
|
||||||
|
: ["15天趋势预报", "实时雷达图", "全平台推送"]
|
||||||
|
).map((item, i) => (
|
||||||
|
<li
|
||||||
|
key={i}
|
||||||
|
className="flex items-center gap-2 text-[12px] text-slate-300"
|
||||||
|
>
|
||||||
|
<CheckCircle2 size={13} className="text-blue-400" /> {item}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{billing.pointsEnabled && billing.isEligible ? (
|
{billing.pointsEnabled && billing.isEligible ? (
|
||||||
<div
|
<div
|
||||||
className={`p-6 rounded-3xl border transition-all duration-300 flex flex-col justify-between ${usePoints ? "bg-indigo-500/10 border-indigo-500/50 shadow-inner" : "bg-white/5 border-white/10"}`}
|
className={`min-h-[208px] rounded-3xl border p-6 transition-all ${usePoints ? "border-indigo-500/55 bg-indigo-500/12 shadow-inner shadow-indigo-500/8" : "border-white/10 bg-white/6"}`}
|
||||||
>
|
>
|
||||||
<div>
|
<div className="mb-4 flex items-center justify-between">
|
||||||
<div className="flex items-center justify-between mb-4">
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div
|
<div
|
||||||
className={`p-1.5 rounded-lg ${usePoints ? "bg-indigo-500 text-white" : "bg-slate-700 text-slate-500"}`}
|
className={`rounded-lg p-1.5 ${usePoints ? "bg-indigo-500 text-white" : "bg-slate-700 text-slate-400"}`}
|
||||||
>
|
>
|
||||||
<Coins size={14} />
|
<Coins size={14} />
|
||||||
</div>
|
</div>
|
||||||
<span className="text-[10px] font-bold text-slate-300 uppercase tracking-widest">
|
<span className="text-[11px] font-bold uppercase tracking-[0.12em] text-slate-300">
|
||||||
{isEn ? "Points Credit" : "积分抵扣"}
|
{isEn ? "Points Credit" : "积分抵扣"}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={onToggleUsePoints}
|
onClick={onToggleUsePoints}
|
||||||
className={`w-10 h-5 rounded-full relative transition-all duration-300 ${usePoints ? "bg-indigo-500" : "bg-slate-700"}`}
|
className={`relative h-6 w-11 rounded-full transition-all ${usePoints ? "bg-indigo-500" : "bg-slate-700"}`}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`absolute top-1 w-3 h-3 rounded-full bg-white shadow-sm transition-all duration-300 ${usePoints ? "right-1" : "left-1"}`}
|
className={`absolute top-1 h-4 w-4 rounded-full bg-white shadow-sm transition-all ${usePoints ? "right-1" : "left-1"}`}
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-baseline gap-1">
|
<div className="flex items-end gap-1">
|
||||||
<span
|
<span
|
||||||
className={`text-3xl font-black ${usePoints ? "text-green-400" : "text-slate-600"}`}
|
className={`text-5xl font-black leading-none ${usePoints ? "text-emerald-400" : "text-slate-500"}`}
|
||||||
>
|
>
|
||||||
-${billing.discountAmount.toFixed(2)}
|
-${billing.discountAmount.toFixed(2)}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-slate-500 text-[10px] font-bold">DISCOUNT</span>
|
<span className="pb-1 text-[11px] font-bold uppercase tracking-[0.08em] text-slate-500">
|
||||||
</div>
|
DISCOUNT
|
||||||
</div>
|
|
||||||
<p className="text-[10px] text-slate-500 leading-tight">
|
|
||||||
{usePoints
|
|
||||||
? isEn
|
|
||||||
? `Using ${billing.pointsUsed} points`
|
|
||||||
: `已自动消耗账户内 ${billing.pointsUsed} 积分`
|
|
||||||
: isEn
|
|
||||||
? `Up to $${billing.maxDiscountUsd.toFixed(2)} off`
|
|
||||||
: `开启后最多可抵扣 $${billing.maxDiscountUsd.toFixed(2)}`}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="p-6 rounded-3xl border border-dashed border-white/10 bg-white/5 flex flex-col justify-between group hover:border-blue-500/30 transition-all">
|
|
||||||
<div>
|
|
||||||
<div className="flex items-center gap-2 mb-3">
|
|
||||||
<div className="p-1.5 bg-slate-800 rounded-lg text-slate-500 group-hover:text-blue-400 transition-colors">
|
|
||||||
<MessageSquare size={14} />
|
|
||||||
</div>
|
|
||||||
<span className="text-[10px] font-bold text-slate-500 uppercase tracking-widest">
|
|
||||||
{!billing.pointsEnabled
|
|
||||||
? isEn
|
|
||||||
? "Points Disabled"
|
|
||||||
: "积分抵扣未开启"
|
|
||||||
: isEn
|
|
||||||
? "Not Enough Points"
|
|
||||||
: "积分不足"}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<h4 className="text-sm font-bold text-white mb-2">
|
<p className="mt-6 text-[12px] leading-6 text-slate-400">
|
||||||
{isEn ? "Earn Credits in Community" : "活跃赚取抵扣"}
|
{usePoints
|
||||||
</h4>
|
|
||||||
<p className="text-[11px] text-slate-400 leading-normal">
|
|
||||||
{!billing.pointsEnabled
|
|
||||||
? isEn
|
? isEn
|
||||||
? "Points redemption is currently unavailable for this plan."
|
? `Using ${billing.pointsUsed} points from your account`
|
||||||
: "当前套餐未开启积分抵扣。"
|
: `已自动消耗账户内 ${billing.pointsUsed} 积分`
|
||||||
: isEn
|
: isEn
|
||||||
? `Need at least ${billing.pointsPerUsd} points. Current: ${points}`
|
? `Enable to save up to $${billing.maxDiscountUsd.toFixed(2)}`
|
||||||
: `需要至少 ${billing.pointsPerUsd} 积分,当前 ${points}`}
|
: `开启后最多可抵扣 $${billing.maxDiscountUsd.toFixed(2)}`}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4 flex items-center justify-center gap-1 text-[10px] text-blue-500/80 font-bold uppercase">
|
) : (
|
||||||
{telegramGroupUrl ? (
|
<div className="group flex min-h-[208px] flex-col justify-between rounded-3xl border border-dashed border-white/20 bg-white/5 p-6 transition-all hover:border-blue-500/35">
|
||||||
<Link href={telegramGroupUrl} target="_blank" className="inline-flex items-center gap-1">
|
<div>
|
||||||
<span>{isEn ? "Open Telegram" : "前往电报群"}</span>
|
<div className="mb-3 flex items-center gap-2">
|
||||||
<TrendingUp size={12} className="animate-bounce" />
|
<div className="rounded-lg bg-slate-800 p-1.5 text-slate-500 transition-colors group-hover:text-blue-400">
|
||||||
</Link>
|
<MessageSquare size={14} />
|
||||||
) : (
|
</div>
|
||||||
<>
|
<span className="text-[11px] font-bold uppercase tracking-[0.1em] text-slate-500">
|
||||||
<span>{isEn ? "Open Telegram" : "前往电报群"}</span>
|
{!billing.pointsEnabled
|
||||||
<TrendingUp size={12} className="animate-bounce" />
|
? isEn
|
||||||
</>
|
? "Points Disabled"
|
||||||
)}
|
: "积分未开启"
|
||||||
|
: isEn
|
||||||
|
? "Not Enough Points"
|
||||||
|
: "积分不足"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<h4 className="mb-2 text-base font-bold text-white">
|
||||||
|
{isEn ? "Earn Credits in Community" : "活跃赚取抵扣"}
|
||||||
|
</h4>
|
||||||
|
<p className="text-[12px] leading-6 text-slate-300">
|
||||||
|
{!billing.pointsEnabled
|
||||||
|
? isEn
|
||||||
|
? "Points redemption is currently unavailable for this plan."
|
||||||
|
: "当前套餐未开启积分抵扣。"
|
||||||
|
: isEn
|
||||||
|
? `Need at least ${billing.pointsPerUsd} points. Current: ${points}`
|
||||||
|
: `需要至少 ${billing.pointsPerUsd} 积分,当前 ${points}`}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="mt-5 flex items-center justify-center text-[11px] font-bold text-blue-400">
|
||||||
|
{telegramGroupUrl ? (
|
||||||
|
<Link
|
||||||
|
href={telegramGroupUrl}
|
||||||
|
target="_blank"
|
||||||
|
className="inline-flex items-center gap-1.5"
|
||||||
|
>
|
||||||
|
<span>{isEn ? "Open Telegram" : "前往电报群"}</span>
|
||||||
|
<TrendingUp size={13} />
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<span className="inline-flex items-center gap-1.5">
|
||||||
|
{isEn ? "Open Telegram" : "前往电报群"}
|
||||||
|
<TrendingUp size={13} />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-7 border-t border-white/10 pt-5">
|
||||||
|
<div className="flex items-end justify-between gap-4 px-1">
|
||||||
|
<span className="text-base font-medium text-slate-300">
|
||||||
|
{isEn ? "Total Due" : "应付总计"}
|
||||||
|
</span>
|
||||||
|
<div className="text-right">
|
||||||
|
<span className="font-mono text-5xl font-black leading-none tracking-tight text-white">
|
||||||
|
${billing.finalPrice.toFixed(2)}
|
||||||
|
</span>
|
||||||
|
<span className="ml-2 font-mono text-sm uppercase tracking-[0.12em] text-slate-500">
|
||||||
|
USD
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-6">
|
<button
|
||||||
<div className="flex items-center justify-between px-2">
|
onClick={onPay}
|
||||||
<span className="text-slate-400 text-sm font-medium">
|
disabled={payBusy}
|
||||||
{isEn ? "Total Due:" : "应付总计:"}
|
className="mt-5 flex w-full items-center justify-center gap-3 rounded-2xl bg-gradient-to-r from-blue-600 via-indigo-600 to-indigo-700 py-4 text-lg font-black text-white shadow-2xl shadow-blue-600/30 transition-all hover:from-blue-500 hover:to-indigo-500 active:scale-[0.99] disabled:opacity-70"
|
||||||
</span>
|
|
||||||
<div className="text-right">
|
|
||||||
<span className="text-4xl font-black text-white font-mono tracking-tighter">
|
|
||||||
${billing.finalPrice.toFixed(2)}
|
|
||||||
</span>
|
|
||||||
<span className="text-slate-500 text-sm ml-2 font-mono uppercase tracking-widest">
|
|
||||||
USD
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
onClick={onPay}
|
|
||||||
disabled={payBusy}
|
|
||||||
className="w-full py-5 bg-gradient-to-r from-blue-600 via-indigo-600 to-indigo-700 hover:from-blue-500 hover:to-indigo-500 text-white font-black text-lg rounded-[1.5rem] shadow-2xl shadow-blue-600/30 transition-all active:scale-[0.98] flex items-center justify-center gap-3 group disabled:opacity-70"
|
|
||||||
>
|
|
||||||
{payBusy ? (
|
|
||||||
<Loader2 size={20} className="animate-spin" />
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Wallet size={20} />
|
|
||||||
{finalPayLabel}
|
|
||||||
<ArrowRight
|
|
||||||
size={20}
|
|
||||||
className="group-hover:translate-x-1 transition-transform"
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div className="flex justify-center items-center gap-6 pt-4 text-[10px] text-slate-600 uppercase tracking-widest font-bold">
|
|
||||||
<span className="flex items-center gap-1.5">
|
|
||||||
<Lock size={12} /> Secure Payment
|
|
||||||
</span>
|
|
||||||
<Link
|
|
||||||
href={faqHref}
|
|
||||||
className="flex items-center gap-1.5 hover:text-slate-400 transition-colors"
|
|
||||||
>
|
>
|
||||||
<BellRing size={12} /> Subscription FAQ
|
{payBusy ? (
|
||||||
</Link>
|
<Loader2 size={20} className="animate-spin" />
|
||||||
</div>
|
) : (
|
||||||
|
<>
|
||||||
|
<Wallet size={20} />
|
||||||
|
{finalPayLabel}
|
||||||
|
<ArrowRight
|
||||||
|
size={20}
|
||||||
|
className="transition-transform group-hover:translate-x-1"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
|
||||||
{errorText ? (
|
<div className="mt-5 flex items-center justify-center gap-6 text-[10px] font-bold uppercase tracking-[0.15em] text-slate-600">
|
||||||
<div className="mt-2 text-xs text-rose-400 bg-rose-500/10 p-2 rounded-lg border border-rose-500/20">
|
<span className="flex items-center gap-1.5">
|
||||||
{errorText}
|
<Lock size={12} /> Secure Payment
|
||||||
|
</span>
|
||||||
|
<Link
|
||||||
|
href={faqHref}
|
||||||
|
className="flex items-center gap-1.5 transition-colors hover:text-slate-400"
|
||||||
|
>
|
||||||
|
<BellRing size={12} /> Subscription FAQ
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
|
||||||
{infoText ? (
|
{errorText ? (
|
||||||
<div className="mt-2 text-xs text-emerald-400 bg-emerald-500/10 p-2 rounded-lg border border-emerald-500/20">
|
<div className="mt-4 rounded-lg border border-rose-500/20 bg-rose-500/10 p-2 text-xs text-rose-400">
|
||||||
{infoText}
|
{errorText}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
{infoText ? (
|
||||||
|
<div className="mt-4 rounded-lg border border-emerald-500/20 bg-emerald-500/10 p-2 text-xs text-emerald-400">
|
||||||
|
{infoText}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user