@shopify/cli-hydrogen 4.0.8 → 4.1.0
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/README.md +9 -0
- package/dist/commands/hydrogen/build.d.ts +4 -1
- package/dist/commands/hydrogen/build.js +21 -17
- package/dist/commands/hydrogen/check.d.ts +3 -6
- package/dist/commands/hydrogen/check.js +10 -9
- package/dist/commands/hydrogen/dev.d.ts +3 -2
- package/dist/commands/hydrogen/dev.js +24 -22
- package/dist/commands/hydrogen/g.d.ts +10 -0
- package/dist/commands/hydrogen/g.js +17 -0
- package/dist/commands/hydrogen/generate/route.d.ts +7 -9
- package/dist/commands/hydrogen/generate/route.js +49 -47
- package/dist/commands/hydrogen/generate/route.test.js +48 -40
- package/dist/commands/hydrogen/generate/routes.d.ts +2 -2
- package/dist/commands/hydrogen/init.d.ts +3 -3
- package/dist/commands/hydrogen/init.js +76 -95
- package/dist/commands/hydrogen/init.test.js +126 -0
- package/dist/commands/hydrogen/preview.d.ts +2 -2
- package/dist/commands/hydrogen/preview.js +4 -4
- package/dist/commands/hydrogen/shortcut.d.ts +9 -0
- package/dist/commands/hydrogen/shortcut.js +74 -0
- package/dist/commands/hydrogen/shortcut.test.js +58 -0
- package/dist/generator-templates/routes/[robots.txt].tsx +35 -1
- package/dist/generator-templates/routes/[sitemap.xml].tsx +45 -10
- package/dist/generator-templates/routes/account/login.tsx +42 -13
- package/dist/generator-templates/routes/account/register.tsx +42 -13
- package/dist/generator-templates/routes/cart.tsx +42 -2
- package/dist/generator-templates/routes/collections/$collectionHandle.tsx +44 -5
- package/dist/generator-templates/routes/index.tsx +33 -0
- package/dist/generator-templates/routes/pages/$pageHandle.tsx +48 -10
- package/dist/generator-templates/routes/policies/$policyHandle.tsx +67 -14
- package/dist/generator-templates/routes/policies/index.tsx +54 -4
- package/dist/generator-templates/routes/products/$productHandle.tsx +44 -9
- package/dist/hooks/init.js +2 -2
- package/dist/{utils → lib}/check-lockfile.js +7 -4
- package/dist/{utils → lib}/check-lockfile.test.js +19 -28
- package/dist/{utils → lib}/check-version.test.js +3 -2
- package/dist/lib/colors.d.ts +8 -0
- package/dist/lib/colors.js +8 -0
- package/dist/{utils → lib}/config.js +10 -19
- package/dist/{utils → lib}/flags.d.ts +9 -3
- package/dist/{utils → lib}/flags.js +19 -4
- package/dist/lib/flags.test.d.ts +1 -0
- package/dist/{utils → lib}/mini-oxygen.js +14 -12
- package/dist/{utils → lib}/missing-routes.js +1 -1
- package/dist/lib/remix-version-interop.d.ts +11 -0
- package/dist/lib/remix-version-interop.js +54 -0
- package/dist/lib/remix-version-interop.test.d.ts +1 -0
- package/dist/lib/remix-version-interop.test.js +93 -0
- package/dist/lib/shell.d.ts +12 -0
- package/dist/lib/shell.js +73 -0
- package/dist/lib/template-downloader.d.ts +6 -0
- package/dist/{utils → lib}/template-downloader.js +21 -16
- package/dist/{utils → lib}/transpile-ts.js +5 -5
- package/dist/lib/virtual-routes.test.d.ts +1 -0
- package/dist/virtual-routes/routes/index.jsx +2 -15
- package/dist/virtual-routes/virtual-root.jsx +5 -6
- package/oclif.manifest.json +1 -1
- package/package.json +11 -10
- package/dist/utils/template-downloader.d.ts +0 -11
- /package/dist/{utils/check-lockfile.test.d.ts → commands/hydrogen/init.test.d.ts} +0 -0
- /package/dist/{utils/check-version.test.d.ts → commands/hydrogen/shortcut.test.d.ts} +0 -0
- /package/dist/{utils → lib}/check-lockfile.d.ts +0 -0
- /package/dist/{utils/flags.test.d.ts → lib/check-lockfile.test.d.ts} +0 -0
- /package/dist/{utils → lib}/check-version.d.ts +0 -0
- /package/dist/{utils → lib}/check-version.js +0 -0
- /package/dist/{utils/virtual-routes.test.d.ts → lib/check-version.test.d.ts} +0 -0
- /package/dist/{utils → lib}/config.d.ts +0 -0
- /package/dist/{utils → lib}/flags.test.js +0 -0
- /package/dist/{utils → lib}/log.d.ts +0 -0
- /package/dist/{utils → lib}/log.js +0 -0
- /package/dist/{utils → lib}/mini-oxygen.d.ts +0 -0
- /package/dist/{utils → lib}/missing-routes.d.ts +0 -0
- /package/dist/{utils → lib}/transpile-ts.d.ts +0 -0
- /package/dist/{utils → lib}/virtual-routes.d.ts +0 -0
- /package/dist/{utils → lib}/virtual-routes.js +0 -0
- /package/dist/{utils → lib}/virtual-routes.test.js +0 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { describe, beforeEach, vi, afterEach, expect, it } from 'vitest';
|
|
2
|
+
import { runCreateShortcut } from './shortcut.js';
|
|
3
|
+
import { mockAndCaptureOutput } from '@shopify/cli-kit/node/testing/output';
|
|
4
|
+
import { supportsShell, isWindows, isGitBash } from '../../lib/shell.js';
|
|
5
|
+
import { execSync, exec } from 'child_process';
|
|
6
|
+
|
|
7
|
+
describe("shortcut", () => {
|
|
8
|
+
const outputMock = mockAndCaptureOutput();
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
vi.resetAllMocks();
|
|
11
|
+
vi.mock("child_process");
|
|
12
|
+
vi.mock("../../lib/shell.js", async () => {
|
|
13
|
+
return {
|
|
14
|
+
isWindows: vi.fn(),
|
|
15
|
+
isGitBash: vi.fn(),
|
|
16
|
+
supportsShell: vi.fn(),
|
|
17
|
+
shellWriteFile: () => true,
|
|
18
|
+
shellRunScript: () => true,
|
|
19
|
+
hasAlias: () => false,
|
|
20
|
+
homeFileExists: () => Promise.resolve(true)
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
vi.mocked(supportsShell).mockImplementation(
|
|
24
|
+
(shell) => !isWindows() || shell === "bash"
|
|
25
|
+
);
|
|
26
|
+
});
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
outputMock.clear();
|
|
29
|
+
expect(execSync).toHaveBeenCalledTimes(0);
|
|
30
|
+
expect(exec).toHaveBeenCalledTimes(0);
|
|
31
|
+
});
|
|
32
|
+
it("creates aliases for Unix", async () => {
|
|
33
|
+
vi.mocked(isWindows).mockReturnValue(false);
|
|
34
|
+
await runCreateShortcut();
|
|
35
|
+
expect(outputMock.info()).toMatch(`zsh, bash, fish`);
|
|
36
|
+
expect(outputMock.error()).toBeFalsy();
|
|
37
|
+
});
|
|
38
|
+
it("creates aliases for Windows", async () => {
|
|
39
|
+
vi.mocked(isWindows).mockReturnValue(true);
|
|
40
|
+
await runCreateShortcut();
|
|
41
|
+
expect(outputMock.info()).toMatch(`PowerShell, PowerShell 7+`);
|
|
42
|
+
expect(outputMock.error()).toBeFalsy();
|
|
43
|
+
});
|
|
44
|
+
it("creates aliases for Windows in Git Bash", async () => {
|
|
45
|
+
vi.mocked(isWindows).mockReturnValue(true);
|
|
46
|
+
vi.mocked(isGitBash).mockReturnValueOnce(true);
|
|
47
|
+
await runCreateShortcut();
|
|
48
|
+
expect(outputMock.info()).toMatch("bash");
|
|
49
|
+
expect(outputMock.error()).toBeFalsy();
|
|
50
|
+
});
|
|
51
|
+
it("warns when not finding shells", async () => {
|
|
52
|
+
vi.mocked(isWindows).mockReturnValue(false);
|
|
53
|
+
vi.mocked(supportsShell).mockReturnValue(false);
|
|
54
|
+
await runCreateShortcut();
|
|
55
|
+
expect(outputMock.info()).toBeFalsy();
|
|
56
|
+
expect(outputMock.error()).toBeTruthy();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type LoaderArgs,
|
|
3
|
+
type ErrorBoundaryComponent,
|
|
4
|
+
} from '@shopify/remix-oxygen';
|
|
5
|
+
import {useCatch, useRouteError, isRouteErrorResponse} from '@remix-run/react';
|
|
2
6
|
|
|
3
7
|
export const loader = ({request}: LoaderArgs) => {
|
|
4
8
|
const url = new URL(request.url);
|
|
@@ -14,6 +18,36 @@ export const loader = ({request}: LoaderArgs) => {
|
|
|
14
18
|
});
|
|
15
19
|
};
|
|
16
20
|
|
|
21
|
+
export const ErrorBoundaryV1: ErrorBoundaryComponent = ({error}) => {
|
|
22
|
+
console.error(error);
|
|
23
|
+
|
|
24
|
+
return <div>There was an error.</div>;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export function CatchBoundary() {
|
|
28
|
+
const caught = useCatch();
|
|
29
|
+
console.error(caught);
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<div>
|
|
33
|
+
There was an error. Status: {caught.status}. Message:{' '}
|
|
34
|
+
{caught.data?.message}
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function ErrorBoundary() {
|
|
40
|
+
const error = useRouteError();
|
|
41
|
+
|
|
42
|
+
if (isRouteErrorResponse(error)) {
|
|
43
|
+
console.error(error.status, error.statusText, error.data);
|
|
44
|
+
return <div>Route Error</div>;
|
|
45
|
+
} else {
|
|
46
|
+
console.error((error as Error).message);
|
|
47
|
+
return <div>Thrown Error</div>;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
17
51
|
function robotsTxtData({url}: {url: string}) {
|
|
18
52
|
const sitemapUrl = url ? `${url}/sitemap.xml` : undefined;
|
|
19
53
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {flattenConnection} from '@shopify/hydrogen';
|
|
2
|
-
import type {LoaderArgs} from '@shopify/remix-oxygen';
|
|
2
|
+
import type {LoaderArgs, ErrorBoundaryComponent} from '@shopify/remix-oxygen';
|
|
3
|
+
import {useCatch, useRouteError, isRouteErrorResponse} from '@remix-run/react';
|
|
3
4
|
import {
|
|
4
5
|
CollectionConnection,
|
|
5
6
|
PageConnection,
|
|
@@ -29,12 +30,12 @@ export async function loader({request, context: {storefront}}: LoaderArgs) {
|
|
|
29
30
|
const data = await storefront.query<SitemapQueryData>(SITEMAP_QUERY, {
|
|
30
31
|
variables: {
|
|
31
32
|
urlLimits: MAX_URLS,
|
|
32
|
-
language: storefront.i18n
|
|
33
|
+
language: storefront.i18n.language,
|
|
33
34
|
},
|
|
34
35
|
});
|
|
35
36
|
|
|
36
37
|
if (!data) {
|
|
37
|
-
throw new Response(
|
|
38
|
+
throw new Response('No data found', {status: 404});
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
return new Response(
|
|
@@ -50,6 +51,40 @@ export async function loader({request, context: {storefront}}: LoaderArgs) {
|
|
|
50
51
|
);
|
|
51
52
|
}
|
|
52
53
|
|
|
54
|
+
export const ErrorBoundaryV1: ErrorBoundaryComponent = ({error}) => {
|
|
55
|
+
console.error(error);
|
|
56
|
+
|
|
57
|
+
return <div>There was an error.</div>;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export function CatchBoundary() {
|
|
61
|
+
const caught = useCatch();
|
|
62
|
+
console.error(caught);
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<div>
|
|
66
|
+
There was an error. Status: {caught.status}. Message:{' '}
|
|
67
|
+
{caught.data?.message}
|
|
68
|
+
</div>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function ErrorBoundary() {
|
|
73
|
+
const error = useRouteError();
|
|
74
|
+
|
|
75
|
+
if (isRouteErrorResponse(error)) {
|
|
76
|
+
console.error(error.status, error.statusText, error.data);
|
|
77
|
+
return <div>Route Error</div>;
|
|
78
|
+
} else {
|
|
79
|
+
console.error((error as Error).message);
|
|
80
|
+
return <div>Thrown Error</div>;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function xmlEncode(string: string) {
|
|
85
|
+
return string.replace(/[&<>'"]/g, (char) => `&#${char.charCodeAt(0)};`);
|
|
86
|
+
}
|
|
87
|
+
|
|
53
88
|
function shopSitemap({
|
|
54
89
|
data,
|
|
55
90
|
baseUrl,
|
|
@@ -60,25 +95,25 @@ function shopSitemap({
|
|
|
60
95
|
const productsData = flattenConnection(data.products)
|
|
61
96
|
.filter((product) => product.onlineStoreUrl)
|
|
62
97
|
.map((product) => {
|
|
63
|
-
const url = `${baseUrl}/products/${product.handle}`;
|
|
98
|
+
const url = `${baseUrl}/products/${xmlEncode(product.handle)}`;
|
|
64
99
|
|
|
65
100
|
const finalObject: ProductEntry = {
|
|
66
101
|
url,
|
|
67
|
-
lastMod: product.updatedAt
|
|
102
|
+
lastMod: product.updatedAt,
|
|
68
103
|
changeFreq: 'daily',
|
|
69
104
|
};
|
|
70
105
|
|
|
71
106
|
if (product.featuredImage?.url) {
|
|
72
107
|
finalObject.image = {
|
|
73
|
-
url: product.featuredImage
|
|
108
|
+
url: xmlEncode(product.featuredImage.url),
|
|
74
109
|
};
|
|
75
110
|
|
|
76
111
|
if (product.title) {
|
|
77
|
-
finalObject.image.title = product.title;
|
|
112
|
+
finalObject.image.title = xmlEncode(product.title);
|
|
78
113
|
}
|
|
79
114
|
|
|
80
|
-
if (product.featuredImage
|
|
81
|
-
finalObject.image.caption = product.featuredImage
|
|
115
|
+
if (product.featuredImage.altText) {
|
|
116
|
+
finalObject.image.caption = xmlEncode(product.featuredImage.altText);
|
|
82
117
|
}
|
|
83
118
|
}
|
|
84
119
|
|
|
@@ -116,7 +151,7 @@ function shopSitemap({
|
|
|
116
151
|
xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
|
|
117
152
|
xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"
|
|
118
153
|
>
|
|
119
|
-
${urlsDatas.map((url) => renderUrlTag(url
|
|
154
|
+
${urlsDatas.map((url) => renderUrlTag(url)).join('')}
|
|
120
155
|
</urlset>`;
|
|
121
156
|
}
|
|
122
157
|
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
|
-
type MetaFunction,
|
|
3
2
|
type ActionFunction,
|
|
4
3
|
type LoaderArgs,
|
|
4
|
+
type ErrorBoundaryComponent,
|
|
5
5
|
redirect,
|
|
6
|
-
json,
|
|
7
6
|
} from '@shopify/remix-oxygen';
|
|
8
|
-
import {
|
|
9
|
-
|
|
7
|
+
import {
|
|
8
|
+
Form,
|
|
9
|
+
useCatch,
|
|
10
|
+
useRouteError,
|
|
11
|
+
isRouteErrorResponse,
|
|
12
|
+
} from '@remix-run/react';
|
|
10
13
|
|
|
11
14
|
export async function loader({context, params}: LoaderArgs) {
|
|
12
15
|
const customerAccessToken = await context.session.get('customerAccessToken');
|
|
@@ -14,15 +17,11 @@ export async function loader({context, params}: LoaderArgs) {
|
|
|
14
17
|
if (customerAccessToken) {
|
|
15
18
|
return redirect(params.lang ? `${params.lang}/account` : '/account');
|
|
16
19
|
}
|
|
17
|
-
}
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
const badRequest = (data: ActionData) => json(data, {status: 400});
|
|
21
|
+
return new Response(null);
|
|
22
|
+
}
|
|
24
23
|
|
|
25
|
-
export const action: ActionFunction = async ({request
|
|
24
|
+
export const action: ActionFunction = async ({request}) => {
|
|
26
25
|
const formData = await request.formData();
|
|
27
26
|
|
|
28
27
|
const email = formData.get('email');
|
|
@@ -34,8 +33,8 @@ export const action: ActionFunction = async ({request, context, params}) => {
|
|
|
34
33
|
typeof email !== 'string' ||
|
|
35
34
|
typeof password !== 'string'
|
|
36
35
|
) {
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
throw new Response('Please provide both an email and a password.', {
|
|
37
|
+
status: 400,
|
|
39
38
|
});
|
|
40
39
|
}
|
|
41
40
|
|
|
@@ -72,3 +71,33 @@ export default function Login() {
|
|
|
72
71
|
</Form>
|
|
73
72
|
);
|
|
74
73
|
}
|
|
74
|
+
|
|
75
|
+
export const ErrorBoundaryV1: ErrorBoundaryComponent = ({error}) => {
|
|
76
|
+
console.error(error);
|
|
77
|
+
|
|
78
|
+
return <div>There was an error.</div>;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export function CatchBoundary() {
|
|
82
|
+
const caught = useCatch();
|
|
83
|
+
console.error(caught);
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<div>
|
|
87
|
+
There was an error. Status: {caught.status}. Message:{' '}
|
|
88
|
+
{caught.data?.message}
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function ErrorBoundary() {
|
|
94
|
+
const error = useRouteError();
|
|
95
|
+
|
|
96
|
+
if (isRouteErrorResponse(error)) {
|
|
97
|
+
console.error(error.status, error.statusText, error.data);
|
|
98
|
+
return <div>Route Error</div>;
|
|
99
|
+
} else {
|
|
100
|
+
console.error((error as Error).message);
|
|
101
|
+
return <div>Thrown Error</div>;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
|
-
type MetaFunction,
|
|
3
2
|
type ActionFunction,
|
|
4
3
|
type LoaderArgs,
|
|
4
|
+
type ErrorBoundaryComponent,
|
|
5
5
|
redirect,
|
|
6
|
-
json,
|
|
7
6
|
} from '@shopify/remix-oxygen';
|
|
8
|
-
import {
|
|
9
|
-
|
|
7
|
+
import {
|
|
8
|
+
Form,
|
|
9
|
+
useCatch,
|
|
10
|
+
useRouteError,
|
|
11
|
+
isRouteErrorResponse,
|
|
12
|
+
} from '@remix-run/react';
|
|
10
13
|
|
|
11
14
|
export async function loader({context, params}: LoaderArgs) {
|
|
12
15
|
const customerAccessToken = await context.session.get('customerAccessToken');
|
|
@@ -14,15 +17,11 @@ export async function loader({context, params}: LoaderArgs) {
|
|
|
14
17
|
if (customerAccessToken) {
|
|
15
18
|
return redirect(params.lang ? `${params.lang}/account` : '/account');
|
|
16
19
|
}
|
|
17
|
-
}
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
const badRequest = (data: ActionData) => json(data, {status: 400});
|
|
21
|
+
return new Response(null);
|
|
22
|
+
}
|
|
24
23
|
|
|
25
|
-
export const action: ActionFunction = async ({request
|
|
24
|
+
export const action: ActionFunction = async ({request}) => {
|
|
26
25
|
const formData = await request.formData();
|
|
27
26
|
|
|
28
27
|
const email = formData.get('email');
|
|
@@ -34,8 +33,8 @@ export const action: ActionFunction = async ({request, context, params}) => {
|
|
|
34
33
|
typeof email !== 'string' ||
|
|
35
34
|
typeof password !== 'string'
|
|
36
35
|
) {
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
throw new Response('Please provide both an email and a password.', {
|
|
37
|
+
status: 404,
|
|
39
38
|
});
|
|
40
39
|
}
|
|
41
40
|
|
|
@@ -72,3 +71,33 @@ export default function Register() {
|
|
|
72
71
|
</Form>
|
|
73
72
|
);
|
|
74
73
|
}
|
|
74
|
+
|
|
75
|
+
export const ErrorBoundaryV1: ErrorBoundaryComponent = ({error}) => {
|
|
76
|
+
console.error(error);
|
|
77
|
+
|
|
78
|
+
return <div>There was an error.</div>;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export function CatchBoundary() {
|
|
82
|
+
const caught = useCatch();
|
|
83
|
+
console.error(caught);
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<div>
|
|
87
|
+
There was an error. Status: {caught.status}. Message:{' '}
|
|
88
|
+
{caught.data?.message}
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function ErrorBoundary() {
|
|
94
|
+
const error = useRouteError();
|
|
95
|
+
|
|
96
|
+
if (isRouteErrorResponse(error)) {
|
|
97
|
+
console.error(error.status, error.statusText, error.data);
|
|
98
|
+
return <div>Route Error</div>;
|
|
99
|
+
} else {
|
|
100
|
+
console.error((error as Error).message);
|
|
101
|
+
return <div>Thrown Error</div>;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Await,
|
|
3
|
+
useMatches,
|
|
4
|
+
useCatch,
|
|
5
|
+
useRouteError,
|
|
6
|
+
isRouteErrorResponse,
|
|
7
|
+
} from '@remix-run/react';
|
|
2
8
|
import {Suspense} from 'react';
|
|
3
9
|
import {flattenConnection} from '@shopify/hydrogen';
|
|
4
10
|
import type {Cart as CartType} from '@shopify/hydrogen/storefront-api-types';
|
|
11
|
+
import {type ErrorBoundaryComponent} from '@shopify/remix-oxygen';
|
|
5
12
|
|
|
6
13
|
export async function action() {
|
|
7
14
|
// @TODO implement cart action
|
|
@@ -11,7 +18,10 @@ export default function CartRoute() {
|
|
|
11
18
|
const [root] = useMatches();
|
|
12
19
|
return (
|
|
13
20
|
<Suspense fallback="loading">
|
|
14
|
-
<Await
|
|
21
|
+
<Await
|
|
22
|
+
resolve={root.data?.cart as CartType}
|
|
23
|
+
errorElement={<div>An error occurred</div>}
|
|
24
|
+
>
|
|
15
25
|
{(cart) => {
|
|
16
26
|
const linesCount = Boolean(cart?.lines?.edges?.length || 0);
|
|
17
27
|
if (!linesCount) {
|
|
@@ -39,3 +49,33 @@ export default function CartRoute() {
|
|
|
39
49
|
</Suspense>
|
|
40
50
|
);
|
|
41
51
|
}
|
|
52
|
+
|
|
53
|
+
export const ErrorBoundaryV1: ErrorBoundaryComponent = ({error}) => {
|
|
54
|
+
console.error(error);
|
|
55
|
+
|
|
56
|
+
return <div>There was an error.</div>;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export function CatchBoundary() {
|
|
60
|
+
const caught = useCatch();
|
|
61
|
+
console.error(caught);
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<div>
|
|
65
|
+
There was an error. Status: {caught.status}. Message:{' '}
|
|
66
|
+
{caught.data?.message}
|
|
67
|
+
</div>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function ErrorBoundary() {
|
|
72
|
+
const error = useRouteError();
|
|
73
|
+
|
|
74
|
+
if (isRouteErrorResponse(error)) {
|
|
75
|
+
console.error(error.status, error.statusText, error.data);
|
|
76
|
+
return <div>Route Error</div>;
|
|
77
|
+
} else {
|
|
78
|
+
console.error((error as Error).message);
|
|
79
|
+
return <div>Thrown Error</div>;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -1,9 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
json,
|
|
3
|
+
type LoaderArgs,
|
|
4
|
+
type ErrorBoundaryComponent,
|
|
5
|
+
} from '@shopify/remix-oxygen';
|
|
6
|
+
import {
|
|
7
|
+
useLoaderData,
|
|
8
|
+
Link,
|
|
9
|
+
useCatch,
|
|
10
|
+
useRouteError,
|
|
11
|
+
isRouteErrorResponse,
|
|
12
|
+
} from '@remix-run/react';
|
|
3
13
|
import type {Collection as CollectionType} from '@shopify/hydrogen/storefront-api-types';
|
|
4
|
-
import {Link} from '@remix-run/react';
|
|
5
14
|
|
|
6
|
-
export async function loader({params,
|
|
15
|
+
export async function loader({params, context}: LoaderArgs) {
|
|
7
16
|
const {collectionHandle} = params;
|
|
8
17
|
|
|
9
18
|
const {collection} = await context.storefront.query<{
|
|
@@ -38,8 +47,38 @@ export default function Collection() {
|
|
|
38
47
|
);
|
|
39
48
|
}
|
|
40
49
|
|
|
50
|
+
export const ErrorBoundaryV1: ErrorBoundaryComponent = ({error}) => {
|
|
51
|
+
console.error(error);
|
|
52
|
+
|
|
53
|
+
return <div>There was an error.</div>;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export function CatchBoundary() {
|
|
57
|
+
const caught = useCatch();
|
|
58
|
+
console.error(caught);
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<div>
|
|
62
|
+
There was an error. Status: {caught.status}. Message:{' '}
|
|
63
|
+
{caught.data?.message}
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function ErrorBoundary() {
|
|
69
|
+
const error = useRouteError();
|
|
70
|
+
|
|
71
|
+
if (isRouteErrorResponse(error)) {
|
|
72
|
+
console.error(error.status, error.statusText, error.data);
|
|
73
|
+
return <div>Route Error</div>;
|
|
74
|
+
} else {
|
|
75
|
+
console.error((error as Error).message);
|
|
76
|
+
return <div>Thrown Error</div>;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
41
80
|
const COLLECTION_QUERY = `#graphql
|
|
42
|
-
query
|
|
81
|
+
query collection_details(
|
|
43
82
|
$handle: String!
|
|
44
83
|
$country: CountryCode
|
|
45
84
|
$language: LanguageCode
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {type ErrorBoundaryComponent} from '@shopify/remix-oxygen';
|
|
2
|
+
import {useCatch, useRouteError, isRouteErrorResponse} from '@remix-run/react';
|
|
3
|
+
|
|
1
4
|
export default function Index() {
|
|
2
5
|
return (
|
|
3
6
|
<p>
|
|
@@ -5,3 +8,33 @@ export default function Index() {
|
|
|
5
8
|
</p>
|
|
6
9
|
);
|
|
7
10
|
}
|
|
11
|
+
|
|
12
|
+
export const ErrorBoundaryV1: ErrorBoundaryComponent = ({error}) => {
|
|
13
|
+
console.error(error);
|
|
14
|
+
|
|
15
|
+
return <div>There was an error.</div>;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function CatchBoundary() {
|
|
19
|
+
const caught = useCatch();
|
|
20
|
+
console.error(caught);
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div>
|
|
24
|
+
There was an error. Status: {caught.status}. Message:{' '}
|
|
25
|
+
{caught.data?.message}
|
|
26
|
+
</div>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function ErrorBoundary() {
|
|
31
|
+
const error = useRouteError();
|
|
32
|
+
|
|
33
|
+
if (isRouteErrorResponse(error)) {
|
|
34
|
+
console.error(error.status, error.statusText, error.data);
|
|
35
|
+
return <div>Route Error</div>;
|
|
36
|
+
} else {
|
|
37
|
+
console.error((error as Error).message);
|
|
38
|
+
return <div>Thrown Error</div>;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -2,15 +2,22 @@ import {
|
|
|
2
2
|
json,
|
|
3
3
|
type MetaFunction,
|
|
4
4
|
type LoaderArgs,
|
|
5
|
-
|
|
5
|
+
type ErrorBoundaryComponent,
|
|
6
6
|
} from '@shopify/remix-oxygen';
|
|
7
|
-
import {
|
|
8
|
-
|
|
7
|
+
import {
|
|
8
|
+
useLoaderData,
|
|
9
|
+
type V2_MetaFunction,
|
|
10
|
+
useCatch,
|
|
11
|
+
useRouteError,
|
|
12
|
+
isRouteErrorResponse,
|
|
13
|
+
} from '@remix-run/react';
|
|
9
14
|
import type {Page as PageType} from '@shopify/hydrogen/storefront-api-types';
|
|
10
15
|
import type {SeoHandleFunction} from '@shopify/hydrogen';
|
|
11
16
|
|
|
12
17
|
export async function loader({params, context}: LoaderArgs) {
|
|
13
|
-
|
|
18
|
+
if (!params.pageHandle) {
|
|
19
|
+
throw new Error('Missing page handle');
|
|
20
|
+
}
|
|
14
21
|
|
|
15
22
|
const {page} = await context.storefront.query<{page: PageType}>(PAGE_QUERY, {
|
|
16
23
|
variables: {
|
|
@@ -36,13 +43,14 @@ export const handle = {
|
|
|
36
43
|
seo,
|
|
37
44
|
};
|
|
38
45
|
|
|
39
|
-
export const
|
|
46
|
+
export const metaV1: MetaFunction = ({data}) => {
|
|
40
47
|
const {title, description} = data?.page.seo ?? {};
|
|
48
|
+
return {title, description};
|
|
49
|
+
};
|
|
41
50
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
};
|
|
51
|
+
export const meta: V2_MetaFunction = ({data}) => {
|
|
52
|
+
const {title, description} = data?.page.seo ?? {};
|
|
53
|
+
return [{title}, {name: 'description', content: description}];
|
|
46
54
|
};
|
|
47
55
|
|
|
48
56
|
export default function Page() {
|
|
@@ -58,8 +66,38 @@ export default function Page() {
|
|
|
58
66
|
);
|
|
59
67
|
}
|
|
60
68
|
|
|
69
|
+
export const ErrorBoundaryV1: ErrorBoundaryComponent = ({error}) => {
|
|
70
|
+
console.error(error);
|
|
71
|
+
|
|
72
|
+
return <div>There was an error.</div>;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export function CatchBoundary() {
|
|
76
|
+
const caught = useCatch();
|
|
77
|
+
console.error(caught);
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<div>
|
|
81
|
+
There was an error. Status: {caught.status}. Message:{' '}
|
|
82
|
+
{caught.data?.message}
|
|
83
|
+
</div>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function ErrorBoundary() {
|
|
88
|
+
const error = useRouteError();
|
|
89
|
+
|
|
90
|
+
if (isRouteErrorResponse(error)) {
|
|
91
|
+
console.error(error.status, error.statusText, error.data);
|
|
92
|
+
return <div>Route Error</div>;
|
|
93
|
+
} else {
|
|
94
|
+
console.error((error as Error).message);
|
|
95
|
+
return <div>Thrown Error</div>;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
61
99
|
const PAGE_QUERY = `#graphql
|
|
62
|
-
query
|
|
100
|
+
query page_details($language: LanguageCode, $handle: String!)
|
|
63
101
|
@inContext(language: $language) {
|
|
64
102
|
page(handle: $handle) {
|
|
65
103
|
id
|