11<script setup lang="ts">
2- import type { ProductFieldsFragment , ProductVariantFieldsFragment } from ' #shopify/storefront'
3-
4- const props = defineProps <{
5- product: ProductFieldsFragment
6- }>()
2+ import type { ProductVariantFieldsFragment } from ' #shopify/storefront'
3+ import type { FormSubmitEvent } from ' #ui/types'
74
85const selectedVariant = defineModel <ProductVariantFieldsFragment >()
96
7+ const { language, country } = useLocalization ()
8+ const { locale } = useI18n ()
9+ const { add } = useCart ()
10+
11+ const handle = computed (() => selectedVariant .value ?.product .handle )
12+
1013const state = reactive ({
11- selectedOptions: props .product .selectedOrFirstAvailableVariant ?.selectedOptions || [],
1214 quantity: 1 ,
15+ selectedOptions: selectedVariant .value ?.selectedOptions ,
16+ })
17+
18+ const { data : product } = await useStorefrontData (` product-options-${locale .value }-${handle .value } ` , ` #graphql
19+ query FetchProductOptions($handle: String, $language: LanguageCode, $country: CountryCode, $selectedOptions: [SelectedOptionInput!])
20+ @inContext(language: $language, country: $country) {
21+ product(handle: $handle) {
22+ options(first: 250) {
23+ ...ProductOptionFields
24+ }
25+
26+ selectedOrFirstAvailableVariant(selectedOptions: $selectedOptions) {
27+ ...ProductVariantFields
28+ }
29+ }
30+ }
31+ ${IMAGE_FRAGMENT }
32+ ${PRICE_FRAGMENT }
33+ ${PRODUCT_VARIANT_FRAGMENT }
34+ ${PRODUCT_OPTION_FRAGMENT }
35+ ` , {
36+ variables: computed (() => productInputSchema .parse ({
37+ handle: handle .value ,
38+ language: language .value ,
39+ country: country .value ,
40+ selectedOptions: state .selectedOptions ,
41+ })),
42+ transform : value => value .product ,
43+ watch: [() => state .selectedOptions ],
44+ cache: ' long' ,
45+ lazy: true ,
1346})
1447
15- const onChange = () => selectedVariant .value = flattenConnection (props .product .variants )
16- .find (variant => variant .selectedOptions .every (option =>
17- state .selectedOptions .every (selectedOption =>
18- selectedOption .name === option .name && selectedOption .value === option .value ,
19- ),
20- ))
48+ const loading = ref (false )
49+
50+ watch (() => product .value ?.selectedOrFirstAvailableVariant , variant => selectedVariant .value = variant ?? undefined )
51+
52+ const onSubmit = async (event : FormSubmitEvent <typeof state >) => {
53+ if (! selectedVariant .value ) return
54+
55+ loading .value = true
56+
57+ await add (selectedVariant .value .id , event .data .quantity ).then (() => loading .value = false )
58+ }
2159 </script >
2260
2361<template >
2462 <div >
2563 <div class =" flex-col lg:flex pb-6 lg:pb-8" >
2664 <h1 class =" text-4xl lg:text-5xl font-extrabold text-gray-900 mb-4" >
27- {{ props .product?.title }}
65+ {{ selectedVariant? .product?.title }}
2866 </h1 >
2967
3068 <ProductPrice
@@ -36,13 +74,15 @@ const onChange = () => selectedVariant.value = flattenConnection(props.product.v
3674
3775 <USeparator class =" mb-6 lg:mb-8" />
3876
39- <UForm >
77+ <UForm
78+ v-if =" product"
79+ :state =" state"
80+ @submit =" onSubmit"
81+ >
4082 <ProductOptionGroup
41- v-if =" product?.options"
4283 v-model =" state.selectedOptions"
4384 :options =" product.options"
4485 class =" order-1 lg:order-2 mb-6 lg:mb-8"
45- @update:model-value =" onChange"
4686 />
4787
4888 <div class =" flex justify-between items-center" >
@@ -65,8 +105,9 @@ const onChange = () => selectedVariant.value = flattenConnection(props.product.v
65105 type =" submit"
66106 size =" xl"
67107 variant =" subtle"
68- :trailing-icon =" 'i-lucide-shopping-bag'"
69- :ui =" { trailingIcon: 'size-5' }"
108+ :disabled =" !selectedVariant || loading"
109+ :trailing-icon =" loading ? 'i-lucide-loader-circle' : 'i-lucide-shopping-bag'"
110+ :ui =" { trailingIcon: loading ? 'animate-spin size-5' : 'size-5' }"
70111 :label =" $t('product.add')"
71112 />
72113 </div >
0 commit comments