diff --git a/.env.example b/.env.example index 833ddc87..f3935649 100644 --- a/.env.example +++ b/.env.example @@ -19,3 +19,8 @@ WEAVERSE_PROJECT_ID=clptu3l4p001sxfsn1u9jzqnm #PUBLIC_GOOGLE_GTM_ID=G-R1KFYYKE48 #JUDGEME_PRIVATE_API_TOKEN="your-judgeme-private-api-token" #ALI_REVIEWS_API_KEY="your-ali-reviews-api-key" + +CUSTOM_COLLECTION_BANNER_METAFIELD="custom.collection_banner" +METAOBJECT_COLORS_TYPE="colors" +METAOBJECT_COLOR_NAME_KEY="label" +METAOBJECT_COLOR_VALUE_KEY="color" diff --git a/app/components/header/cart-drawer.tsx b/app/components/header/cart-drawer.tsx index 21d119e5..78aae901 100644 --- a/app/components/header/cart-drawer.tsx +++ b/app/components/header/cart-drawer.tsx @@ -56,7 +56,7 @@ export function CartDrawer() { "group-hover/header:text-[--color-header-bg]", )} > - {cart?.totalQuantity} + {cart?.totalQuantity} )} diff --git a/app/data/queries.ts b/app/data/queries.ts index dc1c8220..38d8be85 100644 --- a/app/data/queries.ts +++ b/app/data/queries.ts @@ -5,6 +5,22 @@ import { PRODUCT_VARIANT_FRAGMENT, } from "~/data/fragments"; +export let COLORS_CONFIGS_QUERY = `#graphql + query colorsConfigs($type: String!, $nameKey: String!, $valueKey: String!) { + metaobjects(first: 100, type: $type) { + nodes { + id + name: field(key: $nameKey) { + value + } + value: field(key: $valueKey) { + value + } + } + } + } +`; + export let SHOP_QUERY = `#graphql query shopQuery($country: CountryCode, $language: LanguageCode) @inContext(country: $country, language: $language) { diff --git a/app/modules/product-form/options.tsx b/app/modules/product-form/options.tsx index 898bd933..806ed568 100644 --- a/app/modules/product-form/options.tsx +++ b/app/modules/product-form/options.tsx @@ -1,10 +1,16 @@ +import { useRouteLoaderData } from "@remix-run/react"; import { Image, type VariantOptionValue } from "@shopify/hydrogen"; -import { type SwatchesConfigs, useThemeSettings } from "@weaverse/hydrogen"; +import { + type ColorSwatch, + type SwatchesConfigs, + useThemeSettings, +} from "@weaverse/hydrogen"; import { cva } from "class-variance-authority"; import clsx from "clsx"; import type { ProductVariantFragmentFragment } from "storefrontapi.generated"; import { Tooltip, TooltipContent, TooltipTrigger } from "~/components/tooltip"; import { cn } from "~/lib/cn"; +import type { RootLoader } from "~/root"; export let variants = cva( "border border-line hover:border-body cursor-pointer", @@ -53,9 +59,13 @@ interface VariantOptionProps { export function VariantOption(props: VariantOptionProps) { let { name, values, selectedOptionValue, onSelectOptionValue } = props; + let { colorsConfigs } = useRouteLoaderData("root"); let themeSettings = useThemeSettings(); let productSwatches: SwatchesConfigs = themeSettings.productSwatches; let { options, swatches } = productSwatches; + let colorsSwatches: ColorSwatch[] = colorsConfigs?.length + ? colorsConfigs + : swatches.colors; let optionConf = options.find((opt) => { return opt.name.toLowerCase() === name.toLowerCase(); }); @@ -99,9 +109,7 @@ export function VariantOption(props: VariantOptionProps) { {type === "color" && (
{values.map(({ value, isAvailable }) => { - let swatchColor = swatches.colors.find( - ({ name }) => name === value, - ); + let swatchColor = colorsSwatches.find(({ name }) => name === value); return ( diff --git a/app/root.tsx b/app/root.tsx index bbb397cc..7cbcb9dd 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -27,20 +27,25 @@ import type { MetaArgs, } from "@shopify/remix-oxygen"; import { defer } from "@shopify/remix-oxygen"; -import { useThemeSettings, withWeaverse } from "@weaverse/hydrogen"; +import { + type ColorSwatch, + useThemeSettings, + withWeaverse, +} from "@weaverse/hydrogen"; import type { CSSProperties } from "react"; -import type { LayoutQuery } from "storefrontapi.generated"; +import type { ColorsConfigsQuery, LayoutQuery } from "storefrontapi.generated"; import invariant from "tiny-invariant"; import { seoPayload } from "~/lib/seo.server"; import { CustomAnalytics } from "~/modules/custom-analytics"; import { GlobalLoading } from "~/modules/global-loading"; import { Layout as PageLayout } from "~/modules/layout"; import { TooltipProvider } from "./components/tooltip"; +import { COLORS_CONFIGS_QUERY } from "./data/queries"; import { DEFAULT_LOCALE, parseMenu } from "./lib/utils"; -import { GenericError } from "./modules/generic-error"; import { NotFound } from "./modules/not-found"; -import styles from "./styles/app.css?url"; import { GlobalStyle } from "./weaverse/style"; +import { GenericError } from "./modules/generic-error"; +import styles from "./styles/app.css?url"; export type RootLoader = typeof loader; @@ -111,8 +116,9 @@ export async function loader(args: LoaderFunctionArgs) { * needed to render the page. If it's unavailable, the whole page should 400 or 500 error. */ async function loadCriticalData({ request, context }: LoaderFunctionArgs) { - let [layout] = await Promise.all([ + let [layout, colorsConfigs] = await Promise.all([ getLayoutData(context), + getColorsConfigs(context), // Add other queries here, so that they are loaded in parallel ]); @@ -138,6 +144,7 @@ async function loadCriticalData({ request, context }: LoaderFunctionArgs) { selectedLocale: storefront.i18n, weaverseTheme: await context.weaverse.loadThemeSettings(), googleGtmID: context.env.PUBLIC_GOOGLE_GTM_ID, + colorsConfigs, }; } @@ -372,3 +379,31 @@ async function getLayoutData({ storefront, env }: AppLoadContext) { return { shop: data.shop, headerMenu, footerMenu }; } + +async function getColorsConfigs(context: AppLoadContext) { + let { + METAOBJECT_COLORS_TYPE: type, + METAOBJECT_COLOR_NAME_KEY: nameKey, + METAOBJECT_COLOR_VALUE_KEY: valueKey, + } = context.env; + if (!type || !nameKey || !valueKey) { + return []; + } + let { metaobjects } = await context.storefront.query( + COLORS_CONFIGS_QUERY, + { + variables: { + type, + nameKey, + valueKey, + }, + }, + ); + return metaobjects.nodes.map(({ id, name, value }) => { + return { + id, + name: name.value, + value: value.value, + }; + }) as ColorSwatch[]; +} diff --git a/app/sections/collection-filters/filter-item.tsx b/app/sections/collection-filters/filter-item.tsx index 540e0925..9c51253c 100644 --- a/app/sections/collection-filters/filter-item.tsx +++ b/app/sections/collection-filters/filter-item.tsx @@ -1,6 +1,15 @@ -import { useLocation, useNavigate, useSearchParams } from "@remix-run/react"; +import { + useLocation, + useNavigate, + useRouteLoaderData, + useSearchParams, +} from "@remix-run/react"; import type { Filter } from "@shopify/hydrogen/storefront-api-types"; -import { type SwatchesConfigs, useThemeSettings } from "@weaverse/hydrogen"; +import { + type ColorSwatch, + type SwatchesConfigs, + useThemeSettings, +} from "@weaverse/hydrogen"; import clsx from "clsx"; import { useState } from "react"; import { Checkbox } from "~/components/checkbox"; @@ -9,6 +18,7 @@ import { cn } from "~/lib/cn"; import type { AppliedFilter } from "~/lib/filter"; import { getAppliedFilterLink, getFilterLink } from "~/lib/filter"; import { variants as productOptionsVariants } from "~/modules/product-form/options"; +import type { RootLoader } from "~/root"; export function FilterItem({ displayAs, @@ -25,6 +35,7 @@ export function FilterItem({ let [params] = useSearchParams(); let location = useLocation(); let themeSettings = useThemeSettings(); + let { colorsConfigs } = useRouteLoaderData("root"); let { options, swatches }: SwatchesConfigs = themeSettings.productSwatches; let filter = appliedFilters.find( @@ -45,7 +56,10 @@ export function FilterItem({ } if (displayAs === "color-swatch") { - let swatchColor = swatches.colors.find(({ name }) => name === option.label); + let colors: ColorSwatch[] = colorsConfigs?.length + ? colorsConfigs + : swatches.colors; + let swatchColor = colors.find(({ name }) => name === option.label); let optionConf = options.find(({ name }) => { return name.toLowerCase() === option.label.toLowerCase(); }); diff --git a/env.d.ts b/env.d.ts index c06cbeb0..75151fc8 100644 --- a/env.d.ts +++ b/env.d.ts @@ -22,6 +22,10 @@ declare global { // declare additional Env parameter use in the fetch handler and Remix loader context here PUBLIC_GOOGLE_GTM_ID: string; JUDGEME_PRIVATE_API_TOKEN: string; + CUSTOM_COLLECTION_BANNER_METAFIELD: string; + METAOBJECT_COLORS_TYPE: string; + METAOBJECT_COLOR_NAME_KEY: string; + METAOBJECT_COLOR_VALUE_KEY: string; } } diff --git a/storefrontapi.generated.d.ts b/storefrontapi.generated.d.ts index f799f5b4..9642f0c4 100644 --- a/storefrontapi.generated.d.ts +++ b/storefrontapi.generated.d.ts @@ -284,6 +284,27 @@ export type CartApiQueryFragment = Pick< >; }; +export type ColorsConfigsQueryVariables = StorefrontAPI.Exact<{ + type: StorefrontAPI.Scalars['String']['input']; + nameKey: StorefrontAPI.Scalars['String']['input']; + valueKey: StorefrontAPI.Scalars['String']['input']; +}>; + +export type ColorsConfigsQuery = { + metaobjects: { + nodes: Array< + Pick & { + name?: StorefrontAPI.Maybe< + Pick + >; + value?: StorefrontAPI.Maybe< + Pick + >; + } + >; + }; +}; + export type ShopQueryQueryVariables = StorefrontAPI.Exact<{ country?: StorefrontAPI.InputMaybe; language?: StorefrontAPI.InputMaybe; @@ -1816,6 +1837,10 @@ export type OurTeamQuery = { }; interface GeneratedQueryTypes { + '#graphql\n query colorsConfigs($type: String!, $nameKey: String!, $valueKey: String!) {\n metaobjects(first: 100, type: $type) {\n nodes {\n id\n name: field(key: $nameKey) {\n value\n }\n value: field(key: $valueKey) {\n value\n }\n }\n }\n }\n': { + return: ColorsConfigsQuery; + variables: ColorsConfigsQueryVariables; + }; '#graphql\n query shopQuery($country: CountryCode, $language: LanguageCode)\n @inContext(country: $country, language: $language) {\n shop {\n name\n description\n }\n }\n': { return: ShopQueryQuery; variables: ShopQueryQueryVariables;