@shopify/cli-hydrogen 6.0.2 → 7.0.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/dist/commands/hydrogen/build.js +40 -78
- package/dist/commands/hydrogen/codegen.js +8 -3
- package/dist/commands/hydrogen/deploy.js +173 -37
- package/dist/commands/hydrogen/deploy.test.js +192 -20
- package/dist/commands/hydrogen/dev.js +56 -31
- package/dist/commands/hydrogen/init.js +1 -1
- package/dist/commands/hydrogen/init.test.js +155 -53
- package/dist/commands/hydrogen/link.js +5 -21
- package/dist/commands/hydrogen/link.test.js +10 -10
- package/dist/commands/hydrogen/preview.js +22 -11
- package/dist/commands/hydrogen/setup.js +0 -4
- package/dist/commands/hydrogen/setup.test.js +0 -1
- package/dist/commands/hydrogen/shortcut.js +1 -0
- package/dist/commands/hydrogen/upgrade.js +720 -0
- package/dist/commands/hydrogen/upgrade.test.js +786 -0
- package/dist/generator-templates/starter/.graphqlrc.yml +12 -1
- package/dist/generator-templates/starter/CHANGELOG.md +126 -0
- package/dist/generator-templates/starter/README.md +23 -0
- package/dist/generator-templates/starter/app/components/Cart.tsx +1 -1
- package/dist/generator-templates/starter/app/components/Footer.tsx +3 -1
- package/dist/generator-templates/starter/app/components/Header.tsx +5 -1
- package/dist/generator-templates/starter/app/components/Layout.tsx +14 -11
- package/dist/generator-templates/starter/app/components/Search.tsx +1 -1
- package/dist/generator-templates/starter/app/graphql/customer-account/CustomerAddressMutations.ts +61 -0
- package/dist/generator-templates/starter/app/graphql/customer-account/CustomerDetailsQuery.ts +39 -0
- package/dist/generator-templates/starter/app/graphql/customer-account/CustomerOrderQuery.ts +87 -0
- package/dist/generator-templates/starter/app/graphql/customer-account/CustomerOrdersQuery.ts +58 -0
- package/dist/generator-templates/starter/app/graphql/customer-account/CustomerUpdateMutation.ts +24 -0
- package/dist/generator-templates/starter/app/lib/fragments.ts +102 -0
- package/dist/generator-templates/starter/app/lib/session.ts +67 -0
- package/dist/generator-templates/starter/app/root.tsx +11 -45
- package/dist/generator-templates/starter/app/routes/[robots.txt].tsx +0 -27
- package/dist/generator-templates/starter/app/routes/account.$.tsx +8 -4
- package/dist/generator-templates/starter/app/routes/account._index.tsx +5 -0
- package/dist/generator-templates/starter/app/routes/account.addresses.tsx +215 -206
- package/dist/generator-templates/starter/app/routes/account.orders.$id.tsx +56 -163
- package/dist/generator-templates/starter/app/routes/account.orders._index.tsx +32 -109
- package/dist/generator-templates/starter/app/routes/account.profile.tsx +40 -180
- package/dist/generator-templates/starter/app/routes/account.tsx +20 -135
- package/dist/generator-templates/starter/app/routes/account_.authorize.tsx +5 -0
- package/dist/generator-templates/starter/app/routes/account_.login.tsx +3 -140
- package/dist/generator-templates/starter/app/routes/account_.logout.tsx +5 -24
- package/dist/generator-templates/starter/app/routes/cart.tsx +7 -5
- package/dist/generator-templates/starter/app/routes/collections.$handle.tsx +1 -1
- package/dist/generator-templates/starter/app/routes/products.$handle.tsx +2 -2
- package/dist/generator-templates/starter/app/routes/search.tsx +1 -1
- package/dist/generator-templates/starter/customer-accountapi.generated.d.ts +506 -0
- package/dist/generator-templates/starter/package.json +11 -10
- package/dist/generator-templates/starter/remix.config.js +4 -0
- package/dist/generator-templates/starter/remix.env.d.ts +6 -11
- package/dist/generator-templates/starter/server.ts +24 -167
- package/dist/generator-templates/starter/storefrontapi.generated.d.ts +104 -881
- package/dist/hooks/init.js +4 -4
- package/dist/lib/auth.js +5 -10
- package/dist/lib/build.js +6 -1
- package/dist/lib/bundle/analyzer.js +36 -26
- package/dist/lib/check-lockfile.js +1 -0
- package/dist/lib/codegen.js +59 -18
- package/dist/lib/defer.js +12 -0
- package/dist/lib/file.js +52 -3
- package/dist/lib/flags.js +27 -9
- package/dist/lib/get-oxygen-deployment-data.test.js +4 -2
- package/dist/lib/graphql/admin/client.test.js +2 -2
- package/dist/lib/graphql/admin/get-oxygen-data.js +1 -0
- package/dist/lib/log.js +32 -14
- package/dist/lib/mini-oxygen/assets.js +118 -0
- package/dist/lib/mini-oxygen/common.js +2 -1
- package/dist/lib/mini-oxygen/index.js +7 -5
- package/dist/lib/mini-oxygen/mini-oxygen.test.js +214 -0
- package/dist/lib/mini-oxygen/node.js +19 -5
- package/dist/lib/mini-oxygen/workerd-inspector-logs.js +227 -0
- package/dist/lib/mini-oxygen/workerd-inspector-proxy.js +200 -0
- package/dist/lib/mini-oxygen/workerd-inspector.js +62 -235
- package/dist/lib/mini-oxygen/workerd.js +74 -50
- package/dist/lib/missing-routes.js +6 -3
- package/dist/lib/onboarding/common.js +40 -9
- package/dist/lib/onboarding/local.js +19 -11
- package/dist/lib/onboarding/remote.js +48 -28
- package/dist/lib/render-errors.js +2 -0
- package/dist/lib/request-events.js +65 -31
- package/dist/lib/setups/css/assets.js +1 -46
- package/dist/lib/setups/css/css-modules.js +3 -2
- package/dist/lib/setups/css/postcss.js +4 -2
- package/dist/lib/setups/css/tailwind.js +4 -2
- package/dist/lib/setups/css/vanilla-extract.js +3 -2
- package/dist/lib/setups/i18n/replacers.test.js +56 -38
- package/dist/lib/shell.js +1 -1
- package/dist/lib/template-diff.js +89 -0
- package/dist/lib/template-downloader.js +3 -2
- package/dist/lib/transpile/project.js +1 -1
- package/dist/virtual-routes/assets/debug-network.css +592 -0
- package/dist/virtual-routes/assets/favicon-dark.svg +20 -0
- package/dist/virtual-routes/components/FlameChartWrapper.jsx +8 -10
- package/dist/virtual-routes/components/IconClose.jsx +38 -0
- package/dist/virtual-routes/components/IconDiscard.jsx +44 -0
- package/dist/virtual-routes/components/RequestDetails.jsx +179 -0
- package/dist/virtual-routes/components/RequestTable.jsx +92 -0
- package/dist/virtual-routes/components/RequestWaterfall.jsx +151 -0
- package/dist/virtual-routes/lib/useDebugNetworkServer.jsx +176 -0
- package/dist/virtual-routes/routes/subrequest-profiler.jsx +243 -0
- package/oclif.manifest.json +134 -59
- package/package.json +18 -26
- package/dist/generator-templates/starter/app/routes/account_.activate.$id.$activationToken.tsx +0 -161
- package/dist/generator-templates/starter/app/routes/account_.recover.tsx +0 -129
- package/dist/generator-templates/starter/app/routes/account_.register.tsx +0 -207
- package/dist/generator-templates/starter/app/routes/account_.reset.$id.$resetToken.tsx +0 -136
- package/dist/virtual-routes/routes/debug-network.jsx +0 -289
- /package/dist/generator-templates/starter/app/{utils.ts → lib/variants.ts} +0 -0
package/package.json
CHANGED
|
@@ -4,18 +4,19 @@
|
|
|
4
4
|
"access": "public",
|
|
5
5
|
"@shopify:registry": "https://registry.npmjs.org"
|
|
6
6
|
},
|
|
7
|
-
"version": "
|
|
7
|
+
"version": "7.0.0",
|
|
8
8
|
"license": "MIT",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"scripts": {
|
|
11
11
|
"build": "tsup && node scripts/build-check.mjs",
|
|
12
|
-
"dev": "tsup --watch",
|
|
12
|
+
"dev": "tsup --watch ./src",
|
|
13
13
|
"typecheck": "tsc --noEmit",
|
|
14
14
|
"generate:manifest": "node scripts/generate-manifest.mjs",
|
|
15
15
|
"test": "cross-env SHOPIFY_UNIT_TEST=1 vitest run --test-timeout=20000",
|
|
16
16
|
"test:watch": "cross-env SHOPIFY_UNIT_TEST=1 vitest --test-timeout=20000"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
|
+
"@remix-run/dev": "^2.5.1",
|
|
19
20
|
"@types/diff": "^5.0.2",
|
|
20
21
|
"@types/fs-extra": "^11.0.1",
|
|
21
22
|
"@types/gunzip-maybe": "^1.4.0",
|
|
@@ -23,33 +24,36 @@
|
|
|
23
24
|
"@types/recursive-readdir": "^2.2.1",
|
|
24
25
|
"@types/stack-trace": "^0.0.30",
|
|
25
26
|
"@types/tar-fs": "^2.0.1",
|
|
27
|
+
"@vitest/coverage-v8": "^1.0.4",
|
|
26
28
|
"devtools-protocol": "^0.0.1177611",
|
|
27
|
-
"get-port": "^7.0.0",
|
|
28
|
-
"@vitest/coverage-v8": "^0.33.0",
|
|
29
29
|
"fast-glob": "^3.2.12",
|
|
30
|
-
"flame-chart-js": "2.3.
|
|
31
|
-
"
|
|
32
|
-
"
|
|
30
|
+
"flame-chart-js": "2.3.2",
|
|
31
|
+
"get-port": "^7.0.0",
|
|
32
|
+
"type-fest": "^4.5.0",
|
|
33
|
+
"vitest": "^1.0.4"
|
|
33
34
|
},
|
|
34
35
|
"dependencies": {
|
|
35
36
|
"@ast-grep/napi": "0.11.0",
|
|
36
37
|
"@graphql-codegen/cli": "5.0.0",
|
|
37
38
|
"@oclif/core": "2.11.7",
|
|
38
|
-
"@shopify/cli-kit": "3.
|
|
39
|
-
"@shopify/hydrogen-codegen": "^0.
|
|
40
|
-
"@shopify/mini-oxygen": "^2.2.
|
|
41
|
-
"@shopify/oxygen-cli": "^
|
|
39
|
+
"@shopify/cli-kit": "3.52.0",
|
|
40
|
+
"@shopify/hydrogen-codegen": "^0.2.0",
|
|
41
|
+
"@shopify/mini-oxygen": "^2.2.5",
|
|
42
|
+
"@shopify/oxygen-cli": "^4.0.0",
|
|
42
43
|
"ansi-escapes": "^6.2.0",
|
|
44
|
+
"cli-truncate": "^4.0.0",
|
|
43
45
|
"diff": "^5.1.0",
|
|
44
46
|
"fs-extra": "^11.1.0",
|
|
45
47
|
"get-port": "^7.0.0",
|
|
48
|
+
"graphql-config": "5.0.3",
|
|
46
49
|
"gunzip-maybe": "^1.4.2",
|
|
47
|
-
"miniflare": "3.
|
|
50
|
+
"miniflare": "3.20231218.2",
|
|
48
51
|
"prettier": "^2.8.4",
|
|
49
52
|
"semver": "^7.5.3",
|
|
50
53
|
"source-map": "^0.7.4",
|
|
51
54
|
"stack-trace": "^1.0.0-pre2",
|
|
52
55
|
"tar-fs": "^2.1.1",
|
|
56
|
+
"tempy": "^3.0.0",
|
|
53
57
|
"ts-morph": "20.0.0",
|
|
54
58
|
"use-resize-observer": "^9.1.0",
|
|
55
59
|
"ws": "^8.13.0"
|
|
@@ -58,23 +62,11 @@
|
|
|
58
62
|
"@parcel/watcher": "^2.3.0"
|
|
59
63
|
},
|
|
60
64
|
"peerDependencies": {
|
|
61
|
-
"@remix-run/dev": "^2.1.0"
|
|
62
|
-
"@remix-run/react": "^2.1.0",
|
|
63
|
-
"@shopify/hydrogen": "2023.10.2",
|
|
64
|
-
"@shopify/remix-oxygen": "^2.0.0"
|
|
65
|
+
"@remix-run/dev": "^2.1.0"
|
|
65
66
|
},
|
|
66
67
|
"peerDependenciesMeta": {
|
|
67
68
|
"@remix-run/dev": {
|
|
68
69
|
"optional": true
|
|
69
|
-
},
|
|
70
|
-
"@remix-run/react": {
|
|
71
|
-
"optional": true
|
|
72
|
-
},
|
|
73
|
-
"@shopify/hydrogen-react": {
|
|
74
|
-
"optional": true
|
|
75
|
-
},
|
|
76
|
-
"@shopify/remix-oxygen": {
|
|
77
|
-
"optional": true
|
|
78
70
|
}
|
|
79
71
|
},
|
|
80
72
|
"bin": "dist/create-app.js",
|
|
@@ -87,7 +79,7 @@
|
|
|
87
79
|
"oclif.manifest.json"
|
|
88
80
|
],
|
|
89
81
|
"engines": {
|
|
90
|
-
"node": ">=
|
|
82
|
+
"node": ">=18.0.0"
|
|
91
83
|
},
|
|
92
84
|
"oclif": {
|
|
93
85
|
"commands": "dist/commands",
|
package/dist/generator-templates/starter/app/routes/account_.activate.$id.$activationToken.tsx
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
json,
|
|
3
|
-
redirect,
|
|
4
|
-
type ActionFunctionArgs,
|
|
5
|
-
type LoaderFunctionArgs,
|
|
6
|
-
} from '@shopify/remix-oxygen';
|
|
7
|
-
import {Form, useActionData, type MetaFunction} from '@remix-run/react';
|
|
8
|
-
|
|
9
|
-
type ActionResponse = {
|
|
10
|
-
error: string | null;
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export const meta: MetaFunction = () => {
|
|
14
|
-
return [{title: 'Activate Account'}];
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export async function loader({context}: LoaderFunctionArgs) {
|
|
18
|
-
if (await context.session.get('customerAccessToken')) {
|
|
19
|
-
return redirect('/account');
|
|
20
|
-
}
|
|
21
|
-
return json({});
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export async function action({request, context, params}: ActionFunctionArgs) {
|
|
25
|
-
const {session, storefront} = context;
|
|
26
|
-
const {id, activationToken} = params;
|
|
27
|
-
|
|
28
|
-
if (request.method !== 'POST') {
|
|
29
|
-
return json({error: 'Method not allowed'}, {status: 405});
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
try {
|
|
33
|
-
if (!id || !activationToken) {
|
|
34
|
-
throw new Error('Missing token. The link you followed might be wrong.');
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const form = await request.formData();
|
|
38
|
-
const password = form.has('password') ? String(form.get('password')) : null;
|
|
39
|
-
const passwordConfirm = form.has('passwordConfirm')
|
|
40
|
-
? String(form.get('passwordConfirm'))
|
|
41
|
-
: null;
|
|
42
|
-
|
|
43
|
-
const validPasswords =
|
|
44
|
-
password && passwordConfirm && password === passwordConfirm;
|
|
45
|
-
|
|
46
|
-
if (!validPasswords) {
|
|
47
|
-
throw new Error('Passwords do not match');
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const {customerActivate} = await storefront.mutate(
|
|
51
|
-
CUSTOMER_ACTIVATE_MUTATION,
|
|
52
|
-
{
|
|
53
|
-
variables: {
|
|
54
|
-
id: `gid://shopify/Customer/${id}`,
|
|
55
|
-
input: {
|
|
56
|
-
password,
|
|
57
|
-
activationToken,
|
|
58
|
-
},
|
|
59
|
-
},
|
|
60
|
-
},
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
if (customerActivate?.customerUserErrors?.length) {
|
|
64
|
-
throw new Error(customerActivate.customerUserErrors[0].message);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const {customerAccessToken} = customerActivate ?? {};
|
|
68
|
-
if (!customerAccessToken) {
|
|
69
|
-
throw new Error('Could not activate account.');
|
|
70
|
-
}
|
|
71
|
-
session.set('customerAccessToken', customerAccessToken);
|
|
72
|
-
|
|
73
|
-
return redirect('/account', {
|
|
74
|
-
headers: {
|
|
75
|
-
'Set-Cookie': await session.commit(),
|
|
76
|
-
},
|
|
77
|
-
});
|
|
78
|
-
} catch (error: unknown) {
|
|
79
|
-
if (error instanceof Error) {
|
|
80
|
-
return json({error: error.message}, {status: 400});
|
|
81
|
-
}
|
|
82
|
-
return json({error}, {status: 400});
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export default function Activate() {
|
|
87
|
-
const action = useActionData<ActionResponse>();
|
|
88
|
-
const error = action?.error ?? null;
|
|
89
|
-
|
|
90
|
-
return (
|
|
91
|
-
<div className="account-activate">
|
|
92
|
-
<h1>Activate Account.</h1>
|
|
93
|
-
<p>Create your password to activate your account.</p>
|
|
94
|
-
<Form method="POST">
|
|
95
|
-
<fieldset>
|
|
96
|
-
<label htmlFor="password">Password</label>
|
|
97
|
-
<input
|
|
98
|
-
id="password"
|
|
99
|
-
name="password"
|
|
100
|
-
type="password"
|
|
101
|
-
autoComplete="current-password"
|
|
102
|
-
placeholder="Password"
|
|
103
|
-
aria-label="Password"
|
|
104
|
-
minLength={8}
|
|
105
|
-
required
|
|
106
|
-
// eslint-disable-next-line jsx-a11y/no-autofocus
|
|
107
|
-
autoFocus
|
|
108
|
-
/>
|
|
109
|
-
<label htmlFor="passwordConfirm">Re-enter password</label>
|
|
110
|
-
<input
|
|
111
|
-
id="passwordConfirm"
|
|
112
|
-
name="passwordConfirm"
|
|
113
|
-
type="password"
|
|
114
|
-
autoComplete="current-password"
|
|
115
|
-
placeholder="Re-enter password"
|
|
116
|
-
aria-label="Re-enter password"
|
|
117
|
-
minLength={8}
|
|
118
|
-
required
|
|
119
|
-
/>
|
|
120
|
-
</fieldset>
|
|
121
|
-
{error ? (
|
|
122
|
-
<p>
|
|
123
|
-
<mark>
|
|
124
|
-
<small>{error}</small>
|
|
125
|
-
</mark>
|
|
126
|
-
</p>
|
|
127
|
-
) : (
|
|
128
|
-
<br />
|
|
129
|
-
)}
|
|
130
|
-
<button
|
|
131
|
-
className="bg-primary text-contrast rounded py-2 px-4 focus:shadow-outline block w-full"
|
|
132
|
-
type="submit"
|
|
133
|
-
>
|
|
134
|
-
Save
|
|
135
|
-
</button>
|
|
136
|
-
</Form>
|
|
137
|
-
</div>
|
|
138
|
-
);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// NOTE: https://shopify.dev/docs/api/storefront/latest/mutations/customeractivate
|
|
142
|
-
const CUSTOMER_ACTIVATE_MUTATION = `#graphql
|
|
143
|
-
mutation customerActivate(
|
|
144
|
-
$id: ID!,
|
|
145
|
-
$input: CustomerActivateInput!,
|
|
146
|
-
$country: CountryCode,
|
|
147
|
-
$language: LanguageCode
|
|
148
|
-
) @inContext(country: $country, language: $language) {
|
|
149
|
-
customerActivate(id: $id, input: $input) {
|
|
150
|
-
customerAccessToken {
|
|
151
|
-
accessToken
|
|
152
|
-
expiresAt
|
|
153
|
-
}
|
|
154
|
-
customerUserErrors {
|
|
155
|
-
code
|
|
156
|
-
field
|
|
157
|
-
message
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
` as const;
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
json,
|
|
3
|
-
redirect,
|
|
4
|
-
type LoaderFunctionArgs,
|
|
5
|
-
type ActionFunctionArgs,
|
|
6
|
-
} from '@shopify/remix-oxygen';
|
|
7
|
-
import {Form, Link, useActionData} from '@remix-run/react';
|
|
8
|
-
|
|
9
|
-
type ActionResponse = {
|
|
10
|
-
error?: string;
|
|
11
|
-
resetRequested?: boolean;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export async function loader({context}: LoaderFunctionArgs) {
|
|
15
|
-
const customerAccessToken = await context.session.get('customerAccessToken');
|
|
16
|
-
if (customerAccessToken) {
|
|
17
|
-
return redirect('/account');
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return json({});
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export async function action({request, context}: ActionFunctionArgs) {
|
|
24
|
-
const {storefront} = context;
|
|
25
|
-
const form = await request.formData();
|
|
26
|
-
const email = form.has('email') ? String(form.get('email')) : null;
|
|
27
|
-
|
|
28
|
-
if (request.method !== 'POST') {
|
|
29
|
-
return json({error: 'Method not allowed'}, {status: 405});
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
try {
|
|
33
|
-
if (!email) {
|
|
34
|
-
throw new Error('Please provide an email.');
|
|
35
|
-
}
|
|
36
|
-
await storefront.mutate(CUSTOMER_RECOVER_MUTATION, {
|
|
37
|
-
variables: {email},
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
return json({resetRequested: true});
|
|
41
|
-
} catch (error: unknown) {
|
|
42
|
-
const resetRequested = false;
|
|
43
|
-
if (error instanceof Error) {
|
|
44
|
-
return json({error: error.message, resetRequested}, {status: 400});
|
|
45
|
-
}
|
|
46
|
-
return json({error, resetRequested}, {status: 400});
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export default function Recover() {
|
|
51
|
-
const action = useActionData<ActionResponse>();
|
|
52
|
-
|
|
53
|
-
return (
|
|
54
|
-
<div className="account-recover">
|
|
55
|
-
<div>
|
|
56
|
-
{action?.resetRequested ? (
|
|
57
|
-
<>
|
|
58
|
-
<h1>Request Sent.</h1>
|
|
59
|
-
<p>
|
|
60
|
-
If that email address is in our system, you will receive an email
|
|
61
|
-
with instructions about how to reset your password in a few
|
|
62
|
-
minutes.
|
|
63
|
-
</p>
|
|
64
|
-
<br />
|
|
65
|
-
<Link to="/account/login">Return to Login</Link>
|
|
66
|
-
</>
|
|
67
|
-
) : (
|
|
68
|
-
<>
|
|
69
|
-
<h1>Forgot Password.</h1>
|
|
70
|
-
<p>
|
|
71
|
-
Enter the email address associated with your account to receive a
|
|
72
|
-
link to reset your password.
|
|
73
|
-
</p>
|
|
74
|
-
<br />
|
|
75
|
-
<Form method="POST">
|
|
76
|
-
<fieldset>
|
|
77
|
-
<label htmlFor="email">Email</label>
|
|
78
|
-
<input
|
|
79
|
-
aria-label="Email address"
|
|
80
|
-
autoComplete="email"
|
|
81
|
-
// eslint-disable-next-line jsx-a11y/no-autofocus
|
|
82
|
-
autoFocus
|
|
83
|
-
id="email"
|
|
84
|
-
name="email"
|
|
85
|
-
placeholder="Email address"
|
|
86
|
-
required
|
|
87
|
-
type="email"
|
|
88
|
-
/>
|
|
89
|
-
</fieldset>
|
|
90
|
-
{action?.error ? (
|
|
91
|
-
<p>
|
|
92
|
-
<mark>
|
|
93
|
-
<small>{action.error}</small>
|
|
94
|
-
</mark>
|
|
95
|
-
</p>
|
|
96
|
-
) : (
|
|
97
|
-
<br />
|
|
98
|
-
)}
|
|
99
|
-
<button type="submit">Request Reset Link</button>
|
|
100
|
-
</Form>
|
|
101
|
-
<div>
|
|
102
|
-
<br />
|
|
103
|
-
<p>
|
|
104
|
-
<Link to="/account/login">Login →</Link>
|
|
105
|
-
</p>
|
|
106
|
-
</div>
|
|
107
|
-
</>
|
|
108
|
-
)}
|
|
109
|
-
</div>
|
|
110
|
-
</div>
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// NOTE: https://shopify.dev/docs/api/storefront/latest/mutations/customerrecover
|
|
115
|
-
const CUSTOMER_RECOVER_MUTATION = `#graphql
|
|
116
|
-
mutation customerRecover(
|
|
117
|
-
$email: String!,
|
|
118
|
-
$country: CountryCode,
|
|
119
|
-
$language: LanguageCode
|
|
120
|
-
) @inContext(country: $country, language: $language) {
|
|
121
|
-
customerRecover(email: $email) {
|
|
122
|
-
customerUserErrors {
|
|
123
|
-
code
|
|
124
|
-
field
|
|
125
|
-
message
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
` as const;
|
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
json,
|
|
3
|
-
redirect,
|
|
4
|
-
type ActionFunctionArgs,
|
|
5
|
-
type LoaderFunctionArgs,
|
|
6
|
-
} from '@shopify/remix-oxygen';
|
|
7
|
-
import {Form, Link, useActionData} from '@remix-run/react';
|
|
8
|
-
import type {CustomerCreateMutation} from 'storefrontapi.generated';
|
|
9
|
-
|
|
10
|
-
type ActionResponse = {
|
|
11
|
-
error: string | null;
|
|
12
|
-
newCustomer:
|
|
13
|
-
| NonNullable<CustomerCreateMutation['customerCreate']>['customer']
|
|
14
|
-
| null;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export async function loader({context}: LoaderFunctionArgs) {
|
|
18
|
-
const customerAccessToken = await context.session.get('customerAccessToken');
|
|
19
|
-
if (customerAccessToken) {
|
|
20
|
-
return redirect('/account');
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
return json({});
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export async function action({request, context}: ActionFunctionArgs) {
|
|
27
|
-
if (request.method !== 'POST') {
|
|
28
|
-
return json({error: 'Method not allowed'}, {status: 405});
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const {storefront, session} = context;
|
|
32
|
-
const form = await request.formData();
|
|
33
|
-
const email = String(form.has('email') ? form.get('email') : '');
|
|
34
|
-
const password = form.has('password') ? String(form.get('password')) : null;
|
|
35
|
-
const passwordConfirm = form.has('passwordConfirm')
|
|
36
|
-
? String(form.get('passwordConfirm'))
|
|
37
|
-
: null;
|
|
38
|
-
|
|
39
|
-
const validPasswords =
|
|
40
|
-
password && passwordConfirm && password === passwordConfirm;
|
|
41
|
-
|
|
42
|
-
const validInputs = Boolean(email && password);
|
|
43
|
-
try {
|
|
44
|
-
if (!validPasswords) {
|
|
45
|
-
throw new Error('Passwords do not match');
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (!validInputs) {
|
|
49
|
-
throw new Error('Please provide both an email and a password.');
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const {customerCreate} = await storefront.mutate(CUSTOMER_CREATE_MUTATION, {
|
|
53
|
-
variables: {
|
|
54
|
-
input: {email, password},
|
|
55
|
-
},
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
if (customerCreate?.customerUserErrors?.length) {
|
|
59
|
-
throw new Error(customerCreate?.customerUserErrors[0].message);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const newCustomer = customerCreate?.customer;
|
|
63
|
-
if (!newCustomer?.id) {
|
|
64
|
-
throw new Error('Could not create customer');
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// get an access token for the new customer
|
|
68
|
-
const {customerAccessTokenCreate} = await storefront.mutate(
|
|
69
|
-
REGISTER_LOGIN_MUTATION,
|
|
70
|
-
{
|
|
71
|
-
variables: {
|
|
72
|
-
input: {
|
|
73
|
-
email,
|
|
74
|
-
password,
|
|
75
|
-
},
|
|
76
|
-
},
|
|
77
|
-
},
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
if (!customerAccessTokenCreate?.customerAccessToken?.accessToken) {
|
|
81
|
-
throw new Error('Missing access token');
|
|
82
|
-
}
|
|
83
|
-
session.set(
|
|
84
|
-
'customerAccessToken',
|
|
85
|
-
customerAccessTokenCreate?.customerAccessToken,
|
|
86
|
-
);
|
|
87
|
-
|
|
88
|
-
return json(
|
|
89
|
-
{error: null, newCustomer},
|
|
90
|
-
{
|
|
91
|
-
status: 302,
|
|
92
|
-
headers: {
|
|
93
|
-
'Set-Cookie': await session.commit(),
|
|
94
|
-
Location: '/account',
|
|
95
|
-
},
|
|
96
|
-
},
|
|
97
|
-
);
|
|
98
|
-
} catch (error: unknown) {
|
|
99
|
-
if (error instanceof Error) {
|
|
100
|
-
return json({error: error.message}, {status: 400});
|
|
101
|
-
}
|
|
102
|
-
return json({error}, {status: 400});
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export default function Register() {
|
|
107
|
-
const data = useActionData<ActionResponse>();
|
|
108
|
-
const error = data?.error || null;
|
|
109
|
-
return (
|
|
110
|
-
<div className="login">
|
|
111
|
-
<h1>Register.</h1>
|
|
112
|
-
<Form method="POST">
|
|
113
|
-
<fieldset>
|
|
114
|
-
<label htmlFor="email">Email address</label>
|
|
115
|
-
<input
|
|
116
|
-
id="email"
|
|
117
|
-
name="email"
|
|
118
|
-
type="email"
|
|
119
|
-
autoComplete="email"
|
|
120
|
-
required
|
|
121
|
-
placeholder="Email address"
|
|
122
|
-
aria-label="Email address"
|
|
123
|
-
// eslint-disable-next-line jsx-a11y/no-autofocus
|
|
124
|
-
autoFocus
|
|
125
|
-
/>
|
|
126
|
-
<label htmlFor="password">Password</label>
|
|
127
|
-
<input
|
|
128
|
-
id="password"
|
|
129
|
-
name="password"
|
|
130
|
-
type="password"
|
|
131
|
-
autoComplete="current-password"
|
|
132
|
-
placeholder="Password"
|
|
133
|
-
aria-label="Password"
|
|
134
|
-
minLength={8}
|
|
135
|
-
required
|
|
136
|
-
/>
|
|
137
|
-
<label htmlFor="passwordConfirm">Re-enter password</label>
|
|
138
|
-
<input
|
|
139
|
-
id="passwordConfirm"
|
|
140
|
-
name="passwordConfirm"
|
|
141
|
-
type="password"
|
|
142
|
-
autoComplete="current-password"
|
|
143
|
-
placeholder="Re-enter password"
|
|
144
|
-
aria-label="Re-enter password"
|
|
145
|
-
minLength={8}
|
|
146
|
-
required
|
|
147
|
-
/>
|
|
148
|
-
</fieldset>
|
|
149
|
-
{error ? (
|
|
150
|
-
<p>
|
|
151
|
-
<mark>
|
|
152
|
-
<small>{error}</small>
|
|
153
|
-
</mark>
|
|
154
|
-
</p>
|
|
155
|
-
) : (
|
|
156
|
-
<br />
|
|
157
|
-
)}
|
|
158
|
-
<button type="submit">Register</button>
|
|
159
|
-
</Form>
|
|
160
|
-
<br />
|
|
161
|
-
<p>
|
|
162
|
-
<Link to="/account/login">Login →</Link>
|
|
163
|
-
</p>
|
|
164
|
-
</div>
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// NOTE: https://shopify.dev/docs/api/storefront/latest/mutations/customerCreate
|
|
169
|
-
const CUSTOMER_CREATE_MUTATION = `#graphql
|
|
170
|
-
mutation customerCreate(
|
|
171
|
-
$input: CustomerCreateInput!,
|
|
172
|
-
$country: CountryCode,
|
|
173
|
-
$language: LanguageCode
|
|
174
|
-
) @inContext(country: $country, language: $language) {
|
|
175
|
-
customerCreate(input: $input) {
|
|
176
|
-
customer {
|
|
177
|
-
id
|
|
178
|
-
}
|
|
179
|
-
customerUserErrors {
|
|
180
|
-
code
|
|
181
|
-
field
|
|
182
|
-
message
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
` as const;
|
|
187
|
-
|
|
188
|
-
// NOTE: https://shopify.dev/docs/api/storefront/latest/mutations/customeraccesstokencreate
|
|
189
|
-
const REGISTER_LOGIN_MUTATION = `#graphql
|
|
190
|
-
mutation registerLogin(
|
|
191
|
-
$input: CustomerAccessTokenCreateInput!,
|
|
192
|
-
$country: CountryCode,
|
|
193
|
-
$language: LanguageCode
|
|
194
|
-
) @inContext(country: $country, language: $language) {
|
|
195
|
-
customerAccessTokenCreate(input: $input) {
|
|
196
|
-
customerUserErrors {
|
|
197
|
-
code
|
|
198
|
-
field
|
|
199
|
-
message
|
|
200
|
-
}
|
|
201
|
-
customerAccessToken {
|
|
202
|
-
accessToken
|
|
203
|
-
expiresAt
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
` as const;
|