From b743399694807c3eaaa95b93dd6a424c72dee55d Mon Sep 17 00:00:00 2001
From: Michelle Chen
Date: Thu, 13 Jun 2024 09:36:19 -0400
Subject: [PATCH] Upgrade subscription example to use --diff (#2190)
* use -diff for subscription example
* remove tailwind css class & update subscription app link in README
* update package-lock.json
---
docs/preview/package.json | 1 +
examples/b2b/.gitignore | 1 -
examples/b2b/env.d.ts | 54 -
examples/b2b/tsconfig.json | 7 +-
examples/gtm/env.d.ts | 54 -
examples/gtm/tsconfig.json | 7 +-
examples/subscriptions/.env.example | 4 -
examples/subscriptions/.eslintignore | 5 -
examples/subscriptions/.eslintrc.js | 18 -
examples/subscriptions/.gitignore | 8 -
examples/subscriptions/.graphqlrc.yml | 1 -
examples/subscriptions/README.md | 2 +-
examples/subscriptions/app/assets/favicon.svg | 28 -
.../subscriptions/app/components/Aside.tsx | 47 -
.../subscriptions/app/components/Cart.tsx | 85 +-
.../subscriptions/app/components/Header.tsx | 93 -
.../subscriptions/app/components/Layout.tsx | 45 -
.../app/components/SellingPlanSelector.tsx | 8 +-
examples/subscriptions/app/entry.client.tsx | 12 -
examples/subscriptions/app/entry.server.tsx | 47 -
examples/subscriptions/app/root.tsx | 217 --
examples/subscriptions/app/routes/cart.tsx | 102 -
.../app/routes/products.$handle.tsx | 576 ++++--
examples/subscriptions/app/styles/app.css | 459 ----
examples/subscriptions/app/styles/reset.css | 129 --
.../subscriptions/app/styles/selling-plan.css | 24 +
.../subscriptions/app/styles/tailwind.css | 3 -
examples/subscriptions/app/utils.ts | 46 -
examples/subscriptions/package.json | 49 +-
examples/subscriptions/postcss.config.js | 10 -
examples/subscriptions/public/.gitkeep | 0
examples/subscriptions/remix.config.js | 21 -
examples/subscriptions/remix.env.d.ts | 41 -
examples/subscriptions/server.ts | 264 ---
.../storefrontapi.generated.d.ts | 1108 ++++++++--
examples/subscriptions/tailwind.config.js | 8 -
examples/subscriptions/tsconfig.json | 27 +-
package-lock.json | 1837 +----------------
38 files changed, 1532 insertions(+), 3916 deletions(-)
delete mode 100644 examples/b2b/.gitignore
delete mode 100644 examples/b2b/env.d.ts
delete mode 100644 examples/gtm/env.d.ts
delete mode 100644 examples/subscriptions/.env.example
delete mode 100644 examples/subscriptions/.eslintignore
delete mode 100644 examples/subscriptions/.eslintrc.js
delete mode 100644 examples/subscriptions/.gitignore
delete mode 100644 examples/subscriptions/.graphqlrc.yml
delete mode 100644 examples/subscriptions/app/assets/favicon.svg
delete mode 100644 examples/subscriptions/app/components/Aside.tsx
delete mode 100644 examples/subscriptions/app/components/Header.tsx
delete mode 100644 examples/subscriptions/app/components/Layout.tsx
delete mode 100644 examples/subscriptions/app/entry.client.tsx
delete mode 100644 examples/subscriptions/app/entry.server.tsx
delete mode 100644 examples/subscriptions/app/root.tsx
delete mode 100644 examples/subscriptions/app/routes/cart.tsx
delete mode 100644 examples/subscriptions/app/styles/app.css
delete mode 100644 examples/subscriptions/app/styles/reset.css
create mode 100644 examples/subscriptions/app/styles/selling-plan.css
delete mode 100644 examples/subscriptions/app/styles/tailwind.css
delete mode 100644 examples/subscriptions/app/utils.ts
delete mode 100644 examples/subscriptions/postcss.config.js
delete mode 100644 examples/subscriptions/public/.gitkeep
delete mode 100644 examples/subscriptions/remix.config.js
delete mode 100644 examples/subscriptions/remix.env.d.ts
delete mode 100644 examples/subscriptions/server.ts
delete mode 100644 examples/subscriptions/tailwind.config.js
diff --git a/docs/preview/package.json b/docs/preview/package.json
index 64dd8d29fd..af09e6e075 100644
--- a/docs/preview/package.json
+++ b/docs/preview/package.json
@@ -27,6 +27,7 @@
"@types/react-dom": "^18.2.7",
"@types/react-syntax-highlighter": "^15.5.7",
"eslint": "^8.38.0",
+ "tailwindcss": "^3.3.0",
"typescript": "^5.2.2"
},
"engines": {
diff --git a/examples/b2b/.gitignore b/examples/b2b/.gitignore
deleted file mode 100644
index ad5f2cad45..0000000000
--- a/examples/b2b/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-.shopify
diff --git a/examples/b2b/env.d.ts b/examples/b2b/env.d.ts
deleted file mode 100644
index b14ff8eaa2..0000000000
--- a/examples/b2b/env.d.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-///
-///
-///
-
-// Enhance TypeScript's built-in typings.
-import '@total-typescript/ts-reset';
-
-import type {
- Storefront,
- CustomerAccount,
- HydrogenCart,
- HydrogenSessionData,
-} from '@shopify/hydrogen';
-import type {AppSession} from '~/lib/session';
-
-declare global {
- /**
- * A global `process` object is only available during build to access NODE_ENV.
- */
- const process: {env: {NODE_ENV: 'production' | 'development'}};
-
- /**
- * Declare expected Env parameter in fetch handler.
- */
- interface Env {
- SESSION_SECRET: string;
- PUBLIC_STOREFRONT_API_TOKEN: string;
- PRIVATE_STOREFRONT_API_TOKEN: string;
- PUBLIC_STORE_DOMAIN: string;
- PUBLIC_STOREFRONT_ID: string;
- PUBLIC_CUSTOMER_ACCOUNT_API_CLIENT_ID: string;
- PUBLIC_CUSTOMER_ACCOUNT_API_URL: string;
- PUBLIC_CHECKOUT_DOMAIN: string;
- }
-}
-
-declare module '@shopify/remix-oxygen' {
- /**
- * Declare local additions to the Remix loader context.
- */
- interface AppLoadContext {
- env: Env;
- cart: HydrogenCart;
- storefront: Storefront;
- customerAccount: CustomerAccount;
- session: AppSession;
- waitUntil: ExecutionContext['waitUntil'];
- }
-
- /**
- * Declare local additions to the Remix session data.
- */
- interface SessionData extends HydrogenSessionData {}
-}
diff --git a/examples/b2b/tsconfig.json b/examples/b2b/tsconfig.json
index 110d781eea..5b672cc6e1 100644
--- a/examples/b2b/tsconfig.json
+++ b/examples/b2b/tsconfig.json
@@ -1,6 +1,11 @@
{
"extends": "../../templates/skeleton/tsconfig.json",
- "include": ["./**/*.d.ts", "./**/*.ts", "./**/*.tsx"],
+ "include": [
+ "./**/*.d.ts",
+ "./**/*.ts",
+ "./**/*.tsx",
+ "../../templates/skeleton/*.d.ts"
+ ],
"compilerOptions": {
"baseUrl": ".",
"paths": {
diff --git a/examples/gtm/env.d.ts b/examples/gtm/env.d.ts
deleted file mode 100644
index b14ff8eaa2..0000000000
--- a/examples/gtm/env.d.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-///
-///
-///
-
-// Enhance TypeScript's built-in typings.
-import '@total-typescript/ts-reset';
-
-import type {
- Storefront,
- CustomerAccount,
- HydrogenCart,
- HydrogenSessionData,
-} from '@shopify/hydrogen';
-import type {AppSession} from '~/lib/session';
-
-declare global {
- /**
- * A global `process` object is only available during build to access NODE_ENV.
- */
- const process: {env: {NODE_ENV: 'production' | 'development'}};
-
- /**
- * Declare expected Env parameter in fetch handler.
- */
- interface Env {
- SESSION_SECRET: string;
- PUBLIC_STOREFRONT_API_TOKEN: string;
- PRIVATE_STOREFRONT_API_TOKEN: string;
- PUBLIC_STORE_DOMAIN: string;
- PUBLIC_STOREFRONT_ID: string;
- PUBLIC_CUSTOMER_ACCOUNT_API_CLIENT_ID: string;
- PUBLIC_CUSTOMER_ACCOUNT_API_URL: string;
- PUBLIC_CHECKOUT_DOMAIN: string;
- }
-}
-
-declare module '@shopify/remix-oxygen' {
- /**
- * Declare local additions to the Remix loader context.
- */
- interface AppLoadContext {
- env: Env;
- cart: HydrogenCart;
- storefront: Storefront;
- customerAccount: CustomerAccount;
- session: AppSession;
- waitUntil: ExecutionContext['waitUntil'];
- }
-
- /**
- * Declare local additions to the Remix session data.
- */
- interface SessionData extends HydrogenSessionData {}
-}
diff --git a/examples/gtm/tsconfig.json b/examples/gtm/tsconfig.json
index 110d781eea..5b672cc6e1 100644
--- a/examples/gtm/tsconfig.json
+++ b/examples/gtm/tsconfig.json
@@ -1,6 +1,11 @@
{
"extends": "../../templates/skeleton/tsconfig.json",
- "include": ["./**/*.d.ts", "./**/*.ts", "./**/*.tsx"],
+ "include": [
+ "./**/*.d.ts",
+ "./**/*.ts",
+ "./**/*.tsx",
+ "../../templates/skeleton/*.d.ts"
+ ],
"compilerOptions": {
"baseUrl": ".",
"paths": {
diff --git a/examples/subscriptions/.env.example b/examples/subscriptions/.env.example
deleted file mode 100644
index 7d98ff48de..0000000000
--- a/examples/subscriptions/.env.example
+++ /dev/null
@@ -1,4 +0,0 @@
-PUBLIC_STOREFRONT_ID=
-PUBLIC_STORE_DOMAIN=
-SESSION_SECRET=
-PRIVATE_STOREFRONT_API_TOKEN=
diff --git a/examples/subscriptions/.eslintignore b/examples/subscriptions/.eslintignore
deleted file mode 100644
index a362bcaa13..0000000000
--- a/examples/subscriptions/.eslintignore
+++ /dev/null
@@ -1,5 +0,0 @@
-build
-node_modules
-bin
-*.d.ts
-dist
diff --git a/examples/subscriptions/.eslintrc.js b/examples/subscriptions/.eslintrc.js
deleted file mode 100644
index 57a969e3ad..0000000000
--- a/examples/subscriptions/.eslintrc.js
+++ /dev/null
@@ -1,18 +0,0 @@
-/**
- * @type {import("@types/eslint").Linter.BaseConfig}
- */
-module.exports = {
- extends: [
- '@remix-run/eslint-config',
- 'plugin:hydrogen/recommended',
- 'plugin:hydrogen/typescript',
- ],
- rules: {
- '@typescript-eslint/ban-ts-comment': 'off',
- '@typescript-eslint/naming-convention': 'off',
- 'hydrogen/prefer-image-component': 'off',
- 'no-useless-escape': 'off',
- '@typescript-eslint/no-non-null-asserted-optional-chain': 'off',
- 'no-case-declarations': 'off',
- },
-};
diff --git a/examples/subscriptions/.gitignore b/examples/subscriptions/.gitignore
deleted file mode 100644
index e87116641b..0000000000
--- a/examples/subscriptions/.gitignore
+++ /dev/null
@@ -1,8 +0,0 @@
-node_modules
-/.cache
-/build
-/dist
-/public/build
-/.mf
-.env
-.shopify
diff --git a/examples/subscriptions/.graphqlrc.yml b/examples/subscriptions/.graphqlrc.yml
deleted file mode 100644
index bd38d076bc..0000000000
--- a/examples/subscriptions/.graphqlrc.yml
+++ /dev/null
@@ -1 +0,0 @@
-schema: node_modules/@shopify/hydrogen-react/storefront.schema.json
diff --git a/examples/subscriptions/README.md b/examples/subscriptions/README.md
index c258d3a84f..e385234f7d 100644
--- a/examples/subscriptions/README.md
+++ b/examples/subscriptions/README.md
@@ -10,7 +10,7 @@ This example is connected to the `hydrogen-preview` storefront which contains on
To run this example on your own store, you'll need to:
-- Install a [subscription app](https://apps.shopify.com/categories/selling-products-purchase-options-subscriptions).
+- Install a [subscription app](https://apps.shopify.com/shopify-subscriptions).
- Use the subscription app to create a selling plan for a product.
## Install
diff --git a/examples/subscriptions/app/assets/favicon.svg b/examples/subscriptions/app/assets/favicon.svg
deleted file mode 100644
index f6c649733d..0000000000
--- a/examples/subscriptions/app/assets/favicon.svg
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
-
diff --git a/examples/subscriptions/app/components/Aside.tsx b/examples/subscriptions/app/components/Aside.tsx
deleted file mode 100644
index f486f1992e..0000000000
--- a/examples/subscriptions/app/components/Aside.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-/**
- * A side bar component with Overlay that works without JavaScript.
- * @example
- * ```jsx
- *
- * ```
- */
-export function Aside({
- children,
- heading,
- id = 'aside',
-}: {
- children?: React.ReactNode;
- heading: React.ReactNode;
- id?: string;
-}) {
- return (
-
-
{
- history.go(-1);
- window.location.hash = '';
- }}
- />
-
-
- );
-}
-
-function CloseAside() {
- return (
- /* eslint-disable-next-line jsx-a11y/anchor-is-valid */
- history.go(-1)}>
- ×
-
- );
-}
diff --git a/examples/subscriptions/app/components/Cart.tsx b/examples/subscriptions/app/components/Cart.tsx
index d2f45fb7b5..f2b6bf5abf 100644
--- a/examples/subscriptions/app/components/Cart.tsx
+++ b/examples/subscriptions/app/components/Cart.tsx
@@ -1,17 +1,25 @@
-import {CartForm, Image, Money} from '@shopify/hydrogen';
+import {
+ CartForm,
+ Image,
+ Money,
+ useOptimisticCart,
+ type OptimisticCart,
+} from '@shopify/hydrogen';
import type {CartLineUpdateInput} from '@shopify/hydrogen/storefront-api-types';
import {Link} from '@remix-run/react';
import type {CartApiQueryFragment} from 'storefrontapi.generated';
-import {useVariantUrl} from '~/utils';
+import {useVariantUrl} from '~/lib/variants';
-type CartLine = CartApiQueryFragment['lines']['nodes'][0];
+type CartLine = OptimisticCart['lines']['nodes'][0];
type CartMainProps = {
cart: CartApiQueryFragment | null;
layout: 'page' | 'aside';
};
-export function CartMain({layout, cart}: CartMainProps) {
+export function CartMain({layout, cart: originalCart}: CartMainProps) {
+ const cart = useOptimisticCart(originalCart);
+
const linesCount = Boolean(cart?.lines?.nodes?.length || 0);
const withDiscount =
cart &&
@@ -26,12 +34,18 @@ export function CartMain({layout, cart}: CartMainProps) {
);
}
-function CartDetails({layout, cart}: CartMainProps) {
+function CartDetails({
+ layout,
+ cart,
+}: {
+ cart: OptimisticCart;
+ layout: 'page' | 'aside';
+}) {
const cartHasItems = !!cart && cart.totalQuantity > 0;
return (
-
+
{cartHasItems && (
@@ -47,14 +61,14 @@ function CartLines({
layout,
}: {
layout: CartMainProps['layout'];
- lines: CartApiQueryFragment['lines'] | undefined;
+ lines: CartLine[];
}) {
if (!lines) return null;
return (
- {lines.nodes.map((line) => (
+ {lines.map((line) => (
))}
@@ -69,7 +83,12 @@ function CartLineItem({
layout: CartMainProps['layout'];
line: CartLine;
}) {
+ /***********************************************/
+ /********** EXAMPLE UPDATE STARTS ************/
const {id, merchandise, sellingPlanAllocation} = line;
+ /********** EXAMPLE UPDATE END ************/
+ /***********************************************/
+
const {product, title, image, selectedOptions} = merchandise;
const lineItemUrl = useVariantUrl(product.handle, selectedOptions);
@@ -99,27 +118,27 @@ function CartLineItem({
>
{product.title}
-
+ {/***********************************************/
+ /********** EXAMPLE UPDATE STARTS ************/}
{/* Optionally render the selling plan name if available */}
{sellingPlanAllocation && (
{sellingPlanAllocation.sellingPlan.name}
)}
- {selectedOptions.map(
- (option) =>
- option.value !== 'Default Title' && (
-
-
- {option.name}: {option.value}
-
-
- ),
- )}
+ {/********** EXAMPLE UPDATE END ************/
+ /***********************************************/}
+ {selectedOptions.map((option) => (
+
+
+ {option.name}: {option.value}
+
+
+ ))}
@@ -170,15 +189,21 @@ export function CartSummary({
);
}
-function CartLineRemoveButton({lineIds}: {lineIds: string[]}) {
+function CartLineRemoveButton({
+ lineIds,
+ disabled,
+}: {
+ lineIds: string[];
+ disabled: boolean;
+}) {
return (
-
- Remove
+
+ Remove
);
@@ -186,7 +211,7 @@ function CartLineRemoveButton({lineIds}: {lineIds: string[]}) {
function CartLineQuantity({line}: {line: CartLine}) {
if (!line || typeof line?.quantity === 'undefined') return null;
- const {id: lineId, quantity} = line;
+ const {id: lineId, quantity, isOptimistic} = line;
const prevQuantity = Number(Math.max(0, quantity - 1).toFixed(0));
const nextQuantity = Number((quantity + 1).toFixed(0));
@@ -196,7 +221,7 @@ function CartLineQuantity({line}: {line: CartLine}) {
@@ -209,12 +234,13 @@ function CartLineQuantity({line}: {line: CartLine}) {
aria-label="Increase quantity"
name="increase-quantity"
value={nextQuantity}
+ disabled={!!isOptimistic}
>
+
-
+
);
}
@@ -228,7 +254,8 @@ function CartLinePrice({
priceType?: 'regular' | 'compareAt';
[key: string]: any;
}) {
- if (!line?.cost?.amountPerQuantity || !line?.cost?.totalAmount) return null;
+ if (!line?.cost?.amountPerQuantity || !line?.cost?.totalAmount)
+ return
;
const moneyV2 =
priceType === 'regular'
@@ -236,7 +263,7 @@ function CartLinePrice({
: line.cost.compareAtAmountPerQuantity;
if (moneyV2 == null) {
- return null;
+ return
;
}
return (
@@ -295,9 +322,7 @@ function CartDiscounts({
{codes?.join(', ')}
-
- Remove
-
+ Remove
diff --git a/examples/subscriptions/app/components/Header.tsx b/examples/subscriptions/app/components/Header.tsx
deleted file mode 100644
index 578e146df3..0000000000
--- a/examples/subscriptions/app/components/Header.tsx
+++ /dev/null
@@ -1,93 +0,0 @@
-import {Await, NavLink} from '@remix-run/react';
-import {Suspense} from 'react';
-import type {LayoutProps} from './Layout';
-
-type HeaderProps = Pick;
-
-type Viewport = 'desktop' | 'mobile';
-
-export function Header({header, cart}: HeaderProps) {
- const {shop} = header;
- return (
-
- );
-}
-
-export function HeaderMenu({viewport}: {viewport: Viewport}) {
- const className = `header-menu-${viewport}`;
-
- function closeAside(event: React.MouseEvent) {
- if (viewport === 'mobile') {
- event.preventDefault();
- window.location.href = event.currentTarget.href;
- }
- }
-
- return (
-
- {viewport === 'mobile' && (
-
- Home
-
- )}
-
- );
-}
-
-function HeaderCtas({cart}: Pick) {
- return (
-
-
-
-
- );
-}
-
-function HeaderMenuMobileToggle() {
- return (
-
- ☰
-
- );
-}
-
-function CartBadge({count}: {count: number}) {
- return Cart {count} ;
-}
-
-function CartToggle({cart}: Pick) {
- return (
- }>
-
- {(cart) => {
- if (!cart) return ;
- return ;
- }}
-
-
- );
-}
-
-function activeLinkStyle({
- isActive,
- isPending,
-}: {
- isActive: boolean;
- isPending: boolean;
-}) {
- return {
- fontWeight: isActive ? 'bold' : undefined,
- color: isPending ? 'grey' : 'black',
- };
-}
diff --git a/examples/subscriptions/app/components/Layout.tsx b/examples/subscriptions/app/components/Layout.tsx
deleted file mode 100644
index 81f07215fb..0000000000
--- a/examples/subscriptions/app/components/Layout.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import {Await} from '@remix-run/react';
-import {Suspense} from 'react';
-import type {CartApiQueryFragment, HeaderQuery} from 'storefrontapi.generated';
-import {Aside} from '~/components/Aside';
-import {Header, HeaderMenu} from '~/components/Header';
-import {CartMain} from '~/components/Cart';
-
-export type LayoutProps = {
- cart: Promise;
- children?: React.ReactNode;
- header: HeaderQuery;
-};
-
-export function Layout({cart, children = null, header}: LayoutProps) {
- return (
- <>
-
-
-
- {children}
- >
- );
-}
-
-function CartAside({cart}: {cart: LayoutProps['cart']}) {
- return (
-
}>
-
- {(cart) => {
- return ;
- }}
-
-
-
- );
-}
-
-function MobileMenuAside() {
- return (
-
- );
-}
diff --git a/examples/subscriptions/app/components/SellingPlanSelector.tsx b/examples/subscriptions/app/components/SellingPlanSelector.tsx
index adbba34abd..18c6739f24 100644
--- a/examples/subscriptions/app/components/SellingPlanSelector.tsx
+++ b/examples/subscriptions/app/components/SellingPlanSelector.tsx
@@ -43,9 +43,7 @@ export function SellingPlanSelector({
sellingPlanGroups: ProductFragment['sellingPlanGroups'];
selectedSellingPlan: SellingPlanFragment | null;
paramKey?: string;
- children: ({
- sellingPlanGroup,
- }: {
+ children: (params: {
sellingPlanGroup: SellingPlanGroup;
selectedSellingPlan: SellingPlanFragment | null;
}) => React.ReactNode;
@@ -71,9 +69,9 @@ export function SellingPlanSelector({
params.set(paramKey, sellingPlan.id);
sellingPlan.isSelected = selectedSellingPlan?.id === sellingPlan.id;
sellingPlan.url = `${pathname}?${params.toString()}`;
- return sellingPlan as SellingPlan;
+ return sellingPlan;
})
- .filter(Boolean);
+ .filter(Boolean) as SellingPlan[];
sellingPlanGroup.sellingPlans.nodes = sellingPlans;
return children({sellingPlanGroup, selectedSellingPlan});
}),
diff --git a/examples/subscriptions/app/entry.client.tsx b/examples/subscriptions/app/entry.client.tsx
deleted file mode 100644
index ba957c430e..0000000000
--- a/examples/subscriptions/app/entry.client.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import {RemixBrowser} from '@remix-run/react';
-import {startTransition, StrictMode} from 'react';
-import {hydrateRoot} from 'react-dom/client';
-
-startTransition(() => {
- hydrateRoot(
- document,
-
-
- ,
- );
-});
diff --git a/examples/subscriptions/app/entry.server.tsx b/examples/subscriptions/app/entry.server.tsx
deleted file mode 100644
index de368829b7..0000000000
--- a/examples/subscriptions/app/entry.server.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import type {EntryContext, AppLoadContext} from '@shopify/remix-oxygen';
-import {RemixServer} from '@remix-run/react';
-import isbot from 'isbot';
-import {renderToReadableStream} from 'react-dom/server';
-import {createContentSecurityPolicy} from '@shopify/hydrogen';
-
-export default async function handleRequest(
- request: Request,
- responseStatusCode: number,
- responseHeaders: Headers,
- remixContext: EntryContext,
- context: AppLoadContext,
-) {
- const {nonce, header, NonceProvider} = createContentSecurityPolicy({
- shop: {
- checkoutDomain: context.env.PUBLIC_CHECKOUT_DOMAIN,
- storeDomain: context.env.PUBLIC_STORE_DOMAIN,
- }
- });
-
- const body = await renderToReadableStream(
-
-
- ,
- {
- nonce,
- signal: request.signal,
- onError(error) {
- // eslint-disable-next-line no-console
- console.error(error);
- responseStatusCode = 500;
- },
- },
- );
-
- if (isbot(request.headers.get('user-agent'))) {
- await body.allReady;
- }
-
- responseHeaders.set('Content-Type', 'text/html');
- responseHeaders.set('Content-Security-Policy', header);
-
- return new Response(body, {
- headers: responseHeaders,
- status: responseStatusCode,
- });
-}
diff --git a/examples/subscriptions/app/root.tsx b/examples/subscriptions/app/root.tsx
deleted file mode 100644
index df9886015a..0000000000
--- a/examples/subscriptions/app/root.tsx
+++ /dev/null
@@ -1,217 +0,0 @@
-import {useNonce, getShopAnalytics, Analytics} from '@shopify/hydrogen';
-import {
- defer,
- type SerializeFrom,
- type LoaderFunctionArgs,
-} from '@shopify/remix-oxygen';
-import {
- Links,
- Meta,
- Outlet,
- Scripts,
- LiveReload,
- useMatches,
- useRouteError,
- useLoaderData,
- ScrollRestoration,
- isRouteErrorResponse,
- type ShouldRevalidateFunction,
-} from '@remix-run/react';
-import favicon from './assets/favicon.svg';
-import resetStyles from './styles/reset.css';
-import appStyles from './styles/app.css';
-import {Layout} from '~/components/Layout';
-import tailwindCss from './styles/tailwind.css';
-
-/**
- * This is important to avoid re-fetching root queries on sub-navigations
- */
-export const shouldRevalidate: ShouldRevalidateFunction = ({
- formMethod,
- currentUrl,
- nextUrl,
-}) => {
- // revalidate when a mutation is performed e.g add to cart, login...
- if (formMethod && formMethod !== 'GET') {
- return true;
- }
-
- // revalidate when manually revalidating via useRevalidator
- if (currentUrl.toString() === nextUrl.toString()) {
- return true;
- }
-
- return false;
-};
-
-export function links() {
- return [
- {rel: 'stylesheet', href: tailwindCss},
- {rel: 'stylesheet', href: resetStyles},
- {rel: 'stylesheet', href: appStyles},
- {
- rel: 'preconnect',
- href: 'https://cdn.shopify.com',
- },
- {
- rel: 'preconnect',
- href: 'https://shop.app',
- },
- {rel: 'icon', type: 'image/svg+xml', href: favicon},
- ];
-}
-
-export const useRootLoaderData = () => {
- const [root] = useMatches();
- return root?.data as SerializeFrom;
-};
-
-export async function loader(args: LoaderFunctionArgs) {
- // Start fetching non-critical data without blocking time to first byte
- const deferredData = loadDeferredData(args);
-
- // Await the critical data required to render initial state of the page
- const criticalData = await loadCriticalData(args);
-
- const {storefront, env} = args.context;
-
- return defer({
- ...deferredData,
- ...criticalData,
- shop: getShopAnalytics({
- storefront,
- publicStorefrontId: env.PUBLIC_STOREFRONT_ID,
- }),
- consent: {
- checkoutDomain: env.PUBLIC_CHECKOUT_DOMAIN,
- storefrontAccessToken: env.PUBLIC_STOREFRONT_API_TOKEN,
- },
- });
-}
-
-/**
- * Load data necessary for rendering content above the fold. This is the critical data
- * needed to render the page. If it's unavailable, the whole page should 400 or 500 error.
- */
-async function loadCriticalData({context}: LoaderFunctionArgs) {
- const {storefront} = context;
-
- // await the header query (above the fold)
- const [header] = await Promise.all([
- storefront.query(HEADER_QUERY, {
- cache: storefront.CacheLong(),
- }),
- // Add other queries here, so that they are loaded in parallel
- ]);
-
- return {
- header,
- };
-}
-
-/**
- * Load data for rendering content below the fold. This data is deferred and will be
- * fetched after the initial page load. If it's unavailable, the page should still 200.
- * Make sure to not throw any errors here, as it will cause the page to 500.
- */
-function loadDeferredData({context}: LoaderFunctionArgs) {
- // defer the cart query by not awaiting it
- const cartPromise = context.cart.get();
-
- return {cart: cartPromise};
-}
-
-export default function App() {
- const nonce = useNonce();
- const data = useLoaderData();
-
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
-
-export function ErrorBoundary() {
- const error = useRouteError();
- const rootData = useRootLoaderData();
- const nonce = useNonce();
- let errorMessage = 'Unknown error';
- let errorStatus = 500;
-
- if (isRouteErrorResponse(error)) {
- errorMessage = error?.data?.message ?? error.data;
- errorStatus = error.status;
- } else if (error instanceof Error) {
- errorMessage = error.message;
- }
-
- return (
-
-
-
-
-
-
-
-
-
-
-
Oops
-
{errorStatus}
- {errorMessage && (
-
- {errorMessage}
-
- )}
-
-
-
-
-
-
-
- );
-}
-
-const HEADER_QUERY = `#graphql
- fragment Shop on Shop {
- id
- name
- description
- primaryDomain {
- url
- }
- brand {
- logo {
- image {
- url
- }
- }
- }
- }
- query Header {
- shop {
- ...Shop
- }
- }
-` as const;
diff --git a/examples/subscriptions/app/routes/cart.tsx b/examples/subscriptions/app/routes/cart.tsx
deleted file mode 100644
index be481db45a..0000000000
--- a/examples/subscriptions/app/routes/cart.tsx
+++ /dev/null
@@ -1,102 +0,0 @@
-import {Await, type MetaFunction} from '@remix-run/react';
-import {Suspense} from 'react';
-import type {CartQueryDataReturn} from '@shopify/hydrogen';
-import {CartForm} from '@shopify/hydrogen';
-import {json, type ActionFunctionArgs} from '@shopify/remix-oxygen';
-import {CartMain} from '~/components/Cart';
-import {useRootLoaderData} from '~/root';
-
-export const meta: MetaFunction = () => {
- return [{title: `Hydrogen | Cart`}];
-};
-
-export async function action({request, context}: ActionFunctionArgs) {
- const {cart} = context;
-
- const formData = await request.formData();
-
- const {action, inputs} = CartForm.getFormInput(formData);
-
- if (!action) {
- throw new Error('No action provided');
- }
-
- let status = 200;
- let result: CartQueryDataReturn;
-
- switch (action) {
- case CartForm.ACTIONS.LinesAdd:
- result = await cart.addLines(inputs.lines);
- break;
- case CartForm.ACTIONS.LinesUpdate:
- result = await cart.updateLines(inputs.lines);
- break;
- case CartForm.ACTIONS.LinesRemove:
- result = await cart.removeLines(inputs.lineIds);
- break;
- case CartForm.ACTIONS.DiscountCodesUpdate: {
- const formDiscountCode = inputs.discountCode;
-
- // User inputted discount code
- const discountCodes = (
- formDiscountCode ? [formDiscountCode] : []
- ) as string[];
-
- // Combine discount codes already applied on cart
- discountCodes.push(...inputs.discountCodes);
-
- result = await cart.updateDiscountCodes(discountCodes);
- break;
- }
- case CartForm.ACTIONS.BuyerIdentityUpdate: {
- result = await cart.updateBuyerIdentity({
- ...inputs.buyerIdentity,
- });
- break;
- }
- default:
- throw new Error(`${action} cart action is not defined`);
- }
-
- const cartId = result.cart.id;
- const headers = cart.setCartId(result.cart.id);
- const {cart: cartResult, errors} = result;
-
- const redirectTo = formData.get('redirectTo') ?? null;
- if (typeof redirectTo === 'string') {
- status = 303;
- headers.set('Location', redirectTo);
- }
-
- return json(
- {
- cart: cartResult,
- errors,
- analytics: {
- cartId,
- },
- },
- {status, headers},
- );
-}
-
-export default function Cart() {
- const rootData = useRootLoaderData();
- const cartPromise = rootData.cart;
-
- return (
-
-
Cart
-
Loading cart ...}>
- An error occurred }
- >
- {(cart) => {
- return ;
- }}
-
-
-
- );
-}
diff --git a/examples/subscriptions/app/routes/products.$handle.tsx b/examples/subscriptions/app/routes/products.$handle.tsx
index de46f50d70..0a938c1001 100644
--- a/examples/subscriptions/app/routes/products.$handle.tsx
+++ b/examples/subscriptions/app/routes/products.$handle.tsx
@@ -1,5 +1,7 @@
-import {json, redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
+import {Suspense} from 'react';
+import {defer, redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
import {
+ Await,
Link,
useLoaderData,
type MetaFunction,
@@ -7,20 +9,51 @@ import {
} from '@remix-run/react';
import type {
ProductFragment,
+ ProductVariantsQuery,
ProductVariantFragment,
+ /***********************************************/
+ /********** EXAMPLE UPDATE STARTS ************/
SellingPlanFragment,
+ /********** EXAMPLE UPDATE END ************/
+ /***********************************************/
} from 'storefrontapi.generated';
-import {Image, Money, CartForm, Analytics} from '@shopify/hydrogen';
+import {
+ Image,
+ Money,
+ VariantSelector,
+ type VariantOption,
+ getSelectedProductOptions,
+ CartForm,
+ type OptimisticCartLine,
+ Analytics,
+ type CartViewPayload,
+ useAnalytics,
+} from '@shopify/hydrogen';
import type {
- CartLineInput,
+ SelectedOption,
+ /***********************************************/
+ /********** EXAMPLE UPDATE STARTS ************/
CurrencyCode,
+ /********** EXAMPLE UPDATE END ************/
+ /***********************************************/
} from '@shopify/hydrogen/storefront-api-types';
-
+import {getVariantUrl} from '~/lib/variants';
+import {useAside} from '~/components/Aside';
+/***********************************************/
+/********** EXAMPLE UPDATE STARTS ************/
// 1. Import the SellingPlanSelector component and type
import {
SellingPlanSelector,
type SellingPlanGroup,
} from '~/components/SellingPlanSelector';
+import sellingPanStyle from '~/styles/selling-plan.css?url';
+import type {LinksFunction} from '@remix-run/node';
+
+export const links: LinksFunction = () => [
+ {rel: 'stylesheet', href: sellingPanStyle},
+];
+/********** EXAMPLE UPDATE END ************/
+/***********************************************/
export const meta: MetaFunction = ({data}) => {
return [{title: `Hydrogen | ${data?.product.title ?? ''}`}];
@@ -30,56 +63,130 @@ export async function loader({params, request, context}: LoaderFunctionArgs) {
const {handle} = params;
const {storefront} = context;
- // 2. Get the selected selling plan id from the request url
- const selectedSellingPlanId =
- new URL(request.url).searchParams.get('selling_plan') ?? null;
-
if (!handle) {
throw new Error('Expected product handle to be defined');
}
+ // await the query for the critical product data
const {product} = await storefront.query(PRODUCT_QUERY, {
- variables: {handle},
+ variables: {handle, selectedOptions: getSelectedProductOptions(request)},
});
if (!product?.id) {
throw new Response(null, {status: 404});
}
+ /***********************************************/
+ /********** EXAMPLE UPDATE STARTS ************/
+ // 2. Get the selected selling plan id from the request url
+ const selectedSellingPlanId =
+ new URL(request.url).searchParams.get('selling_plan') ?? null;
// 3. Get the selected selling plan from the product
const selectedSellingPlan =
product.sellingPlanGroups.nodes?.[0]?.sellingPlans.nodes?.find(
- (sellingPlan) => sellingPlan.id === selectedSellingPlanId,
+ (sellingPlan: SellingPlanFragment) =>
+ sellingPlan.id === selectedSellingPlanId,
) ?? null;
- /**
- 4. If the product includes selling plans but no selling plan is selected, we
- redirect to the first selling plan, so that's is selected by default
- **/
+ // 4. If the product includes selling plans but no selling plan is selected,
+ // we redirect to the first selling plan, so that's is selected by default
if (product.sellingPlanGroups.nodes?.length && !selectedSellingPlan) {
const firstSellingPlanId =
product.sellingPlanGroups.nodes[0].sellingPlans.nodes[0].id;
return redirect(
- `/products/${product.handle}?selling_plan=${firstSellingPlanId}`,
+ `/products/${product.handle}?${new URLSearchParams({
+ selling_plan: firstSellingPlanId,
+ }).toString()}`,
);
}
+ /********** EXAMPLE UPDATE END ************/
+ /***********************************************/
+
+ const firstVariant = product.variants.nodes[0];
+ const firstVariantIsDefault = Boolean(
+ firstVariant.selectedOptions.find(
+ (option: SelectedOption) =>
+ option.name === 'Title' && option.value === 'Default Title',
+ ),
+ );
- const selectedVariant = product.variants.nodes[0];
+ if (firstVariantIsDefault) {
+ product.selectedVariant = firstVariant;
+ } else {
+ // if no selected variant was returned from the selected options,
+ // we redirect to the first variant's url with it's selected options applied
+ if (!product.selectedVariant) {
+ throw redirectToFirstVariant({product, request});
+ }
+ }
- // 5. Pass the selectedSellingPlan to the client
- return json({product, selectedVariant, selectedSellingPlan});
+ // In order to show which variants are available in the UI, we need to query
+ // all of them. But there might be a *lot*, so instead separate the variants
+ // into it's own separate query that is deferred. So there's a brief moment
+ // where variant options might show as available when they're not, but after
+ // this deffered query resolves, the UI will update.
+ const variants = storefront.query(VARIANTS_QUERY, {
+ variables: {handle},
+ });
+
+ return defer({
+ product,
+ variants,
+ /***********************************************/
+ /********** EXAMPLE UPDATE STARTS ************/
+ // 5. Pass the selectedSellingPlan to the client
+ selectedSellingPlan,
+ /********** EXAMPLE UPDATE END ************/
+ /***********************************************/
+ });
+}
+
+function redirectToFirstVariant({
+ product,
+ request,
+}: {
+ product: ProductFragment;
+ request: Request;
+}) {
+ const url = new URL(request.url);
+ const firstVariant = product.variants.nodes[0];
+
+ return redirect(
+ getVariantUrl({
+ pathname: url.pathname,
+ handle: product.handle,
+ selectedOptions: firstVariant.selectedOptions,
+ searchParams: new URLSearchParams(url.search),
+ }),
+ {
+ status: 302,
+ },
+ );
}
export default function Product() {
- const {product, selectedSellingPlan, selectedVariant} =
- useLoaderData();
+ const {
+ product,
+ variants,
+ /***********************************************/
+ /********** EXAMPLE UPDATE STARTS ************/
+ selectedSellingPlan,
+ /********** EXAMPLE UPDATE END ************/
+ /***********************************************/
+ } = useLoaderData();
+ const {selectedVariant} = product;
return (
;
+ /***********************************************/
+ /********** EXAMPLE UPDATE STARTS ************/
selectedSellingPlan: SellingPlanFragment | null;
+ /********** EXAMPLE UPDATE END ************/
+ /***********************************************/
}) {
- const {title, descriptionHtml, sellingPlanGroups} = product;
+ const {
+ title,
+ descriptionHtml,
+ /***********************************************/
+ /********** EXAMPLE UPDATE STARTS ************/
+ sellingPlanGroups,
+ /********** EXAMPLE UPDATE END ************/
+ /***********************************************/
+ } = product;
return (
{title}
-
+
+ }
+ >
+
+ {(data) => (
+
+ )}
+
+
+
Description
@@ -156,23 +311,48 @@ function ProductPrice({
selectedVariant,
selectedSellingPlan,
}: {
- selectedVariant: ProductVariantFragment;
+ selectedVariant: ProductFragment['selectedVariant'];
+ /***********************************************/
+ /********** EXAMPLE UPDATE STARTS ************/
selectedSellingPlan: SellingPlanFragment | null;
+ /********** EXAMPLE UPDATE END ************/
+ /***********************************************/
}) {
+ /***********************************************/
+ /********** EXAMPLE UPDATE STARTS ************/
+ if (selectedSellingPlan) {
+ return (
+
+ );
+ }
+ /********** EXAMPLE UPDATE END ************/
+ /***********************************************/
+
return (
- {selectedSellingPlan ? (
-
+ {selectedVariant?.compareAtPrice ? (
+ <>
+
Sale
+
+
+ {selectedVariant ? : null}
+
+
+
+
+ >
) : (
-
+ selectedVariant?.price &&
)}
);
}
+/***********************************************/
+/********** EXAMPLE UPDATE STARTS ************/
type SellingPlanPrice = {
amount: number;
currencyCode: CurrencyCode;
@@ -186,12 +366,16 @@ function SellingPlanPrice({
selectedVariant,
}: {
selectedSellingPlan: SellingPlanFragment;
- selectedVariant: ProductVariantFragment;
+ selectedVariant: ProductFragment['selectedVariant'];
}) {
+ if (!selectedVariant) {
+ return null;
+ }
+
const sellingPlanPriceAdjustments = selectedSellingPlan?.priceAdjustments;
if (!sellingPlanPriceAdjustments?.length) {
- return
;
+ return selectedVariant ?
: null;
}
const selectedVariantPrice: SellingPlanPrice = {
@@ -218,7 +402,7 @@ function SellingPlanPrice({
return {
amount:
acc.amount *
- (1 - adjustment.adjustmentValue.adjustmentPercentage),
+ (1 - adjustment.adjustmentValue.adjustmentPercentage / 100),
currencyCode: acc.currencyCode,
};
default:
@@ -240,124 +424,172 @@ function SellingPlanPrice({
);
}
-/**
- Render the price of a product that does not have selling plans
-**/
-function ProductVariantPrice({
- selectedVariant,
+// Update as you see fit to match your design and requirements
+function SellingPlanGroup({
+ sellingPlanGroup,
}: {
- selectedVariant: ProductVariantFragment;
+ sellingPlanGroup: SellingPlanGroup;
}) {
- return selectedVariant?.compareAtPrice ? (
- <>
-
Sale
-
-
- {selectedVariant ? : null}
-
-
-
-
- >
- ) : (
- selectedVariant?.price &&
+ return (
+
+
+ {sellingPlanGroup.name}:
+
+ {sellingPlanGroup.sellingPlans.nodes.map((sellingPlan) => {
+ return (
+
+
+ {sellingPlan.options.map(
+ (option) => `${option.name} ${option.value}`,
+ )}
+
+
+ );
+ })}
+
);
}
+/********** EXAMPLE UPDATE END ************/
+/***********************************************/
function ProductForm({
- selectedSellingPlan,
+ product,
selectedVariant,
+ variants,
+ selectedSellingPlan,
sellingPlanGroups,
}: {
+ product: ProductFragment;
+ selectedVariant: ProductFragment['selectedVariant'];
+ variants: Array
;
+ /***********************************************/
+ /********** EXAMPLE UPDATE STARTS ************/
selectedSellingPlan: SellingPlanFragment | null;
- selectedVariant: ProductVariantFragment;
sellingPlanGroups: ProductFragment['sellingPlanGroups'];
+ /********** EXAMPLE UPDATE END ************/
+ /***********************************************/
}) {
+ const {open} = useAside();
+ const {publish, shop, cart, prevCart} = useAnalytics();
+
return (
- {/* 4. Add the SellingPlanSelector component inside the ProductForm */}
-
- {({sellingPlanGroup}) => (
- /* 5. Render the SellingPlanGroup component inside the SellingPlanSelector */
-
- )}
-
+ {/***********************************************/
+ /********** EXAMPLE UPDATE STARTS ************/}
+ {sellingPlanGroups.nodes.length > 0 ? (
+ <>
+ {/* 4. Add the SellingPlanSelector component inside the ProductForm */}
+
+ {({sellingPlanGroup}) => (
+ /* 5. Render the SellingPlanGroup component inside the SellingPlanSelector */
+
+ )}
+
+ >
+ ) : (
+
+ {({option}) => }
+
+ )}
+ {/********** EXAMPLE UPDATE END ************/
+ /***********************************************/}
-
- {/* 6. Update the AddToCart button text and pass in the sellingPlanId */}
0 && !selectedSellingPlan)
+ /********** EXAMPLE UPDATE END ************/
+ /***********************************************/
}
onClick={() => {
- window.location.href = window.location.href + '#cart-aside';
+ open('cart');
+ publish('cart_viewed', {
+ cart,
+ prevCart,
+ shop,
+ url: window.location.href || '',
+ } as CartViewPayload);
}}
lines={
selectedVariant
? [
{
- merchandiseId: selectedVariant?.id,
- sellingPlanId: selectedSellingPlan?.id,
+ merchandiseId: selectedVariant.id,
quantity: 1,
+ selectedVariant,
+ /***********************************************/
+ /********** EXAMPLE UPDATE STARTS ************/
+ sellingPlanId: selectedSellingPlan?.id,
+ /********** EXAMPLE UPDATE END ************/
+ /***********************************************/
},
]
: []
}
>
- {sellingPlanGroups.nodes
+ {/***********************************************/
+ /********** EXAMPLE UPDATE STARTS ************/}
+ {sellingPlanGroups.nodes.length > 0
? selectedSellingPlan
? 'Subscribe'
: 'Select a subscription'
: selectedVariant?.availableForSale
? 'Add to cart'
: 'Sold out'}
+ {/********** EXAMPLE UPDATE END ************/
+ /***********************************************/}
);
}
-// Update as you see fit to match your design and requirements
-function SellingPlanGroup({
- sellingPlanGroup,
-}: {
- sellingPlanGroup: SellingPlanGroup;
-}) {
+function ProductOptions({option}: {option: VariantOption}) {
return (
-
-
- {sellingPlanGroup.name}:
-
- {sellingPlanGroup.sellingPlans.nodes.map((sellingPlan) => {
- return (
-
-
- {sellingPlan.options.map(
- (option) => `${option.name} ${option.value}`,
- )}
-
-
- );
- })}
+
+
{option.name}
+
+ {option.values.map(({value, isAvailable, isActive, to}) => {
+ return (
+
+ {value}
+
+ );
+ })}
+
+
);
}
@@ -372,7 +604,7 @@ function AddToCartButton({
analytics?: unknown;
children: React.ReactNode;
disabled?: boolean;
- lines: CartLineInput[];
+ lines: Array
;
onClick?: () => void;
}) {
return (
@@ -387,7 +619,6 @@ function AddToCartButton({
{children}
@@ -398,43 +629,8 @@ function AddToCartButton({
);
}
-const PRODUCT_VARIANT_FRAGMENT = `#graphql
- fragment ProductVariant on ProductVariant {
- availableForSale
- compareAtPrice {
- amount
- currencyCode
- }
- id
- image {
- __typename
- id
- url
- altText
- width
- height
- }
- price {
- amount
- currencyCode
- }
- product {
- title
- handle
- }
- selectedOptions {
- name
- value
- }
- sku
- title
- unitPrice {
- amount
- currencyCode
- }
- }
-` as const;
-
+/***********************************************/
+/********** EXAMPLE UPDATE STARTS ************/
// 7. Add the SellingPlanGroup fragment to the Product fragment
const SELLING_PLAN_FRAGMENT = `#graphql
fragment SellingPlanMoney on MoneyV2 {
@@ -503,6 +699,45 @@ const SELLING_PLAN_GROUP_FRAGMENT = `#graphql
}
${SELLING_PLAN_FRAGMENT}
` as const;
+/********** EXAMPLE UPDATE END ************/
+/***********************************************/
+
+const PRODUCT_VARIANT_FRAGMENT = `#graphql
+ fragment ProductVariant on ProductVariant {
+ availableForSale
+ compareAtPrice {
+ amount
+ currencyCode
+ }
+ id
+ image {
+ __typename
+ id
+ url
+ altText
+ width
+ height
+ }
+ price {
+ amount
+ currencyCode
+ }
+ product {
+ title
+ handle
+ }
+ selectedOptions {
+ name
+ value
+ }
+ sku
+ title
+ unitPrice {
+ amount
+ currencyCode
+ }
+ }
+` as const;
const PRODUCT_FRAGMENT = `#graphql
fragment Product on Product {
@@ -516,6 +751,9 @@ const PRODUCT_FRAGMENT = `#graphql
name
values
}
+ selectedVariant: variantBySelectedOptions(selectedOptions: $selectedOptions, ignoreUnknownOptions: true, caseInsensitiveMatch: true) {
+ ...ProductVariant
+ }
variants(first: 1) {
nodes {
...ProductVariant
@@ -525,16 +763,23 @@ const PRODUCT_FRAGMENT = `#graphql
description
title
}
-
+ #/***********************************************/
+ #/********** EXAMPLE UPDATE STARTS ************/
# 9. Add the SellingPlanGroups fragment to the Product fragment
sellingPlanGroups(first:10) {
nodes {
...SellingPlanGroup
}
}
+ #/********** EXAMPLE UPDATE END ************/
+ #/***********************************************/
}
${PRODUCT_VARIANT_FRAGMENT}
+ #/***********************************************/
+ #/********** EXAMPLE UPDATE STARTS ************/
${SELLING_PLAN_GROUP_FRAGMENT}
+ #/********** EXAMPLE UPDATE END ************/
+ #/***********************************************/
` as const;
const PRODUCT_QUERY = `#graphql
@@ -542,6 +787,7 @@ const PRODUCT_QUERY = `#graphql
$country: CountryCode
$handle: String!
$language: LanguageCode
+ $selectedOptions: [SelectedOptionInput!]!
) @inContext(country: $country, language: $language) {
product(handle: $handle) {
...Product
@@ -549,3 +795,27 @@ const PRODUCT_QUERY = `#graphql
}
${PRODUCT_FRAGMENT}
` as const;
+
+const PRODUCT_VARIANTS_FRAGMENT = `#graphql
+ fragment ProductVariants on Product {
+ variants(first: 250) {
+ nodes {
+ ...ProductVariant
+ }
+ }
+ }
+ ${PRODUCT_VARIANT_FRAGMENT}
+` as const;
+
+const VARIANTS_QUERY = `#graphql
+ ${PRODUCT_VARIANTS_FRAGMENT}
+ query ProductVariants(
+ $country: CountryCode
+ $language: LanguageCode
+ $handle: String!
+ ) @inContext(country: $country, language: $language) {
+ product(handle: $handle) {
+ ...ProductVariants
+ }
+ }
+` as const;
diff --git a/examples/subscriptions/app/styles/app.css b/examples/subscriptions/app/styles/app.css
deleted file mode 100644
index c27886bb8e..0000000000
--- a/examples/subscriptions/app/styles/app.css
+++ /dev/null
@@ -1,459 +0,0 @@
-:root {
- --aside-width: 400px;
- --cart-aside-summary-height-with-discount: 300px;
- --cart-aside-summary-height: 250px;
- --grid-item-width: 355px;
- --header-height: 64px;
- --color-dark: #000;
- --color-light: #fff;
-}
-
-img {
- border-radius: 4px;
-}
-
-/*
-* --------------------------------------------------
-* components/Aside
-* --------------------------------------------------
-*/
-aside {
- background: var(--color-light);
- box-shadow: 0 0 50px rgba(0, 0, 0, 0.3);
- height: 100vh;
- max-width: var(--aside-width);
- min-width: var(--aside-width);
- position: fixed;
- right: calc(-1 * var(--aside-width));
- top: 0;
- transition: transform 200ms ease-in-out;
-}
-
-aside header {
- align-items: center;
- border-bottom: 1px solid var(--color-dark);
- display: flex;
- height: var(--header-height);
- justify-content: space-between;
- padding: 0 20px;
-}
-
-aside header h3 {
- margin: 0;
-}
-
-aside header .close {
- font-weight: bold;
- opacity: 0.8;
- text-decoration: none;
- transition: all 200ms;
- width: 20px;
-}
-
-aside header .close:hover {
- opacity: 1;
-}
-
-aside header h2 {
- margin-bottom: 0.6rem;
- margin-top: 0;
-}
-
-aside main {
- margin: 1rem;
-}
-
-aside p {
- margin: 0 0 0.25rem;
-}
-
-aside p:last-child {
- margin: 0;
-}
-
-aside li {
- margin-bottom: 0.125rem;
-}
-
-.overlay {
- background: rgba(0, 0, 0, 0.2);
- bottom: 0;
- left: 0;
- opacity: 0;
- pointer-events: none;
- position: fixed;
- right: 0;
- top: 0;
- transition: opacity 400ms ease-in-out;
- transition: opacity 400ms;
- visibility: hidden;
- z-index: 10;
-}
-
-.overlay .close-outside {
- background: transparent;
- border: none;
- color: transparent;
- height: 100%;
- left: 0;
- position: absolute;
- top: 0;
- width: calc(100% - var(--aside-width));
-}
-
-.overlay .light {
- background: rgba(255, 255, 255, 0.5);
-}
-
-.overlay .cancel {
- cursor: default;
- height: 100%;
- position: absolute;
- width: 100%;
-}
-
-.overlay:target {
- opacity: 1;
- pointer-events: auto;
- visibility: visible;
-}
-/* reveal aside */
-.overlay:target aside {
- transform: translateX(calc(var(--aside-width) * -1));
-}
-
-/*
-* --------------------------------------------------
-* components/Header
-* --------------------------------------------------
-*/
-.header {
- align-items: center;
- background: #fff;
- display: flex;
- height: var(--header-height);
- padding: 0 1rem;
- position: sticky;
- top: 0;
- z-index: 1;
-}
-
-.header-menu-mobile-toggle {
- @media (min-width: 48em) {
- display: none;
- }
-}
-
-.header-menu-mobile {
- display: flex;
- flex-direction: column;
- grid-gap: 1rem;
-}
-
-.header-menu-desktop {
- display: none;
- grid-gap: 1rem;
- @media (min-width: 45em) {
- display: flex;
- grid-gap: 1rem;
- margin-left: 3rem;
- }
-}
-
-.header-menu-item {
- cursor: pointer;
-}
-
-.header-ctas {
- align-items: center;
- display: flex;
- grid-gap: 1rem;
- margin-left: auto;
-}
-
-/*
-* --------------------------------------------------
-* components/Footer
-* --------------------------------------------------
-*/
-.footer {
- background: var(--color-dark);
- margin-top: auto;
-}
-
-.footer-menu {
- align-items: center;
- display: flex;
- grid-gap: 1rem;
- padding: 1rem;
-}
-
-.footer-menu a {
- color: var(--color-light);
-}
-
-/*
-* --------------------------------------------------
-* components/Cart
-* --------------------------------------------------
-*/
-.cart-main {
- height: 100%;
- max-height: calc(100vh - var(--cart-aside-summary-height));
- overflow-y: auto;
- width: auto;
-}
-
-.cart-main.with-discount {
- max-height: calc(100vh - var(--cart-aside-summary-height-with-discount));
-}
-
-.cart-line {
- display: flex;
- padding: 0.75rem 0;
-}
-
-.cart-line img {
- height: 100%;
- display: block;
- margin-right: 0.75rem;
-}
-
-.cart-summary-page {
- position: relative;
-}
-
-.cart-summary-aside {
- background: white;
- border-top: 1px solid var(--color-dark);
- bottom: 0;
- padding-top: 0.75rem;
- position: absolute;
- width: calc(var(--aside-width) - 40px);
-}
-
-.cart-line-quantity {
- display: flex;
-}
-
-.cart-discount {
- align-items: center;
- display: flex;
- margin-top: 0.25rem;
-}
-
-.cart-subtotal {
- align-items: center;
- display: flex;
-}
-/*
-* --------------------------------------------------
-* components/Search
-* --------------------------------------------------
-*/
-.predictive-search {
- height: calc(100vh - var(--header-height) - 40px);
- overflow-y: auto;
-}
-
-.predictive-search-form {
- background: var(--color-light);
- position: sticky;
- top: 0;
-}
-
-.predictive-search-result {
- margin-bottom: 2rem;
-}
-
-.predictive-search-result h5 {
- text-transform: uppercase;
-}
-
-.predictive-search-result-item {
- margin-bottom: 0.5rem;
-}
-
-.predictive-search-result-item a {
- align-items: center;
- display: flex;
-}
-
-.predictive-search-result-item a img {
- margin-right: 0.75rem;
- height: 100%;
-}
-
-.search-result {
- margin-bottom: 1.5rem;
-}
-
-.search-results-item {
- margin-bottom: 0.5rem;
-}
-
-/*
-* --------------------------------------------------
-* routes/__index
-* --------------------------------------------------
-*/
-.featured-collection {
- display: block;
- margin-bottom: 2rem;
- position: relative;
-}
-
-.featured-collection-image {
- aspect-ratio: 1 / 1;
- @media (min-width: 45em) {
- aspect-ratio: 16 / 9;
- }
-}
-
-.featured-collection img {
- height: auto;
- max-height: 100%;
- object-fit: cover;
-}
-
-.recommended-products-grid {
- display: grid;
- grid-gap: 1.5rem;
- grid-template-columns: repeat(2, 1fr);
- @media (min-width: 45em) {
- grid-template-columns: repeat(4, 1fr);
- }
-}
-
-.recommended-product img {
- height: auto;
-}
-
-/*
-* --------------------------------------------------
-* routes/collections._index.tsx
-* --------------------------------------------------
-*/
-.collections-grid {
- display: grid;
- grid-gap: 1.5rem;
- grid-template-columns: repeat(auto-fit, minmax(var(--grid-item-width), 1fr));
- margin-bottom: 2rem;
-}
-
-.collection-item img {
- height: auto;
-}
-
-/*
-* --------------------------------------------------
-* routes/collections.$handle.tsx
-* --------------------------------------------------
-*/
-.collection-description {
- margin-bottom: 1rem;
- max-width: 95%;
- @media (min-width: 45em) {
- max-width: 600px;
- }
-}
-
-.products-grid {
- display: grid;
- grid-gap: 1.5rem;
- grid-template-columns: repeat(auto-fit, minmax(var(--grid-item-width), 1fr));
- margin-bottom: 2rem;
-}
-
-.product-item img {
- height: auto;
- width: 100%;
-}
-
-/*
-* --------------------------------------------------
-* routes/products.$handle.tsx
-* --------------------------------------------------
-*/
-.product {
- display: grid;
- @media (min-width: 45em) {
- grid-template-columns: 1fr 1fr;
- grid-gap: 4rem;
- }
-}
-
-.product h1 {
- margin-top: 0;
-}
-
-.product-image img {
- height: auto;
- width: 100%;
-}
-
-.product-main {
- align-self: start;
- position: sticky;
- top: 6rem;
-}
-
-.product-price-on-sale {
- display: flex;
- grid-gap: 0.5rem;
-}
-
-.product-price-on-sale s {
- opacity: 0.5;
-}
-
-.product-options-grid {
- display: flex;
- flex-wrap: wrap;
- grid-gap: 0.75rem;
-}
-
-.product-options-item {
- padding: 0.25rem 0.5rem;
-}
-
-/*
-* --------------------------------------------------
-* routes/blog._index.tsx
-* --------------------------------------------------
-*/
-.blog-grid {
- display: grid;
- grid-gap: 1.5rem;
- grid-template-columns: repeat(auto-fit, minmax(var(--grid-item-width), 1fr));
- margin-bottom: 2rem;
-}
-
-.blog-article-image {
- aspect-ratio: 3/2;
- display: block;
-}
-
-.blog-article-image img {
- height: 100%;
-}
-
-/*
-* --------------------------------------------------
-* routes/blog.$articlehandle.tsx
-* --------------------------------------------------
-*/
-.article img {
- height: auto;
- width: 100%;
-}
-
-/*
-* --------------------------------------------------
-* routes/account
-* --------------------------------------------------
-*/
-
-.account-logout {
- display: inline-block;
-}
diff --git a/examples/subscriptions/app/styles/reset.css b/examples/subscriptions/app/styles/reset.css
deleted file mode 100644
index 451a9a58de..0000000000
--- a/examples/subscriptions/app/styles/reset.css
+++ /dev/null
@@ -1,129 +0,0 @@
-body {
- font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
- Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
- margin: 0;
- padding: 0;
-}
-
-h1,
-h2,
-p {
- margin: 0;
- padding: 0;
-}
-
-h1 {
- font-size: 1.6rem;
- font-weight: 700;
- line-height: 1.4;
- margin-bottom: 2rem;
- margin-top: 2rem;
-}
-
-h2 {
- font-size: 1.2rem;
- font-weight: 700;
- line-height: 1.4;
- margin-bottom: 1rem;
-}
-
-h4 {
- margin-top: 0.5rem;
- margin-bottom: 0.5rem;
-}
-
-h5 {
- margin-bottom: 1rem;
- margin-top: 0.5rem;
-}
-
-p {
- font-size: 1rem;
- line-height: 1.4;
-}
-
-a {
- color: #000;
- text-decoration: none;
-}
-
-a:hover {
- text-decoration: underline;
- cursor: pointer;
-}
-
-hr {
- border-bottom: none;
- border-top: 1px solid #000;
- margin: 0;
-}
-
-pre {
- white-space: pre-wrap;
-}
-
-body {
- display: flex;
- flex-direction: column;
- min-height: 100vh;
-}
-
-body > main {
- margin: 0 1rem 1rem 1rem;
-}
-
-section {
- padding: 1rem 0;
- @media (min-width: 768px) {
- padding: 2rem 0;
- }
-}
-
-fieldset {
- display: flex;
- flex-direction: column;
- margin-bottom: 0.5rem;
- padding: 1rem;
-}
-
-form {
- max-width: 100%;
- @media (min-width: 768px) {
- max-width: 400px;
- }
-}
-
-input {
- border-radius: 4px;
- border: 1px solid #000;
- font-size: 1rem;
- margin-bottom: 0.5rem;
- margin-top: 0.25rem;
- padding: 0.5rem;
-}
-
-legend {
- font-weight: 600;
- margin-bottom: 0.5rem;
-}
-
-ul {
- list-style: none;
- margin: 0;
- padding: 0;
-}
-
-li {
- margin-bottom: 0.5rem;
-}
-
-dl {
- margin: 0.5rem 0;
-}
-
-code {
- background: #ddd;
- border-radius: 4px;
- font-family: monospace;
- padding: 0.25rem;
-}
diff --git a/examples/subscriptions/app/styles/selling-plan.css b/examples/subscriptions/app/styles/selling-plan.css
new file mode 100644
index 0000000000..2b8e0eb48e
--- /dev/null
+++ b/examples/subscriptions/app/styles/selling-plan.css
@@ -0,0 +1,24 @@
+.selling-plan {
+ border: 1px solid;
+ display: inline-block;
+ padding: 1rem;
+ margin-right: 0.5rem;
+ line-height: 1;
+ padding-top: 0.25rem;
+ padding-bottom: 0.25rem;
+ border-bottom-width: 1.5px;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.selling-plan:hover {
+ text-decoration: none;
+}
+
+.selling-plan.selected {
+ border-color: #6b7280; /* Equivalent to 'border-gray-500' */
+}
+
+.selling-plan.unselected {
+ border-color: #fafafa; /* Equivalent to 'border-neutral-50' */
+}
diff --git a/examples/subscriptions/app/styles/tailwind.css b/examples/subscriptions/app/styles/tailwind.css
deleted file mode 100644
index b5c61c9567..0000000000
--- a/examples/subscriptions/app/styles/tailwind.css
+++ /dev/null
@@ -1,3 +0,0 @@
-@tailwind base;
-@tailwind components;
-@tailwind utilities;
diff --git a/examples/subscriptions/app/utils.ts b/examples/subscriptions/app/utils.ts
deleted file mode 100644
index ffea0a7306..0000000000
--- a/examples/subscriptions/app/utils.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import {useLocation} from '@remix-run/react';
-import type {SelectedOption} from '@shopify/hydrogen/storefront-api-types';
-import {useMemo} from 'react';
-
-export function useVariantUrl(
- handle: string,
- selectedOptions: SelectedOption[],
-) {
- const {pathname} = useLocation();
-
- return useMemo(() => {
- return getVariantUrl({
- handle,
- pathname,
- searchParams: new URLSearchParams(),
- selectedOptions,
- });
- }, [handle, selectedOptions, pathname]);
-}
-
-export function getVariantUrl({
- handle,
- pathname,
- searchParams,
- selectedOptions,
-}: {
- handle: string;
- pathname: string;
- searchParams: URLSearchParams;
- selectedOptions: SelectedOption[];
-}) {
- const match = /(\/[a-zA-Z]{2}-[a-zA-Z]{2}\/)/g.exec(pathname);
- const isLocalePathname = match && match.length > 0;
-
- const path = isLocalePathname
- ? `${match![0]}products/${handle}`
- : `/products/${handle}`;
-
- selectedOptions.forEach((option) => {
- searchParams.set(option.name, option.value);
- });
-
- const searchString = searchParams.toString();
-
- return path + (searchString ? '?' + searchParams.toString() : '');
-}
diff --git a/examples/subscriptions/package.json b/examples/subscriptions/package.json
index 19a5ec79b1..31a54fa566 100644
--- a/examples/subscriptions/package.json
+++ b/examples/subscriptions/package.json
@@ -1,53 +1,16 @@
{
"name": "example-subscriptions",
"private": true,
- "sideEffects": false,
+ "prettier": "@shopify/prettier-config",
"scripts": {
- "build": "shopify hydrogen build",
- "dev": "shopify hydrogen dev --codegen",
- "preview": "shopify hydrogen preview --build",
+ "build": "shopify hydrogen build --diff",
+ "dev": "shopify hydrogen dev --codegen --diff",
+ "preview": "shopify hydrogen preview --build --diff",
"lint": "eslint --no-error-on-unmatched-pattern --ext .js,.ts,.jsx,.tsx .",
"typecheck": "tsc --noEmit",
"codegen": "shopify hydrogen codegen"
},
- "prettier": "@shopify/prettier-config",
"dependencies": {
- "@remix-run/react": "^2.9.2",
- "@shopify/cli": "3.61.2",
- "@shopify/cli-hydrogen": "^8.1.0",
- "@shopify/hydrogen": "2024.4.3",
- "@shopify/remix-oxygen": "^2.0.4",
- "graphql": "^16.6.0",
- "graphql-tag": "^2.12.6",
- "isbot": "^3.6.6",
- "react": "^18.2.0",
- "react-dom": "^18.2.0"
- },
- "devDependencies": {
- "@remix-run/dev": "^2.9.2",
- "@remix-run/eslint-config": "^2.9.2",
- "@shopify/mini-oxygen": "^3.0.3",
- "@shopify/oxygen-workers-types": "^4.0.0",
- "@shopify/prettier-config": "^1.1.2",
- "@tailwindcss/forms": "^0.5.3",
- "@tailwindcss/typography": "^0.5.9",
- "@total-typescript/ts-reset": "^0.4.2",
- "@types/eslint": "^8.4.10",
- "@types/react": "^18.2.22",
- "@types/react-dom": "^18.2.7",
- "eslint": "^8.20.0",
- "eslint-plugin-hydrogen": "0.12.2",
- "postcss": "^8.4.21",
- "postcss-import": "^15.1.0",
- "postcss-preset-env": "^8.2.0",
- "prettier": "^2.8.4",
- "tailwindcss": "^3.3.0",
- "typescript": "^5.2.2"
- },
- "engines": {
- "node": ">=18.0.0"
- },
- "browserslist": [
- "defaults"
- ]
+ "@shopify/cli-hydrogen": "*"
+ }
}
diff --git a/examples/subscriptions/postcss.config.js b/examples/subscriptions/postcss.config.js
deleted file mode 100644
index 0a37ff00bc..0000000000
--- a/examples/subscriptions/postcss.config.js
+++ /dev/null
@@ -1,10 +0,0 @@
-module.exports = {
- plugins: {
- 'postcss-import': {},
- 'tailwindcss/nesting': {},
- tailwindcss: {},
- 'postcss-preset-env': {
- features: {'nesting-rules': false},
- },
- },
-};
diff --git a/examples/subscriptions/public/.gitkeep b/examples/subscriptions/public/.gitkeep
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/examples/subscriptions/remix.config.js b/examples/subscriptions/remix.config.js
deleted file mode 100644
index c79293082c..0000000000
--- a/examples/subscriptions/remix.config.js
+++ /dev/null
@@ -1,21 +0,0 @@
-/** @type {import('@remix-run/dev').AppConfig} */
-module.exports = {
- appDirectory: 'app',
- ignoredRouteFiles: ['**/.*'],
- watchPaths: ['./public', './.env'],
- server: './server.ts',
- /**
- * The following settings are required to deploy Hydrogen apps to Oxygen:
- */
- publicPath: (process.env.HYDROGEN_ASSET_BASE_URL ?? '/') + 'build/',
- assetsBuildDirectory: 'dist/client/build',
- serverBuildPath: 'dist/worker/index.js',
- serverMainFields: ['browser', 'module', 'main'],
- serverConditions: ['worker', process.env.NODE_ENV],
- serverDependenciesToBundle: 'all',
- serverModuleFormat: 'esm',
- serverPlatform: 'neutral',
- serverMinify: process.env.NODE_ENV === 'production',
- tailwind: true,
- postcss: true,
-};
diff --git a/examples/subscriptions/remix.env.d.ts b/examples/subscriptions/remix.env.d.ts
deleted file mode 100644
index 12a824b411..0000000000
--- a/examples/subscriptions/remix.env.d.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-///
-///
-///
-
-// Enhance TypeScript's built-in typings.
-import '@total-typescript/ts-reset';
-
-import type {Storefront, HydrogenCart} from '@shopify/hydrogen';
-import type {AppSession} from './server';
-
-declare global {
- /**
- * A global `process` object is only available during build to access NODE_ENV.
- */
- const process: {env: {NODE_ENV: 'production' | 'development'}};
-
- /**
- * Declare expected Env parameter in fetch handler.
- */
- interface Env {
- SESSION_SECRET: string;
- PUBLIC_STOREFRONT_API_TOKEN: string;
- PRIVATE_STOREFRONT_API_TOKEN: string;
- PUBLIC_STORE_DOMAIN: string;
- PUBLIC_STOREFRONT_ID: string;
- PUBLIC_CHECKOUT_DOMAIN: string;
- }
-}
-
-declare module '@shopify/remix-oxygen' {
- /**
- * Declare local additions to the Remix loader context.
- */
- export interface AppLoadContext {
- env: Env;
- cart: HydrogenCart;
- storefront: Storefront;
- session: AppSession;
- waitUntil: ExecutionContext['waitUntil'];
- }
-}
diff --git a/examples/subscriptions/server.ts b/examples/subscriptions/server.ts
deleted file mode 100644
index 455286bb4b..0000000000
--- a/examples/subscriptions/server.ts
+++ /dev/null
@@ -1,264 +0,0 @@
-// Virtual entry point for the app
-import * as remixBuild from '@remix-run/dev/server-build';
-import {
- cartGetIdDefault,
- cartSetIdDefault,
- createCartHandler,
- createStorefrontClient,
- storefrontRedirect,
- type HydrogenSession,
-} from '@shopify/hydrogen';
-import {
- createRequestHandler,
- getStorefrontHeaders,
- createCookieSessionStorage,
- type SessionStorage,
- type Session,
-} from '@shopify/remix-oxygen';
-
-/**
- * Export a fetch handler in module format.
- */
-export default {
- async fetch(
- request: Request,
- env: Env,
- executionContext: ExecutionContext,
- ): Promise {
- try {
- /**
- * Open a cache instance in the worker and a custom session instance.
- */
- if (!env?.SESSION_SECRET) {
- throw new Error('SESSION_SECRET environment variable is not set');
- }
-
- const waitUntil = executionContext.waitUntil.bind(executionContext);
- const [cache, session] = await Promise.all([
- caches.open('hydrogen'),
- AppSession.init(request, [env.SESSION_SECRET]),
- ]);
-
- /**
- * Create Hydrogen's Storefront client.
- */
- const {storefront} = createStorefrontClient({
- cache,
- waitUntil,
- i18n: {language: 'EN', country: 'US'},
- publicStorefrontToken: env.PUBLIC_STOREFRONT_API_TOKEN,
- privateStorefrontToken: env.PRIVATE_STOREFRONT_API_TOKEN,
- storeDomain: env.PUBLIC_STORE_DOMAIN,
- storefrontId: env.PUBLIC_STOREFRONT_ID,
- storefrontHeaders: getStorefrontHeaders(request),
- });
-
- /*
- * Create a cart handler that will be used to
- * create and update the cart in the session.
- */
- const cart = createCartHandler({
- storefront,
- getCartId: cartGetIdDefault(request.headers),
- setCartId: cartSetIdDefault(),
- cartQueryFragment: CART_QUERY_FRAGMENT,
- });
-
- /**
- * Create a Remix request handler and pass
- * Hydrogen's Storefront client to the loader context.
- */
- const handleRequest = createRequestHandler({
- build: remixBuild,
- mode: process.env.NODE_ENV,
- getLoadContext: () => ({session, storefront, cart, env, waitUntil}),
- });
-
- const response = await handleRequest(request);
-
- if (response.status === 404) {
- /**
- * Check for redirects only when there's a 404 from the app.
- * If the redirect doesn't exist, then `storefrontRedirect`
- * will pass through the 404 response.
- */
- return storefrontRedirect({request, response, storefront});
- }
-
- return response;
- } catch (error) {
- // eslint-disable-next-line no-console
- console.error(error);
- return new Response('An unexpected error occurred', {status: 500});
- }
- },
-};
-
-/**
- * This is a custom session implementation for your Hydrogen shop.
- * Feel free to customize it to your needs, add helper methods, or
- * swap out the cookie-based implementation with something else!
- */
-export class AppSession implements HydrogenSession {
- #sessionStorage;
- #session;
-
- constructor(sessionStorage: SessionStorage, session: Session) {
- this.#sessionStorage = sessionStorage;
- this.#session = session;
- }
-
- static async init(request: Request, secrets: string[]) {
- const storage = createCookieSessionStorage({
- cookie: {
- name: 'session',
- httpOnly: true,
- path: '/',
- sameSite: 'lax',
- secrets,
- },
- });
-
- const session = await storage
- .getSession(request.headers.get('Cookie'))
- .catch(() => storage.getSession());
-
- return new this(storage, session);
- }
-
- get has() {
- return this.#session.has;
- }
-
- get get() {
- return this.#session.get;
- }
-
- get flash() {
- return this.#session.flash;
- }
-
- get unset() {
- return this.#session.unset;
- }
-
- get set() {
- return this.#session.set;
- }
-
- destroy() {
- return this.#sessionStorage.destroySession(this.#session);
- }
-
- commit() {
- return this.#sessionStorage.commitSession(this.#session);
- }
-}
-
-// NOTE: https://shopify.dev/docs/api/storefront/latest/queries/cart
-const CART_QUERY_FRAGMENT = `#graphql
- fragment Money on MoneyV2 {
- currencyCode
- amount
- }
- fragment CartLine on CartLine {
- id
- quantity
- attributes {
- key
- value
- }
- cost {
- totalAmount {
- ...Money
- }
- amountPerQuantity {
- ...Money
- }
- compareAtAmountPerQuantity {
- ...Money
- }
- }
- sellingPlanAllocation {
- sellingPlan {
- name
- }
- }
- merchandise {
- ... on ProductVariant {
- id
- availableForSale
- compareAtPrice {
- ...Money
- }
- price {
- ...Money
- }
- requiresShipping
- title
- image {
- id
- url
- altText
- width
- height
-
- }
- product {
- handle
- title
- id
- }
- selectedOptions {
- name
- value
- }
- }
- }
- }
- fragment CartApiQuery on Cart {
- id
- checkoutUrl
- totalQuantity
- buyerIdentity {
- countryCode
- customer {
- id
- email
- firstName
- lastName
- displayName
- }
- email
- phone
- }
- lines(first: $numCartLines) {
- nodes {
- ...CartLine
- }
- }
- cost {
- subtotalAmount {
- ...Money
- }
- totalAmount {
- ...Money
- }
- totalDutyAmount {
- ...Money
- }
- totalTaxAmount {
- ...Money
- }
- }
- note
- attributes {
- key
- value
- }
- discountCodes {
- code
- applicable
- }
- }
-` as const;
diff --git a/examples/subscriptions/storefrontapi.generated.d.ts b/examples/subscriptions/storefrontapi.generated.d.ts
index af8155fbf1..249c0a3918 100644
--- a/examples/subscriptions/storefrontapi.generated.d.ts
+++ b/examples/subscriptions/storefrontapi.generated.d.ts
@@ -3,6 +3,149 @@
/* eslint-disable */
import type * as StorefrontAPI from '@shopify/hydrogen/storefront-api-types';
+export type MoneyFragment = Pick<
+ StorefrontAPI.MoneyV2,
+ 'currencyCode' | 'amount'
+>;
+
+export type CartLineFragment = Pick<
+ StorefrontAPI.CartLine,
+ 'id' | 'quantity'
+> & {
+ attributes: Array>;
+ cost: {
+ totalAmount: Pick;
+ amountPerQuantity: Pick;
+ compareAtAmountPerQuantity?: StorefrontAPI.Maybe<
+ Pick
+ >;
+ };
+ merchandise: Pick<
+ StorefrontAPI.ProductVariant,
+ 'id' | 'availableForSale' | 'requiresShipping' | 'title'
+ > & {
+ compareAtPrice?: StorefrontAPI.Maybe<
+ Pick
+ >;
+ price: Pick;
+ image?: StorefrontAPI.Maybe<
+ Pick
+ >;
+ product: Pick;
+ selectedOptions: Array<
+ Pick
+ >;
+ };
+};
+
+export type CartApiQueryFragment = Pick<
+ StorefrontAPI.Cart,
+ 'updatedAt' | 'id' | 'checkoutUrl' | 'totalQuantity' | 'note'
+> & {
+ buyerIdentity: Pick<
+ StorefrontAPI.CartBuyerIdentity,
+ 'countryCode' | 'email' | 'phone'
+ > & {
+ customer?: StorefrontAPI.Maybe<
+ Pick<
+ StorefrontAPI.Customer,
+ 'id' | 'email' | 'firstName' | 'lastName' | 'displayName'
+ >
+ >;
+ };
+ lines: {
+ nodes: Array<
+ Pick & {
+ attributes: Array>;
+ cost: {
+ totalAmount: Pick;
+ amountPerQuantity: Pick<
+ StorefrontAPI.MoneyV2,
+ 'currencyCode' | 'amount'
+ >;
+ compareAtAmountPerQuantity?: StorefrontAPI.Maybe<
+ Pick
+ >;
+ };
+ merchandise: Pick<
+ StorefrontAPI.ProductVariant,
+ 'id' | 'availableForSale' | 'requiresShipping' | 'title'
+ > & {
+ compareAtPrice?: StorefrontAPI.Maybe<
+ Pick
+ >;
+ price: Pick;
+ image?: StorefrontAPI.Maybe<
+ Pick<
+ StorefrontAPI.Image,
+ 'id' | 'url' | 'altText' | 'width' | 'height'
+ >
+ >;
+ product: Pick<
+ StorefrontAPI.Product,
+ 'handle' | 'title' | 'id' | 'vendor'
+ >;
+ selectedOptions: Array<
+ Pick
+ >;
+ };
+ }
+ >;
+ };
+ cost: {
+ subtotalAmount: Pick;
+ totalAmount: Pick;
+ totalDutyAmount?: StorefrontAPI.Maybe<
+ Pick
+ >;
+ totalTaxAmount?: StorefrontAPI.Maybe<
+ Pick
+ >;
+ };
+ attributes: Array>;
+ discountCodes: Array<
+ Pick
+ >;
+};
+
+export type MenuItemFragment = Pick<
+ StorefrontAPI.MenuItem,
+ 'id' | 'resourceId' | 'tags' | 'title' | 'type' | 'url'
+>;
+
+export type ChildMenuItemFragment = Pick<
+ StorefrontAPI.MenuItem,
+ 'id' | 'resourceId' | 'tags' | 'title' | 'type' | 'url'
+>;
+
+export type ParentMenuItemFragment = Pick<
+ StorefrontAPI.MenuItem,
+ 'id' | 'resourceId' | 'tags' | 'title' | 'type' | 'url'
+> & {
+ items: Array<
+ Pick<
+ StorefrontAPI.MenuItem,
+ 'id' | 'resourceId' | 'tags' | 'title' | 'type' | 'url'
+ >
+ >;
+};
+
+export type MenuFragment = Pick & {
+ items: Array<
+ Pick<
+ StorefrontAPI.MenuItem,
+ 'id' | 'resourceId' | 'tags' | 'title' | 'type' | 'url'
+ > & {
+ items: Array<
+ Pick<
+ StorefrontAPI.MenuItem,
+ 'id' | 'resourceId' | 'tags' | 'title' | 'type' | 'url'
+ >
+ >;
+ }
+ >;
+};
+
export type ShopFragment = Pick<
StorefrontAPI.Shop,
'id' | 'name' | 'description'
@@ -15,38 +158,579 @@ export type ShopFragment = Pick<
}>;
};
-export type HeaderQueryVariables = StorefrontAPI.Exact<{[key: string]: never}>;
+export type HeaderQueryVariables = StorefrontAPI.Exact<{
+ country?: StorefrontAPI.InputMaybe;
+ headerMenuHandle: StorefrontAPI.Scalars['String']['input'];
+ language?: StorefrontAPI.InputMaybe;
+}>;
+
+export type HeaderQuery = {
+ shop: Pick & {
+ primaryDomain: Pick;
+ brand?: StorefrontAPI.Maybe<{
+ logo?: StorefrontAPI.Maybe<{
+ image?: StorefrontAPI.Maybe>;
+ }>;
+ }>;
+ };
+ menu?: StorefrontAPI.Maybe<
+ Pick & {
+ items: Array<
+ Pick<
+ StorefrontAPI.MenuItem,
+ 'id' | 'resourceId' | 'tags' | 'title' | 'type' | 'url'
+ > & {
+ items: Array<
+ Pick<
+ StorefrontAPI.MenuItem,
+ 'id' | 'resourceId' | 'tags' | 'title' | 'type' | 'url'
+ >
+ >;
+ }
+ >;
+ }
+ >;
+};
+
+export type FooterQueryVariables = StorefrontAPI.Exact<{
+ country?: StorefrontAPI.InputMaybe;
+ footerMenuHandle: StorefrontAPI.Scalars['String']['input'];
+ language?: StorefrontAPI.InputMaybe;
+}>;
+
+export type FooterQuery = {
+ menu?: StorefrontAPI.Maybe<
+ Pick & {
+ items: Array<
+ Pick<
+ StorefrontAPI.MenuItem,
+ 'id' | 'resourceId' | 'tags' | 'title' | 'type' | 'url'
+ > & {
+ items: Array<
+ Pick<
+ StorefrontAPI.MenuItem,
+ 'id' | 'resourceId' | 'tags' | 'title' | 'type' | 'url'
+ >
+ >;
+ }
+ >;
+ }
+ >;
+};
+
+export type StoreRobotsQueryVariables = StorefrontAPI.Exact<{
+ country?: StorefrontAPI.InputMaybe;
+ language?: StorefrontAPI.InputMaybe;
+}>;
+
+export type StoreRobotsQuery = {shop: Pick};
+
+export type SitemapQueryVariables = StorefrontAPI.Exact<{
+ urlLimits?: StorefrontAPI.InputMaybe;
+ language?: StorefrontAPI.InputMaybe;
+}>;
+
+export type SitemapQuery = {
+ products: {
+ nodes: Array<
+ Pick<
+ StorefrontAPI.Product,
+ 'updatedAt' | 'handle' | 'onlineStoreUrl' | 'title'
+ > & {
+ featuredImage?: StorefrontAPI.Maybe<
+ Pick
+ >;
+ }
+ >;
+ };
+ collections: {
+ nodes: Array<
+ Pick
+ >;
+ };
+ pages: {
+ nodes: Array<
+ Pick
+ >;
+ };
+};
+
+export type PredictiveArticleFragment = {__typename: 'Article'} & Pick<
+ StorefrontAPI.Article,
+ 'id' | 'title' | 'handle' | 'trackingParameters'
+> & {
+ blog: Pick;
+ image?: StorefrontAPI.Maybe<
+ Pick
+ >;
+ };
+
+export type PredictiveCollectionFragment = {__typename: 'Collection'} & Pick<
+ StorefrontAPI.Collection,
+ 'id' | 'title' | 'handle' | 'trackingParameters'
+> & {
+ image?: StorefrontAPI.Maybe<
+ Pick
+ >;
+ };
+
+export type PredictivePageFragment = {__typename: 'Page'} & Pick<
+ StorefrontAPI.Page,
+ 'id' | 'title' | 'handle' | 'trackingParameters'
+>;
+
+export type PredictiveProductFragment = {__typename: 'Product'} & Pick<
+ StorefrontAPI.Product,
+ 'id' | 'title' | 'handle' | 'trackingParameters'
+> & {
+ variants: {
+ nodes: Array<
+ Pick & {
+ image?: StorefrontAPI.Maybe<
+ Pick
+ >;
+ price: Pick;
+ }
+ >;
+ };
+ };
+
+export type PredictiveQueryFragment = {
+ __typename: 'SearchQuerySuggestion';
+} & Pick<
+ StorefrontAPI.SearchQuerySuggestion,
+ 'text' | 'styledText' | 'trackingParameters'
+>;
+
+export type PredictiveSearchQueryVariables = StorefrontAPI.Exact<{
+ country?: StorefrontAPI.InputMaybe;
+ language?: StorefrontAPI.InputMaybe;
+ limit: StorefrontAPI.Scalars['Int']['input'];
+ limitScope: StorefrontAPI.PredictiveSearchLimitScope;
+ searchTerm: StorefrontAPI.Scalars['String']['input'];
+ types?: StorefrontAPI.InputMaybe<
+ | Array
+ | StorefrontAPI.PredictiveSearchType
+ >;
+}>;
+
+export type PredictiveSearchQuery = {
+ predictiveSearch?: StorefrontAPI.Maybe<{
+ articles: Array<
+ {__typename: 'Article'} & Pick<
+ StorefrontAPI.Article,
+ 'id' | 'title' | 'handle' | 'trackingParameters'
+ > & {
+ blog: Pick;
+ image?: StorefrontAPI.Maybe<
+ Pick
+ >;
+ }
+ >;
+ collections: Array<
+ {__typename: 'Collection'} & Pick<
+ StorefrontAPI.Collection,
+ 'id' | 'title' | 'handle' | 'trackingParameters'
+ > & {
+ image?: StorefrontAPI.Maybe<
+ Pick
+ >;
+ }
+ >;
+ pages: Array<
+ {__typename: 'Page'} & Pick<
+ StorefrontAPI.Page,
+ 'id' | 'title' | 'handle' | 'trackingParameters'
+ >
+ >;
+ products: Array<
+ {__typename: 'Product'} & Pick<
+ StorefrontAPI.Product,
+ 'id' | 'title' | 'handle' | 'trackingParameters'
+ > & {
+ variants: {
+ nodes: Array<
+ Pick & {
+ image?: StorefrontAPI.Maybe<
+ Pick<
+ StorefrontAPI.Image,
+ 'url' | 'altText' | 'width' | 'height'
+ >
+ >;
+ price: Pick;
+ }
+ >;
+ };
+ }
+ >;
+ queries: Array<
+ {__typename: 'SearchQuerySuggestion'} & Pick<
+ StorefrontAPI.SearchQuerySuggestion,
+ 'text' | 'styledText' | 'trackingParameters'
+ >
+ >;
+ }>;
+};
+
+export type ArticleQueryVariables = StorefrontAPI.Exact<{
+ articleHandle: StorefrontAPI.Scalars['String']['input'];
+ blogHandle: StorefrontAPI.Scalars['String']['input'];
+ country?: StorefrontAPI.InputMaybe;
+ language?: StorefrontAPI.InputMaybe;
+}>;
+
+export type ArticleQuery = {
+ blog?: StorefrontAPI.Maybe<{
+ articleByHandle?: StorefrontAPI.Maybe<
+ Pick & {
+ author?: StorefrontAPI.Maybe>;
+ image?: StorefrontAPI.Maybe<
+ Pick<
+ StorefrontAPI.Image,
+ 'id' | 'altText' | 'url' | 'width' | 'height'
+ >
+ >;
+ seo?: StorefrontAPI.Maybe<
+ Pick
+ >;
+ }
+ >;
+ }>;
+};
+
+export type BlogQueryVariables = StorefrontAPI.Exact<{
+ language?: StorefrontAPI.InputMaybe;
+ blogHandle: StorefrontAPI.Scalars['String']['input'];
+ first?: StorefrontAPI.InputMaybe;
+ last?: StorefrontAPI.InputMaybe;
+ startCursor?: StorefrontAPI.InputMaybe<
+ StorefrontAPI.Scalars['String']['input']
+ >;
+ endCursor?: StorefrontAPI.InputMaybe<
+ StorefrontAPI.Scalars['String']['input']
+ >;
+}>;
+
+export type BlogQuery = {
+ blog?: StorefrontAPI.Maybe<
+ Pick & {
+ seo?: StorefrontAPI.Maybe<
+ Pick
+ >;
+ articles: {
+ nodes: Array<
+ Pick<
+ StorefrontAPI.Article,
+ 'contentHtml' | 'handle' | 'id' | 'publishedAt' | 'title'
+ > & {
+ author?: StorefrontAPI.Maybe<
+ Pick
+ >;
+ image?: StorefrontAPI.Maybe<
+ Pick<
+ StorefrontAPI.Image,
+ 'id' | 'altText' | 'url' | 'width' | 'height'
+ >
+ >;
+ blog: Pick;
+ }
+ >;
+ pageInfo: Pick<
+ StorefrontAPI.PageInfo,
+ 'hasPreviousPage' | 'hasNextPage' | 'endCursor' | 'startCursor'
+ >;
+ };
+ }
+ >;
+};
+
+export type ArticleItemFragment = Pick<
+ StorefrontAPI.Article,
+ 'contentHtml' | 'handle' | 'id' | 'publishedAt' | 'title'
+> & {
+ author?: StorefrontAPI.Maybe>;
+ image?: StorefrontAPI.Maybe<
+ Pick
+ >;
+ blog: Pick;
+};
+
+export type BlogsQueryVariables = StorefrontAPI.Exact<{
+ country?: StorefrontAPI.InputMaybe;
+ endCursor?: StorefrontAPI.InputMaybe<
+ StorefrontAPI.Scalars['String']['input']
+ >;
+ first?: StorefrontAPI.InputMaybe;
+ language?: StorefrontAPI.InputMaybe;
+ last?: StorefrontAPI.InputMaybe;
+ startCursor?: StorefrontAPI.InputMaybe<
+ StorefrontAPI.Scalars['String']['input']
+ >;
+}>;
+
+export type BlogsQuery = {
+ blogs: {
+ pageInfo: Pick<
+ StorefrontAPI.PageInfo,
+ 'hasNextPage' | 'hasPreviousPage' | 'startCursor' | 'endCursor'
+ >;
+ nodes: Array<
+ Pick & {
+ seo?: StorefrontAPI.Maybe<
+ Pick
+ >;
+ }
+ >;
+ };
+};
+
+export type MoneyProductItemFragment = Pick<
+ StorefrontAPI.MoneyV2,
+ 'amount' | 'currencyCode'
+>;
+
+export type ProductItemFragment = Pick<
+ StorefrontAPI.Product,
+ 'id' | 'handle' | 'title'
+> & {
+ featuredImage?: StorefrontAPI.Maybe<
+ Pick
+ >;
+ priceRange: {
+ minVariantPrice: Pick;
+ maxVariantPrice: Pick;
+ };
+ variants: {
+ nodes: Array<{
+ selectedOptions: Array<
+ Pick
+ >;
+ }>;
+ };
+};
+
+export type CollectionQueryVariables = StorefrontAPI.Exact<{
+ handle: StorefrontAPI.Scalars['String']['input'];
+ country?: StorefrontAPI.InputMaybe;
+ language?: StorefrontAPI.InputMaybe;
+ first?: StorefrontAPI.InputMaybe;
+ last?: StorefrontAPI.InputMaybe;
+ startCursor?: StorefrontAPI.InputMaybe<
+ StorefrontAPI.Scalars['String']['input']
+ >;
+ endCursor?: StorefrontAPI.InputMaybe<
+ StorefrontAPI.Scalars['String']['input']
+ >;
+}>;
+
+export type CollectionQuery = {
+ collection?: StorefrontAPI.Maybe<
+ Pick<
+ StorefrontAPI.Collection,
+ 'id' | 'handle' | 'title' | 'description'
+ > & {
+ products: {
+ nodes: Array<
+ Pick & {
+ featuredImage?: StorefrontAPI.Maybe<
+ Pick<
+ StorefrontAPI.Image,
+ 'id' | 'altText' | 'url' | 'width' | 'height'
+ >
+ >;
+ priceRange: {
+ minVariantPrice: Pick<
+ StorefrontAPI.MoneyV2,
+ 'amount' | 'currencyCode'
+ >;
+ maxVariantPrice: Pick<
+ StorefrontAPI.MoneyV2,
+ 'amount' | 'currencyCode'
+ >;
+ };
+ variants: {
+ nodes: Array<{
+ selectedOptions: Array<
+ Pick
+ >;
+ }>;
+ };
+ }
+ >;
+ pageInfo: Pick<
+ StorefrontAPI.PageInfo,
+ 'hasPreviousPage' | 'hasNextPage' | 'endCursor' | 'startCursor'
+ >;
+ };
+ }
+ >;
+};
+
+export type CollectionFragment = Pick<
+ StorefrontAPI.Collection,
+ 'id' | 'title' | 'handle'
+> & {
+ image?: StorefrontAPI.Maybe<
+ Pick
+ >;
+};
+
+export type StoreCollectionsQueryVariables = StorefrontAPI.Exact<{
+ country?: StorefrontAPI.InputMaybe;
+ endCursor?: StorefrontAPI.InputMaybe<
+ StorefrontAPI.Scalars['String']['input']
+ >;
+ first?: StorefrontAPI.InputMaybe;
+ language?: StorefrontAPI.InputMaybe;
+ last?: StorefrontAPI.InputMaybe;
+ startCursor?: StorefrontAPI.InputMaybe<
+ StorefrontAPI.Scalars['String']['input']
+ >;
+}>;
+
+export type StoreCollectionsQuery = {
+ collections: {
+ nodes: Array<
+ Pick & {
+ image?: StorefrontAPI.Maybe<
+ Pick<
+ StorefrontAPI.Image,
+ 'id' | 'url' | 'altText' | 'width' | 'height'
+ >
+ >;
+ }
+ >;
+ pageInfo: Pick<
+ StorefrontAPI.PageInfo,
+ 'hasNextPage' | 'hasPreviousPage' | 'startCursor' | 'endCursor'
+ >;
+ };
+};
+
+export type CatalogQueryVariables = StorefrontAPI.Exact<{
+ country?: StorefrontAPI.InputMaybe;
+ language?: StorefrontAPI.InputMaybe;
+ first?: StorefrontAPI.InputMaybe;
+ last?: StorefrontAPI.InputMaybe;
+ startCursor?: StorefrontAPI.InputMaybe<
+ StorefrontAPI.Scalars['String']['input']
+ >;
+ endCursor?: StorefrontAPI.InputMaybe<
+ StorefrontAPI.Scalars['String']['input']
+ >;
+}>;
+
+export type CatalogQuery = {
+ products: {
+ nodes: Array<
+ Pick & {
+ featuredImage?: StorefrontAPI.Maybe<
+ Pick<
+ StorefrontAPI.Image,
+ 'id' | 'altText' | 'url' | 'width' | 'height'
+ >
+ >;
+ priceRange: {
+ minVariantPrice: Pick<
+ StorefrontAPI.MoneyV2,
+ 'amount' | 'currencyCode'
+ >;
+ maxVariantPrice: Pick<
+ StorefrontAPI.MoneyV2,
+ 'amount' | 'currencyCode'
+ >;
+ };
+ variants: {
+ nodes: Array<{
+ selectedOptions: Array<
+ Pick
+ >;
+ }>;
+ };
+ }
+ >;
+ pageInfo: Pick<
+ StorefrontAPI.PageInfo,
+ 'hasPreviousPage' | 'hasNextPage' | 'startCursor' | 'endCursor'
+ >;
+ };
+};
+
+export type PageQueryVariables = StorefrontAPI.Exact<{
+ language?: StorefrontAPI.InputMaybe;
+ country?: StorefrontAPI.InputMaybe;
+ handle: StorefrontAPI.Scalars['String']['input'];
+}>;
-export type HeaderQuery = {
- shop: Pick & {
- primaryDomain: Pick;
- brand?: StorefrontAPI.Maybe<{
- logo?: StorefrontAPI.Maybe<{
- image?: StorefrontAPI.Maybe>;
- }>;
- }>;
+export type PageQuery = {
+ page?: StorefrontAPI.Maybe<
+ Pick & {
+ seo?: StorefrontAPI.Maybe<
+ Pick
+ >;
+ }
+ >;
+};
+
+export type PolicyFragment = Pick<
+ StorefrontAPI.ShopPolicy,
+ 'body' | 'handle' | 'id' | 'title' | 'url'
+>;
+
+export type PolicyQueryVariables = StorefrontAPI.Exact<{
+ country?: StorefrontAPI.InputMaybe;
+ language?: StorefrontAPI.InputMaybe;
+ privacyPolicy: StorefrontAPI.Scalars['Boolean']['input'];
+ refundPolicy: StorefrontAPI.Scalars['Boolean']['input'];
+ shippingPolicy: StorefrontAPI.Scalars['Boolean']['input'];
+ termsOfService: StorefrontAPI.Scalars['Boolean']['input'];
+}>;
+
+export type PolicyQuery = {
+ shop: {
+ privacyPolicy?: StorefrontAPI.Maybe<
+ Pick
+ >;
+ shippingPolicy?: StorefrontAPI.Maybe<
+ Pick
+ >;
+ termsOfService?: StorefrontAPI.Maybe<
+ Pick
+ >;
+ refundPolicy?: StorefrontAPI.Maybe<
+ Pick
+ >;
};
};
-export type ProductVariantFragment = Pick<
- StorefrontAPI.ProductVariant,
- 'availableForSale' | 'id' | 'sku' | 'title'
-> & {
- compareAtPrice?: StorefrontAPI.Maybe<
- Pick
- >;
- image?: StorefrontAPI.Maybe<
- {__typename: 'Image'} & Pick<
- StorefrontAPI.Image,
- 'id' | 'url' | 'altText' | 'width' | 'height'
- >
- >;
- price: Pick;
- product: Pick;
- selectedOptions: Array>;
- unitPrice?: StorefrontAPI.Maybe<
- Pick
- >;
+export type PolicyItemFragment = Pick<
+ StorefrontAPI.ShopPolicy,
+ 'id' | 'title' | 'handle'
+>;
+
+export type PoliciesQueryVariables = StorefrontAPI.Exact<{
+ country?: StorefrontAPI.InputMaybe;
+ language?: StorefrontAPI.InputMaybe;
+}>;
+
+export type PoliciesQuery = {
+ shop: {
+ privacyPolicy?: StorefrontAPI.Maybe<
+ Pick
+ >;
+ shippingPolicy?: StorefrontAPI.Maybe<
+ Pick
+ >;
+ termsOfService?: StorefrontAPI.Maybe<
+ Pick
+ >;
+ refundPolicy?: StorefrontAPI.Maybe<
+ Pick
+ >;
+ subscriptionPolicy?: StorefrontAPI.Maybe<
+ Pick
+ >;
+ };
};
export type SellingPlanMoneyFragment = Pick<
@@ -130,11 +814,56 @@ export type SellingPlanGroupFragment = Pick<
};
};
+export type ProductVariantFragment = Pick<
+ StorefrontAPI.ProductVariant,
+ 'availableForSale' | 'id' | 'sku' | 'title'
+> & {
+ compareAtPrice?: StorefrontAPI.Maybe<
+ Pick
+ >;
+ image?: StorefrontAPI.Maybe<
+ {__typename: 'Image'} & Pick<
+ StorefrontAPI.Image,
+ 'id' | 'url' | 'altText' | 'width' | 'height'
+ >
+ >;
+ price: Pick;
+ product: Pick;
+ selectedOptions: Array>;
+ unitPrice?: StorefrontAPI.Maybe<
+ Pick
+ >;
+};
+
export type ProductFragment = Pick<
StorefrontAPI.Product,
'id' | 'title' | 'vendor' | 'handle' | 'descriptionHtml' | 'description'
> & {
options: Array>;
+ selectedVariant?: StorefrontAPI.Maybe<
+ Pick<
+ StorefrontAPI.ProductVariant,
+ 'availableForSale' | 'id' | 'sku' | 'title'
+ > & {
+ compareAtPrice?: StorefrontAPI.Maybe<
+ Pick
+ >;
+ image?: StorefrontAPI.Maybe<
+ {__typename: 'Image'} & Pick<
+ StorefrontAPI.Image,
+ 'id' | 'url' | 'altText' | 'width' | 'height'
+ >
+ >;
+ price: Pick;
+ product: Pick;
+ selectedOptions: Array<
+ Pick
+ >;
+ unitPrice?: StorefrontAPI.Maybe<
+ Pick
+ >;
+ }
+ >;
variants: {
nodes: Array<
Pick<
@@ -220,6 +949,9 @@ export type ProductQueryVariables = StorefrontAPI.Exact<{
country?: StorefrontAPI.InputMaybe;
handle: StorefrontAPI.Scalars['String']['input'];
language?: StorefrontAPI.InputMaybe;
+ selectedOptions:
+ | Array
+ | StorefrontAPI.SelectedOptionInput;
}>;
export type ProductQuery = {
@@ -229,6 +961,30 @@ export type ProductQuery = {
'id' | 'title' | 'vendor' | 'handle' | 'descriptionHtml' | 'description'
> & {
options: Array>;
+ selectedVariant?: StorefrontAPI.Maybe<
+ Pick<
+ StorefrontAPI.ProductVariant,
+ 'availableForSale' | 'id' | 'sku' | 'title'
+ > & {
+ compareAtPrice?: StorefrontAPI.Maybe<
+ Pick
+ >;
+ image?: StorefrontAPI.Maybe<
+ {__typename: 'Image'} & Pick<
+ StorefrontAPI.Image,
+ 'id' | 'url' | 'altText' | 'width' | 'height'
+ >
+ >;
+ price: Pick;
+ product: Pick;
+ selectedOptions: Array<
+ Pick
+ >;
+ unitPrice?: StorefrontAPI.Maybe<
+ Pick
+ >;
+ }
+ >;
variants: {
nodes: Array<
Pick<
@@ -320,123 +1076,245 @@ export type ProductQuery = {
>;
};
-export type MoneyFragment = Pick<
- StorefrontAPI.MoneyV2,
- 'currencyCode' | 'amount'
->;
-
-export type CartLineFragment = Pick<
- StorefrontAPI.CartLine,
- 'id' | 'quantity'
-> & {
- attributes: Array>;
- cost: {
- totalAmount: Pick;
- amountPerQuantity: Pick;
- compareAtAmountPerQuantity?: StorefrontAPI.Maybe<
- Pick
- >;
- };
- sellingPlanAllocation?: StorefrontAPI.Maybe<{
- sellingPlan: Pick;
- }>;
- merchandise: Pick<
- StorefrontAPI.ProductVariant,
- 'id' | 'availableForSale' | 'requiresShipping' | 'title'
- > & {
- compareAtPrice?: StorefrontAPI.Maybe<
- Pick
- >;
- price: Pick;
- image?: StorefrontAPI.Maybe<
- Pick
- >;
- product: Pick;
- selectedOptions: Array<
- Pick
+export type ProductVariantsFragment = {
+ variants: {
+ nodes: Array<
+ Pick<
+ StorefrontAPI.ProductVariant,
+ 'availableForSale' | 'id' | 'sku' | 'title'
+ > & {
+ compareAtPrice?: StorefrontAPI.Maybe<
+ Pick
+ >;
+ image?: StorefrontAPI.Maybe<
+ {__typename: 'Image'} & Pick<
+ StorefrontAPI.Image,
+ 'id' | 'url' | 'altText' | 'width' | 'height'
+ >
+ >;
+ price: Pick;
+ product: Pick;
+ selectedOptions: Array<
+ Pick
+ >;
+ unitPrice?: StorefrontAPI.Maybe<
+ Pick
+ >;
+ }
>;
};
};
-export type CartApiQueryFragment = Pick<
- StorefrontAPI.Cart,
- 'id' | 'checkoutUrl' | 'totalQuantity' | 'note'
-> & {
- buyerIdentity: Pick<
- StorefrontAPI.CartBuyerIdentity,
- 'countryCode' | 'email' | 'phone'
- > & {
- customer?: StorefrontAPI.Maybe<
- Pick<
- StorefrontAPI.Customer,
- 'id' | 'email' | 'firstName' | 'lastName' | 'displayName'
- >
- >;
- };
- lines: {
- nodes: Array<
- Pick & {
- attributes: Array>;
- cost: {
- totalAmount: Pick;
- amountPerQuantity: Pick<
- StorefrontAPI.MoneyV2,
- 'currencyCode' | 'amount'
- >;
- compareAtAmountPerQuantity?: StorefrontAPI.Maybe<
- Pick
- >;
- };
- sellingPlanAllocation?: StorefrontAPI.Maybe<{
- sellingPlan: Pick;
- }>;
- merchandise: Pick<
+export type ProductVariantsQueryVariables = StorefrontAPI.Exact<{
+ country?: StorefrontAPI.InputMaybe;
+ language?: StorefrontAPI.InputMaybe;
+ handle: StorefrontAPI.Scalars['String']['input'];
+}>;
+
+export type ProductVariantsQuery = {
+ product?: StorefrontAPI.Maybe<{
+ variants: {
+ nodes: Array<
+ Pick<
StorefrontAPI.ProductVariant,
- 'id' | 'availableForSale' | 'requiresShipping' | 'title'
+ 'availableForSale' | 'id' | 'sku' | 'title'
> & {
compareAtPrice?: StorefrontAPI.Maybe<
- Pick
+ Pick
>;
- price: Pick;
image?: StorefrontAPI.Maybe<
- Pick<
+ {__typename: 'Image'} & Pick<
StorefrontAPI.Image,
'id' | 'url' | 'altText' | 'width' | 'height'
>
>;
- product: Pick;
+ price: Pick;
+ product: Pick;
selectedOptions: Array<
Pick
>;
- };
- }
+ unitPrice?: StorefrontAPI.Maybe<
+ Pick
+ >;
+ }
+ >;
+ };
+ }>;
+};
+
+export type SearchProductFragment = {__typename: 'Product'} & Pick<
+ StorefrontAPI.Product,
+ 'handle' | 'id' | 'publishedAt' | 'title' | 'trackingParameters' | 'vendor'
+> & {
+ variants: {
+ nodes: Array<
+ Pick & {
+ image?: StorefrontAPI.Maybe<
+ Pick
+ >;
+ price: Pick;
+ compareAtPrice?: StorefrontAPI.Maybe<
+ Pick
+ >;
+ selectedOptions: Array<
+ Pick
+ >;
+ product: Pick;
+ }
+ >;
+ };
+ };
+
+export type SearchPageFragment = {__typename: 'Page'} & Pick<
+ StorefrontAPI.Page,
+ 'handle' | 'id' | 'title' | 'trackingParameters'
+>;
+
+export type SearchArticleFragment = {__typename: 'Article'} & Pick<
+ StorefrontAPI.Article,
+ 'handle' | 'id' | 'title' | 'trackingParameters'
+>;
+
+export type SearchQueryVariables = StorefrontAPI.Exact<{
+ country?: StorefrontAPI.InputMaybe;
+ endCursor?: StorefrontAPI.InputMaybe<
+ StorefrontAPI.Scalars['String']['input']
+ >;
+ first?: StorefrontAPI.InputMaybe;
+ language?: StorefrontAPI.InputMaybe;
+ last?: StorefrontAPI.InputMaybe;
+ query: StorefrontAPI.Scalars['String']['input'];
+ startCursor?: StorefrontAPI.InputMaybe<
+ StorefrontAPI.Scalars['String']['input']
+ >;
+}>;
+
+export type SearchQuery = {
+ products: {
+ nodes: Array<
+ {__typename: 'Product'} & Pick<
+ StorefrontAPI.Product,
+ | 'handle'
+ | 'id'
+ | 'publishedAt'
+ | 'title'
+ | 'trackingParameters'
+ | 'vendor'
+ > & {
+ variants: {
+ nodes: Array<
+ Pick & {
+ image?: StorefrontAPI.Maybe<
+ Pick<
+ StorefrontAPI.Image,
+ 'url' | 'altText' | 'width' | 'height'
+ >
+ >;
+ price: Pick;
+ compareAtPrice?: StorefrontAPI.Maybe<
+ Pick
+ >;
+ selectedOptions: Array<
+ Pick
+ >;
+ product: Pick;
+ }
+ >;
+ };
+ }
+ >;
+ pageInfo: Pick<
+ StorefrontAPI.PageInfo,
+ 'hasNextPage' | 'hasPreviousPage' | 'startCursor' | 'endCursor'
>;
};
- cost: {
- subtotalAmount: Pick;
- totalAmount: Pick;
- totalDutyAmount?: StorefrontAPI.Maybe<
- Pick
+ pages: {
+ nodes: Array<
+ {__typename: 'Page'} & Pick<
+ StorefrontAPI.Page,
+ 'handle' | 'id' | 'title' | 'trackingParameters'
+ >
>;
- totalTaxAmount?: StorefrontAPI.Maybe<
- Pick
+ };
+ articles: {
+ nodes: Array<
+ {__typename: 'Article'} & Pick<
+ StorefrontAPI.Article,
+ 'handle' | 'id' | 'title' | 'trackingParameters'
+ >
>;
};
- attributes: Array>;
- discountCodes: Array<
- Pick
- >;
};
interface GeneratedQueryTypes {
- '#graphql\n fragment Shop on Shop {\n id\n name\n description\n primaryDomain {\n url\n }\n brand {\n logo {\n image {\n url\n }\n }\n }\n }\n query Header {\n shop {\n ...Shop\n }\n }\n': {
+ '#graphql\n fragment Shop on Shop {\n id\n name\n description\n primaryDomain {\n url\n }\n brand {\n logo {\n image {\n url\n }\n }\n }\n }\n query Header(\n $country: CountryCode\n $headerMenuHandle: String!\n $language: LanguageCode\n ) @inContext(language: $language, country: $country) {\n shop {\n ...Shop\n }\n menu(handle: $headerMenuHandle) {\n ...Menu\n }\n }\n #graphql\n fragment MenuItem on MenuItem {\n id\n resourceId\n tags\n title\n type\n url\n }\n fragment ChildMenuItem on MenuItem {\n ...MenuItem\n }\n fragment ParentMenuItem on MenuItem {\n ...MenuItem\n items {\n ...ChildMenuItem\n }\n }\n fragment Menu on Menu {\n id\n items {\n ...ParentMenuItem\n }\n }\n\n': {
return: HeaderQuery;
variables: HeaderQueryVariables;
};
- '#graphql\n query Product(\n $country: CountryCode\n $handle: String!\n $language: LanguageCode\n ) @inContext(country: $country, language: $language) {\n product(handle: $handle) {\n ...Product\n }\n }\n #graphql\n fragment Product on Product {\n id\n title\n vendor\n handle\n descriptionHtml\n description\n options {\n name\n values\n }\n variants(first: 1) {\n nodes {\n ...ProductVariant\n }\n }\n seo {\n description\n title\n }\n\n # 9. Add the SellingPlanGroups fragment to the Product fragment\n sellingPlanGroups(first:10) {\n nodes {\n ...SellingPlanGroup\n }\n }\n }\n #graphql\n fragment ProductVariant on ProductVariant {\n availableForSale\n compareAtPrice {\n amount\n currencyCode\n }\n id\n image {\n __typename\n id\n url\n altText\n width\n height\n }\n price {\n amount\n currencyCode\n }\n product {\n title\n handle\n }\n selectedOptions {\n name\n value\n }\n sku\n title\n unitPrice {\n amount\n currencyCode\n }\n }\n\n #graphql\n fragment SellingPlanGroup on SellingPlanGroup {\n name\n options {\n name\n values\n }\n sellingPlans(first:10) {\n nodes {\n ...SellingPlan\n }\n }\n }\n #graphql\n fragment SellingPlanMoney on MoneyV2 {\n amount\n currencyCode\n }\n fragment SellingPlan on SellingPlan {\n id\n options {\n name\n value\n }\n priceAdjustments {\n adjustmentValue {\n ... on SellingPlanFixedAmountPriceAdjustment {\n __typename\n adjustmentAmount {\n ... on MoneyV2 {\n ...SellingPlanMoney\n }\n }\n }\n ... on SellingPlanFixedPriceAdjustment {\n __typename\n price {\n ... on MoneyV2 {\n ...SellingPlanMoney\n }\n }\n }\n ... on SellingPlanPercentagePriceAdjustment {\n __typename\n adjustmentPercentage\n }\n }\n orderCount\n }\n recurringDeliveries\n checkoutCharge {\n type\n value {\n ... on MoneyV2 {\n ...SellingPlanMoney\n }\n ... on SellingPlanCheckoutChargePercentageValue {\n percentage\n }\n }\n }\n }\n\n\n\n': {
+ '#graphql\n query Footer(\n $country: CountryCode\n $footerMenuHandle: String!\n $language: LanguageCode\n ) @inContext(language: $language, country: $country) {\n menu(handle: $footerMenuHandle) {\n ...Menu\n }\n }\n #graphql\n fragment MenuItem on MenuItem {\n id\n resourceId\n tags\n title\n type\n url\n }\n fragment ChildMenuItem on MenuItem {\n ...MenuItem\n }\n fragment ParentMenuItem on MenuItem {\n ...MenuItem\n items {\n ...ChildMenuItem\n }\n }\n fragment Menu on Menu {\n id\n items {\n ...ParentMenuItem\n }\n }\n\n': {
+ return: FooterQuery;
+ variables: FooterQueryVariables;
+ };
+ '#graphql\n query StoreRobots($country: CountryCode, $language: LanguageCode)\n @inContext(country: $country, language: $language) {\n shop {\n id\n }\n }\n': {
+ return: StoreRobotsQuery;
+ variables: StoreRobotsQueryVariables;
+ };
+ '#graphql\n query Sitemap($urlLimits: Int, $language: LanguageCode)\n @inContext(language: $language) {\n products(\n first: $urlLimits\n query: "published_status:\'online_store:visible\'"\n ) {\n nodes {\n updatedAt\n handle\n onlineStoreUrl\n title\n featuredImage {\n url\n altText\n }\n }\n }\n collections(\n first: $urlLimits\n query: "published_status:\'online_store:visible\'"\n ) {\n nodes {\n updatedAt\n handle\n onlineStoreUrl\n }\n }\n pages(first: $urlLimits, query: "published_status:\'published\'") {\n nodes {\n updatedAt\n handle\n onlineStoreUrl\n }\n }\n }\n': {
+ return: SitemapQuery;
+ variables: SitemapQueryVariables;
+ };
+ '#graphql\n fragment PredictiveArticle on Article {\n __typename\n id\n title\n handle\n blog {\n handle\n }\n image {\n url\n altText\n width\n height\n }\n trackingParameters\n }\n fragment PredictiveCollection on Collection {\n __typename\n id\n title\n handle\n image {\n url\n altText\n width\n height\n }\n trackingParameters\n }\n fragment PredictivePage on Page {\n __typename\n id\n title\n handle\n trackingParameters\n }\n fragment PredictiveProduct on Product {\n __typename\n id\n title\n handle\n trackingParameters\n variants(first: 1) {\n nodes {\n id\n image {\n url\n altText\n width\n height\n }\n price {\n amount\n currencyCode\n }\n }\n }\n }\n fragment PredictiveQuery on SearchQuerySuggestion {\n __typename\n text\n styledText\n trackingParameters\n }\n query predictiveSearch(\n $country: CountryCode\n $language: LanguageCode\n $limit: Int!\n $limitScope: PredictiveSearchLimitScope!\n $searchTerm: String!\n $types: [PredictiveSearchType!]\n ) @inContext(country: $country, language: $language) {\n predictiveSearch(\n limit: $limit,\n limitScope: $limitScope,\n query: $searchTerm,\n types: $types,\n ) {\n articles {\n ...PredictiveArticle\n }\n collections {\n ...PredictiveCollection\n }\n pages {\n ...PredictivePage\n }\n products {\n ...PredictiveProduct\n }\n queries {\n ...PredictiveQuery\n }\n }\n }\n': {
+ return: PredictiveSearchQuery;
+ variables: PredictiveSearchQueryVariables;
+ };
+ '#graphql\n query Article(\n $articleHandle: String!\n $blogHandle: String!\n $country: CountryCode\n $language: LanguageCode\n ) @inContext(language: $language, country: $country) {\n blog(handle: $blogHandle) {\n articleByHandle(handle: $articleHandle) {\n title\n contentHtml\n publishedAt\n author: authorV2 {\n name\n }\n image {\n id\n altText\n url\n width\n height\n }\n seo {\n description\n title\n }\n }\n }\n }\n': {
+ return: ArticleQuery;
+ variables: ArticleQueryVariables;
+ };
+ '#graphql\n query Blog(\n $language: LanguageCode\n $blogHandle: String!\n $first: Int\n $last: Int\n $startCursor: String\n $endCursor: String\n ) @inContext(language: $language) {\n blog(handle: $blogHandle) {\n title\n seo {\n title\n description\n }\n articles(\n first: $first,\n last: $last,\n before: $startCursor,\n after: $endCursor\n ) {\n nodes {\n ...ArticleItem\n }\n pageInfo {\n hasPreviousPage\n hasNextPage\n hasNextPage\n endCursor\n startCursor\n }\n\n }\n }\n }\n fragment ArticleItem on Article {\n author: authorV2 {\n name\n }\n contentHtml\n handle\n id\n image {\n id\n altText\n url\n width\n height\n }\n publishedAt\n title\n blog {\n handle\n }\n }\n': {
+ return: BlogQuery;
+ variables: BlogQueryVariables;
+ };
+ '#graphql\n query Blogs(\n $country: CountryCode\n $endCursor: String\n $first: Int\n $language: LanguageCode\n $last: Int\n $startCursor: String\n ) @inContext(country: $country, language: $language) {\n blogs(\n first: $first,\n last: $last,\n before: $startCursor,\n after: $endCursor\n ) {\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n nodes {\n title\n handle\n seo {\n title\n description\n }\n }\n }\n }\n': {
+ return: BlogsQuery;
+ variables: BlogsQueryVariables;
+ };
+ '#graphql\n #graphql\n fragment MoneyProductItem on MoneyV2 {\n amount\n currencyCode\n }\n fragment ProductItem on Product {\n id\n handle\n title\n featuredImage {\n id\n altText\n url\n width\n height\n }\n priceRange {\n minVariantPrice {\n ...MoneyProductItem\n }\n maxVariantPrice {\n ...MoneyProductItem\n }\n }\n variants(first: 1) {\n nodes {\n selectedOptions {\n name\n value\n }\n }\n }\n }\n\n query Collection(\n $handle: String!\n $country: CountryCode\n $language: LanguageCode\n $first: Int\n $last: Int\n $startCursor: String\n $endCursor: String\n ) @inContext(country: $country, language: $language) {\n collection(handle: $handle) {\n id\n handle\n title\n description\n products(\n first: $first,\n last: $last,\n before: $startCursor,\n after: $endCursor\n ) {\n nodes {\n ...ProductItem\n }\n pageInfo {\n hasPreviousPage\n hasNextPage\n endCursor\n startCursor\n }\n }\n }\n }\n': {
+ return: CollectionQuery;
+ variables: CollectionQueryVariables;
+ };
+ '#graphql\n fragment Collection on Collection {\n id\n title\n handle\n image {\n id\n url\n altText\n width\n height\n }\n }\n query StoreCollections(\n $country: CountryCode\n $endCursor: String\n $first: Int\n $language: LanguageCode\n $last: Int\n $startCursor: String\n ) @inContext(country: $country, language: $language) {\n collections(\n first: $first,\n last: $last,\n before: $startCursor,\n after: $endCursor\n ) {\n nodes {\n ...Collection\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n': {
+ return: StoreCollectionsQuery;
+ variables: StoreCollectionsQueryVariables;
+ };
+ '#graphql\n query Catalog(\n $country: CountryCode\n $language: LanguageCode\n $first: Int\n $last: Int\n $startCursor: String\n $endCursor: String\n ) @inContext(country: $country, language: $language) {\n products(first: $first, last: $last, before: $startCursor, after: $endCursor) {\n nodes {\n ...ProductItem\n }\n pageInfo {\n hasPreviousPage\n hasNextPage\n startCursor\n endCursor\n }\n }\n }\n #graphql\n fragment MoneyProductItem on MoneyV2 {\n amount\n currencyCode\n }\n fragment ProductItem on Product {\n id\n handle\n title\n featuredImage {\n id\n altText\n url\n width\n height\n }\n priceRange {\n minVariantPrice {\n ...MoneyProductItem\n }\n maxVariantPrice {\n ...MoneyProductItem\n }\n }\n variants(first: 1) {\n nodes {\n selectedOptions {\n name\n value\n }\n }\n }\n }\n\n': {
+ return: CatalogQuery;
+ variables: CatalogQueryVariables;
+ };
+ '#graphql\n query Page(\n $language: LanguageCode,\n $country: CountryCode,\n $handle: String!\n )\n @inContext(language: $language, country: $country) {\n page(handle: $handle) {\n id\n title\n body\n seo {\n description\n title\n }\n }\n }\n': {
+ return: PageQuery;
+ variables: PageQueryVariables;
+ };
+ '#graphql\n fragment Policy on ShopPolicy {\n body\n handle\n id\n title\n url\n }\n query Policy(\n $country: CountryCode\n $language: LanguageCode\n $privacyPolicy: Boolean!\n $refundPolicy: Boolean!\n $shippingPolicy: Boolean!\n $termsOfService: Boolean!\n ) @inContext(language: $language, country: $country) {\n shop {\n privacyPolicy @include(if: $privacyPolicy) {\n ...Policy\n }\n shippingPolicy @include(if: $shippingPolicy) {\n ...Policy\n }\n termsOfService @include(if: $termsOfService) {\n ...Policy\n }\n refundPolicy @include(if: $refundPolicy) {\n ...Policy\n }\n }\n }\n': {
+ return: PolicyQuery;
+ variables: PolicyQueryVariables;
+ };
+ '#graphql\n fragment PolicyItem on ShopPolicy {\n id\n title\n handle\n }\n query Policies ($country: CountryCode, $language: LanguageCode)\n @inContext(country: $country, language: $language) {\n shop {\n privacyPolicy {\n ...PolicyItem\n }\n shippingPolicy {\n ...PolicyItem\n }\n termsOfService {\n ...PolicyItem\n }\n refundPolicy {\n ...PolicyItem\n }\n subscriptionPolicy {\n id\n title\n handle\n }\n }\n }\n': {
+ return: PoliciesQuery;
+ variables: PoliciesQueryVariables;
+ };
+ '#graphql\n query Product(\n $country: CountryCode\n $handle: String!\n $language: LanguageCode\n $selectedOptions: [SelectedOptionInput!]!\n ) @inContext(country: $country, language: $language) {\n product(handle: $handle) {\n ...Product\n }\n }\n #graphql\n fragment Product on Product {\n id\n title\n vendor\n handle\n descriptionHtml\n description\n options {\n name\n values\n }\n selectedVariant: variantBySelectedOptions(selectedOptions: $selectedOptions, ignoreUnknownOptions: true, caseInsensitiveMatch: true) {\n ...ProductVariant\n }\n variants(first: 1) {\n nodes {\n ...ProductVariant\n }\n }\n seo {\n description\n title\n }\n #/***********************************************/\n #/********** EXAMPLE UPDATE STARTS ************/\n # 9. Add the SellingPlanGroups fragment to the Product fragment\n sellingPlanGroups(first:10) {\n nodes {\n ...SellingPlanGroup\n }\n }\n #/********** EXAMPLE UPDATE END ************/\n #/***********************************************/\n }\n #graphql\n fragment ProductVariant on ProductVariant {\n availableForSale\n compareAtPrice {\n amount\n currencyCode\n }\n id\n image {\n __typename\n id\n url\n altText\n width\n height\n }\n price {\n amount\n currencyCode\n }\n product {\n title\n handle\n }\n selectedOptions {\n name\n value\n }\n sku\n title\n unitPrice {\n amount\n currencyCode\n }\n }\n\n #/***********************************************/\n #/********** EXAMPLE UPDATE STARTS ************/\n #graphql\n fragment SellingPlanGroup on SellingPlanGroup {\n name\n options {\n name\n values\n }\n sellingPlans(first:10) {\n nodes {\n ...SellingPlan\n }\n }\n }\n #graphql\n fragment SellingPlanMoney on MoneyV2 {\n amount\n currencyCode\n }\n fragment SellingPlan on SellingPlan {\n id\n options {\n name\n value\n }\n priceAdjustments {\n adjustmentValue {\n ... on SellingPlanFixedAmountPriceAdjustment {\n __typename\n adjustmentAmount {\n ... on MoneyV2 {\n ...SellingPlanMoney\n }\n }\n }\n ... on SellingPlanFixedPriceAdjustment {\n __typename\n price {\n ... on MoneyV2 {\n ...SellingPlanMoney\n }\n }\n }\n ... on SellingPlanPercentagePriceAdjustment {\n __typename\n adjustmentPercentage\n }\n }\n orderCount\n }\n recurringDeliveries\n checkoutCharge {\n type\n value {\n ... on MoneyV2 {\n ...SellingPlanMoney\n }\n ... on SellingPlanCheckoutChargePercentageValue {\n percentage\n }\n }\n }\n }\n\n\n #/********** EXAMPLE UPDATE END ************/\n #/***********************************************/\n\n': {
return: ProductQuery;
variables: ProductQueryVariables;
};
+ '#graphql\n #graphql\n fragment ProductVariants on Product {\n variants(first: 250) {\n nodes {\n ...ProductVariant\n }\n }\n }\n #graphql\n fragment ProductVariant on ProductVariant {\n availableForSale\n compareAtPrice {\n amount\n currencyCode\n }\n id\n image {\n __typename\n id\n url\n altText\n width\n height\n }\n price {\n amount\n currencyCode\n }\n product {\n title\n handle\n }\n selectedOptions {\n name\n value\n }\n sku\n title\n unitPrice {\n amount\n currencyCode\n }\n }\n\n\n query ProductVariants(\n $country: CountryCode\n $language: LanguageCode\n $handle: String!\n ) @inContext(country: $country, language: $language) {\n product(handle: $handle) {\n ...ProductVariants\n }\n }\n': {
+ return: ProductVariantsQuery;
+ variables: ProductVariantsQueryVariables;
+ };
+ '#graphql\n fragment SearchProduct on Product {\n __typename\n handle\n id\n publishedAt\n title\n trackingParameters\n vendor\n variants(first: 1) {\n nodes {\n id\n image {\n url\n altText\n width\n height\n }\n price {\n amount\n currencyCode\n }\n compareAtPrice {\n amount\n currencyCode\n }\n selectedOptions {\n name\n value\n }\n product {\n handle\n title\n }\n }\n }\n }\n fragment SearchPage on Page {\n __typename\n handle\n id\n title\n trackingParameters\n }\n fragment SearchArticle on Article {\n __typename\n handle\n id\n title\n trackingParameters\n }\n query search(\n $country: CountryCode\n $endCursor: String\n $first: Int\n $language: LanguageCode\n $last: Int\n $query: String!\n $startCursor: String\n ) @inContext(country: $country, language: $language) {\n products: search(\n query: $query,\n unavailableProducts: HIDE,\n types: [PRODUCT],\n first: $first,\n sortKey: RELEVANCE,\n last: $last,\n before: $startCursor,\n after: $endCursor\n ) {\n nodes {\n ...on Product {\n ...SearchProduct\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n pages: search(\n query: $query,\n types: [PAGE],\n first: 10\n ) {\n nodes {\n ...on Page {\n ...SearchPage\n }\n }\n }\n articles: search(\n query: $query,\n types: [ARTICLE],\n first: 10\n ) {\n nodes {\n ...on Article {\n ...SearchArticle\n }\n }\n }\n }\n': {
+ return: SearchQuery;
+ variables: SearchQueryVariables;
+ };
}
interface GeneratedMutationTypes {}
diff --git a/examples/subscriptions/tailwind.config.js b/examples/subscriptions/tailwind.config.js
deleted file mode 100644
index 3ed6e085c5..0000000000
--- a/examples/subscriptions/tailwind.config.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import formsPlugin from '@tailwindcss/forms';
-import typographyPlugin from '@tailwindcss/typography';
-
-/** @type {import('tailwindcss').Config} */
-export default {
- content: ['./app/**/*.{js,ts,jsx,tsx}'],
- plugins: [formsPlugin, typographyPlugin],
-};
diff --git a/examples/subscriptions/tsconfig.json b/examples/subscriptions/tsconfig.json
index dcd7c7237a..5b672cc6e1 100644
--- a/examples/subscriptions/tsconfig.json
+++ b/examples/subscriptions/tsconfig.json
@@ -1,23 +1,16 @@
{
- "include": ["./**/*.d.ts", "./**/*.ts", "./**/*.tsx"],
+ "extends": "../../templates/skeleton/tsconfig.json",
+ "include": [
+ "./**/*.d.ts",
+ "./**/*.ts",
+ "./**/*.tsx",
+ "../../templates/skeleton/*.d.ts"
+ ],
"compilerOptions": {
- "lib": ["DOM", "DOM.Iterable", "ES2022"],
- "isolatedModules": true,
- "esModuleInterop": true,
- "jsx": "react-jsx",
- "moduleResolution": "Bundler",
- "resolveJsonModule": true,
- "module": "ES2022",
- "target": "ES2022",
- "strict": true,
- "allowJs": true,
- "forceConsistentCasingInFileNames": true,
- "skipLibCheck": true,
"baseUrl": ".",
- "types": ["@shopify/oxygen-workers-types"],
"paths": {
- "~/*": ["app/*"]
- },
- "noEmit": true
+ "*": ["./*", "../../templates/skeleton/*"],
+ "~/*": ["app/*", "../../templates/skeleton/app/*"]
+ }
}
}
diff --git a/package-lock.json b/package-lock.json
index 10a3f07d1c..ac08bdeba8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -74,6 +74,7 @@
"@types/react-dom": "^18.2.7",
"@types/react-syntax-highlighter": "^15.5.7",
"eslint": "^8.38.0",
+ "tailwindcss": "^3.3.0",
"typescript": "^5.2.2"
},
"engines": {
@@ -226,40 +227,7 @@
"examples/subscriptions": {
"name": "example-subscriptions",
"dependencies": {
- "@remix-run/react": "^2.9.2",
- "@shopify/cli": "3.61.2",
- "@shopify/cli-hydrogen": "^8.1.0",
- "@shopify/hydrogen": "2024.4.3",
- "@shopify/remix-oxygen": "^2.0.4",
- "graphql": "^16.6.0",
- "graphql-tag": "^2.12.6",
- "isbot": "^3.6.6",
- "react": "^18.2.0",
- "react-dom": "^18.2.0"
- },
- "devDependencies": {
- "@remix-run/dev": "^2.9.2",
- "@remix-run/eslint-config": "^2.9.2",
- "@shopify/mini-oxygen": "^3.0.3",
- "@shopify/oxygen-workers-types": "^4.0.0",
- "@shopify/prettier-config": "^1.1.2",
- "@tailwindcss/forms": "^0.5.3",
- "@tailwindcss/typography": "^0.5.9",
- "@total-typescript/ts-reset": "^0.4.2",
- "@types/eslint": "^8.4.10",
- "@types/react": "^18.2.22",
- "@types/react-dom": "^18.2.7",
- "eslint": "^8.20.0",
- "eslint-plugin-hydrogen": "0.12.2",
- "postcss": "^8.4.21",
- "postcss-import": "^15.1.0",
- "postcss-preset-env": "^8.2.0",
- "prettier": "^2.8.4",
- "tailwindcss": "^3.3.0",
- "typescript": "^5.2.2"
- },
- "engines": {
- "node": ">=18.0.0"
+ "@shopify/cli-hydrogen": "*"
}
},
"examples/third-party-queries-caching": {
@@ -2841,543 +2809,6 @@
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
- "node_modules/@csstools/cascade-layer-name-parser": {
- "version": "1.0.1",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "@csstools/css-parser-algorithms": "^2.0.0",
- "@csstools/css-tokenizer": "^2.0.0"
- }
- },
- "node_modules/@csstools/color-helpers": {
- "version": "1.0.0",
- "dev": true,
- "license": "CC0-1.0",
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- }
- },
- "node_modules/@csstools/css-calc": {
- "version": "1.0.1",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "@csstools/css-parser-algorithms": "^2.0.1",
- "@csstools/css-tokenizer": "^2.0.1"
- }
- },
- "node_modules/@csstools/css-color-parser": {
- "version": "1.1.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@csstools/color-helpers": "^2.0.0",
- "@csstools/css-calc": "^1.0.1"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "@csstools/css-parser-algorithms": "^2.1.0",
- "@csstools/css-tokenizer": "^2.1.0"
- }
- },
- "node_modules/@csstools/css-color-parser/node_modules/@csstools/color-helpers": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-2.1.0.tgz",
- "integrity": "sha512-OWkqBa7PDzZuJ3Ha7T5bxdSVfSCfTq6K1mbAhbO1MD+GSULGjrp45i5RudyJOedstSarN/3mdwu9upJE7gDXfw==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/csstools"
- },
- {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- }
- ],
- "engines": {
- "node": "^14 || ^16 || >=18"
- }
- },
- "node_modules/@csstools/css-parser-algorithms": {
- "version": "2.1.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "@csstools/css-tokenizer": "^2.0.0"
- }
- },
- "node_modules/@csstools/css-tokenizer": {
- "version": "2.1.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- }
- },
- "node_modules/@csstools/media-query-list-parser": {
- "version": "2.0.1",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "@csstools/css-parser-algorithms": "^2.0.0",
- "@csstools/css-tokenizer": "^2.0.0"
- }
- },
- "node_modules/@csstools/postcss-cascade-layers": {
- "version": "3.0.1",
- "dev": true,
- "license": "CC0-1.0",
- "dependencies": {
- "@csstools/selector-specificity": "^2.0.2",
- "postcss-selector-parser": "^6.0.10"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
- "node_modules/@csstools/postcss-color-function": {
- "version": "2.1.0",
- "dev": true,
- "license": "CC0-1.0",
- "dependencies": {
- "@csstools/color-helpers": "^1.0.0",
- "@csstools/postcss-progressive-custom-properties": "^2.0.0",
- "postcss-value-parser": "^4.2.0"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
- "node_modules/@csstools/postcss-color-mix-function": {
- "version": "1.0.0",
- "dev": true,
- "license": "CC0-1.0",
- "dependencies": {
- "@csstools/css-color-parser": "^1.0.0",
- "@csstools/css-parser-algorithms": "^2.0.1",
- "@csstools/css-tokenizer": "^2.1.0",
- "@csstools/postcss-progressive-custom-properties": "^2.0.0"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
- "node_modules/@csstools/postcss-font-format-keywords": {
- "version": "2.0.2",
- "dev": true,
- "license": "CC0-1.0",
- "dependencies": {
- "postcss-value-parser": "^4.2.0"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
- "node_modules/@csstools/postcss-gradients-interpolation-method": {
- "version": "3.0.1",
- "dev": true,
- "license": "CC0-1.0",
- "dependencies": {
- "@csstools/css-color-parser": "^1.1.0",
- "@csstools/css-parser-algorithms": "^2.1.0",
- "@csstools/css-tokenizer": "^2.1.0",
- "@csstools/postcss-progressive-custom-properties": "^2.0.0"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
- "node_modules/@csstools/postcss-hwb-function": {
- "version": "2.2.0",
- "dev": true,
- "license": "CC0-1.0",
- "dependencies": {
- "@csstools/css-color-parser": "^1.0.0",
- "@csstools/css-parser-algorithms": "^2.0.1",
- "@csstools/css-tokenizer": "^2.1.0"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
- "node_modules/@csstools/postcss-ic-unit": {
- "version": "2.0.2",
- "dev": true,
- "license": "CC0-1.0",
- "dependencies": {
- "@csstools/postcss-progressive-custom-properties": "^2.0.0",
- "postcss-value-parser": "^4.2.0"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
- "node_modules/@csstools/postcss-is-pseudo-class": {
- "version": "3.1.1",
- "dev": true,
- "license": "CC0-1.0",
- "dependencies": {
- "@csstools/selector-specificity": "^2.0.0",
- "postcss-selector-parser": "^6.0.10"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
- "node_modules/@csstools/postcss-logical-float-and-clear": {
- "version": "1.0.1",
- "dev": true,
- "license": "CC0-1.0",
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
- "node_modules/@csstools/postcss-logical-resize": {
- "version": "1.0.1",
- "dev": true,
- "license": "CC0-1.0",
- "dependencies": {
- "postcss-value-parser": "^4.2.0"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
- "node_modules/@csstools/postcss-logical-viewport-units": {
- "version": "1.0.2",
- "dev": true,
- "license": "CC0-1.0",
- "dependencies": {
- "@csstools/css-tokenizer": "^2.0.0"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
- "node_modules/@csstools/postcss-media-queries-aspect-ratio-number-values": {
- "version": "1.0.1",
- "dev": true,
- "license": "CC0-1.0",
- "dependencies": {
- "@csstools/css-parser-algorithms": "^2.0.0",
- "@csstools/css-tokenizer": "^2.0.0",
- "@csstools/media-query-list-parser": "^2.0.0"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
- "node_modules/@csstools/postcss-nested-calc": {
- "version": "2.0.2",
- "dev": true,
- "license": "CC0-1.0",
- "dependencies": {
- "postcss-value-parser": "^4.2.0"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
- "node_modules/@csstools/postcss-normalize-display-values": {
- "version": "2.0.1",
- "dev": true,
- "license": "CC0-1.0",
- "dependencies": {
- "postcss-value-parser": "^4.2.0"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
- "node_modules/@csstools/postcss-oklab-function": {
- "version": "2.2.0",
- "dev": true,
- "license": "CC0-1.0",
- "dependencies": {
- "@csstools/css-color-parser": "^1.0.0",
- "@csstools/css-parser-algorithms": "^2.0.1",
- "@csstools/css-tokenizer": "^2.1.0",
- "@csstools/postcss-progressive-custom-properties": "^2.0.0"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
- "node_modules/@csstools/postcss-progressive-custom-properties": {
- "version": "2.1.0",
- "dev": true,
- "license": "CC0-1.0",
- "dependencies": {
- "postcss-value-parser": "^4.2.0"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
- "node_modules/@csstools/postcss-scope-pseudo-class": {
- "version": "2.0.2",
- "dev": true,
- "license": "CC0-1.0",
- "dependencies": {
- "postcss-selector-parser": "^6.0.10"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
- "node_modules/@csstools/postcss-stepped-value-functions": {
- "version": "2.1.0",
- "dev": true,
- "license": "CC0-1.0",
- "dependencies": {
- "@csstools/css-calc": "^1.0.0",
- "@csstools/css-parser-algorithms": "^2.0.1",
- "@csstools/css-tokenizer": "^2.0.1"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
- "node_modules/@csstools/postcss-text-decoration-shorthand": {
- "version": "2.2.1",
- "dev": true,
- "license": "CC0-1.0",
- "dependencies": {
- "@csstools/color-helpers": "^1.0.0",
- "postcss-value-parser": "^4.2.0"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
- "node_modules/@csstools/postcss-trigonometric-functions": {
- "version": "2.1.0",
- "dev": true,
- "license": "CC0-1.0",
- "dependencies": {
- "@csstools/css-calc": "^1.0.0",
- "@csstools/css-parser-algorithms": "^2.0.1",
- "@csstools/css-tokenizer": "^2.0.1"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
- "node_modules/@csstools/postcss-unset-value": {
- "version": "2.0.1",
- "dev": true,
- "license": "CC0-1.0",
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
- "node_modules/@csstools/selector-specificity": {
- "version": "2.1.1",
- "dev": true,
- "license": "CC0-1.0",
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4",
- "postcss-selector-parser": "^6.0.10"
- }
- },
"node_modules/@emotion/hash": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz",
@@ -9013,31 +8444,6 @@
"node": ">=14.16"
}
},
- "node_modules/@tailwindcss/forms": {
- "version": "0.5.3",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "mini-svg-data-uri": "^1.2.3"
- },
- "peerDependencies": {
- "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1"
- }
- },
- "node_modules/@tailwindcss/typography": {
- "version": "0.5.9",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "lodash.castarray": "^4.4.0",
- "lodash.isplainobject": "^4.0.6",
- "lodash.merge": "^4.6.2",
- "postcss-selector-parser": "6.0.10"
- },
- "peerDependencies": {
- "tailwindcss": ">=3.0.0 || insiders"
- }
- },
"node_modules/@testing-library/dom": {
"version": "8.20.1",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.1.tgz",
@@ -10977,38 +10383,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/autoprefixer": {
- "version": "10.4.14",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/autoprefixer"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "browserslist": "^4.21.5",
- "caniuse-lite": "^1.0.30001464",
- "fraction.js": "^4.2.0",
- "normalize-range": "^0.1.2",
- "picocolors": "^1.0.0",
- "postcss-value-parser": "^4.2.0"
- },
- "bin": {
- "autoprefixer": "bin/autoprefixer"
- },
- "engines": {
- "node": "^10 || ^12 || >=14"
- },
- "peerDependencies": {
- "postcss": "^8.1.0"
- }
- },
"node_modules/available-typed-arrays": {
"version": "1.0.5",
"license": "MIT",
@@ -12653,59 +12027,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/css-blank-pseudo": {
- "version": "5.0.2",
- "dev": true,
- "license": "CC0-1.0",
- "dependencies": {
- "postcss-selector-parser": "^6.0.10"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
- "node_modules/css-has-pseudo": {
- "version": "5.0.2",
- "dev": true,
- "license": "CC0-1.0",
- "dependencies": {
- "@csstools/selector-specificity": "^2.0.1",
- "postcss-selector-parser": "^6.0.10",
- "postcss-value-parser": "^4.2.0"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
- "node_modules/css-prefers-color-scheme": {
- "version": "8.0.2",
- "dev": true,
- "license": "CC0-1.0",
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
"node_modules/css-selector-parser": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-3.0.5.tgz",
@@ -12739,15 +12060,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/cssdb": {
- "version": "7.5.2",
- "dev": true,
- "license": "CC0-1.0",
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- }
- },
"node_modules/cssesc": {
"version": "3.0.0",
"dev": true,
@@ -15188,18 +14500,6 @@
"node": ">= 0.6"
}
},
- "node_modules/fraction.js": {
- "version": "4.2.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "*"
- },
- "funding": {
- "type": "patreon",
- "url": "https://www.patreon.com/infusion"
- }
- },
"node_modules/fresh": {
"version": "0.5.2",
"license": "MIT",
@@ -19105,11 +18405,6 @@
"integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
"dev": true
},
- "node_modules/lodash.castarray": {
- "version": "4.4.0",
- "dev": true,
- "license": "MIT"
- },
"node_modules/lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
@@ -21905,14 +21200,6 @@
"node": ">=4"
}
},
- "node_modules/mini-svg-data-uri": {
- "version": "1.4.4",
- "dev": true,
- "license": "MIT",
- "bin": {
- "mini-svg-data-uri": "cli.js"
- }
- },
"node_modules/miniflare": {
"version": "3.20240304.2",
"resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20240304.2.tgz",
@@ -22501,14 +21788,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/normalize-range": {
- "version": "0.1.2",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/normalize-url": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.0.tgz",
@@ -23811,173 +23090,6 @@
"node": "^10 || ^12 || >=14"
}
},
- "node_modules/postcss-attribute-case-insensitive": {
- "version": "6.0.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "postcss-selector-parser": "^6.0.10"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
- "node_modules/postcss-clamp": {
- "version": "4.1.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "postcss-value-parser": "^4.2.0"
- },
- "engines": {
- "node": ">=7.6.0"
- },
- "peerDependencies": {
- "postcss": "^8.4.6"
- }
- },
- "node_modules/postcss-color-functional-notation": {
- "version": "5.0.2",
- "dev": true,
- "license": "CC0-1.0",
- "dependencies": {
- "postcss-value-parser": "^4.2.0"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
- "node_modules/postcss-color-hex-alpha": {
- "version": "9.0.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "postcss-value-parser": "^4.2.0"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
- "node_modules/postcss-color-rebeccapurple": {
- "version": "8.0.2",
- "dev": true,
- "license": "CC0-1.0",
- "dependencies": {
- "postcss-value-parser": "^4.2.0"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
- "node_modules/postcss-custom-media": {
- "version": "9.1.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@csstools/cascade-layer-name-parser": "^1.0.0",
- "@csstools/css-parser-algorithms": "^2.0.0",
- "@csstools/css-tokenizer": "^2.0.0",
- "@csstools/media-query-list-parser": "^2.0.0"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
- "node_modules/postcss-custom-properties": {
- "version": "13.1.4",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@csstools/cascade-layer-name-parser": "^1.0.0",
- "@csstools/css-parser-algorithms": "^2.0.0",
- "@csstools/css-tokenizer": "^2.0.0",
- "postcss-value-parser": "^4.2.0"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
- "node_modules/postcss-custom-selectors": {
- "version": "7.1.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@csstools/cascade-layer-name-parser": "^1.0.0",
- "@csstools/css-parser-algorithms": "^2.0.0",
- "@csstools/css-tokenizer": "^2.0.0",
- "postcss-selector-parser": "^6.0.4"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
- "node_modules/postcss-dir-pseudo-class": {
- "version": "7.0.2",
- "dev": true,
- "license": "CC0-1.0",
- "dependencies": {
- "postcss-selector-parser": "^6.0.10"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
"node_modules/postcss-discard-duplicates": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz",
@@ -23990,126 +23102,6 @@
"postcss": "^8.2.15"
}
},
- "node_modules/postcss-double-position-gradients": {
- "version": "4.0.2",
- "dev": true,
- "license": "CC0-1.0",
- "dependencies": {
- "@csstools/postcss-progressive-custom-properties": "^2.0.0",
- "postcss-value-parser": "^4.2.0"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
- "node_modules/postcss-focus-visible": {
- "version": "8.0.2",
- "dev": true,
- "license": "CC0-1.0",
- "dependencies": {
- "postcss-selector-parser": "^6.0.10"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
- "node_modules/postcss-focus-within": {
- "version": "7.0.2",
- "dev": true,
- "license": "CC0-1.0",
- "dependencies": {
- "postcss-selector-parser": "^6.0.10"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
- "node_modules/postcss-font-variant": {
- "version": "5.0.0",
- "dev": true,
- "license": "MIT",
- "peerDependencies": {
- "postcss": "^8.1.0"
- }
- },
- "node_modules/postcss-gap-properties": {
- "version": "4.0.1",
- "dev": true,
- "license": "CC0-1.0",
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
- "node_modules/postcss-image-set-function": {
- "version": "5.0.2",
- "dev": true,
- "license": "CC0-1.0",
- "dependencies": {
- "postcss-value-parser": "^4.2.0"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
- "node_modules/postcss-import": {
- "version": "15.1.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "postcss-value-parser": "^4.0.0",
- "read-cache": "^1.0.0",
- "resolve": "^1.1.7"
- },
- "engines": {
- "node": ">=14.0.0"
- },
- "peerDependencies": {
- "postcss": "^8.0.0"
- }
- },
- "node_modules/postcss-initial": {
- "version": "4.0.1",
- "dev": true,
- "license": "MIT",
- "peerDependencies": {
- "postcss": "^8.0.0"
- }
- },
"node_modules/postcss-js": {
"version": "4.0.0",
"dev": true,
@@ -24128,27 +23120,6 @@
"postcss": "^8.3.3"
}
},
- "node_modules/postcss-lab-function": {
- "version": "5.2.0",
- "dev": true,
- "license": "CC0-1.0",
- "dependencies": {
- "@csstools/css-color-parser": "^1.0.0",
- "@csstools/css-parser-algorithms": "^2.0.1",
- "@csstools/css-tokenizer": "^2.1.0",
- "@csstools/postcss-progressive-custom-properties": "^2.0.0"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
"node_modules/postcss-load-config": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
@@ -24196,35 +23167,6 @@
"url": "https://github.com/sponsors/antonk52"
}
},
- "node_modules/postcss-logical": {
- "version": "6.1.0",
- "dev": true,
- "license": "CC0-1.0",
- "dependencies": {
- "postcss-value-parser": "^4.2.0"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
- "node_modules/postcss-media-minmax": {
- "version": "5.0.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10.0.0"
- },
- "peerDependencies": {
- "postcss": "^8.1.0"
- }
- },
"node_modules/postcss-modules": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/postcss-modules/-/postcss-modules-6.0.0.tgz",
@@ -24321,206 +23263,6 @@
"postcss": "^8.2.14"
}
},
- "node_modules/postcss-nesting": {
- "version": "11.2.1",
- "dev": true,
- "license": "CC0-1.0",
- "dependencies": {
- "@csstools/selector-specificity": "^2.0.0",
- "postcss-selector-parser": "^6.0.10"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
- "node_modules/postcss-opacity-percentage": {
- "version": "2.0.0",
- "dev": true,
- "funding": [
- {
- "type": "kofi",
- "url": "https://ko-fi.com/mrcgrtz"
- },
- {
- "type": "liberapay",
- "url": "https://liberapay.com/mrcgrtz"
- }
- ],
- "license": "MIT",
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "peerDependencies": {
- "postcss": "^8.2"
- }
- },
- "node_modules/postcss-overflow-shorthand": {
- "version": "4.0.1",
- "dev": true,
- "license": "CC0-1.0",
- "dependencies": {
- "postcss-value-parser": "^4.2.0"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
- "node_modules/postcss-page-break": {
- "version": "3.0.4",
- "dev": true,
- "license": "MIT",
- "peerDependencies": {
- "postcss": "^8"
- }
- },
- "node_modules/postcss-place": {
- "version": "8.0.1",
- "dev": true,
- "license": "CC0-1.0",
- "dependencies": {
- "postcss-value-parser": "^4.2.0"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
- "node_modules/postcss-preset-env": {
- "version": "8.2.0",
- "dev": true,
- "license": "CC0-1.0",
- "dependencies": {
- "@csstools/postcss-cascade-layers": "^3.0.1",
- "@csstools/postcss-color-function": "^2.1.0",
- "@csstools/postcss-color-mix-function": "^1.0.0",
- "@csstools/postcss-font-format-keywords": "^2.0.2",
- "@csstools/postcss-gradients-interpolation-method": "^3.0.1",
- "@csstools/postcss-hwb-function": "^2.2.0",
- "@csstools/postcss-ic-unit": "^2.0.2",
- "@csstools/postcss-is-pseudo-class": "^3.1.1",
- "@csstools/postcss-logical-float-and-clear": "^1.0.1",
- "@csstools/postcss-logical-resize": "^1.0.1",
- "@csstools/postcss-logical-viewport-units": "^1.0.2",
- "@csstools/postcss-media-queries-aspect-ratio-number-values": "^1.0.1",
- "@csstools/postcss-nested-calc": "^2.0.2",
- "@csstools/postcss-normalize-display-values": "^2.0.1",
- "@csstools/postcss-oklab-function": "^2.2.0",
- "@csstools/postcss-progressive-custom-properties": "^2.1.0",
- "@csstools/postcss-scope-pseudo-class": "^2.0.2",
- "@csstools/postcss-stepped-value-functions": "^2.1.0",
- "@csstools/postcss-text-decoration-shorthand": "^2.2.1",
- "@csstools/postcss-trigonometric-functions": "^2.1.0",
- "@csstools/postcss-unset-value": "^2.0.1",
- "autoprefixer": "^10.4.14",
- "browserslist": "^4.21.5",
- "css-blank-pseudo": "^5.0.2",
- "css-has-pseudo": "^5.0.2",
- "css-prefers-color-scheme": "^8.0.2",
- "cssdb": "^7.5.2",
- "postcss-attribute-case-insensitive": "^6.0.2",
- "postcss-clamp": "^4.1.0",
- "postcss-color-functional-notation": "^5.0.2",
- "postcss-color-hex-alpha": "^9.0.2",
- "postcss-color-rebeccapurple": "^8.0.2",
- "postcss-custom-media": "^9.1.2",
- "postcss-custom-properties": "^13.1.4",
- "postcss-custom-selectors": "^7.1.2",
- "postcss-dir-pseudo-class": "^7.0.2",
- "postcss-double-position-gradients": "^4.0.2",
- "postcss-focus-visible": "^8.0.2",
- "postcss-focus-within": "^7.0.2",
- "postcss-font-variant": "^5.0.0",
- "postcss-gap-properties": "^4.0.1",
- "postcss-image-set-function": "^5.0.2",
- "postcss-initial": "^4.0.1",
- "postcss-lab-function": "^5.2.0",
- "postcss-logical": "^6.1.0",
- "postcss-media-minmax": "^5.0.0",
- "postcss-nesting": "^11.2.1",
- "postcss-opacity-percentage": "^2.0.0",
- "postcss-overflow-shorthand": "^4.0.1",
- "postcss-page-break": "^3.0.4",
- "postcss-place": "^8.0.1",
- "postcss-pseudo-class-any-link": "^8.0.2",
- "postcss-replace-overflow-wrap": "^4.0.0",
- "postcss-selector-not": "^7.0.1",
- "postcss-value-parser": "^4.2.0"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
- "node_modules/postcss-pseudo-class-any-link": {
- "version": "8.0.2",
- "dev": true,
- "license": "CC0-1.0",
- "dependencies": {
- "postcss-selector-parser": "^6.0.10"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
- "node_modules/postcss-replace-overflow-wrap": {
- "version": "4.0.0",
- "dev": true,
- "license": "MIT",
- "peerDependencies": {
- "postcss": "^8.0.3"
- }
- },
- "node_modules/postcss-selector-not": {
- "version": "7.0.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "postcss-selector-parser": "^6.0.10"
- },
- "engines": {
- "node": "^14 || ^16 || >=18"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/csstools"
- },
- "peerDependencies": {
- "postcss": "^8.4"
- }
- },
"node_modules/postcss-selector-parser": {
"version": "6.0.10",
"dev": true,
@@ -35156,221 +33898,6 @@
}
}
},
- "@csstools/cascade-layer-name-parser": {
- "version": "1.0.1",
- "dev": true,
- "requires": {}
- },
- "@csstools/color-helpers": {
- "version": "1.0.0",
- "dev": true
- },
- "@csstools/css-calc": {
- "version": "1.0.1",
- "dev": true,
- "requires": {}
- },
- "@csstools/css-color-parser": {
- "version": "1.1.0",
- "dev": true,
- "requires": {
- "@csstools/color-helpers": "^2.0.0",
- "@csstools/css-calc": "^1.0.1"
- },
- "dependencies": {
- "@csstools/color-helpers": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-2.1.0.tgz",
- "integrity": "sha512-OWkqBa7PDzZuJ3Ha7T5bxdSVfSCfTq6K1mbAhbO1MD+GSULGjrp45i5RudyJOedstSarN/3mdwu9upJE7gDXfw==",
- "dev": true
- }
- }
- },
- "@csstools/css-parser-algorithms": {
- "version": "2.1.0",
- "dev": true,
- "requires": {}
- },
- "@csstools/css-tokenizer": {
- "version": "2.1.0",
- "dev": true
- },
- "@csstools/media-query-list-parser": {
- "version": "2.0.1",
- "dev": true,
- "requires": {}
- },
- "@csstools/postcss-cascade-layers": {
- "version": "3.0.1",
- "dev": true,
- "requires": {
- "@csstools/selector-specificity": "^2.0.2",
- "postcss-selector-parser": "^6.0.10"
- }
- },
- "@csstools/postcss-color-function": {
- "version": "2.1.0",
- "dev": true,
- "requires": {
- "@csstools/color-helpers": "^1.0.0",
- "@csstools/postcss-progressive-custom-properties": "^2.0.0",
- "postcss-value-parser": "^4.2.0"
- }
- },
- "@csstools/postcss-color-mix-function": {
- "version": "1.0.0",
- "dev": true,
- "requires": {
- "@csstools/css-color-parser": "^1.0.0",
- "@csstools/css-parser-algorithms": "^2.0.1",
- "@csstools/css-tokenizer": "^2.1.0",
- "@csstools/postcss-progressive-custom-properties": "^2.0.0"
- }
- },
- "@csstools/postcss-font-format-keywords": {
- "version": "2.0.2",
- "dev": true,
- "requires": {
- "postcss-value-parser": "^4.2.0"
- }
- },
- "@csstools/postcss-gradients-interpolation-method": {
- "version": "3.0.1",
- "dev": true,
- "requires": {
- "@csstools/css-color-parser": "^1.1.0",
- "@csstools/css-parser-algorithms": "^2.1.0",
- "@csstools/css-tokenizer": "^2.1.0",
- "@csstools/postcss-progressive-custom-properties": "^2.0.0"
- }
- },
- "@csstools/postcss-hwb-function": {
- "version": "2.2.0",
- "dev": true,
- "requires": {
- "@csstools/css-color-parser": "^1.0.0",
- "@csstools/css-parser-algorithms": "^2.0.1",
- "@csstools/css-tokenizer": "^2.1.0"
- }
- },
- "@csstools/postcss-ic-unit": {
- "version": "2.0.2",
- "dev": true,
- "requires": {
- "@csstools/postcss-progressive-custom-properties": "^2.0.0",
- "postcss-value-parser": "^4.2.0"
- }
- },
- "@csstools/postcss-is-pseudo-class": {
- "version": "3.1.1",
- "dev": true,
- "requires": {
- "@csstools/selector-specificity": "^2.0.0",
- "postcss-selector-parser": "^6.0.10"
- }
- },
- "@csstools/postcss-logical-float-and-clear": {
- "version": "1.0.1",
- "dev": true,
- "requires": {}
- },
- "@csstools/postcss-logical-resize": {
- "version": "1.0.1",
- "dev": true,
- "requires": {
- "postcss-value-parser": "^4.2.0"
- }
- },
- "@csstools/postcss-logical-viewport-units": {
- "version": "1.0.2",
- "dev": true,
- "requires": {
- "@csstools/css-tokenizer": "^2.0.0"
- }
- },
- "@csstools/postcss-media-queries-aspect-ratio-number-values": {
- "version": "1.0.1",
- "dev": true,
- "requires": {
- "@csstools/css-parser-algorithms": "^2.0.0",
- "@csstools/css-tokenizer": "^2.0.0",
- "@csstools/media-query-list-parser": "^2.0.0"
- }
- },
- "@csstools/postcss-nested-calc": {
- "version": "2.0.2",
- "dev": true,
- "requires": {
- "postcss-value-parser": "^4.2.0"
- }
- },
- "@csstools/postcss-normalize-display-values": {
- "version": "2.0.1",
- "dev": true,
- "requires": {
- "postcss-value-parser": "^4.2.0"
- }
- },
- "@csstools/postcss-oklab-function": {
- "version": "2.2.0",
- "dev": true,
- "requires": {
- "@csstools/css-color-parser": "^1.0.0",
- "@csstools/css-parser-algorithms": "^2.0.1",
- "@csstools/css-tokenizer": "^2.1.0",
- "@csstools/postcss-progressive-custom-properties": "^2.0.0"
- }
- },
- "@csstools/postcss-progressive-custom-properties": {
- "version": "2.1.0",
- "dev": true,
- "requires": {
- "postcss-value-parser": "^4.2.0"
- }
- },
- "@csstools/postcss-scope-pseudo-class": {
- "version": "2.0.2",
- "dev": true,
- "requires": {
- "postcss-selector-parser": "^6.0.10"
- }
- },
- "@csstools/postcss-stepped-value-functions": {
- "version": "2.1.0",
- "dev": true,
- "requires": {
- "@csstools/css-calc": "^1.0.0",
- "@csstools/css-parser-algorithms": "^2.0.1",
- "@csstools/css-tokenizer": "^2.0.1"
- }
- },
- "@csstools/postcss-text-decoration-shorthand": {
- "version": "2.2.1",
- "dev": true,
- "requires": {
- "@csstools/color-helpers": "^1.0.0",
- "postcss-value-parser": "^4.2.0"
- }
- },
- "@csstools/postcss-trigonometric-functions": {
- "version": "2.1.0",
- "dev": true,
- "requires": {
- "@csstools/css-calc": "^1.0.0",
- "@csstools/css-parser-algorithms": "^2.0.1",
- "@csstools/css-tokenizer": "^2.0.1"
- }
- },
- "@csstools/postcss-unset-value": {
- "version": "2.0.1",
- "dev": true,
- "requires": {}
- },
- "@csstools/selector-specificity": {
- "version": "2.1.1",
- "dev": true,
- "requires": {}
- },
"@emotion/hash": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz",
@@ -40845,23 +39372,6 @@
"defer-to-connect": "^2.0.1"
}
},
- "@tailwindcss/forms": {
- "version": "0.5.3",
- "dev": true,
- "requires": {
- "mini-svg-data-uri": "^1.2.3"
- }
- },
- "@tailwindcss/typography": {
- "version": "0.5.9",
- "dev": true,
- "requires": {
- "lodash.castarray": "^4.4.0",
- "lodash.isplainobject": "^4.0.6",
- "lodash.merge": "^4.6.2",
- "postcss-selector-parser": "6.0.10"
- }
- },
"@testing-library/dom": {
"version": "8.20.1",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.1.tgz",
@@ -42333,18 +40843,6 @@
"auto-bind": {
"version": "4.0.0"
},
- "autoprefixer": {
- "version": "10.4.14",
- "dev": true,
- "requires": {
- "browserslist": "^4.21.5",
- "caniuse-lite": "^1.0.30001464",
- "fraction.js": "^4.2.0",
- "normalize-range": "^0.1.2",
- "picocolors": "^1.0.0",
- "postcss-value-parser": "^4.2.0"
- }
- },
"available-typed-arrays": {
"version": "1.0.5"
},
@@ -43444,27 +41942,6 @@
}
}
},
- "css-blank-pseudo": {
- "version": "5.0.2",
- "dev": true,
- "requires": {
- "postcss-selector-parser": "^6.0.10"
- }
- },
- "css-has-pseudo": {
- "version": "5.0.2",
- "dev": true,
- "requires": {
- "@csstools/selector-specificity": "^2.0.1",
- "postcss-selector-parser": "^6.0.10",
- "postcss-value-parser": "^4.2.0"
- }
- },
- "css-prefers-color-scheme": {
- "version": "8.0.2",
- "dev": true,
- "requires": {}
- },
"css-selector-parser": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-3.0.5.tgz",
@@ -43481,10 +41958,6 @@
"version": "1.5.1",
"dev": true
},
- "cssdb": {
- "version": "7.5.2",
- "dev": true
- },
"cssesc": {
"version": "3.0.0",
"dev": true
@@ -43824,6 +42297,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-syntax-highlighter": "^15.5.0",
+ "tailwindcss": "^3.3.0",
"typescript": "^5.2.2"
}
},
@@ -44860,35 +43334,7 @@
"example-subscriptions": {
"version": "file:examples/subscriptions",
"requires": {
- "@remix-run/dev": "^2.9.2",
- "@remix-run/eslint-config": "^2.9.2",
- "@remix-run/react": "^2.9.2",
- "@shopify/cli": "3.61.2",
- "@shopify/cli-hydrogen": "^8.1.0",
- "@shopify/hydrogen": "2024.4.3",
- "@shopify/mini-oxygen": "^3.0.3",
- "@shopify/oxygen-workers-types": "^4.0.0",
- "@shopify/prettier-config": "^1.1.2",
- "@shopify/remix-oxygen": "^2.0.4",
- "@tailwindcss/forms": "^0.5.3",
- "@tailwindcss/typography": "^0.5.9",
- "@total-typescript/ts-reset": "^0.4.2",
- "@types/eslint": "^8.4.10",
- "@types/react": "^18.2.22",
- "@types/react-dom": "^18.2.7",
- "eslint": "^8.20.0",
- "eslint-plugin-hydrogen": "0.12.2",
- "graphql": "^16.6.0",
- "graphql-tag": "^2.12.6",
- "isbot": "^3.6.6",
- "postcss": "^8.4.21",
- "postcss-import": "^15.1.0",
- "postcss-preset-env": "^8.2.0",
- "prettier": "^2.8.4",
- "react": "^18.2.0",
- "react-dom": "^18.2.0",
- "tailwindcss": "^3.3.0",
- "typescript": "^5.2.2"
+ "@shopify/cli-hydrogen": "*"
}
},
"example-third-party-queries-caching": {
@@ -45298,10 +43744,6 @@
"forwarded": {
"version": "0.2.0"
},
- "fraction.js": {
- "version": "4.2.0",
- "dev": true
- },
"fresh": {
"version": "0.5.2"
},
@@ -47821,10 +46263,6 @@
"integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
"dev": true
},
- "lodash.castarray": {
- "version": "4.4.0",
- "dev": true
- },
"lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
@@ -49575,10 +48013,6 @@
"version": "1.0.1",
"dev": true
},
- "mini-svg-data-uri": {
- "version": "1.4.4",
- "dev": true
- },
"miniflare": {
"version": "3.20240304.2",
"resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20240304.2.tgz",
@@ -49985,10 +48419,6 @@
"normalize-path": {
"version": "3.0.0"
},
- "normalize-range": {
- "version": "0.1.2",
- "dev": true
- },
"normalize-url": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.0.tgz",
@@ -50860,78 +49290,6 @@
"source-map-js": "^1.2.0"
}
},
- "postcss-attribute-case-insensitive": {
- "version": "6.0.2",
- "dev": true,
- "requires": {
- "postcss-selector-parser": "^6.0.10"
- }
- },
- "postcss-clamp": {
- "version": "4.1.0",
- "dev": true,
- "requires": {
- "postcss-value-parser": "^4.2.0"
- }
- },
- "postcss-color-functional-notation": {
- "version": "5.0.2",
- "dev": true,
- "requires": {
- "postcss-value-parser": "^4.2.0"
- }
- },
- "postcss-color-hex-alpha": {
- "version": "9.0.2",
- "dev": true,
- "requires": {
- "postcss-value-parser": "^4.2.0"
- }
- },
- "postcss-color-rebeccapurple": {
- "version": "8.0.2",
- "dev": true,
- "requires": {
- "postcss-value-parser": "^4.2.0"
- }
- },
- "postcss-custom-media": {
- "version": "9.1.2",
- "dev": true,
- "requires": {
- "@csstools/cascade-layer-name-parser": "^1.0.0",
- "@csstools/css-parser-algorithms": "^2.0.0",
- "@csstools/css-tokenizer": "^2.0.0",
- "@csstools/media-query-list-parser": "^2.0.0"
- }
- },
- "postcss-custom-properties": {
- "version": "13.1.4",
- "dev": true,
- "requires": {
- "@csstools/cascade-layer-name-parser": "^1.0.0",
- "@csstools/css-parser-algorithms": "^2.0.0",
- "@csstools/css-tokenizer": "^2.0.0",
- "postcss-value-parser": "^4.2.0"
- }
- },
- "postcss-custom-selectors": {
- "version": "7.1.2",
- "dev": true,
- "requires": {
- "@csstools/cascade-layer-name-parser": "^1.0.0",
- "@csstools/css-parser-algorithms": "^2.0.0",
- "@csstools/css-tokenizer": "^2.0.0",
- "postcss-selector-parser": "^6.0.4"
- }
- },
- "postcss-dir-pseudo-class": {
- "version": "7.0.2",
- "dev": true,
- "requires": {
- "postcss-selector-parser": "^6.0.10"
- }
- },
"postcss-discard-duplicates": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz",
@@ -50939,59 +49297,6 @@
"dev": true,
"requires": {}
},
- "postcss-double-position-gradients": {
- "version": "4.0.2",
- "dev": true,
- "requires": {
- "@csstools/postcss-progressive-custom-properties": "^2.0.0",
- "postcss-value-parser": "^4.2.0"
- }
- },
- "postcss-focus-visible": {
- "version": "8.0.2",
- "dev": true,
- "requires": {
- "postcss-selector-parser": "^6.0.10"
- }
- },
- "postcss-focus-within": {
- "version": "7.0.2",
- "dev": true,
- "requires": {
- "postcss-selector-parser": "^6.0.10"
- }
- },
- "postcss-font-variant": {
- "version": "5.0.0",
- "dev": true,
- "requires": {}
- },
- "postcss-gap-properties": {
- "version": "4.0.1",
- "dev": true,
- "requires": {}
- },
- "postcss-image-set-function": {
- "version": "5.0.2",
- "dev": true,
- "requires": {
- "postcss-value-parser": "^4.2.0"
- }
- },
- "postcss-import": {
- "version": "15.1.0",
- "dev": true,
- "requires": {
- "postcss-value-parser": "^4.0.0",
- "read-cache": "^1.0.0",
- "resolve": "^1.1.7"
- }
- },
- "postcss-initial": {
- "version": "4.0.1",
- "dev": true,
- "requires": {}
- },
"postcss-js": {
"version": "4.0.0",
"dev": true,
@@ -50999,16 +49304,6 @@
"camelcase-css": "^2.0.1"
}
},
- "postcss-lab-function": {
- "version": "5.2.0",
- "dev": true,
- "requires": {
- "@csstools/css-color-parser": "^1.0.0",
- "@csstools/css-parser-algorithms": "^2.0.1",
- "@csstools/css-tokenizer": "^2.1.0",
- "@csstools/postcss-progressive-custom-properties": "^2.0.0"
- }
- },
"postcss-load-config": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
@@ -51027,18 +49322,6 @@
}
}
},
- "postcss-logical": {
- "version": "6.1.0",
- "dev": true,
- "requires": {
- "postcss-value-parser": "^4.2.0"
- }
- },
- "postcss-media-minmax": {
- "version": "5.0.0",
- "dev": true,
- "requires": {}
- },
"postcss-modules": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/postcss-modules/-/postcss-modules-6.0.0.tgz",
@@ -51098,118 +49381,6 @@
"postcss-selector-parser": "^6.0.10"
}
},
- "postcss-nesting": {
- "version": "11.2.1",
- "dev": true,
- "requires": {
- "@csstools/selector-specificity": "^2.0.0",
- "postcss-selector-parser": "^6.0.10"
- }
- },
- "postcss-opacity-percentage": {
- "version": "2.0.0",
- "dev": true,
- "requires": {}
- },
- "postcss-overflow-shorthand": {
- "version": "4.0.1",
- "dev": true,
- "requires": {
- "postcss-value-parser": "^4.2.0"
- }
- },
- "postcss-page-break": {
- "version": "3.0.4",
- "dev": true,
- "requires": {}
- },
- "postcss-place": {
- "version": "8.0.1",
- "dev": true,
- "requires": {
- "postcss-value-parser": "^4.2.0"
- }
- },
- "postcss-preset-env": {
- "version": "8.2.0",
- "dev": true,
- "requires": {
- "@csstools/postcss-cascade-layers": "^3.0.1",
- "@csstools/postcss-color-function": "^2.1.0",
- "@csstools/postcss-color-mix-function": "^1.0.0",
- "@csstools/postcss-font-format-keywords": "^2.0.2",
- "@csstools/postcss-gradients-interpolation-method": "^3.0.1",
- "@csstools/postcss-hwb-function": "^2.2.0",
- "@csstools/postcss-ic-unit": "^2.0.2",
- "@csstools/postcss-is-pseudo-class": "^3.1.1",
- "@csstools/postcss-logical-float-and-clear": "^1.0.1",
- "@csstools/postcss-logical-resize": "^1.0.1",
- "@csstools/postcss-logical-viewport-units": "^1.0.2",
- "@csstools/postcss-media-queries-aspect-ratio-number-values": "^1.0.1",
- "@csstools/postcss-nested-calc": "^2.0.2",
- "@csstools/postcss-normalize-display-values": "^2.0.1",
- "@csstools/postcss-oklab-function": "^2.2.0",
- "@csstools/postcss-progressive-custom-properties": "^2.1.0",
- "@csstools/postcss-scope-pseudo-class": "^2.0.2",
- "@csstools/postcss-stepped-value-functions": "^2.1.0",
- "@csstools/postcss-text-decoration-shorthand": "^2.2.1",
- "@csstools/postcss-trigonometric-functions": "^2.1.0",
- "@csstools/postcss-unset-value": "^2.0.1",
- "autoprefixer": "^10.4.14",
- "browserslist": "^4.21.5",
- "css-blank-pseudo": "^5.0.2",
- "css-has-pseudo": "^5.0.2",
- "css-prefers-color-scheme": "^8.0.2",
- "cssdb": "^7.5.2",
- "postcss-attribute-case-insensitive": "^6.0.2",
- "postcss-clamp": "^4.1.0",
- "postcss-color-functional-notation": "^5.0.2",
- "postcss-color-hex-alpha": "^9.0.2",
- "postcss-color-rebeccapurple": "^8.0.2",
- "postcss-custom-media": "^9.1.2",
- "postcss-custom-properties": "^13.1.4",
- "postcss-custom-selectors": "^7.1.2",
- "postcss-dir-pseudo-class": "^7.0.2",
- "postcss-double-position-gradients": "^4.0.2",
- "postcss-focus-visible": "^8.0.2",
- "postcss-focus-within": "^7.0.2",
- "postcss-font-variant": "^5.0.0",
- "postcss-gap-properties": "^4.0.1",
- "postcss-image-set-function": "^5.0.2",
- "postcss-initial": "^4.0.1",
- "postcss-lab-function": "^5.2.0",
- "postcss-logical": "^6.1.0",
- "postcss-media-minmax": "^5.0.0",
- "postcss-nesting": "^11.2.1",
- "postcss-opacity-percentage": "^2.0.0",
- "postcss-overflow-shorthand": "^4.0.1",
- "postcss-page-break": "^3.0.4",
- "postcss-place": "^8.0.1",
- "postcss-pseudo-class-any-link": "^8.0.2",
- "postcss-replace-overflow-wrap": "^4.0.0",
- "postcss-selector-not": "^7.0.1",
- "postcss-value-parser": "^4.2.0"
- }
- },
- "postcss-pseudo-class-any-link": {
- "version": "8.0.2",
- "dev": true,
- "requires": {
- "postcss-selector-parser": "^6.0.10"
- }
- },
- "postcss-replace-overflow-wrap": {
- "version": "4.0.0",
- "dev": true,
- "requires": {}
- },
- "postcss-selector-not": {
- "version": "7.0.1",
- "dev": true,
- "requires": {
- "postcss-selector-parser": "^6.0.10"
- }
- },
"postcss-selector-parser": {
"version": "6.0.10",
"dev": true,