@teamlunaris/easypoints-hydrogen
v0.1.0
Published
Headless React building blocks and a server-side client to integrate the easyPoints loyalty program into Shopify Hydrogen storefronts.
Downloads
1,143
Readme
@teamlunaris/easypoints-hydrogen
Headless React building blocks and a server-side client that integrate the easyPoints loyalty program into Shopify Hydrogen storefronts.
- Server loyalty client: holds the API token, runs in Hydrogen loaders/actions, talks to the easyPoints REST API and reads loyalty data from Shopify metafields.
- React hooks + provider: cart points, redemption, tier progress, customer loyalty.
- Headless (unstyled) components: render-prop building blocks; you own all markup and styles.
- TypeScript types: validated at the trust boundary with Valibot and exported for consumers.
Install
Use whichever package manager your Hydrogen app uses:
npm install @teamlunaris/easypoints-hydrogenpnpm add @teamlunaris/easypoints-hydrogenPeer dependencies, provided by your Hydrogen app: @shopify/hydrogen, react, react-router
(and react-dom if your usage needs it).
Requirements: Hydrogen 2026.x · React Router 7.12+ · React 19 · Node ≥ 22.13.
Entry points
The package splits along the browser/server trust boundary. Respect it:
| Import | Environment | Contents |
| ----------------------------------------- | --------------- | --------------------------------------------------------------------------------------------------------------------- |
| @teamlunaris/easypoints-hydrogen | browser + SSR | isomorphic: domain types, tier logic, and the headless render-prop components |
| @teamlunaris/easypoints-hydrogen/client | browser + SSR | React hooks (useCartPoints, useCartRedemption, useTierProgress, useCustomerLoyalty) + EasyPointsProvider |
| @teamlunaris/easypoints-hydrogen/server | server only | loyalty client (createEasyPointsClient), cart-points route action, product/customer/shop queries, GraphQL fragments |
| @teamlunaris/easypoints-hydrogen/types | types only | TypeScript types for loyalty data |
The
/serverentry holdsEASY_POINTS_API_TOKENand throws if imported in the browser. Never import it from client code. The root and/cliententries are safe everywhere.
Quickstart
The wiring is four steps. See the getting-started guide for the full
walkthrough and examples/storefront for a complete working app.
1. Mount the client on the Hydrogen context (app/lib/context.ts): add it to the
additional-context object passed to createHydrogenContext(...), then init() it:
import {
createEasyPointsClient,
type EasyPointsClient,
} from "@teamlunaris/easypoints-hydrogen/server";
interface LoyaltyContext {
loyalty: EasyPointsClient;
}
declare global {
interface HydrogenAdditionalContext extends LoyaltyContext {}
interface Env {
EASY_POINTS_API_TOKEN?: string;
EASY_POINTS_API_ENDPOINT?: string;
}
}
const additionalContext: LoyaltyContext = {
loyalty: createEasyPointsClient({
cache,
waitUntil,
request,
token: env.EASY_POINTS_API_TOKEN ?? "",
endpoint: env.EASY_POINTS_API_ENDPOINT, // optional, defaults to https://loyalty.slrs.io/api
}),
};
const hydrogenContext = createHydrogenContext(
{
/* env, request, cache, session, cart… */
},
additionalContext,
);
hydrogenContext.loyalty.init(hydrogenContext); // bind storefront + customer-account handles
return hydrogenContext;2. Add the cart-points resource route at /api/cart/points (app/routes/api.cart.points.tsx).
Import /server at module scope. React Router's automatic code splitting
removes server-only route exports (action/loader) — and their now-unused imports — from the
client bundle, so /server never reaches the browser and its server-only guard never runs there:
import { createCartPointsAction } from "@teamlunaris/easypoints-hydrogen/server";
import type { Route } from "./+types/api.cart.points";
export async function action(args: Route.ActionArgs) {
const handleAction = createCartPointsAction(); // optional `lineFilter` to exclude cart lines
return handleAction<Route.ActionArgs>(args);
}3. Wrap your app in the provider (app/root.tsx) to share currency + route config:
import { EasyPointsProvider } from "@teamlunaris/easypoints-hydrogen/client";
<EasyPointsProvider currencyCode="USD">{/* app */}</EasyPointsProvider>;4. Render headless components / use hooks. All components are unstyled and expose data via render props; the markup is yours:
import { CustomerLoyalty, TierProgress, CartRedemption } from "@teamlunaris/easypoints-hydrogen";
import { useCartPoints } from "@teamlunaris/easypoints-hydrogen/client";
<CustomerLoyalty loyalty={loyalty}>
{({ balance, tier }) => (
<p>
{balance} pts · {tier?.name}
</p>
)}
</CustomerLoyalty>;
const { totalPoints } = useCartPoints(cart);Documentation
- Getting started: step-by-step Hydrogen integration.
examples/storefront: a complete Hydrogen app using every entry point.- API reference: published to JSR, generated from the source doc comments.
