@decocms/apps-shopify 8.1.0-next.0 → 8.1.0-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +7 -24
- package/src/client.ts +113 -47
- package/src/index.ts +8 -50
- package/src/actions/cart/addItems.ts +0 -36
- package/src/actions/cart/updateCoupons.ts +0 -31
- package/src/actions/cart/updateItems.ts +0 -31
- package/src/actions/user/signIn.ts +0 -40
- package/src/actions/user/signUp.ts +0 -31
- package/src/init.ts +0 -39
- package/src/loaders/ProductDetailsPage.ts +0 -31
- package/src/loaders/ProductList.ts +0 -102
- package/src/loaders/ProductListingPage.ts +0 -172
- package/src/loaders/RelatedProducts.ts +0 -42
- package/src/loaders/cart.ts +0 -74
- package/src/loaders/shop.ts +0 -35
- package/src/loaders/user.ts +0 -39
- package/src/manifest.gen.ts +0 -39
- package/src/mod.ts +0 -67
- package/src/registry.ts +0 -9
- package/src/utils/__tests__/instrumentedFetch.test.ts +0 -84
- package/src/utils/__tests__/operationRouter.test.ts +0 -56
- package/src/utils/__tests__/transform.test.ts +0 -216
- package/src/utils/admin/admin.ts +0 -57
- package/src/utils/admin/queries.ts +0 -29
- package/src/utils/cart.ts +0 -32
- package/src/utils/cookies.ts +0 -82
- package/src/utils/enums.ts +0 -438
- package/src/utils/graphql.ts +0 -70
- package/src/utils/graphqlOperationName.ts +0 -2
- package/src/utils/instrumentedFetch.ts +0 -56
- package/src/utils/operationRouter.ts +0 -70
- package/src/utils/storefront/queries.ts +0 -525
- package/src/utils/storefront/storefront.graphql.gen.ts +0 -129
- package/src/utils/transform.ts +0 -430
- package/src/utils/types.ts +0 -191
- package/src/utils/user.ts +0 -23
- package/src/utils/utils.ts +0 -159
- package/src/v8/client.test.ts +0 -136
- package/src/v8/client.ts +0 -125
- package/src/v8/graphqlOperationName.test.ts +0 -80
- package/tsconfig.json +0 -7
- /package/src/{v8/graphqlOperationName.ts → graphqlOperationName.ts} +0 -0
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decocms/apps-shopify",
|
|
3
|
-
"version": "8.1.0-next.
|
|
3
|
+
"version": "8.1.0-next.1",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Thin Shopify Storefront API client for Deco sites",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "https://github.com/decocms/blocks.git",
|
|
@@ -10,39 +10,22 @@
|
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
12
|
"src",
|
|
13
|
-
"
|
|
13
|
+
"!src/**/*.test.ts",
|
|
14
|
+
"!src/**/__tests__"
|
|
14
15
|
],
|
|
15
16
|
"main": "./src/index.ts",
|
|
16
17
|
"exports": {
|
|
17
|
-
".": "./src/index.ts"
|
|
18
|
-
"./mod": "./src/mod.ts",
|
|
19
|
-
"./client": "./src/client.ts",
|
|
20
|
-
"./loaders/*": "./src/loaders/*.ts",
|
|
21
|
-
"./actions/*": "./src/actions/*.ts",
|
|
22
|
-
"./actions/cart/*": "./src/actions/cart/*.ts",
|
|
23
|
-
"./actions/user/*": "./src/actions/user/*.ts",
|
|
24
|
-
"./utils/*": "./src/utils/*.ts",
|
|
25
|
-
"./registry": "./src/registry.ts"
|
|
18
|
+
".": "./src/index.ts"
|
|
26
19
|
},
|
|
27
20
|
"scripts": {
|
|
28
21
|
"build": "tsc",
|
|
29
22
|
"test": "vitest run --root ../.. packages/apps-shopify/",
|
|
30
|
-
"typecheck": "tsc --noEmit"
|
|
31
|
-
"lint:unused": "knip"
|
|
23
|
+
"typecheck": "tsc --noEmit"
|
|
32
24
|
},
|
|
33
25
|
"dependencies": {
|
|
34
|
-
"@decocms/blocks": "8.1.0-next.
|
|
35
|
-
"@decocms/apps-commerce": "8.1.0-next.0",
|
|
36
|
-
"@decocms/tanstack": "8.1.0-next.0"
|
|
37
|
-
},
|
|
38
|
-
"peerDependencies": {
|
|
39
|
-
"react": "^19.0.0",
|
|
40
|
-
"react-dom": "^19.0.0"
|
|
26
|
+
"@decocms/blocks": "8.1.0-next.1"
|
|
41
27
|
},
|
|
42
28
|
"devDependencies": {
|
|
43
|
-
"@types/react": "^19.0.0",
|
|
44
|
-
"@types/react-dom": "^19.0.0",
|
|
45
|
-
"knip": "^5.86.0",
|
|
46
29
|
"typescript": "^5.9.0"
|
|
47
30
|
},
|
|
48
31
|
"publishConfig": {
|
package/src/client.ts
CHANGED
|
@@ -1,59 +1,125 @@
|
|
|
1
|
-
import type { FetchFn } from "@decocms/blocks/sdk/fetchTimeout";
|
|
2
|
-
import { createGraphqlClient, type GraphQLClient } from "./utils/graphql";
|
|
3
|
-
|
|
4
|
-
export interface ShopifyConfig {
|
|
5
|
-
storeName: string;
|
|
6
|
-
storefrontAccessToken: string;
|
|
7
|
-
publicUrl?: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
let _client: GraphQLClient | null = null;
|
|
11
|
-
let _config: ShopifyConfig | null = null;
|
|
12
|
-
let _fetch: FetchFn | undefined;
|
|
13
|
-
|
|
14
1
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
2
|
+
* The Shopify upstream client (/next/upstream-clients): the Storefront and
|
|
3
|
+
* Admin GraphQL APIs over the framework's instrumented fetch, and nothing
|
|
4
|
+
* else. Converters to commerce types, cart and customer flows, hooks and
|
|
5
|
+
* loaders live in the Shopify platform template and site code.
|
|
17
6
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* ```
|
|
7
|
+
* Every request is measured as provider "shopify", labeled with the GraphQL
|
|
8
|
+
* operation's own name (`query ProductByHandle { … }` → "ProductByHandle"),
|
|
9
|
+
* or "storefront.graphql" / "admin.graphql" for an unnamed document. Retries
|
|
10
|
+
* and the circuit breaker stay off, as for every client but VTEX's; caching
|
|
11
|
+
* is the site's job.
|
|
24
12
|
*/
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
13
|
+
import { createInstrumentedFetch } from "@decocms/blocks/fetch";
|
|
14
|
+
import { extractGraphqlOperationName } from "./graphqlOperationName";
|
|
15
|
+
|
|
16
|
+
export interface ShopifyClientConfig {
|
|
17
|
+
/** The store's subdomain: "acme" for acme.myshopify.com. */
|
|
18
|
+
storeName: string;
|
|
19
|
+
/** Storefront API access token. */
|
|
20
|
+
storefrontAccessToken: string;
|
|
21
|
+
/** Admin API access token; `admin` is only available when it's set. */
|
|
22
|
+
adminAccessToken?: string;
|
|
23
|
+
/** The Shopify API version, e.g. "2026-07". The site owns it, so there is no default to go stale. */
|
|
24
|
+
apiVersion: string;
|
|
28
25
|
}
|
|
29
26
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
{
|
|
35
|
-
"X-Shopify-Storefront-Access-Token": config.storefrontAccessToken,
|
|
36
|
-
},
|
|
37
|
-
_fetch,
|
|
38
|
-
);
|
|
27
|
+
interface ShopifyRequestOptions {
|
|
28
|
+
/** Extra request headers, e.g. `Shopify-Storefront-Buyer-IP`. */
|
|
29
|
+
headers?: Record<string, string>;
|
|
30
|
+
signal?: AbortSignal;
|
|
39
31
|
}
|
|
40
32
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
33
|
+
/** One GraphQL endpoint: send a document, get its `data` back. */
|
|
34
|
+
interface ShopifyGraphQL {
|
|
35
|
+
query<T, V extends Record<string, unknown> = Record<string, unknown>>(
|
|
36
|
+
document: string,
|
|
37
|
+
variables?: V,
|
|
38
|
+
options?: ShopifyRequestOptions,
|
|
39
|
+
): Promise<T>;
|
|
48
40
|
}
|
|
49
41
|
|
|
50
|
-
export
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
return _config;
|
|
42
|
+
export interface ShopifyClient {
|
|
43
|
+
storefront: ShopifyGraphQL;
|
|
44
|
+
/** Throws when called without `adminAccessToken` in the config. */
|
|
45
|
+
admin: ShopifyGraphQL;
|
|
55
46
|
}
|
|
56
47
|
|
|
57
|
-
|
|
58
|
-
|
|
48
|
+
/** A failed request. Carries the operation, the HTTP status and Shopify's error codes; never bodies or tokens. */
|
|
49
|
+
export class ShopifyError extends Error {
|
|
50
|
+
constructor(
|
|
51
|
+
readonly operation: string,
|
|
52
|
+
readonly status: number,
|
|
53
|
+
readonly codes: string[] = [],
|
|
54
|
+
) {
|
|
55
|
+
super(
|
|
56
|
+
codes.length > 0
|
|
57
|
+
? `shopify ${operation} returned GraphQL errors (${codes.join(", ")})`
|
|
58
|
+
: `shopify ${operation} failed with HTTP ${status}`,
|
|
59
|
+
);
|
|
60
|
+
this.name = "ShopifyError";
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function createShopifyClient(
|
|
65
|
+
config: ShopifyClientConfig,
|
|
66
|
+
options: { fetch?: typeof globalThis.fetch } = {},
|
|
67
|
+
): ShopifyClient {
|
|
68
|
+
const request = createInstrumentedFetch({ provider: "shopify", fetch: options.fetch });
|
|
69
|
+
const origin = `https://${config.storeName}.myshopify.com`;
|
|
70
|
+
const version = config.apiVersion;
|
|
71
|
+
|
|
72
|
+
const endpoint = (surface: "storefront" | "admin", url: string, auth: Record<string, string>) =>
|
|
73
|
+
({
|
|
74
|
+
async query(document, variables, { headers, signal } = {}) {
|
|
75
|
+
const operation = extractGraphqlOperationName(document) ?? `${surface}.graphql`;
|
|
76
|
+
const response = await request(url, {
|
|
77
|
+
operation,
|
|
78
|
+
method: "POST",
|
|
79
|
+
headers: { "content-type": "application/json", ...headers, ...auth },
|
|
80
|
+
body: JSON.stringify({ query: document, variables }),
|
|
81
|
+
signal,
|
|
82
|
+
});
|
|
83
|
+
if (!response.ok) {
|
|
84
|
+
void response.body?.cancel().catch(() => {});
|
|
85
|
+
throw new ShopifyError(operation, response.status);
|
|
86
|
+
}
|
|
87
|
+
// A non-JSON body (an HTML error page) must not reach the error: the
|
|
88
|
+
// SyntaxError's message quotes the start of the body.
|
|
89
|
+
let body: { data?: unknown; errors?: { extensions?: { code?: unknown } }[] };
|
|
90
|
+
try {
|
|
91
|
+
body = await response.json();
|
|
92
|
+
} catch {
|
|
93
|
+
throw new ShopifyError(operation, response.status, ["INVALID_JSON"]);
|
|
94
|
+
}
|
|
95
|
+
// Partial data alongside errors is rejected as a whole, deliberately:
|
|
96
|
+
// a thin client doesn't guess which fields are still trustworthy.
|
|
97
|
+
if (body.errors?.length || body.data == null) {
|
|
98
|
+
const codes = body.errors?.length
|
|
99
|
+
? body.errors.map((e) =>
|
|
100
|
+
typeof e.extensions?.code === "string" ? e.extensions.code : "UNKNOWN",
|
|
101
|
+
)
|
|
102
|
+
: ["MISSING_DATA"];
|
|
103
|
+
throw new ShopifyError(operation, response.status, [...new Set(codes)]);
|
|
104
|
+
}
|
|
105
|
+
return body.data as never;
|
|
106
|
+
},
|
|
107
|
+
}) satisfies ShopifyGraphQL;
|
|
108
|
+
|
|
109
|
+
const adminToken = config.adminAccessToken;
|
|
110
|
+
return {
|
|
111
|
+
storefront: endpoint("storefront", `${origin}/api/${version}/graphql.json`, {
|
|
112
|
+
"x-shopify-storefront-access-token": config.storefrontAccessToken,
|
|
113
|
+
}),
|
|
114
|
+
admin: adminToken
|
|
115
|
+
? endpoint("admin", `${origin}/admin/api/${version}/graphql.json`, {
|
|
116
|
+
"x-shopify-access-token": adminToken,
|
|
117
|
+
})
|
|
118
|
+
: {
|
|
119
|
+
query: () =>
|
|
120
|
+
Promise.reject(
|
|
121
|
+
new Error("shopify admin: pass adminAccessToken to createShopifyClient"),
|
|
122
|
+
),
|
|
123
|
+
},
|
|
124
|
+
};
|
|
59
125
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,52 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export { default as addItems } from "./actions/cart/addItems";
|
|
7
|
-
export { default as updateCoupons } from "./actions/cart/updateCoupons";
|
|
8
|
-
export { default as updateItems } from "./actions/cart/updateItems";
|
|
9
|
-
export { default as signIn } from "./actions/user/signIn";
|
|
10
|
-
export { default as signUp } from "./actions/user/signUp";
|
|
11
|
-
export type { ShopifyConfig } from "./client";
|
|
1
|
+
/**
|
|
2
|
+
* `@decocms/apps-shopify`: a thin client for the Shopify Storefront API.
|
|
3
|
+
* See /next/upstream-clients.
|
|
4
|
+
*/
|
|
12
5
|
export {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
setShopifyFetch,
|
|
6
|
+
createShopifyClient,
|
|
7
|
+
type ShopifyClient,
|
|
8
|
+
type ShopifyClientConfig,
|
|
9
|
+
ShopifyError,
|
|
18
10
|
} from "./client";
|
|
19
|
-
export { initShopify, initShopifyFromBlocks } from "./init";
|
|
20
|
-
export type { CartLine, ShopifyCart } from "./loaders/cart";
|
|
21
|
-
// Cart
|
|
22
|
-
export { createCart, getCart } from "./loaders/cart";
|
|
23
|
-
export { default as productDetailsPageLoader } from "./loaders/ProductDetailsPage";
|
|
24
|
-
// Product Loaders
|
|
25
|
-
export { default as productListLoader } from "./loaders/ProductList";
|
|
26
|
-
export { default as productListingPageLoader } from "./loaders/ProductListingPage";
|
|
27
|
-
export { default as relatedProductsLoader } from "./loaders/RelatedProducts";
|
|
28
|
-
export type { Shop } from "./loaders/shop";
|
|
29
|
-
// Shop
|
|
30
|
-
export { default as shopLoader } from "./loaders/shop";
|
|
31
|
-
export type { ShopifyUser } from "./loaders/user";
|
|
32
|
-
// User
|
|
33
|
-
export { default as userLoader } from "./loaders/user";
|
|
34
|
-
export { getCartCookie, setCartCookie } from "./utils/cart";
|
|
35
|
-
// Cookie utils
|
|
36
|
-
export { getCookies, setCookie } from "./utils/cookies";
|
|
37
|
-
export { extractGraphqlOperationName } from "./v8/graphqlOperationName";
|
|
38
|
-
export {
|
|
39
|
-
type CreateShopifyFetchOptions,
|
|
40
|
-
createShopifyFetch,
|
|
41
|
-
} from "./utils/instrumentedFetch";
|
|
42
|
-
export { shopifyOperationRouter } from "./utils/operationRouter";
|
|
43
|
-
export { getUserCookie, setUserCookie } from "./utils/user";
|
|
44
|
-
|
|
45
|
-
// The next-major upstream client (/next/upstream-clients). Everything above is
|
|
46
|
-
// the v7 surface, kept for v7 consumers until v7 is dropped.
|
|
47
|
-
export {
|
|
48
|
-
createShopifyClient,
|
|
49
|
-
type ShopifyClient,
|
|
50
|
-
type ShopifyClientConfig,
|
|
51
|
-
ShopifyError,
|
|
52
|
-
} from "./v8/client";
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { getShopifyClient } from "../../client";
|
|
2
|
-
import type { ShopifyCart } from "../../loaders/cart";
|
|
3
|
-
import { getCartCookie, setCartCookie } from "../../utils/cart";
|
|
4
|
-
import { AddItemToCart } from "../../utils/storefront/queries";
|
|
5
|
-
|
|
6
|
-
export interface AddItemProps {
|
|
7
|
-
lines: {
|
|
8
|
-
merchandiseId: string;
|
|
9
|
-
attributes?: Array<{ key: string; value: string }>;
|
|
10
|
-
quantity?: number;
|
|
11
|
-
sellingPlanId?: string;
|
|
12
|
-
};
|
|
13
|
-
requestHeaders: Headers;
|
|
14
|
-
responseHeaders?: Headers;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export default async function addItems({
|
|
18
|
-
lines,
|
|
19
|
-
requestHeaders,
|
|
20
|
-
responseHeaders,
|
|
21
|
-
}: AddItemProps): Promise<ShopifyCart | null> {
|
|
22
|
-
const client = getShopifyClient();
|
|
23
|
-
const cartId = getCartCookie(requestHeaders);
|
|
24
|
-
|
|
25
|
-
if (!cartId) throw new Error("Missing cart cookie");
|
|
26
|
-
|
|
27
|
-
const data = await client.query<{
|
|
28
|
-
payload?: { cart?: ShopifyCart };
|
|
29
|
-
}>(AddItemToCart, { cartId, lines });
|
|
30
|
-
|
|
31
|
-
if (responseHeaders) {
|
|
32
|
-
setCartCookie(responseHeaders, cartId);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return data.payload?.cart ?? null;
|
|
36
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { getShopifyClient } from "../../client";
|
|
2
|
-
import type { ShopifyCart } from "../../loaders/cart";
|
|
3
|
-
import { getCartCookie, setCartCookie } from "../../utils/cart";
|
|
4
|
-
import { AddCoupon } from "../../utils/storefront/queries";
|
|
5
|
-
|
|
6
|
-
export interface UpdateCouponsProps {
|
|
7
|
-
discountCodes: string[];
|
|
8
|
-
requestHeaders: Headers;
|
|
9
|
-
responseHeaders?: Headers;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export default async function updateCoupons({
|
|
13
|
-
discountCodes,
|
|
14
|
-
requestHeaders,
|
|
15
|
-
responseHeaders,
|
|
16
|
-
}: UpdateCouponsProps): Promise<ShopifyCart | null> {
|
|
17
|
-
const client = getShopifyClient();
|
|
18
|
-
const cartId = getCartCookie(requestHeaders);
|
|
19
|
-
|
|
20
|
-
if (!cartId) throw new Error("Missing cart cookie");
|
|
21
|
-
|
|
22
|
-
const data = await client.query<{
|
|
23
|
-
payload?: { cart?: ShopifyCart };
|
|
24
|
-
}>(AddCoupon, { cartId, discountCodes });
|
|
25
|
-
|
|
26
|
-
if (responseHeaders) {
|
|
27
|
-
setCartCookie(responseHeaders, cartId);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return data.payload?.cart ?? null;
|
|
31
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { getShopifyClient } from "../../client";
|
|
2
|
-
import type { ShopifyCart } from "../../loaders/cart";
|
|
3
|
-
import { getCartCookie, setCartCookie } from "../../utils/cart";
|
|
4
|
-
import { UpdateItems } from "../../utils/storefront/queries";
|
|
5
|
-
|
|
6
|
-
export interface UpdateItemsProps {
|
|
7
|
-
lines: Array<{ id: string; quantity: number }>;
|
|
8
|
-
requestHeaders: Headers;
|
|
9
|
-
responseHeaders?: Headers;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export default async function updateItems({
|
|
13
|
-
lines,
|
|
14
|
-
requestHeaders,
|
|
15
|
-
responseHeaders,
|
|
16
|
-
}: UpdateItemsProps): Promise<ShopifyCart | null> {
|
|
17
|
-
const client = getShopifyClient();
|
|
18
|
-
const cartId = getCartCookie(requestHeaders);
|
|
19
|
-
|
|
20
|
-
if (!cartId) throw new Error("Missing cart cookie");
|
|
21
|
-
|
|
22
|
-
const data = await client.query<{
|
|
23
|
-
payload?: { cart?: ShopifyCart };
|
|
24
|
-
}>(UpdateItems, { cartId, lines });
|
|
25
|
-
|
|
26
|
-
if (responseHeaders) {
|
|
27
|
-
setCartCookie(responseHeaders, cartId);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return data.payload?.cart ?? null;
|
|
31
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { getShopifyClient } from "../../client";
|
|
2
|
-
import { SignInWithEmailAndPassword } from "../../utils/storefront/queries";
|
|
3
|
-
import { getUserCookie, setUserCookie } from "../../utils/user";
|
|
4
|
-
|
|
5
|
-
export interface SignInProps {
|
|
6
|
-
email: string;
|
|
7
|
-
password: string;
|
|
8
|
-
requestHeaders: Headers;
|
|
9
|
-
responseHeaders?: Headers;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface SignInResult {
|
|
13
|
-
customerAccessTokenCreate: {
|
|
14
|
-
customerAccessToken?: { accessToken: string; expiresAt: string } | null;
|
|
15
|
-
customerUserErrors?: Array<{ code?: string; message: string }>;
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export default async function signIn(props: SignInProps): Promise<SignInResult | null> {
|
|
20
|
-
const client = getShopifyClient();
|
|
21
|
-
const { email, password, requestHeaders, responseHeaders } = props;
|
|
22
|
-
|
|
23
|
-
const existingToken = getUserCookie(requestHeaders);
|
|
24
|
-
if (existingToken) return null;
|
|
25
|
-
|
|
26
|
-
try {
|
|
27
|
-
const data = await client.query<SignInResult>(SignInWithEmailAndPassword, { email, password });
|
|
28
|
-
|
|
29
|
-
if (data.customerAccessTokenCreate.customerAccessToken && responseHeaders) {
|
|
30
|
-
setUserCookie(
|
|
31
|
-
responseHeaders,
|
|
32
|
-
data.customerAccessTokenCreate.customerAccessToken.accessToken,
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return data;
|
|
37
|
-
} catch {
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { getShopifyClient } from "../../client";
|
|
2
|
-
import { RegisterAccount } from "../../utils/storefront/queries";
|
|
3
|
-
|
|
4
|
-
export interface SignUpProps {
|
|
5
|
-
email: string;
|
|
6
|
-
password: string;
|
|
7
|
-
firstName?: string;
|
|
8
|
-
lastName?: string;
|
|
9
|
-
acceptsMarketing?: boolean;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface SignUpResult {
|
|
13
|
-
customerCreate: {
|
|
14
|
-
customer?: { id: string } | null;
|
|
15
|
-
customerUserErrors?: Array<{ code?: string; message: string }>;
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export default async function signUp(props: SignUpProps): Promise<SignUpResult> {
|
|
20
|
-
const client = getShopifyClient();
|
|
21
|
-
|
|
22
|
-
const data = await client.query<SignUpResult>(RegisterAccount, {
|
|
23
|
-
email: props.email,
|
|
24
|
-
password: props.password,
|
|
25
|
-
firstName: props.firstName,
|
|
26
|
-
lastName: props.lastName,
|
|
27
|
-
acceptsMarketing: props.acceptsMarketing,
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
return data;
|
|
31
|
-
}
|
package/src/init.ts
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { configureShopify } from "./client";
|
|
2
|
-
|
|
3
|
-
let initialized = false;
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Initialize Shopify from raw block data.
|
|
7
|
-
* The site is responsible for reading the blocks and passing the config here.
|
|
8
|
-
*/
|
|
9
|
-
export function initShopify(config: { storeName: string; storefrontAccessToken: string }) {
|
|
10
|
-
if (initialized) return;
|
|
11
|
-
|
|
12
|
-
if (!config.storeName || !config.storefrontAccessToken) {
|
|
13
|
-
console.warn("[Shopify] Missing storeName or storefrontAccessToken.");
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
console.log(`[Shopify] Initializing: ${config.storeName}.myshopify.com`);
|
|
18
|
-
configureShopify(config);
|
|
19
|
-
initialized = true;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Initialize Shopify from a blocks map (convenience wrapper).
|
|
24
|
-
* Looks for the "deco-shopify" block and extracts credentials.
|
|
25
|
-
*/
|
|
26
|
-
export function initShopifyFromBlocks(blocks: Record<string, unknown>) {
|
|
27
|
-
const shopifyBlock = blocks["deco-shopify"] as
|
|
28
|
-
| { storeName: string; storefrontAccessToken: string }
|
|
29
|
-
| undefined;
|
|
30
|
-
if (!shopifyBlock) {
|
|
31
|
-
console.warn("[Shopify] No deco-shopify block found.");
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
initShopify({
|
|
36
|
-
storeName: shopifyBlock.storeName,
|
|
37
|
-
storefrontAccessToken: shopifyBlock.storefrontAccessToken,
|
|
38
|
-
});
|
|
39
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import type { ProductDetailsPage } from "@decocms/apps-commerce/types";
|
|
2
|
-
import { getShopifyClient } from "../client";
|
|
3
|
-
import { GetProduct } from "../utils/storefront/queries";
|
|
4
|
-
import { type ProductShopify, toProductPage } from "../utils/transform";
|
|
5
|
-
import type { Metafield } from "../utils/types";
|
|
6
|
-
|
|
7
|
-
export interface Props {
|
|
8
|
-
slug: string;
|
|
9
|
-
metafields?: Metafield[];
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export default async function productDetailsPageLoader(
|
|
13
|
-
props: Props,
|
|
14
|
-
url?: URL,
|
|
15
|
-
): Promise<ProductDetailsPage | null> {
|
|
16
|
-
const client = getShopifyClient();
|
|
17
|
-
const { slug, metafields = [] } = props;
|
|
18
|
-
|
|
19
|
-
const splitted = slug?.split("-") ?? [];
|
|
20
|
-
const maybeSkuId = Number(splitted[splitted.length - 1]);
|
|
21
|
-
const handle = splitted.slice(0, maybeSkuId ? -1 : undefined).join("-");
|
|
22
|
-
|
|
23
|
-
const data = await client.query<{ product?: ProductShopify }>(GetProduct, {
|
|
24
|
-
handle,
|
|
25
|
-
identifiers: metafields,
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
if (!data?.product) return null;
|
|
29
|
-
|
|
30
|
-
return toProductPage(data.product, url ?? new URL("https://localhost"), maybeSkuId || undefined);
|
|
31
|
-
}
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import type { Product } from "@decocms/apps-commerce/types";
|
|
2
|
-
import { getShopifyClient } from "../client";
|
|
3
|
-
import { ProductsByCollection, SearchProducts } from "../utils/storefront/queries";
|
|
4
|
-
import { type ProductShopify, toProduct } from "../utils/transform";
|
|
5
|
-
import type { Metafield } from "../utils/types";
|
|
6
|
-
import {
|
|
7
|
-
type CollectionSortKeys,
|
|
8
|
-
type SearchSortKeys,
|
|
9
|
-
searchSortShopify,
|
|
10
|
-
sortShopify,
|
|
11
|
-
} from "../utils/utils";
|
|
12
|
-
|
|
13
|
-
export interface QueryProps {
|
|
14
|
-
query: string;
|
|
15
|
-
count: number;
|
|
16
|
-
sort?: SearchSortKeys;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface CollectionProps {
|
|
20
|
-
collection: string;
|
|
21
|
-
count: number;
|
|
22
|
-
sort?: CollectionSortKeys;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface FilterProps {
|
|
26
|
-
tags?: string[];
|
|
27
|
-
productTypes?: string[];
|
|
28
|
-
productVendors?: string[];
|
|
29
|
-
priceMin?: number;
|
|
30
|
-
priceMax?: number;
|
|
31
|
-
variantOptions?: { name: string; value: string }[];
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export type Props = {
|
|
35
|
-
props: QueryProps | CollectionProps;
|
|
36
|
-
filters?: FilterProps;
|
|
37
|
-
metafields?: Metafield[];
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
const isQueryList = (p: QueryProps | CollectionProps): p is QueryProps =>
|
|
41
|
-
"query" in p && typeof p.query === "string" && typeof p.count === "number";
|
|
42
|
-
|
|
43
|
-
export default async function productListLoader(
|
|
44
|
-
expandedProps: Props,
|
|
45
|
-
url?: URL,
|
|
46
|
-
): Promise<Product[] | null> {
|
|
47
|
-
const client = getShopifyClient();
|
|
48
|
-
|
|
49
|
-
const props = expandedProps.props ?? (expandedProps as unknown as Props["props"]);
|
|
50
|
-
|
|
51
|
-
const count = props.count ?? 12;
|
|
52
|
-
const metafields = expandedProps.metafields || [];
|
|
53
|
-
const sort = props.sort ?? "";
|
|
54
|
-
|
|
55
|
-
const filters: Record<string, unknown>[] = [];
|
|
56
|
-
for (const tag of expandedProps.filters?.tags ?? []) {
|
|
57
|
-
filters.push({ tag });
|
|
58
|
-
}
|
|
59
|
-
for (const productType of expandedProps.filters?.productTypes ?? []) {
|
|
60
|
-
filters.push({ productType });
|
|
61
|
-
}
|
|
62
|
-
for (const productVendor of expandedProps.filters?.productVendors ?? []) {
|
|
63
|
-
filters.push({ productVendor });
|
|
64
|
-
}
|
|
65
|
-
if (expandedProps.filters?.priceMin != null)
|
|
66
|
-
filters.push({ price: { min: expandedProps.filters.priceMin } });
|
|
67
|
-
if (expandedProps.filters?.priceMax != null)
|
|
68
|
-
filters.push({ price: { max: expandedProps.filters.priceMax } });
|
|
69
|
-
for (const variantOption of expandedProps.filters?.variantOptions ?? []) {
|
|
70
|
-
filters.push({ variantOption });
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
let shopifyProducts: { nodes: ProductShopify[] } | undefined;
|
|
74
|
-
|
|
75
|
-
if (isQueryList(props)) {
|
|
76
|
-
const data = await client.query<{ search: { nodes: ProductShopify[] } }>(SearchProducts, {
|
|
77
|
-
first: count,
|
|
78
|
-
query: props.query,
|
|
79
|
-
productFilters: filters,
|
|
80
|
-
identifiers: metafields,
|
|
81
|
-
...searchSortShopify[sort],
|
|
82
|
-
});
|
|
83
|
-
shopifyProducts = data.search;
|
|
84
|
-
} else {
|
|
85
|
-
const data = await client.query<{
|
|
86
|
-
collection?: { products: { nodes: ProductShopify[] } };
|
|
87
|
-
}>(ProductsByCollection, {
|
|
88
|
-
first: count,
|
|
89
|
-
handle: (props as CollectionProps).collection,
|
|
90
|
-
filters,
|
|
91
|
-
identifiers: metafields,
|
|
92
|
-
...sortShopify[sort],
|
|
93
|
-
});
|
|
94
|
-
shopifyProducts = data.collection?.products;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const baseUrl = url ?? new URL("https://localhost");
|
|
98
|
-
|
|
99
|
-
const products = shopifyProducts?.nodes.map((p) => toProduct(p, p.variants.nodes[0], baseUrl));
|
|
100
|
-
|
|
101
|
-
return products ?? [];
|
|
102
|
-
}
|