124 lines
4.0 KiB
JavaScript
124 lines
4.0 KiB
JavaScript
import { useEffect, useState } from "react";
|
|
import SharedNavbar from "../components/SharedNavbar";
|
|
import { formatChf } from "../shop/money";
|
|
import { useShop } from "../shop/useShop";
|
|
import "./SmallBatchPage.css";
|
|
|
|
function Requirement({ label, met, children }) {
|
|
return (
|
|
<div className="small-requirement">
|
|
<span>{label}</span>
|
|
<strong className={met ? "met" : ""}>{children || (met ? "met" : "open")}</strong>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function SmallBatchPage() {
|
|
const { openProfile, token, user } = useShop();
|
|
const [state, setState] = useState({
|
|
loading: false,
|
|
error: "",
|
|
loyaltyStatus: user?.loyaltyStatus || null,
|
|
releases: [],
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (!token) return;
|
|
|
|
fetch("/api/small-batch", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
})
|
|
.then(async (response) => {
|
|
const data = await response.json().catch(() => ({}));
|
|
if (!response.ok) throw new Error(data.error || "Small Batch request failed.");
|
|
setState({
|
|
loading: false,
|
|
error: "",
|
|
loyaltyStatus: data.loyaltyStatus,
|
|
releases: data.releases || [],
|
|
});
|
|
})
|
|
.catch((error) => {
|
|
setState((current) => ({
|
|
...current,
|
|
loading: false,
|
|
error:
|
|
error instanceof Error
|
|
? error.message
|
|
: "Shop API unreachable. Start it with npm run dev and try again.",
|
|
}));
|
|
});
|
|
}, [token, user?.loyaltyStatus]);
|
|
|
|
const loyalty = state.loyaltyStatus || {
|
|
hasDiscoverySet: false,
|
|
hasFullSize: false,
|
|
purchases: 0,
|
|
spent_cents: 0,
|
|
unlocked: false,
|
|
};
|
|
|
|
return (
|
|
<div className="small-page">
|
|
<SharedNavbar variant="light" />
|
|
|
|
<main className="small-shell">
|
|
<section className="small-hero">
|
|
<span className="small-kicker">SMALL BATCH / ARCHIVE / PROTOTYPE</span>
|
|
<h1>EARLY ACCESS</h1>
|
|
<p>
|
|
Limited releases are reserved for customers with enough purchase
|
|
history to understand the atmos material language.
|
|
</p>
|
|
</section>
|
|
|
|
{!user ? (
|
|
<section className="small-panel">
|
|
<span className="small-kicker">LOGIN REQUIRED</span>
|
|
<h2>Sign in to check access.</h2>
|
|
<p>Small Batch access is calculated from your completed orders.</p>
|
|
<button type="button" onClick={openProfile}>
|
|
Login / Register
|
|
</button>
|
|
</section>
|
|
) : (
|
|
<>
|
|
<section className="small-panel">
|
|
<span className="small-kicker">ACCESS STATUS</span>
|
|
<h2>{loyalty.unlocked ? "Unlocked" : "Locked"}</h2>
|
|
<div className="small-requirements">
|
|
<Requirement label="Discovery Set" met={loyalty.hasDiscoverySet} />
|
|
<Requirement label="Full Size" met={loyalty.hasFullSize} />
|
|
<Requirement label="Purchases" met={loyalty.purchases >= 3}>
|
|
{loyalty.purchases}/3 Purchases
|
|
</Requirement>
|
|
<Requirement label="Spend" met={loyalty.spent_cents > 50000}>
|
|
{formatChf(loyalty.spent_cents)} / CHF 500+
|
|
</Requirement>
|
|
</div>
|
|
</section>
|
|
|
|
{state.error && <p className="small-error">{state.error}</p>}
|
|
{state.loading && <p className="small-error">Loading access...</p>}
|
|
|
|
{loyalty.unlocked && (
|
|
<section className="release-grid">
|
|
{state.releases.map((release) => (
|
|
<article className="release-card" key={release.name}>
|
|
<span>{release.type}</span>
|
|
<h3>{release.name}</h3>
|
|
<p>{release.note}</p>
|
|
<button type="button">Request Allocation</button>
|
|
</article>
|
|
))}
|
|
</section>
|
|
)}
|
|
</>
|
|
)}
|
|
</main>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default SmallBatchPage;
|