
Bem -vindo ao BSMNT Commerce Toolkit : Pacotes para ajudá -lo a enviar melhores fachadas de lojas, mais rápido e com mais confiança.
Este kit de ferramentas nos ajudou - Basement.studio - fachadas de lojas confiáveis que poderiam lidar com quantidades loucas de tráfego. Alguns deles incluem: shopmrbeast.com, karljacobs.co, shopmrballen.com e ranboo.fashion.
Se você está procurando um exemplo com o Next.js + Shopify, confira nosso exemplo aqui.
Atualmente, este repositório possui três pacotes:
@bsmnt/storefront-hooks : React Hooks para gerenciar o estado do lado do cliente da loja.
@tanstack/react-query e localStorage @bsmnt/sdk-gen : Uma CLI que gera um SDK grafql seguro para tipo.
graphql para produção @bsmnt/drop : ajudantes para gerenciar uma contagem regressiva. Geralmente usado para criar hype em torno de uma queda de mercadoria.
Eles jogam muito bem juntos, mas também podem ser usados separadamente. Vamos ver como eles funcionam!
@bsmnt/storefront-hooks yarn add @bsmnt/storefront-hooks @tanstack/react-queryEste pacote exporta:
createStorefrontHooks : Função que cria os ganchos necessários para interagir com o carrinho. 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
} )Dê uma olhada em alguns exemplos:
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 hereEste exemplo depende 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 Este pacote instala uma CLI com um único comando: generate . Executando -o atingirá seu terminal do GraphQL e gerará tipos de texto de texto a partir de suas consultas e mutações. É alimentado pelo GenQL, por isso não deixe de conferir seus documentos.
# 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 : { }
}E então você pode executar o gerador:
yarn sdk-gen Isso olhará para dentro ./sdk-gen/ para um arquivo config.js e para todos os seus arquivos .{graphql,gql} nesse diretório.
Se você deseja usar um diretório personalizado (e não o padrão, que é ./sdk-gen/ ), você pode usar o argumento --dir .
yarn sdk-gen --dir ./my-custom/directoryDepois de executar o gerador, você deve obter o seguinte resultado:
.
└── sdk-gen/
├── config.js
├── documents.gql
├── generated/ # <- generated
│ ├── index.ts
│ └── graphql.schema.json
└── sdk.ts # <- generated Dentro sdk.ts , você terá o bsmntSdk sendo exportado:
import config from './config'
import { createSdk } from './generated'
export const bsmntSdk = createSdk ( config )E isso é tudo. Você deve usar isso para atingir sua API GraphQL de maneira segura do tipo.
Um benefício adicional é que este SDK não depende do graphql . Muitos clientes GraphQL exigem isso como uma dependência de pares (por exemplo, graphql-request ), que adiciona KBs importantes ao pacote.
↳ Para uma maneira padrão de usar isso com a API Shopify Storefront, dê uma olhada no nosso exemplo com o Next.js + Shopify.
@bsmnt/drop yarn add @bsmnt/dropEste pacote exporta:
CountdownProvider : provedor de contexto para o CountdownStoreuseCountdownStore : Hook que consome o contexto CountdownProvider e retorna o CountdownStorezeroPad : utilidade para encaixar um número com zeros Para usar, basta embrulhar o CountdownProvider onde quiser adicionar sua contagem regressiva. Por exemplo, com o 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 >
)
}E então sua contagem regressiva pode parecer algo como:
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 >
)
} Se você renderizar humanTimeRemaining.seconds .
Na maioria dos casos, você pode suppressHydrationWarning com segurança owarning (consulte a edição 21 para obter mais informações):
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 >
)
}Se você não quer correr esse risco, uma opção mais segura está esperando até que seu aplicativo seja hidratado antes de renderizar os tempo real:
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 >
)
} Alguns exemplos para você começar:
localStorage Solicitações de tração são bem -vindas. Questões são bem -vindas. Para grandes mudanças, abra um problema primeiro para discutir o que você gostaria de mudar.
Mit