
Bienvenue dans la boîte à outils BSMNT Commerce : Packages pour vous aider à expédier de meilleures vitrines, plus rapidement et avec plus de confiance.
Cette boîte à outils nous a aidés - enveloppement.Studio - des vitrines de navires qui pourraient gérer des quantités folles de trafic. Certains d'entre eux incluent: shopmrbeast.com, karljacobs.co, shopmrballen.com et ranboo.fashion.
Si vous cherchez un exemple avec Next.js + Shopify, consultez notre exemple ici.
Ce référentiel contient actuellement trois packages:
@bsmnt/storefront-hooks : React Hooks pour gérer l'état côté client.
@tanstack/react-query et localStorage @bsmnt/sdk-gen : une CLI qui génère un SDK GraphQL à sécurité.
graphql pour la production @bsmnt/drop : Aiders pour gérer un compte à rebours. Généralement utilisé pour créer un battage médiatique autour d'une goutte de merch.
Ceux-ci jouent très bien ensemble, mais peuvent également être utilisés séparément. Voyons comment ils fonctionnent!
@bsmnt/storefront-hooks yarn add @bsmnt/storefront-hooks @tanstack/react-queryCe package exporte:
createStorefrontHooks : fonction qui crée les crochets nécessaires pour interagir avec le panier. import { createStorefrontHooks } from '@bsmnt/storefront-hooks'
export const hooks = createStorefrontHooks ( {
cartCookieKey : '' , // to save cart id in cookie
fetchers : { } , // hooks will use these internally
mutators : { } , // hooks will use these internally
createCartIfNotFound : false , // defaults to false. if true, will create a cart if none is found
queryClientConfig : { } // internal query client config
} )Jetez un œil à quelques exemples:
localStorage import { createStorefrontHooks } from '@bsmnt/storefront-hooks'
type LineItem = {
merchandiseId : string
quantity : number
}
type Cart = {
id : string
lines : LineItem [ ]
}
export const {
QueryClientProvider ,
useCartQuery ,
useAddLineItemsToCartMutation ,
useOptimisticCartUpdate ,
useRemoveLineItemsFromCartMutation ,
useUpdateLineItemsInCartMutation
} = createStorefrontHooks < Cart > ( {
cartCookieKey : 'example-nextjs-localstorage' ,
fetchers : {
fetchCart : ( cartId : string ) => {
const cartFromLocalStorage = localStorage . getItem ( cartId )
if ( ! cartFromLocalStorage ) throw new Error ( 'Cart not found' )
const cart : Cart = JSON . parse ( cartFromLocalStorage )
return cart
}
} ,
mutators : {
addLineItemsToCart : ( cartId , lines ) => {
const cartFromLocalStorage = localStorage . getItem ( cartId )
if ( ! cartFromLocalStorage ) throw new Error ( 'Cart not found' )
const cart : Cart = JSON . parse ( cartFromLocalStorage )
// Add line if not exists, update quantity if exists
const updatedCart = lines . reduce ( ( cart , line ) => {
const lineIndex = cart . lines . findIndex (
( cartLine ) => cartLine . merchandiseId === line . merchandiseId
)
if ( lineIndex === - 1 ) {
cart . lines . push ( line )
} else {
cart . lines [ lineIndex ] ! . quantity += line . quantity
}
return cart
} , cart )
localStorage . setItem ( cartId , JSON . stringify ( updatedCart ) )
return {
data : updatedCart
}
} ,
createCart : ( ) => {
const cart : Cart = { id : 'cart' , lines : [ ] }
localStorage . setItem ( cart . id , JSON . stringify ( cart ) )
return { data : cart }
} ,
createCartWithLines : ( lines ) => {
const cart = { id : 'cart' , lines }
localStorage . setItem ( cart . id , JSON . stringify ( cart ) )
return { data : cart }
} ,
removeLineItemsFromCart : ( cartId , lineIds ) => {
const cartFromLocalStorage = localStorage . getItem ( cartId )
if ( ! cartFromLocalStorage ) throw new Error ( 'Cart not found' )
const cart : Cart = JSON . parse ( cartFromLocalStorage )
cart . lines = cart . lines . filter (
( line ) => ! lineIds . includes ( line . merchandiseId )
)
localStorage . setItem ( cart . id , JSON . stringify ( cart ) )
return {
data : cart
}
} ,
updateLineItemsInCart : ( cartId , lines ) => {
const cartFromLocalStorage = localStorage . getItem ( cartId )
if ( ! cartFromLocalStorage ) throw new Error ( 'Cart not found' )
const cart : Cart = JSON . parse ( cartFromLocalStorage )
cart . lines = lines
localStorage . setItem ( cart . id , JSON . stringify ( cart ) )
return {
data : cart
}
}
} ,
logging : {
onError ( type , error ) {
console . info ( { type , error } )
} ,
onSuccess ( type , data ) {
console . info ( { type , data } )
}
}
} )@bsmnt/sdk-gen # Given the following file tree:
.
└── storefront/
├── sdk-gen/
│ └── sdk.ts # generated with @bsmnt/sdk-gen
└── hooks.ts # <- we'll work hereCet exemple dépend de @ bsmnt / sdk-gen.
// ./storefront/hooks.ts
import { createStorefrontHooks } from '@bsmnt/storefront-hooks'
import { storefront } from '../sdk-gen/sdk'
import type {
CartGenqlSelection ,
CartUserErrorGenqlSelection ,
FieldsSelection ,
Cart as GenqlCart
} from '../sdk-gen/generated'
const cartFragment = {
id : true ,
checkoutUrl : true ,
createdAt : true ,
cost : { subtotalAmount : { amount : true , currencyCode : true } }
} satisfies CartGenqlSelection
export type Cart = FieldsSelection < GenqlCart , typeof cartFragment >
const userErrorFragment = {
message : true ,
code : true ,
field : true
} satisfies CartUserErrorGenqlSelection
export const {
QueryClientProvider ,
useCartQuery ,
useAddLineItemsToCartMutation ,
useOptimisticCartUpdate ,
useRemoveLineItemsFromCartMutation ,
useUpdateLineItemsInCartMutation
} = createStorefrontHooks ( {
cartCookieKey : 'example-nextjs-shopify' ,
fetchers : {
fetchCart : async ( cartId ) => {
const { cart } = await storefront . query ( {
cart : {
__args : { id : cartId } ,
... cartFragment
}
} )
if ( cart === undefined ) throw new Error ( 'Request failed' )
return cart
}
} ,
mutators : {
addLineItemsToCart : async ( cartId , lines ) => {
const { cartLinesAdd } = await storefront . mutation ( {
cartLinesAdd : {
__args : {
cartId ,
lines
} ,
cart : cartFragment ,
userErrors : userErrorFragment
}
} )
return {
data : cartLinesAdd ?. cart ,
userErrors : cartLinesAdd ?. userErrors
}
} ,
createCart : async ( ) => {
const { cartCreate } = await storefront . mutation ( {
cartCreate : {
cart : cartFragment ,
userErrors : userErrorFragment
}
} )
return {
data : cartCreate ?. cart ,
userErrors : cartCreate ?. userErrors
}
} ,
// TODO we could use the same mutation as createCart?
createCartWithLines : async ( lines ) => {
const { cartCreate } = await storefront . mutation ( {
cartCreate : {
__args : { input : { lines } } ,
cart : cartFragment ,
userErrors : userErrorFragment
}
} )
return {
data : cartCreate ?. cart ,
userErrors : cartCreate ?. userErrors
}
} ,
removeLineItemsFromCart : async ( cartId , lineIds ) => {
const { cartLinesRemove } = await storefront . mutation ( {
cartLinesRemove : {
__args : { cartId , lineIds } ,
cart : cartFragment ,
userErrors : userErrorFragment
}
} )
return {
data : cartLinesRemove ?. cart ,
userErrors : cartLinesRemove ?. userErrors
}
} ,
updateLineItemsInCart : async ( cartId , lines ) => {
const { cartLinesUpdate } = await storefront . mutation ( {
cartLinesUpdate : {
__args : {
cartId ,
lines : lines . map ( ( l ) => ( {
id : l . merchandiseId ,
quantity : l . quantity ,
attributes : l . attributes
} ) )
} ,
cart : cartFragment ,
userErrors : userErrorFragment
}
} )
return {
data : cartLinesUpdate ?. cart ,
userErrors : cartLinesUpdate ?. userErrors
}
}
} ,
createCartIfNotFound : true
} ) @bsmnt/sdk-gen yarn add @bsmnt/sdk-gen --dev Ce package installe une CLI avec une seule commande: generate . L'exécution appuyera sur votre point de terminaison GraphQL et générera des types de typeScript à partir de vos requêtes et mutations. Il est alimenté par GenQL, alors assurez-vous de consulter leurs documents.
# By default, you can have a file tree like the following:
.
└── sdk-gen/
└── config.js // ./sdk-gen/config.js
/**
* @type {import("@bsmnt/sdk-gen").Config}
*/
module . exports = {
endpoint : '' ,
headers : { }
}Et puis vous pouvez exécuter le générateur:
yarn sdk-gen Cela semblera à l'intérieur ./sdk-gen/ pour un fichier config.js , et pour tous vos fichiers .{graphql,gql} sous ce répertoire.
Si vous souhaitez utiliser un répertoire personnalisé (et non la valeur par défaut, qui est ./sdk-gen/ ), vous pouvez utiliser l'argument --dir .
yarn sdk-gen --dir ./my-custom/directoryAprès avoir exécuté le générateur, vous devriez obtenir le résultat suivant:
.
└── sdk-gen/
├── config.js
├── documents.gql
├── generated/ # <- generated
│ ├── index.ts
│ └── graphql.schema.json
└── sdk.ts # <- generated À l'intérieur sdk.ts , vous aurez l'exportation bsmntSdk :
import config from './config'
import { createSdk } from './generated'
export const bsmntSdk = createSdk ( config )Et c'est tout. Vous devriez être en mesure de l'utiliser pour appuyer sur votre API GraphQL de manière sûre.
Un avantage supplémentaire est que ce SDK ne dépend pas de graphql . De nombreux clients GraphQL l'exigent comme une dépendance par les pairs (par exemple graphql-request ), ce qui ajoute des KB importants au bundle.
↳ Pour une façon standard de l'utiliser avec l'API Shopify Storefront, jetez un œil à notre exemple avec Next.js + Shopify.
@bsmnt/drop yarn add @bsmnt/dropCe package exporte:
CountdownProvider : fournisseur de contexte pour le CountdownStoreuseCountdownStore : Hook qui consomme le contexte CountdownProvider et renvoie le CountdownStorezeroPad : Utilité pour remplir un numéro avec des zéros Pour utiliser, enveloppez simplement le CountdownProvider où vous souhaitez ajouter votre compte à rebours. Par exemple avec next.js:
// _app.tsx
import type { AppProps } from 'next/app'
import { CountdownProvider } from '@bsmnt/drop'
import { Countdown } from '../components/countdown'
export default function App ( { Component , pageProps } : AppProps ) {
return (
< CountdownProvider
endDate = { Date . now ( ) + 1000 * 5 } // set this to 5 seconds from now just to test
countdownChildren = { < Countdown /> }
exitDelay = { 1000 } // optional, just to give some time to animate the countdown before finally unmounting it
startDate = { Date . now ( ) } // optional, just if you need some kind of progress UI
>
< Component { ... pageProps } />
</ CountdownProvider >
)
}Et puis votre compte à rebours peut ressembler à quelque chose:
import { useCountdownStore } from '@bsmnt/drop'
export const Countdown = ( ) => {
const humanTimeRemaining = useCountdownStore ( ) (
( state ) => state . humanTimeRemaining // keep in mind this is zustand, so you can slice this store
)
return (
< div >
< h1 > Countdown </ h1 >
< ul >
< li > Days: { humanTimeRemaining . days } </ li >
< li > Hours: { humanTimeRemaining . hours } </ li >
< li > Minutes: { humanTimeRemaining . minutes } </ li >
< li > Seconds: { humanTimeRemaining . seconds } </ li >
</ ul >
</ div >
)
} Si vous rendez humanTimeRemaining.seconds .
Dans la plupart des cas, vous pouvez suppressHydrationWarning en toute sécurité la rémunération (voir le numéro 21 pour plus d'informations):
import { useCountdownStore } from '@bsmnt/drop'
export const Countdown = ( ) => {
const humanTimeRemaining = useCountdownStore ( ) (
( state ) => state . humanTimeRemaining // keep in mind this is zustand, so you can slice this store
)
return (
< div >
< h1 > Countdown </ h1 >
< ul >
< li suppressHydrationWarning > Days: { humanTimeRemaining . days } </ li >
< li suppressHydrationWarning > Hours: { humanTimeRemaining . hours } </ li >
< li suppressHydrationWarning > Minutes: { humanTimeRemaining . minutes } </ li >
< li suppressHydrationWarning > Seconds: { humanTimeRemaining . seconds } </ li >
</ ul >
</ div >
)
}Si vous ne voulez pas prendre ce risque, une option plus sûre attend que votre application soit hydratée avant de rendre le temps réel restant:
import { useEffect , useState } from 'react'
import { useCountdownStore } from '@bsmnt/drop'
const Countdown = ( ) => {
const humanTimeRemaining = useCountdownStore ( ) (
( state ) => state . humanTimeRemaining // keep in mind this is zustand, so you can slice this store
)
const [ hasRenderedOnce , setHasRenderedOnce ] = useState ( false )
useEffect ( ( ) => {
setHasRenderedOnce ( true )
} , [ ] )
return (
< div >
< h1 > Countdown </ h1 >
< ul >
< li > Days: { humanTimeRemaining . days } </ li >
< li > Hours: { humanTimeRemaining . hours } </ li >
< li > Minutes: { hasRenderedOnce ? humanTimeRemaining . minutes : '59' } </ li >
< li > Seconds: { hasRenderedOnce ? humanTimeRemaining . seconds : '59' } </ li >
</ ul >
</ div >
)
} Quelques exemples pour vous aider à démarrer:
localStorage Les demandes de traction sont les bienvenues. Les problèmes sont les bienvenus. Pour les changements majeurs, veuillez d'abord ouvrir un problème pour discuter de ce que vous souhaitez changer.
Mit