nomadweb/frontend/src/components/ebook/BuyEbook.tsx
eric 8b80d18006 Add avatar upload, harden payments, and ebook checkout.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-30 19:55:05 -05:00

80 lines
2.8 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { trackEvent } from "@/lib/ebook/analytics";
import { getFeatures } from "@/lib/ebook/features";
import { useT } from "@/components/ebook/LocaleProvider";
import { useAuth } from "@/lib/auth";
import { api } from "@/lib/api";
/** Buy download edition via FastAPI /pay/create (pay_type=ebook). */
export function BuyEbook({ compact = false }: { compact?: boolean }) {
const t = useT();
const { token } = useAuth();
const router = useRouter();
const [busy, setBusy] = useState(false);
const [error, setError] = useState("");
const [owned, setOwned] = useState(false);
const features = getFeatures();
const enabled = features.payment?.checkout?.enabled !== false;
const productName = features.payment?.checkout?.productName || "Download Edition";
const buttonText = features.payment?.checkout?.buttonText || "购买下载版";
useEffect(() => {
if (!token) return;
api.ebookStatus(token).then((s) => setOwned(s.owned)).catch(() => {});
}, [token]);
if (!enabled) return null;
const buy = async () => {
setError("");
if (!token) {
router.push("/login?next=/book");
return;
}
if (owned) {
router.push("/book/thanks");
return;
}
setBusy(true);
trackEvent("buy_click", { from: compact ? "reader" : "cover" });
try {
const returnUrl = `${window.location.origin}/book/thanks`;
const res = await api.createPayment(token, returnUrl, "ebook");
if (res.order_id) {
localStorage.setItem("nomadro-ebook-order", res.order_id);
}
// DEV_AUTO_PAY or already-paid: confirm then go to thanks
if (res.status === "paid" && res.order_id) {
await api.completePayment(token, res.order_id).catch(() => null);
router.push(`/book/thanks?order_id=${encodeURIComponent(res.order_id)}`);
return;
}
if (!res.redirect_url) {
throw new Error(t("payment.checkoutFailed"));
}
window.location.href = res.redirect_url;
} catch (err) {
setError(err instanceof Error ? err.message : t("payment.checkoutFailed"));
setBusy(false);
}
};
return (
<section className={`buy-ebook${compact ? " is-compact" : ""}`}>
<h2 className="buy-ebook-title">{t("payment.buyGet", { name: productName })}</h2>
<p className="buy-ebook-sub">{t("payment.buySub")}</p>
<button type="button" className="buy-ebook-btn" disabled={busy} onClick={buy}>
{busy ? t("payment.openingCheckout") : owned ? "已购买 · 去下载" : buttonText}
</button>
{error ? <p className="buy-ebook-error">{error}</p> : null}
<p className="buy-ebook-hint">
也可免费 <Link href="/book/read">在线阅读</Link>
</p>
</section>
);
}