From 494be2a8fa49c49bb01cecdcf29374718bbc1d43 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 22 Jan 2024 12:18:20 +0700 Subject: [PATCH] sync demo --- .env.example | 15 +- app/components/Icon.tsx | 8 + app/components/Link.tsx | 24 +-- app/data/queries.ts | 13 ++ app/sections/SlideShow/SlideItems.tsx | 136 ++++++++++++ app/sections/SlideShow/SlideShow.tsx | 122 +++++++++++ app/sections/image-hotspots/image-hotspot.tsx | 109 ++++++++++ app/sections/image-hotspots/items.tsx | 124 +++++++++++ app/sections/newsletter.tsx | 197 ++++++++++++++++++ app/weaverse/components.ts | 21 +- 10 files changed, 749 insertions(+), 20 deletions(-) create mode 100644 app/sections/SlideShow/SlideItems.tsx create mode 100644 app/sections/SlideShow/SlideShow.tsx create mode 100644 app/sections/image-hotspots/image-hotspot.tsx create mode 100644 app/sections/image-hotspots/items.tsx create mode 100644 app/sections/newsletter.tsx diff --git a/.env.example b/.env.example index 59e4ae6..5d286cc 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,11 @@ -SESSION_SECRET="super-secret" -PUBLIC_STORE_DOMAIN="mock.shop" -#PUBLIC_STOREFRONT_API_TOKEN="your-public-storefront-api-token" -#WEAVERSE_PROJECT_ID="your-weaverse-project-id" -#WEAVERSE_API_KEY="your-weaverse-api-key" +SESSION_SECRET="foobar" +PUBLIC_STOREFRONT_API_TOKEN=14cdb1b48742401da3963517bb5a1700 +PUBLIC_STORE_DOMAIN=hydrogen-preview.myshopify.com +PUBLIC_CUSTOMER_ACCOUNT_API_CLIENT_ID=shp_7318c7c5-46d4-4090-a9aa-a08424aafd00 +PUBLIC_CUSTOMER_ACCOUNT_API_URL=https://shopify.com/55145660472 #PRIVATE_STOREFRONT_API_TOKEN="your-private-storefront-api-token" + +WEAVERSE_PROJECT_ID="clptu3l4p001sxfsn1u9jzqnm" +#WEAVERSE_API_KEY="your-weaverse-api-key" +#WEAVERSE_HOST="https://studio.weaverse.io" + diff --git a/app/components/Icon.tsx b/app/components/Icon.tsx index 3317bbf..b9553c8 100644 --- a/app/components/Icon.tsx +++ b/app/components/Icon.tsx @@ -367,3 +367,11 @@ export function IconVideoBlank(props: IconProps) { ); } +export function IconArrowInput(props: IconProps) { + return ( + + + + + ); +} diff --git a/app/components/Link.tsx b/app/components/Link.tsx index e2511ed..56154de 100644 --- a/app/components/Link.tsx +++ b/app/components/Link.tsx @@ -4,6 +4,7 @@ import { type NavLinkProps as RemixNavLinkProps, type LinkProps as RemixLinkProps, } from '@remix-run/react'; + import {useRootLoaderData} from '~/root'; import {useThemeSettings} from '@weaverse/hydrogen'; @@ -31,10 +32,13 @@ export function Link(props: LinkProps) { const rootData = useRootLoaderData(); let {enableViewTransition} = useThemeSettings(); const selectedLocale = rootData?.selectedLocale; + let toWithLocale = to; - if (typeof to === 'string') { - toWithLocale = selectedLocale ? `${selectedLocale.pathPrefix}${to}` : to; + if (typeof toWithLocale === 'string' && selectedLocale?.pathPrefix) { + if (!toWithLocale.toLowerCase().startsWith(selectedLocale.pathPrefix)) { + toWithLocale = `${selectedLocale.pathPrefix}${to}`; + } } if (typeof className === 'function') { @@ -43,17 +47,13 @@ export function Link(props: LinkProps) { unstable_viewTransition={enableViewTransition} to={toWithLocale} className={className} - {...resOfProps} - /> + {...resOfProps} /> ); } - return ( - - ); + return ; } diff --git a/app/data/queries.ts b/app/data/queries.ts index 724049b..f663817 100644 --- a/app/data/queries.ts +++ b/app/data/queries.ts @@ -462,3 +462,16 @@ export let VARIANTS_QUERY = `#graphql } ${PRODUCT_VARIANT_FRAGMENT} ` as const; +export let CUSTOMER_CREATE = `#graphql mutation customerCreate($input: CustomerCreateInput!) { + customerCreate(input: $input) { + customer { + id + email + } + customerUserErrors { + field + message + code + } + } +}` as const; diff --git a/app/sections/SlideShow/SlideItems.tsx b/app/sections/SlideShow/SlideItems.tsx new file mode 100644 index 0000000..0387c91 --- /dev/null +++ b/app/sections/SlideShow/SlideItems.tsx @@ -0,0 +1,136 @@ +import type { + HydrogenComponentProps, + HydrogenComponentSchema, + WeaverseImage, +} from '@weaverse/hydrogen'; +import { forwardRef, CSSProperties } from 'react'; +import {Image} from '@shopify/hydrogen'; +import {IconImageBlank} from '~/components'; +import clsx from 'clsx'; + +interface CountDownProps extends HydrogenComponentProps { + backgroundImage: WeaverseImage; + overlayColor: string; + overlayOpacity: number; + contentPosition: string; +} + +let SlideShowItem = forwardRef((props, ref) => { + let { + backgroundImage, + overlayColor, + overlayOpacity, + contentPosition, + children, + ...rest + } = props; + + let positionClass: {[key: string]: string} = { + 'top left': 'items-start justify-start', + 'top right': 'items-start justify-end', + 'top center': 'items-start justify-center', + 'center left': 'items-center justify-start', + 'center center': 'items-center justify-center', + 'center right': 'items-center justify-end', + 'bottom left': 'items-end justify-start', + 'bottom center': 'items-end justify-center', + 'bottom right': 'items-end justify-end', + }; + + let slideStyle: CSSProperties = { + '--overlay-color': overlayColor, + '--overlay-opacity': `${overlayOpacity}%`, + } as CSSProperties; + + return ( +
+
+ {backgroundImage ? ( + + ) : ( +
+ +
+ )} + {backgroundImage && ( +
+ )} +
+
+ {children} +
+
+ + ); +}); + +export default SlideShowItem; + +export let schema: HydrogenComponentSchema = { + type: 'slide-show--item', + title: 'Slide', + toolbar: ['general-settings', ['duplicate', 'delete']], + inspector: [ + { + group: 'Slide', + inputs: [ + { + type: 'image', + name: 'backgroundImage', + label: 'Background image', + }, + { + type: 'position', + name: 'contentPosition', + label: 'Content position', + defaultValue: 'center center', + }, + { + type: 'color', + name: 'overlayColor', + label: 'Overlay color', + }, + { + type: 'range', + name: 'overlayOpacity', + label: 'Overlay opacity', + defaultValue: 50, + configs: { + min: 10, + max: 100, + step: 10, + unit: '%', + }, + }, + ], + }, + ], + childTypes: ['subheading', 'heading', 'description', 'button'], + presets: { + children: [ + { + type: 'subheading', + content: 'Subheading', + }, + { + type: 'heading', + content: 'Slide Heading' + }, + { + type: 'description', + content: "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown." + }, + { + type: 'button', + content: 'Button section', + }, + ], + }, +}; diff --git a/app/sections/SlideShow/SlideShow.tsx b/app/sections/SlideShow/SlideShow.tsx new file mode 100644 index 0000000..979e8a6 --- /dev/null +++ b/app/sections/SlideShow/SlideShow.tsx @@ -0,0 +1,122 @@ +import type { + HydrogenComponentProps, + HydrogenComponentSchema, +} from '@weaverse/hydrogen'; +import { forwardRef, CSSProperties, useState, useCallback, useEffect } from 'react'; +import { useKeenSlider } from 'keen-slider/react'; +import clsx from 'clsx'; + +interface SlideShowProps extends HydrogenComponentProps { + sectionHeight: number; +} + +let SlideShow = forwardRef((props, ref) => { + let { + sectionHeight, + children, + ...rest + } = props; + const [activeIndex, setActiveIndex] = useState(0); + const [sliderRef, instanceRef] = useKeenSlider({ + loop: true, + slideChanged(slider) { + let pos = slider.track.details.rel; + setActiveIndex(pos); + }, + }); + + let moveToIdx = useCallback( + (idx: number) => { + setActiveIndex(idx); + if (instanceRef.current) { + instanceRef.current.moveToIdx(idx); + } + }, + [instanceRef], + ); + + let handleClickNavigation = (idx: number) => { + moveToIdx(idx); + }; + + let renderNavigation = () => { + if (children && children?.length > 1) { + return children?.map((child, index) => ( + handleClickNavigation(index)} + > + )); + } + return null; + }; + + useEffect(() => { + if (instanceRef) { + instanceRef.current?.update(); + } + }, [children, instanceRef]); + + let sectionStyle: CSSProperties = { + '--section-height': `${sectionHeight}px`, + } as CSSProperties; + + return ( +
+
+ {children?.map((child, index) => ( +
+ {child} +
+ ))} +
+
+ {renderNavigation()} +
+
+ ); +}); + +export default SlideShow; + +export let schema: HydrogenComponentSchema = { + type: 'slide-show', + title: 'Slide show', + toolbar: ['general-settings', ['duplicate', 'delete']], + inspector: [ + { + group: 'SLideshow', + inputs: [ + { + type: 'range', + name: 'sectionHeight', + label: 'Section height', + defaultValue: 500, + configs: { + min: 400, + max: 700, + step: 10, + unit: 'px', + }, + }, + ], + }, + ], + childTypes: ['slide-show--item'], + presets: { + children: [ + { + type: 'slide-show--item', + }, + ], + }, +}; diff --git a/app/sections/image-hotspots/image-hotspot.tsx b/app/sections/image-hotspots/image-hotspot.tsx new file mode 100644 index 0000000..1571598 --- /dev/null +++ b/app/sections/image-hotspots/image-hotspot.tsx @@ -0,0 +1,109 @@ +import type { + HydrogenComponentProps, + HydrogenComponentSchema, + WeaverseImage, +} from '@weaverse/hydrogen'; +import { forwardRef } from 'react'; +import { Image } from '@shopify/hydrogen'; +import { CSSProperties } from 'react'; +import { IconImageBlank } from '~/components'; + + +interface ImageHotspotProps extends HydrogenComponentProps { + imageHostpots: WeaverseImage; + heading: string; + sectionHeight: number; + topPadding: number; + bottomPadding: number; +} + +let ImageHotspot = forwardRef((props, ref) => { + let { imageHostpots, heading, children, sectionHeight, topPadding, bottomPadding, ...rest } = props; + let sectionStyles: CSSProperties = { + '--section-height': `${sectionHeight}px`, + paddingTop: `${topPadding}px`, + paddingBottom: `${bottomPadding}px` + } as CSSProperties; + return ( +
+

{heading}

+
+ {imageHostpots ? : +
+ +
} + {children} +
+
+ ); +}); + +export default ImageHotspot; + +export let schema: HydrogenComponentSchema = { + type: 'image-hotspot', + title: 'Image hotspot', + toolbar: ['general-settings', ['duplicate', 'delete']], + inspector: [ + { + group: 'Image', + inputs: [ + { + type: 'image', + name: 'imageHostpots', + label: 'Image hotspots', + }, + { + type: 'text', + name: 'heading', + label: 'Heading', + defaultValue: 'Shop the look', + }, + { + type: 'range', + name: 'sectionHeight', + label: 'Section height', + defaultValue: 400, + configs: { + min: 400, + max: 700, + step: 10, + unit: 'px', + }, + }, + { + type: 'range', + name: 'topPadding', + label: 'Top padding', + defaultValue: 10, + configs: { + min: 5, + max: 70, + step: 5, + unit: 'px', + }, + }, + { + type: 'range', + name: 'bottomPadding', + label: 'Bottom padding', + defaultValue: 10, + configs: { + min: 5, + max: 70, + step: 5, + unit: 'px', + }, + }, + ], + }, + ], + childTypes: ['product-hotspot--items'], + presets: { + children: [ + { + type: 'product-hotspot--items', + }, + ], + }, +}; diff --git a/app/sections/image-hotspots/items.tsx b/app/sections/image-hotspots/items.tsx new file mode 100644 index 0000000..ca5ad78 --- /dev/null +++ b/app/sections/image-hotspots/items.tsx @@ -0,0 +1,124 @@ +import { + HydrogenComponentProps, + HydrogenComponentSchema, + ComponentLoaderArgs, + getSelectedProductOptions, + WeaverseProduct, +} from '@weaverse/hydrogen'; +import { forwardRef } from 'react'; +import { Image } from '@shopify/hydrogen'; +import { ProductQuery } from 'storefrontapi.generated'; +import { PRODUCT_QUERY } from '~/data/queries'; +import clsx from 'clsx'; +import { CSSProperties } from 'react'; +import { IconImageBlank } from '~/components'; + + +type ProductData = { + verticalPosition: number; + horizontalPosition: number; + product: WeaverseProduct; +}; + +type ProductsHotspotProps = HydrogenComponentProps< + Awaited> +> & + ProductData; + +let ProductHotspotItems = forwardRef((props, ref) => { + let { product, verticalPosition, horizontalPosition, loaderData, ...rest } = props; + const ProductImage = loaderData?.product?.variants.nodes.map((variant) => variant.image); + const ProductPrice = loaderData?.product?.variants.nodes.map((variant) => variant.price.amount) || '0.00'; + const ProductCurrency = loaderData?.product?.variants.nodes.map((variant) => variant.price.currencyCode) || '$'; + const ProductTittle = loaderData?.product?.title || 'Product title'; + console.log(loaderData?.product); + + let Horizontal = horizontalPosition >= 50 ? 'left-auto right-1/2' : 'right-auto left-1/2'; + let Vertical = verticalPosition >= 50 ? 'top-auto bottom-full' : 'bottom-auto top-full'; + let sectionStyle: CSSProperties = { + left: `${horizontalPosition}%`, + top: `${verticalPosition}%`, + } as CSSProperties; + return ( +
+ + + +
+
+ {ProductImage ? ProductImage.map((image, index) => ( + + )) : + } +
+
+

{ProductTittle}

+

{`${ProductPrice} ${ProductCurrency}`}

+
+
+
+ ); +}); + +export default ProductHotspotItems; + +export let loader = async (args: ComponentLoaderArgs) => { + let { weaverse, data } = args; + let { storefront, request } = weaverse; + if (data.product) { + return await storefront.query(PRODUCT_QUERY, { + variables: { + handle: data.product.handle, + selectedOptions: getSelectedProductOptions(request), + language: storefront.i18n.language, + country: storefront.i18n.country, + }, + }); + } + return null; +}; + + +export let schema: HydrogenComponentSchema = { + type: 'product-hotspot--items', + title: 'Product hotspot items', + inspector: [ + { + group: 'Hotspot', + inputs: [ + { + type: 'product', + name: 'product', + label: 'Product', + }, + { + type: 'range', + name: 'verticalPosition', + label: 'Vertical position', + defaultValue: 50, + configs: { + min: 5, + max: 90, + step: 5, + unit: '%', + }, + }, + { + type: 'range', + name: 'horizontalPosition', + label: 'Horizontal position', + defaultValue: 50, + configs: { + min: 5, + max: 90, + step: 5, + unit: '%', + }, + }, + ], + }, + ], +}; diff --git a/app/sections/newsletter.tsx b/app/sections/newsletter.tsx new file mode 100644 index 0000000..e65cc13 --- /dev/null +++ b/app/sections/newsletter.tsx @@ -0,0 +1,197 @@ +import type { + HydrogenComponentProps, + HydrogenComponentSchema, + ComponentLoaderArgs, +} from '@weaverse/hydrogen'; +import React, { forwardRef, useState, CSSProperties } from 'react'; +import clsx from 'clsx'; +import { IconArrowInput } from '~/components'; +import { CustomerCreateMutation } from 'storefrontapi.generated'; +import { CUSTOMER_CREATE } from '~/data/queries'; + + +type NewsLetterData = { + contentAlignment: string; + sectionHeight: string; + backgroundColor: string; + subheading: string; + heading: string; + buttonLabel: string; + buttonLink: string; + openInNewTab: boolean; + buttonStyle: string; + topPadding: string; + bottomPadding: string; +}; + +type NewsletterProps = HydrogenComponentProps< + Awaited> +> & + NewsLetterData; + + +let NewsLetter = forwardRef((props, ref) => { + let { loaderData, contentAlignment, sectionHeight, backgroundColor, subheading, heading, buttonLabel, buttonLink, openInNewTab, buttonStyle, topPadding, bottomPadding, ...rest } = props; + let sectionStyle: CSSProperties = { + alignItems: `${contentAlignment}`, + '--section-height': `${sectionHeight}px`, + backgroundColor: `${backgroundColor}`, + paddingTop: `${topPadding}px`, + paddingBottom: `${bottomPadding}px`, + } as CSSProperties; + + const [emailValue, setEmailValue] = useState(''); + const handleInputChange = (event: React.ChangeEvent) => { + setEmailValue(event.target.value); + }; + + const handleSubmit = () => { + }; + + return ( +
+
+ {subheading &&

{subheading}

} + {heading &&

{heading}

} +
+
+ + +
+ {buttonLabel && {buttonLabel}} +
+
+
+ ); +}); + +export default NewsLetter; + +export let loader = async (args: ComponentLoaderArgs) => { + let { weaverse } = args; + let { storefront } = weaverse; + let abc = await storefront.mutate(CUSTOMER_CREATE, { + variables: { + input: { + email: 'Thang@gmail.com', + password: '5hopify', + }, + } + }); + return abc; +}; + + +export let schema: HydrogenComponentSchema = { + type: 'news-letter', + title: 'News letter', + toolbar: ['general-settings', ['duplicate', 'delete']], + inspector: [ + { + group: 'Text', + inputs: [ + { + type: 'color', + name: 'backgroundColor', + label: 'Background color', + defaultValue: '#F7F7F7', + }, + { + type: 'toggle-group', + label: 'Content alignment', + name: 'contentAlignment', + configs: { + options: [ + { label: 'Left', value: 'flex-start' }, + { label: 'Center', value: 'center' }, + { label: 'Right', value: 'flex-end' }, + ], + }, + defaultValue: 'center', + }, + { + type: 'text', + name: 'subheading', + label: 'Subheading', + defaultValue: 'Subscribe to our Newsletter', + }, + { + type: 'text', + name: 'heading', + label: 'Heading', + defaultValue: 'Subscribe and save more', + }, + { + type: 'text', + label: 'Button label', + name: 'buttonLabel', + placeholder: 'Button label', + defaultValue: 'Button', + }, + { + type: 'text', + label: 'Button link', + name: 'buttonLink', + placeholder: 'Button link', + }, + { + type: 'switch', + name: 'openInNewTab', + label: 'Open in new tab', + defaultValue: true, + }, + { + type: 'toggle-group', + label: 'Button style', + name: 'buttonStyle', + configs: { + options: [ + { label: '1', value: 'transition hover:bg-white border-2 border-solid hover:border-gray-900 hover:text-black bg-black text-white' }, + { label: '2', value: 'transition bg-white border-2 border-solid border-gray-900 text-black hover:bg-black hover:text-white' }, + { label: '3', value: 'transition hover:bg-white border-2 border-solid border-white hover:text-black bg-gray-200 text-white' }, + ], + }, + defaultValue: 'transition hover:bg-white border-2 border-solid hover:border-gray-900 hover:text-black bg-black text-white', + }, + { + type: 'range', + name: 'sectionHeight', + label: 'Section height', + defaultValue: 400, + configs: { + min: 300, + max: 700, + step: 10, + unit: 'px', + }, + }, + { + type: 'range', + name: 'topPadding', + label: 'Top padding', + defaultValue: 10, + configs: { + min: 0, + max: 100, + step: 1, + unit: 'px', + }, + }, + { + type: 'range', + name: 'bottomPadding', + label: 'Bottom padding', + defaultValue: 10, + configs: { + min: 0, + max: 100, + step: 1, + unit: 'px', + }, + }, + ], + }, + ], +} diff --git a/app/weaverse/components.ts b/app/weaverse/components.ts index ad6115e..fef2fc0 100644 --- a/app/weaverse/components.ts +++ b/app/weaverse/components.ts @@ -1,4 +1,4 @@ -import type { HydrogenComponent } from '@weaverse/hydrogen'; +import type {HydrogenComponent} from '@weaverse/hydrogen'; import * as AllProducts from '~/sections/all-products'; import * as BlogPost from '~/sections/blog-post'; import * as Blogs from '~/sections/blogs'; @@ -27,7 +27,7 @@ import * as PromotionGridButtons from '~/sections/promotion-grid/buttons'; import * as PromotionGridItem from '~/sections/promotion-grid/item'; import * as RelatedArticles from '~/sections/related-articles'; import * as RelatedProducts from '~/sections/related-products'; -import { commonComponents } from '~/sections/shared/atoms'; +import {commonComponents} from '~/sections/shared/atoms'; import * as SingleProduct from '~/sections/single-product'; import * as Judgeme from '~/components/product-form/judgeme-review'; import * as Testimonial from '~/sections/testimonials'; @@ -37,6 +37,13 @@ import * as VideoBanner from '~/sections/video-banner'; import * as VideoEmbed from '~/sections/video-embed'; import * as VideoEmbedItem from '~/sections/video-embed/video'; import * as MetaDemo from '~/sections/meta-demo'; +import * as SlideShow from '~/sections/SlideShow/SlideShow'; +import * as SlideShowItem from '~/sections/SlideShow/SlideItems'; + +import * as NewsLetter from '~/sections/newsletter'; +import * as ImageHotspot from '~/sections/image-hotspots/image-hotspot'; +import * as ImageHotspotItem from '~/sections/image-hotspots/items'; + export let components: HydrogenComponent[] = [ ...commonComponents, AllProducts, @@ -57,9 +64,15 @@ export let components: HydrogenComponent[] = [ PromotionGrid, PromotionGridItem, PromotionGridButtons, + ImageHotspot, + ImageHotspotItem, Countdown, CountDownTimer, CountdownActions, + NewsLetter, + Blogs, + BlogPost, + AllProducts, FeaturedProducts, FeaturedCollections, Testimonial, @@ -75,5 +88,7 @@ export let components: HydrogenComponent[] = [ CollectionList, SingleProduct, Judgeme, - MetaDemo + MetaDemo, + SlideShow, + SlideShowItem, ];