@neowhale/storefront
v0.4.0
Published
React/Next.js SDK for WhaleTools storefronts — cart, auth, analytics, and more
Maintainers
Readme
Provides a typed API client, React hooks for cart/auth/analytics, and Next.js server utilities -- everything needed to build a headless storefront against the Whale Gateway API.
Install
npm install @neowhale/storefront zustandPeer dependencies:
| Package | Version | Required | |-------------|----------|----------| | react | >=18 | yes | | react-dom | >=18 | yes | | zustand | >=5 | yes | | next | >=14 | no |
Quick Start
1. Environment Variables
WHALE_API_KEY=your-server-side-api-key
NEXT_PUBLIC_API_URL=https://vm.whaletools.cloud
WHALE_MEDIA_SIGNING_SECRET=your-signing-secret # optional, for signed image URLs2. Root Layout
// app/layout.tsx
import { WhaleProvider } from '@neowhale/storefront/react';
import { getAllProducts } from '@neowhale/storefront/next';
export default async function RootLayout({ children }: { children: React.ReactNode }) {
const products = await getAllProducts();
return (
<html lang="en">
<body>
<WhaleProvider
products={products}
>
{children}
</WhaleProvider>
</body>
</html>
);
}3. Use Hooks in a Page
'use client';
import { useProducts, useCart } from '@neowhale/storefront/react';
export default function Shop() {
const { products } = useProducts();
const { addItem, itemCount, openCart } = useCart();
return (
<div>
<button onClick={openCart}>Cart ({itemCount})</button>
{products.map((p) => (
<div key={p.id}>
<h2>{p.name}</h2>
<button onClick={() => addItem(p.id, 1)}>Add to Cart</button>
</div>
))}
</div>
);
}4. Next.js Config (security headers)
// next.config.ts
import { withSecurityHeaders } from '@neowhale/storefront/next';
const nextConfig = {
headers: withSecurityHeaders(),
};
export default nextConfig;Entry Points
The package ships three entry points, each with ESM, CJS, and TypeScript declarations.
| Import path | Purpose |
|----------------------------------|----------------------------------|
| @neowhale/storefront | Core API client and types |
| @neowhale/storefront/react | React provider, hooks, components|
| @neowhale/storefront/next | Next.js server utilities |
API Reference -- Core
import { WhaleClient } from '@neowhale/storefront';WhaleClient
Constructor
new WhaleClient(config: WhaleStorefrontConfig)| Config field | Type | Default | Description |
|----------------------|----------|----------------------------------|------------------------------------|
| apiKey | string | -- | Optional server-side API key for direct gateway calls. Browser clients should use proxyPath. |
| gatewayUrl | string | https://vm.whaletools.cloud | Base URL of the Whale Gateway. |
| proxyPath | string | /api/gw | Local proxy route prefix. |
| mediaSigningSecret | string | -- | Secret for signed image URLs. |
| mediaHost | string | -- | Media storage host for image URLs. |
| storagePrefix | string | whale | Prefix for local storage keys. |
| sessionTtl | number | 1800000 (30 min) | Session TTL in milliseconds. |
| debug | boolean | -- | Enable debug logging. |
Products
client.listProducts(params?) // → ListResponse<Product>
client.getProduct(id) // → Product
client.getAllProducts(options?) // → Product[] (auto-paginates, default status='published', maxPages=20)Cart
client.createCart(customerEmail?) // → Cart
client.getCart(cartId) // → Cart
client.addToCart(cartId, productId, quantity, opts?) // → CartItem
client.updateCartItem(cartId, itemId, quantity) // → Cart
client.removeCartItem(cartId, itemId) // → void
client.checkout(cartId, customerEmail?, payment?) // → OrderCustomers
client.findCustomer(query) // → Customer[]
client.getCustomer(id) // → Customer
client.createCustomer(data) // → CustomerOrders
client.listOrders(params?) // → ListResponse<Order>
client.getOrder(id) // → Order
client.getCustomerOrders(customerId) // → Order[] (auto-paginates)Auth
client.sendCode(email) // → SendCodeResponse
client.verifyCode(email, code) // → VerifyCodeResponse
client.setSessionToken(token) // → void
client.getSessionToken() // → string | nullAnalytics
client.getCustomerAnalytics(customerId, customerName?) // → CustomerAnalytics | null
client.createSession(params) // → StorefrontSession
client.updateSession(sessionId, params) // → StorefrontSession
client.trackEvent(params) // → voidLocations
client.listLocations() // → ListResponse<Location>COA
client.getCOAEmbedUrl(productId) // → stringStatic Methods
WhaleClient.signMedia(secret, encodedUrl, w, q, f) // → string (signed token)
WhaleClient.encodeBase64Url(url) // → string (base64url-encoded)API Reference -- React
import {
WhaleProvider,
useCart,
useCartItemCount,
useCartTotal,
useAuth,
useProducts,
useProduct,
useAnalytics,
useCustomerOrders,
useCustomerAnalytics,
useWhaleClient,
} from '@neowhale/storefront/react';WhaleProvider
Wrap your application tree with this provider. Browser traffic goes through proxyPath; the provider intentionally does not accept apiKey.
| Prop | Type | Description | |-----------|------------|-----------------------------------------| | products | Product[] | Pre-fetched product catalog to hydrate. | | children | ReactNode | Application tree. |
Internally renders AuthInitializer, CartInitializer, and AnalyticsTracker.
useCart()
Manage cart state and operations.
| Return field | Type | |------------------|------------------------------------------------| | cartId | string | null | | items | CartItem[] | | itemCount | number | | subtotal | number | | taxAmount | number | | total | number | | taxBreakdown | object | | cartOpen | boolean | | cartLoading | boolean | | productImages | Record<string, string> | | addItem | (productId: string, qty: number) => Promise | | removeItem | (itemId: string) => Promise | | updateQuantity | (itemId: string, qty: number) => Promise | | openCart | () => void | | closeCart | () => void | | toggleCart | () => void | | initCart | () => Promise | | syncCart | () => Promise | | clearCart | () => void | | checkout | (email?: string, payment?: object) => Promise |
useCartItemCount()
const count: number = useCartItemCount();Returns the current number of items in the cart. Optimized selector -- only re-renders when the count changes.
useCartTotal()
const total: number = useCartTotal();Returns the cart total. Optimized selector -- only re-renders when the total changes.
useAuth()
Manage authentication state and OTP flow.
| Return field | Type | |------------------|---------------------------------------------------| | customer | Customer | null | | authLoading | boolean | | sessionToken | string | null | | isAuthenticated | boolean | | sendCode | (email: string) => Promise | | verifyCode | (email: string, code: string) => Promise | | restoreSession | () => Promise | | logout | () => void | | fetchCustomer | (id: string) => Promise |
useProducts(opts?)
Client-side filtering over the products hydrated by WhaleProvider.
const { products, allProducts, loading } = useProducts({ categoryId: '...', search: 'term' });| Option | Type | Description | |-------------|---------|--------------------------------| | categoryId | string | Filter by category ID. | | search | string | Filter by search term. |
Returns { products: Product[], allProducts: Product[], loading: false }.
useProduct(slug)
Look up a single product by slug from the hydrated catalog.
const { product, loading } = useProduct('blue-dream-3-5g');Returns { product: Product | null, loading: false }.
useAnalytics()
Track storefront events.
| Method | Description | |---------------------|--------------------------------------| | track() | Send a custom event. | | trackPageView() | Record a page view. | | trackProductView() | Record a product detail view. | | trackCategoryView() | Record a category page view. | | trackSearch() | Record a search query. | | trackBeginCheckout()| Record checkout initiation. | | trackPurchase() | Record a completed purchase. | | trackAddToCart() | Record an add-to-cart event. | | trackRemoveFromCart()| Record a remove-from-cart event. | | linkCustomer() | Associate session with a customer. | | getOrCreateSession()| Get or create an analytics session. |
useCustomerOrders()
Auto-fetches orders when a customer is authenticated.
const { orders, loading, refresh } = useCustomerOrders();useCustomerAnalytics()
Returns analytics for the authenticated customer.
const { analytics, loading } = useCustomerAnalytics();useWhaleClient()
Access the underlying WhaleClient instance from context.
const client = useWhaleClient();API Reference -- Next.js
import {
withSecurityHeaders,
createServerClient,
getAllProducts,
createImageLoader,
createAuthMiddleware,
} from '@neowhale/storefront/next';withSecurityHeaders(extra?)
Returns a headers() config function for next.config. Applies security headers (CSP, HSTS, etc.). Pass an optional object to merge additional headers.
// next.config.ts
export default {
headers: withSecurityHeaders({ 'X-Custom': 'value' }),
};Gateway Proxy Route
Create src/app/api/gw/[...path]/route.ts and export the SDK route handlers. This keeps the gateway API key server-side and preserves storefront cookies.
export { GET, POST, PUT, PATCH, DELETE, HEAD } from '@neowhale/storefront/next/gw-route';createServerClient(config?)
Creates a WhaleClient on the server. Reads WHALE_API_KEY and NEXT_PUBLIC_API_URL from environment variables when config is omitted.
const client = createServerClient();
const products = await client.getAllProducts();getAllProducts(options?)
Server-side product fetch with ISR caching. Default revalidate is 60 seconds.
const products = await getAllProducts(); // cached, revalidates every 60s
const products = await getAllProducts({ revalidate: 120 });createImageLoader(config)
Returns a Next.js image loader function for use with next/image. Handles media signing when a secret is provided.
import Image from 'next/image';
import { createImageLoader } from '@neowhale/storefront/next';
const loader = createImageLoader({
mediaSigningSecret: process.env.WHALE_MEDIA_SIGNING_SECRET,
mediaHost: 'vm.whaletools.cloud',
});
<Image loader={loader} src={product.imageUrl} width={400} height={400} alt="" />createAuthMiddleware(options)
Returns a Next.js middleware function for protecting routes with session-based auth.
// middleware.ts
import { createAuthMiddleware } from '@neowhale/storefront/next';
export default createAuthMiddleware({
protectedPaths: ['/account', '/orders'],
loginPath: '/login',
});
export const config = { matcher: ['/account/:path*', '/orders/:path*'] };Key Types
| Type | Description |
|----------------------|--------------------------------------------|
| WhaleStorefrontConfig| Client constructor config object. |
| Product | Product with name, slug, price, images, etc.|
| Cart | Cart with items, totals, tax breakdown. |
| CartItem | Single cart line item. |
| Customer | Customer profile. |
| Order | Completed or pending order. |
| ListResponse<T> | Paginated list: { data: T[], meta }. |
| SendCodeResponse | Response from OTP send. |
| VerifyCodeResponse | Response from OTP verify (includes token). |
| CustomerAnalytics | Aggregated analytics for a customer. |
| StorefrontSession | Analytics session object. |
| Location | Store/pickup location. |
All types are exported from the root entry point (@neowhale/storefront).
License
MIT
