Add sponsor price endpoint and fetch pricing from backend on frontend

Made-with: Cursor
This commit is contained in:
Michilis
2026-03-16 18:21:41 +00:00
parent a47868d17c
commit f43f0bc501
4 changed files with 44 additions and 13 deletions

View File

@@ -9,6 +9,11 @@ const router = Router();
const SNAP_DAYS = [1, 3, 7, 14, 30, 60, 90, 180, 365];
/** Public endpoint: returns base sponsor price per day for display. */
router.get("/price", (_req: Request, res: Response) => {
res.json({ baseSponsorPricePerDay: config.baseSponsorPricePerDay });
});
function snapDays(days: number): number {
let best = SNAP_DAYS[0];
for (const d of SNAP_DAYS) {

View File

@@ -375,6 +375,14 @@ export interface SponsorCreateResult {
status: string;
}
export interface SponsorPrice {
baseSponsorPricePerDay: number;
}
export async function getSponsorPrice(): Promise<SponsorPrice> {
return request<SponsorPrice>("/sponsor/price");
}
export async function getSponsorHomepage(): Promise<SponsorHomepageItem[]> {
return request<SponsorHomepageItem[]>("/sponsor/homepage");
}

View File

@@ -2,10 +2,8 @@ import { useState, useCallback, useMemo } from "react";
import { postSponsorCreate, type SponsorCreateResult } from "../api";
import { SponsorTimeSlider } from "./SponsorTimeSlider";
const BASE_PRICE = 200;
function calculatePrice(days: number): number {
let price = BASE_PRICE * days;
function calculatePrice(basePricePerDay: number, days: number): number {
let price = basePricePerDay * days;
if (days >= 180) price *= 0.7;
else if (days >= 90) price *= 0.8;
else if (days >= 30) price *= 0.9;
@@ -13,11 +11,12 @@ function calculatePrice(days: number): number {
}
interface SponsorFormProps {
basePricePerDay: number;
onSuccess?: (result: SponsorCreateResult) => void;
onCancel?: () => void;
}
export function SponsorForm({ onSuccess, onCancel }: SponsorFormProps) {
export function SponsorForm({ basePricePerDay, onSuccess, onCancel }: SponsorFormProps) {
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const [linkUrl, setLinkUrl] = useState("");
@@ -26,7 +25,7 @@ export function SponsorForm({ onSuccess, onCancel }: SponsorFormProps) {
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const priceSats = useMemo(() => calculatePrice(durationDays), [durationDays]);
const priceSats = useMemo(() => calculatePrice(basePricePerDay, durationDays), [basePricePerDay, durationDays]);
const handleSubmit = useCallback(
async (e: React.FormEvent) => {

View File

@@ -1,6 +1,6 @@
import { useState, useEffect } from "react";
import { useSearchParams } from "react-router-dom";
import { getSponsorList, getToken, postSponsorExtend } from "../api";
import { getSponsorList, getSponsorPrice, getToken, postSponsorExtend } from "../api";
import { SponsorCard } from "../components/SponsorCard";
import { SponsorForm } from "../components/SponsorForm";
import { SponsorInvoiceModal } from "../components/SponsorInvoiceModal";
@@ -27,6 +27,7 @@ export function SponsorsPage() {
const [extendDuration, setExtendDuration] = useState(30);
const [extendLoading, setExtendLoading] = useState(false);
const [extendError, setExtendError] = useState<string | null>(null);
const [basePricePerDay, setBasePricePerDay] = useState<number | null>(null);
useEffect(() => {
document.title = "Sponsors — Sats Faucet";
@@ -41,6 +42,12 @@ export function SponsorsPage() {
.finally(() => setLoading(false));
};
useEffect(() => {
getSponsorPrice()
.then((p) => setBasePricePerDay(p.baseSponsorPricePerDay))
.catch(() => setBasePricePerDay(200)); // fallback if fetch fails
}, []);
useEffect(() => {
let cancelled = false;
setLoading(true);
@@ -127,7 +134,9 @@ export function SponsorsPage() {
<div className="sponsors-pricing">
<h3>Pricing</h3>
<p>Base: 200 sats/day. Discounts: 30+ days 10% off, 90+ days 20% off, 180+ days 30% off.</p>
<p>
Base: {basePricePerDay != null ? basePricePerDay.toLocaleString() : "…"} sats/day. Discounts: 30+ days 10% off, 90+ days 20% off, 180+ days 30% off.
</p>
</div>
<div className="sponsors-cta-wrap">
@@ -158,10 +167,15 @@ export function SponsorsPage() {
</section>
<Modal open={formOpen} onClose={() => setFormOpen(false)} title="Create Sponsor" variant="sponsor">
<SponsorForm
onSuccess={handleCreateSuccess}
onCancel={() => setFormOpen(false)}
/>
{basePricePerDay != null ? (
<SponsorForm
basePricePerDay={basePricePerDay}
onSuccess={handleCreateSuccess}
onCancel={() => setFormOpen(false)}
/>
) : (
<div className="sponsors-loading">Loading pricing</div>
)}
</Modal>
<SponsorInvoiceModal
@@ -190,7 +204,12 @@ export function SponsorsPage() {
<div className="sponsor-form-price">
<span className="sponsor-form-price-label">Total:</span>
<strong className="sponsor-form-price-value">
{Math.round(200 * extendDuration * (extendDuration >= 180 ? 0.7 : extendDuration >= 90 ? 0.8 : extendDuration >= 30 ? 0.9 : 1)).toLocaleString()} sats
{basePricePerDay != null
? Math.round(
basePricePerDay * extendDuration * (extendDuration >= 180 ? 0.7 : extendDuration >= 90 ? 0.8 : extendDuration >= 30 ? 0.9 : 1)
).toLocaleString()
: "…"}{" "}
sats
</strong>
</div>
{extendError && <p className="sponsor-form-error" role="alert">{extendError}</p>}