BSMNT Commerce Toolkit

repository·main·Indexed 43 days ago

https://github.com/basementstudio/commerce-toolkit

A suite of specialized packages for building high-performance e-commerce storefronts targeting Shopify and GraphQL architectures. Includes @bsmnt/storefront-hooks for client-side state and cart management, @bsmnt/sdk-gen for generating type-safe GraphQL SDKs via Genql, and @bsmnt/drop for implementing countdown systems and merchandise drop overlays.

Tokens
11.1K
Snippets
32
Records
43
Agent score
54%

What's inside BSMNT Commerce Toolkit

  1. Overview of BSMNT Commerce Toolkit

    main

    The BSMNT Commerce Toolkit is a collection of packages designed to help developers build reliable, high-traffic storefronts. It currently consists of three main packages:

    1. @bsmnt/storefront-hooks: React Hooks for managing client-side storefront state (like carts) using @tanstack/react-query and localStorage.
    2. @bsmnt/sdk-gen: A CLI tool that generates a type-safe GraphQL SDK from your queries and mutations.
    3. @bsmnt/drop: Helpers for managing countdowns, typically used for merchandise drops.

    These packages can be used together or independently.

  2. Architecture of the nextjs-shopify example

    main

    The nextjs-shopify example demonstrates how three core BSMNT packages compose to create a functional commerce application:

    1. @bsmnt/sdk-gen: Used to hook into the Shopify Storefront API. In this example, the implementation is located in /src/storefront/sdk-gen.
    2. @bsmnt/storefront-hooks: Provides client-side hooks for managing commerce state, such as cart management. Implementation is located in /src/storefront/hooks.
    3. @bsmnt/drop: Used to implement UI overlays, such as a drop countdown, in front of the application. Implementation is located in /src/pages/_app.
  3. Configure and run @bsmnt/sdk-gen

    main

    The @bsmnt/sdk-gen CLI generates a type-safe GraphQL SDK using Genql.

    1. Configuration

    Create a config.js file in your target directory (default is ./sdk-gen/). The configuration object must include an endpoint and optional headers.

    /**
     * @type {import("@bsmnt/sdk-gen").Config}
     */
    module.exports = {
      endpoint: '',
      headers: {}
    }

    2. Running the Generator

    Run the generator using pnpm sdk-gen. By default, it looks for config.js and all .{graphql,gql} files within the ./sdk-gen/ directory.

    To use a custom directory, use the --dir flag:

    pnpm sdk-gen --dir ./my-custom/directory

    3. Using the Generated SDK

    After generation, you will see a generated/ folder and an sdk.ts file. You can use the exported bsmntSdk to make type-safe GraphQL calls without needing the graphql package as a production dependency.

    // ./sdk-gen/config.js
    
    /**
     * @type {import("@bsmnt/sdk-gen").Config}
     */
    module.exports = {
      endpoint: '',
      headers: {}
    }

    // To run: pnpm sdk-gen

    // To run with custom directory: pnpm sdk-gen --dir ./my-custom/directory

    // Usage in your code: import config from './config' import { createSdk } from './generated'

    export const bsmntSdk = createSdk(config)

  4. Setup the nextjs-shopify example

    main

    To run the nextjs-shopify example, you must first have a Shopify Storefront App configured. This example demonstrates the integration of several BSMNT packages to build a commerce storefront.

    Prerequisites

    • A Shopify Storefront App (follow Shopify's guide to create one).
    • pnpm installed on your system.

    Installation and Development

    1. Install pnpm globally if you haven't already:

      npm install -g pnpm
    2. Install project dependencies:

      pnpm
    3. Start the development server:

      pnpm dev
    npm install -g pnpm
    pnpm
    pnpm dev
  5. Use @bsmnt/drop for countdown functionality

    main

    The @bsmnt/drop package provides a countdown system powered by a Zustand store.

    Core API

    • CountdownProvider: A Context Provider that manages the CountdownStore state.
    • useCountdownStore: A hook to consume the CountdownStore context. Since it uses Zustand, you can pass a selector function to slice the state.
    • zeroPad: A utility function to pad numbers with zeroes.

    Implementation Steps

    1. Wrap your application (or a specific subtree) with CountdownProvider.
    2. Provide configuration props such as endDate, startDate, countdownChildren, and an optional exitDelay.
    3. Consume the state in your components using useCountdownStore() with a selector to access properties like humanTimeRemaining.
    // 1. Setup the Provider (e.g., in Next.js _app.tsx)
    import { CountdownProvider } from '@bsmnt/drop'
    
    export default function App({ Component, pageProps }) {
      return (
        <CountdownProvider
          endDate={Date.now() + 1000 * 5}
          countdownChildren={<Countdown />}
          exitDelay={1000}
          startDate={Date.now()}
        >
          <Component {...pageProps} />
        </CountdownProvider>
      )
    }
    
    // 2. Consume the store in a component
    import { useCountdownStore } from '@bsmnt/drop'
    
    export const Countdown = () => {
      const humanTimeRemaining = useCountdownStore(
        (state) => state.humanTimeRemaining
      )
    
      return (
        <ul>
          <li>Days: {humanTimeRemaining.days}</li>
          <li>Seconds: {humanTimeRemaining.seconds}</li>
        </ul>
      )
    }
  6. Understand MutationError and MutationSuccess shapes

    main

    Commerce mutations in the toolkit return discriminated union types for success and error states. These types are generic and depend on the Cart type provided.

    MutationError

    An error object containing the type of the failed operation and the error object itself.

    MutationSuccess

    A success object containing the type of the successful operation and the resulting data (the updated Cart).

    Supported operation types include:

    • fetchCart
    • createCart
    • createCartWithLines
    • addLineItem
    • updateLineItem
    • removeLineItem
    export type MutationError<Cart extends BarebonesCart> = {
      type: keyof Pick<
        STOREFRONT_EVENT_MAP<Cart>,
        | 'addLineItemError'
        | 'fetchCartError'
        | 'createCartError'
        | 'removeLineItemError'
        | 'updateLineItemError'
      >
      error: Error
    }
    
    export type MutationSuccess<Cart extends BarebonesCart> = {
      type: keyof Pick<
        STOREFRONT_EVENT_MAP<Cart>,
        | 'addLineItemSuccess'
        | 'fetchCartSuccess'
        | 'createCartSuccess'
        | 'createCartWithLinesSuccess'
        | 'removeLineItemSuccess'
        | 'updateLineItemSuccess'
      >
      data: Cart | null | undefined
    }
  7. Handle SSR hydration mismatches with @bsmnt/drop

    main

    Because the countdown values (like humanTimeRemaining.seconds) change every second, rendering them directly in a Server-Side Rendered (SSR) environment like Next.js will likely cause a hydration mismatch between the server's static HTML and the client's dynamic state.

    Option 1: Suppress Hydration Warning

    If you want to render the values immediately, you can use the suppressHydrationWarning attribute on the specific elements containing the time values.

    To avoid mismatch risks entirely, use a useEffect hook to ensure the component has mounted on the client before rendering the real-time values. You can provide a fallback value (e.g., '59') for the initial server render.

    import { useEffect, useState } from 'react'
    import { useCountdownStore } from '@bsmnt/drop'
    
    const Countdown = () => {
      const humanTimeRemaining = useCountdownStore((state) => state.humanTimeRemaining)
      const [hasRenderedOnce, setHasRenderedOnce] = useState(false)
    
      useEffect(() => {
        setHasRenderedOnce(true)
      }, [])
    
      return (
        <ul>
          <li>Minutes: {hasRenderedOnce ? humanTimeRemaining.minutes : '59'}</li>
          <li>Seconds: {hasRenderedOnce ? humanTimeRemaining.seconds : '59'}</li>
        </ul>
      )
    }