JSPM

@dtfgangsheetapp/hydrogen

1.0.2
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 4
  • Score
    100M100P100Q77360F
  • License MIT

DTF Gang Sheet Builder for Shopify Hydrogen, Next.js Commerce, Remix, and any React-based headless storefront.

Package Exports

  • @dtfgangsheetapp/hydrogen
  • @dtfgangsheetapp/hydrogen/hydrogen

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/hydrogen

Peer 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

  1. Sign up at builder.dtfgangsheetapp.com
  2. Dashboard → Integrations → Generate Embed Key
  3. Copy the ek_... key, save it as DTFGSA_EMBED_KEY in 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:

  1. A $0.01 carrier variant in your Shopify store (create via Admin API)
  2. A Shopify Function (delivery or discount) to override line totals based on the Calculated Price attribute

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

  1. <GangSheetButton> renders a styled <button> that opens <GangSheetModal> on click.
  2. <GangSheetModal> renders an overlay with an iframe pointing to builder.dtfgangsheetapp.com/?embed=1&key=....
  3. Customer designs their gang sheet inside the iframe.
  4. When customer clicks "Add to cart" or "Checkout" in the builder, the iframe sends a postMessage to the parent window.
  5. The modal listens for messages with origin === builderUrl, parses the payload into a GangSheetOrder object, and calls your onOrderComplete handler.
  6. Your handler does whatever it needs — create a draft order, add to Hydrogen cart, etc. — and returns { redirectUrl }.
  7. 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 touch window during render; fetchEmbedConfig only runs in useEffect
  • <GangSheetModal> returns null if document is 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>.

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