
مرحبًا بك في مجموعة أدوات BSMNT Commerce : حزم لمساعدتك في شحن واجهات المتاجر بشكل أفضل ، أسرع ، وبثقة أكبر.
ساعدتنا مجموعة الأدوات هذه - 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 : CLI الذي يولد نوعًا آمنًا ، GraphQl SDK.
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 وإنشاء أنواع Typescript من استفساراتك وطفراتك. إنه مدعوم من 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 بطريقة آمنة من النوع.
فائدة إضافية هي أن SDK هذا لا يعتمد على graphql . يتطلب العديد من عملاء GRATEQL ذلك كاعتماد على الأقران (EG graphql-request ) ، والذي يضيف KBS المهمة إلى الحزمة.
↳ للحصول على طريقة قياسية لاستخدام هذا مع واجهة برمجة تطبيقات متجر Shopify ، ألق نظرة على مثالنا مع Next.js + Shopify.
@bsmnt/drop yarn add @bsmnt/dropتصدير هذه الحزمة:
CountdownProvider : موفر سياق CountdownStoreuseCountdownStore : Hook الذي يستهلك سياق CountdownProvider ويعيد CountdownStorezeroPad : فائدة لتوفير رقم مع الأصفار للاستخدام ، فقط لف 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 طلبات السحب موضع ترحيب. القضايا موضع ترحيب. للتغييرات الرئيسية ، يرجى فتح مشكلة أولاً لمناقشة ما تريد تغييره.
معهد ماساتشوستس للتكنولوجيا