
欢迎来到BSMNT Commerce Toolkit :包装,以帮助您更快,更自信地运送更好的店面。
这个工具包帮助我们 - Basement.studio-可靠的店面,可以处理疯狂的流量。其中一些包括:shopmrbeast.com,karljacobs.co,shopmrballen.com和ranboo.fashion。
如果您正在寻找Next.js + Shopify的示例,请在此处查看我们的示例。
该存储库目前包含三个包:
@bsmnt/storefront-hooks :React Hooks以管理店面客户端状态。
@tanstack/react-query和localStorage的帮助下管理整个购物车生命周期@bsmnt/sdk-gen :生成类型安全的graphql sdk的CLI。
graphql进行生产@bsmnt/drop :用于管理倒计时的帮助者。通常用于围绕商品滴炒作。
这些播放非常好,但也可以单独使用。让我们看看它们的工作方式!
@bsmnt/storefront-hooks yarn add @bsmnt/storefront-hooks @tanstack/react-query此软件包导出:
createStorefrontHooks :创建与购物车相互作用所需的钩子的函数。 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
} )看看一些例子:
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 here此示例取决于 @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此软件包将带有单个命令的CLI安装: generate 。运行它将击中您的GraphQl端点,并从查询和突变中生成打字稿类型。它由GenQL提供动力,因此请务必查看他们的文档。
# 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 : { }
}然后您可以运行发电机:
yarn sdk-gen这将查看内部./sdk-gen/中的config.js文件,以及您的所有内容.{graphql,gql}
如果要使用自定义目录(而不是默认值为./sdk-gen/ ),则可以使用--dir参数。
yarn sdk-gen --dir ./my-custom/directory运行发电机后,您应该得到以下结果:
.
└── sdk-gen/
├── config.js
├── documents.gql
├── generated/ # <- generated
│ ├── index.ts
│ └── graphql.schema.json
└── sdk.ts # <- generated在sdk.ts中,您将导出bsmntSdk :
import config from './config'
import { createSdk } from './generated'
export const bsmntSdk = createSdk ( config )仅此而已。您应该能够以一种安全的方式使用它来击中GraphQl API。
另一个好处是,此SDK不依赖graphql 。许多GraphQl客户端要求它作为对等依赖性(例如graphql-request ),这为捆绑包增加了重要的KBS。
↳对于Shopify Storefront API的标准方法,请与Next.js + Shopify一起查看我们的示例。
@bsmnt/drop yarn add @bsmnt/drop此软件包导出:
CountdownProvider : CountdownStore的上下文提供商useCountdownStore :消耗CountdownProvider上下文并返回CountdownStore挂钩zeroPad :实用程序可以用零填充一个数字要使用,只需在要添加倒计时的任何地方包装CountdownProvider提供者即可。例如,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 >
)
}然后,您的倒计时可能看起来像:
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 >
)
}如果您呈现humanTimeRemaining.seconds ,则您的服务器很有可能会渲染与客户不同的东西,因为该值每秒都会更改。
在大多数情况下,您可以安全地suppressHydrationWarning (有关更多信息,请参见第21期):
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 >
)
}如果您不想承担这种风险,那么一个更安全的选项等到您的应用程序水合之前,才能剩下实时时间:
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 >
)
} 一些示例可以让您入门:
localStorage 欢迎拉动请求。欢迎问题。对于重大更改,请先开设一个问题,以讨论您想更改的内容。
麻省理工学院