@dtfgangsheetapp/hydrogen
v1.0.2
Published
DTF Gang Sheet Builder for Shopify Hydrogen, Next.js Commerce, Remix, and any React-based headless storefront.
Downloads
348
Maintainers
Readme
@dtfgangsheetapp/hydrogen
React components for embedding the DTF Gang Sheet Builder in any headless Shopify storefront — Hydrogen, Next.js Commerce, Remix, Gatsby, or custom React apps.
Zero runtime dependencies except React. Inline styles, Shadow DOM-safe, SSR-compatible, fully typed.
Installation
npm install @dtfgangsheetapp/hydrogen
# or
pnpm add @dtfgangsheetapp/hydrogen
# or
yarn add @dtfgangsheetapp/hydrogenPeer dependencies: react >= 17, react-dom >= 17.
Quick start
1. Wrap your app with GangSheetProvider
import { GangSheetProvider } from '@dtfgangsheetapp/hydrogen';
function App({ children }) {
return (
<GangSheetProvider
config={{
embedKey: process.env.NEXT_PUBLIC_DTFGSA_EMBED_KEY!,
onOrderComplete: async (order) => {
// Your cart/checkout integration
const res = await fetch('/api/gangsheet/checkout', {
method: 'POST',
body: JSON.stringify(order),
});
const { invoiceUrl } = await res.json();
return { redirectUrl: invoiceUrl };
},
}}
>
{children}
</GangSheetProvider>
);
}2. Add the button to product pages
import { GangSheetButton } from '@dtfgangsheetapp/hydrogen';
function ProductPage({ product }) {
return (
<>
<h1>{product.title}</h1>
{/* ... your normal add-to-cart ... */}
<GangSheetButton
productId={product.id}
variantId={product.variants[0]?.id}
productHandle={product.handle}
/>
</>
);
}That's it. Customer clicks the button, a modal opens with the builder, they design their gang sheet, and your onOrderComplete handler is called.
Get an embed key
- Sign up at builder.dtfgangsheetapp.com
- Dashboard → Integrations → Generate Embed Key
- Copy the
ek_...key, save it asDTFGSA_EMBED_KEYin your env vars
Free tier includes 5 m² of gang sheets per month. Paid plans start at $9/month.
Complete guides
API reference
<GangSheetProvider>
Root context provider. Must wrap any components that use the builder.
<GangSheetProvider
config={{
embedKey: string, // required — get from dashboard
apiUrl?: string, // default: 'https://api.dtfgangsheetapp.com'
builderUrl?: string, // default: 'https://builder.dtfgangsheetapp.com'
buttonText?: string, // default: 'Create a DTF Gang Sheet'
buttonColor?: string, // default: '#2563eb'
buttonTextColor?: string, // default: '#ffffff'
mode?: 'modal' | 'page' | 'inline', // default: 'modal'
replaceCart?: boolean, // default: false
currency?: string, // default: 'USD'
customer?: {
email?: string,
id?: string,
name?: string,
},
onOrderComplete: (order) => Promise<{ redirectUrl?: string }>, // your cart integration
onClose?: () => void,
onError?: (error: Error) => void,
}}
fetchRemoteConfig={true} // fetch subscriber's preferences from backend
>
{children}
</GangSheetProvider><GangSheetButton>
Renders a button that opens the builder modal when clicked.
<GangSheetButton
text?="Custom button text" // overrides provider default
color?="#ff6600" // overrides provider default
textColor?="#ffffff" // overrides provider default
className?="my-custom-class"
style?={{ marginTop: 12 }}
productId?={product.id} // passes to builder for context
variantId?={variant.id}
productHandle?={product.handle}
// Render-prop for custom button UI:
render?={({ onClick, text }) => (
<button onClick={onClick} className="my-button">
✨ {text}
</button>
)}
/><GangSheetModal>
If you want programmatic control over when the modal opens, use this directly.
const [open, setOpen] = useState(false);
<button onClick={() => setOpen(true)}>Open manually</button>
<GangSheetModal
open={open}
onClose={() => setOpen(false)}
productId={product.id}
/>useGangSheetConfig()
Hook that returns the resolved config from the provider.
function MyComponent() {
const config = useGangSheetConfig();
return <div>Button text: {config.buttonText}</div>;
}useHydrogenCart() (Hydrogen-specific subpath)
Returns an onOrderComplete handler suitable for Hydrogen stores.
import { useHydrogenCart } from '@dtfgangsheetapp/hydrogen/hydrogen';
const handleOrder = useHydrogenCart({
shop: 'my-store.myshopify.com',
mode: 'draft-order', // recommended — creates a draft order and redirects
});
<GangSheetProvider config={{ embedKey, onOrderComplete: handleOrder }}>
...
</GangSheetProvider>Draft-order mode (default): Calls our backend, which creates a Shopify draft order with the exact gang sheet price. Returns the invoice URL — customer gets redirected there to pay. Simplest + most reliable.
Cart-add mode: Adds a carrier variant to Hydrogen's cart with gang sheet attributes. Requires:
- A $0.01 carrier variant in your Shopify store (create via Admin API)
- A Shopify Function (delivery or discount) to override line totals based on the
Calculated Priceattribute
For most stores, draft-order mode is the right choice.
notifyOrder() (server-side)
After a Hydrogen/Next.js order is confirmed on your end, call this to mark the credit reservation as confirmed on our backend.
import { notifyOrder } from '@dtfgangsheetapp/hydrogen';
// In your order-confirmation webhook or API route:
await notifyOrder(embedKey, {
orderId: order.orderId, // gang sheet order ID (from the customer's design session)
reservation: order.reservation, // reservation code
platform: 'hydrogen',
event: 'paid',
externalOrderId: shopifyOrderId,
total: order.price,
currency: order.currency,
customerEmail: customer.email,
});How it works under the hood
<GangSheetButton>renders a styled<button>that opens<GangSheetModal>on click.<GangSheetModal>renders an overlay with an iframe pointing tobuilder.dtfgangsheetapp.com/?embed=1&key=....- Customer designs their gang sheet inside the iframe.
- When customer clicks "Add to cart" or "Checkout" in the builder, the iframe sends a
postMessageto the parent window. - The modal listens for messages with
origin === builderUrl, parses the payload into aGangSheetOrderobject, and calls youronOrderCompletehandler. - Your handler does whatever it needs — create a draft order, add to Hydrogen cart, etc. — and returns
{ redirectUrl }. - The modal either redirects (if URL provided) or closes.
All origin validation is strict — postMessage events from any other origin are ignored. CSS is fully self-contained via inline styles. Modal is rendered via React portal into document.body.
SSR / hydration safety
All components are SSR-safe:
<GangSheetProvider>doesn't touchwindowduring render;fetchEmbedConfigonly runs inuseEffect<GangSheetModal>returnsnullifdocumentis undefined (preventing SSR mismatch)- No hydration warnings — button renders identically on server and client
TypeScript
Full types exported. Every component, hook, and helper is typed.
import type {
GangSheetConfig,
GangSheetOrder,
OrderCompleteResult,
GangSheetButtonProps,
} from '@dtfgangsheetapp/hydrogen';Customization
Custom button styling (bring your own CSS)
<GangSheetButton
render={({ onClick, text }) => (
<button onClick={onClick} className="tw-btn tw-btn-primary tw-w-full">
{text}
</button>
)}
/>Custom modal (fully replace)
Don't use <GangSheetButton> — instead use <GangSheetModal> directly or build your own iframe host. See src/components/GangSheetModal.tsx for reference.
Translations
<GangSheetProvider
config={{
embedKey,
buttonText: t('gangsheet.button'), // your i18n library
}}
>
<GangSheetButton />
</GangSheetProvider>Browser support
- Chrome/Edge 90+
- Firefox 88+
- Safari 14+
- iOS Safari 14+
- Android Chrome 90+
Troubleshooting
"useGangSheetConfig must be called inside "
The button/hook is being used outside the provider. Make sure <GangSheetProvider> wraps the component tree that contains <GangSheetButton>.
Modal doesn't close after order
Make sure your onOrderComplete handler returns (resolves). If it throws, the modal stays open showing an error.
Button appears but clicking does nothing
Check browser console for CORS errors. The builder iframe must load from builder.dtfgangsheetapp.com — if you've proxied or firewalled it, the iframe won't load.
"Invalid embed key" errors
Visit the dashboard to regenerate your key, or verify the key is set correctly in env vars.
License
MIT © DTFGSA Inc. See LICENSE.
Support
- Docs: dtfgangsheetapp.com/docs/hydrogen
- Issues: github.com/dtfgsa/hydrogen/issues
- Email: [email protected]
