@shopify/cli-hydrogen 5.0.2 → 5.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/dist/commands/hydrogen/build.js +16 -2
- package/dist/commands/hydrogen/codegen-unstable.js +13 -24
- package/dist/commands/hydrogen/dev.js +45 -39
- package/dist/commands/hydrogen/env/list.js +25 -24
- package/dist/commands/hydrogen/env/list.test.js +46 -43
- package/dist/commands/hydrogen/env/pull.js +53 -25
- package/dist/commands/hydrogen/env/pull.test.js +123 -42
- package/dist/commands/hydrogen/generate/route.js +31 -132
- package/dist/commands/hydrogen/generate/route.test.js +34 -126
- package/dist/commands/hydrogen/init.js +46 -127
- package/dist/commands/hydrogen/init.test.js +352 -100
- package/dist/commands/hydrogen/link.js +70 -69
- package/dist/commands/hydrogen/link.test.js +72 -107
- package/dist/commands/hydrogen/list.js +22 -12
- package/dist/commands/hydrogen/list.test.js +51 -48
- package/dist/commands/hydrogen/login.js +31 -0
- package/dist/commands/hydrogen/logout.js +21 -0
- package/dist/commands/hydrogen/setup/css.js +79 -0
- package/dist/commands/hydrogen/setup/markets.js +53 -0
- package/dist/commands/hydrogen/setup.js +133 -0
- package/dist/commands/hydrogen/shortcut.js +2 -45
- package/dist/commands/hydrogen/shortcut.test.js +10 -37
- package/dist/generator-templates/assets/css-modules/package.json +6 -0
- package/dist/generator-templates/assets/postcss/package.json +10 -0
- package/dist/generator-templates/assets/postcss/postcss.config.js +8 -0
- package/dist/generator-templates/assets/tailwind/package.json +13 -0
- package/dist/generator-templates/assets/tailwind/postcss.config.js +10 -0
- package/dist/generator-templates/assets/tailwind/tailwind.config.js +8 -0
- package/dist/generator-templates/assets/tailwind/tailwind.css +3 -0
- package/dist/generator-templates/assets/vanilla-extract/package.json +9 -0
- package/dist/generator-templates/starter/.eslintignore +5 -0
- package/dist/generator-templates/starter/.eslintrc.js +18 -0
- package/dist/generator-templates/starter/.graphqlrc.yml +1 -0
- package/dist/generator-templates/starter/README.md +40 -0
- package/dist/generator-templates/starter/app/components/Aside.tsx +47 -0
- package/dist/generator-templates/starter/app/components/Cart.tsx +340 -0
- package/dist/generator-templates/starter/app/components/Footer.tsx +99 -0
- package/dist/generator-templates/starter/app/components/Header.tsx +178 -0
- package/dist/generator-templates/starter/app/components/Layout.tsx +95 -0
- package/dist/generator-templates/starter/app/components/Search.tsx +480 -0
- package/dist/generator-templates/starter/app/entry.client.tsx +12 -0
- package/dist/generator-templates/starter/app/entry.server.tsx +33 -0
- package/dist/generator-templates/starter/app/root.tsx +270 -0
- package/dist/generator-templates/starter/app/routes/$.tsx +7 -0
- package/dist/generator-templates/{routes → starter/app/routes}/[robots.txt].tsx +47 -69
- package/dist/generator-templates/starter/app/routes/[sitemap.xml].tsx +174 -0
- package/dist/generator-templates/starter/app/routes/_index.tsx +145 -0
- package/dist/generator-templates/starter/app/routes/account.$.tsx +9 -0
- package/dist/generator-templates/starter/app/routes/account.addresses.tsx +563 -0
- package/dist/generator-templates/starter/app/routes/account.orders.$id.tsx +309 -0
- package/dist/generator-templates/starter/app/routes/account.orders._index.tsx +196 -0
- package/dist/generator-templates/starter/app/routes/account.profile.tsx +289 -0
- package/dist/generator-templates/starter/app/routes/account.tsx +203 -0
- package/dist/generator-templates/starter/app/routes/account_.activate.$id.$activationToken.tsx +157 -0
- package/dist/generator-templates/starter/app/routes/account_.login.tsx +143 -0
- package/dist/generator-templates/starter/app/routes/account_.logout.tsx +33 -0
- package/dist/generator-templates/starter/app/routes/account_.recover.tsx +124 -0
- package/dist/generator-templates/starter/app/routes/account_.register.tsx +207 -0
- package/dist/generator-templates/starter/app/routes/account_.reset.$id.$resetToken.tsx +136 -0
- package/dist/generator-templates/starter/app/routes/api.predictive-search.tsx +342 -0
- package/dist/generator-templates/starter/app/routes/blogs.$blogHandle.$articleHandle.tsx +88 -0
- package/dist/generator-templates/starter/app/routes/blogs.$blogHandle._index.tsx +162 -0
- package/dist/generator-templates/starter/app/routes/blogs._index.tsx +94 -0
- package/dist/generator-templates/starter/app/routes/cart.tsx +104 -0
- package/dist/generator-templates/starter/app/routes/collections.$handle.tsx +184 -0
- package/dist/generator-templates/starter/app/routes/collections._index.tsx +120 -0
- package/dist/generator-templates/starter/app/routes/pages.$handle.tsx +57 -0
- package/dist/generator-templates/starter/app/routes/policies.$handle.tsx +94 -0
- package/dist/generator-templates/starter/app/routes/policies._index.tsx +63 -0
- package/dist/generator-templates/starter/app/routes/products.$handle.tsx +418 -0
- package/dist/generator-templates/starter/app/routes/search.tsx +168 -0
- package/dist/generator-templates/starter/app/styles/app.css +473 -0
- package/dist/generator-templates/starter/app/styles/reset.css +129 -0
- package/dist/generator-templates/starter/app/utils.ts +46 -0
- package/dist/generator-templates/starter/package.json +43 -0
- package/dist/generator-templates/starter/public/favicon.svg +28 -0
- package/dist/generator-templates/starter/remix.config.js +26 -0
- package/dist/generator-templates/starter/remix.env.d.ts +39 -0
- package/dist/generator-templates/starter/server.ts +253 -0
- package/dist/generator-templates/starter/storefrontapi.generated.d.ts +1906 -0
- package/dist/generator-templates/starter/tsconfig.json +22 -0
- package/dist/lib/auth.js +123 -0
- package/dist/lib/auth.test.js +157 -0
- package/dist/lib/build.js +51 -0
- package/dist/lib/check-version.js +3 -3
- package/dist/lib/check-version.test.js +24 -0
- package/dist/lib/codegen.js +26 -17
- package/dist/lib/environment-variables.js +68 -0
- package/dist/lib/environment-variables.test.js +147 -0
- package/dist/lib/file.js +41 -0
- package/dist/lib/file.test.js +69 -0
- package/dist/lib/flags.js +39 -2
- package/dist/lib/format-code.js +26 -0
- package/dist/lib/gid.js +12 -0
- package/dist/lib/{graphql.test.js → gid.test.js} +1 -1
- package/dist/lib/graphql/admin/client.js +27 -0
- package/dist/lib/graphql/admin/client.test.js +51 -0
- package/dist/lib/graphql/admin/create-storefront.js +13 -15
- package/dist/lib/graphql/admin/create-storefront.test.js +64 -0
- package/dist/lib/graphql/admin/fetch-job.js +6 -15
- package/dist/lib/graphql/admin/link-storefront.js +7 -11
- package/dist/lib/graphql/admin/link-storefront.test.js +38 -0
- package/dist/lib/graphql/admin/list-environments.js +2 -2
- package/dist/lib/graphql/admin/list-environments.test.js +44 -0
- package/dist/lib/graphql/admin/list-storefronts.js +7 -11
- package/dist/lib/graphql/admin/list-storefronts.test.js +44 -0
- package/dist/lib/graphql/admin/pull-variables.js +3 -3
- package/dist/lib/graphql/admin/pull-variables.test.js +37 -0
- package/dist/lib/graphql/business-platform/user-account.js +83 -0
- package/dist/lib/graphql/business-platform/user-account.test.js +80 -0
- package/dist/lib/log.js +185 -9
- package/dist/lib/log.test.js +92 -0
- package/dist/lib/mini-oxygen.js +19 -9
- package/dist/lib/missing-routes.js +0 -2
- package/dist/lib/onboarding/common.js +456 -0
- package/dist/lib/onboarding/index.js +2 -0
- package/dist/lib/onboarding/local.js +229 -0
- package/dist/lib/onboarding/remote.js +89 -0
- package/dist/lib/remix-version-interop.js +5 -5
- package/dist/lib/remix-version-interop.test.js +11 -1
- package/dist/lib/render-errors.js +13 -11
- package/dist/lib/setups/css/assets.js +89 -0
- package/dist/lib/setups/css/css-modules.js +22 -0
- package/dist/lib/setups/css/index.js +44 -0
- package/dist/lib/setups/css/postcss.js +34 -0
- package/dist/lib/setups/css/replacers.js +137 -0
- package/dist/lib/setups/css/tailwind.js +54 -0
- package/dist/lib/setups/css/vanilla-extract.js +22 -0
- package/dist/lib/setups/i18n/domains.test.js +25 -0
- package/dist/lib/setups/i18n/index.js +46 -0
- package/dist/lib/setups/i18n/replacers.js +227 -0
- package/dist/lib/setups/i18n/subdomains.test.js +25 -0
- package/dist/lib/setups/i18n/subfolders.test.js +25 -0
- package/dist/lib/setups/i18n/templates/domains.js +14 -0
- package/dist/lib/setups/i18n/templates/domains.ts +25 -0
- package/dist/lib/setups/i18n/templates/subdomains.js +14 -0
- package/dist/lib/setups/i18n/templates/subdomains.ts +24 -0
- package/dist/lib/setups/i18n/templates/subfolders.js +14 -0
- package/dist/lib/setups/i18n/templates/subfolders.ts +28 -0
- package/dist/lib/setups/routes/generate.js +244 -0
- package/dist/lib/setups/routes/generate.test.js +313 -0
- package/dist/lib/shell.js +52 -5
- package/dist/lib/shell.test.js +42 -16
- package/dist/lib/shopify-config.js +23 -18
- package/dist/lib/shopify-config.test.js +63 -73
- package/dist/lib/template-downloader.js +9 -7
- package/dist/lib/transpile-ts.js +9 -29
- package/dist/virtual-routes/routes/index.jsx +40 -19
- package/oclif.manifest.json +710 -1
- package/package.json +16 -16
- package/dist/commands/hydrogen/build.d.ts +0 -23
- package/dist/commands/hydrogen/check.d.ts +0 -15
- package/dist/commands/hydrogen/codegen-unstable.d.ts +0 -15
- package/dist/commands/hydrogen/dev.d.ts +0 -21
- package/dist/commands/hydrogen/env/list.d.ts +0 -18
- package/dist/commands/hydrogen/env/pull.d.ts +0 -22
- package/dist/commands/hydrogen/g.d.ts +0 -10
- package/dist/commands/hydrogen/generate/route.d.ts +0 -32
- package/dist/commands/hydrogen/generate/route.test.d.ts +0 -1
- package/dist/commands/hydrogen/generate/routes.d.ts +0 -16
- package/dist/commands/hydrogen/init.d.ts +0 -24
- package/dist/commands/hydrogen/init.test.d.ts +0 -1
- package/dist/commands/hydrogen/link.d.ts +0 -23
- package/dist/commands/hydrogen/link.test.d.ts +0 -1
- package/dist/commands/hydrogen/list.d.ts +0 -21
- package/dist/commands/hydrogen/list.test.d.ts +0 -1
- package/dist/commands/hydrogen/preview.d.ts +0 -17
- package/dist/commands/hydrogen/shortcut.d.ts +0 -9
- package/dist/commands/hydrogen/shortcut.test.d.ts +0 -1
- package/dist/commands/hydrogen/unlink.d.ts +0 -16
- package/dist/commands/hydrogen/unlink.test.d.ts +0 -1
- package/dist/create-app.d.ts +0 -1
- package/dist/generator-templates/routes/[sitemap.xml].tsx +0 -235
- package/dist/generator-templates/routes/account/login.tsx +0 -103
- package/dist/generator-templates/routes/account/register.tsx +0 -103
- package/dist/generator-templates/routes/cart.tsx +0 -81
- package/dist/generator-templates/routes/collections/$collectionHandle.tsx +0 -104
- package/dist/generator-templates/routes/collections/index.tsx +0 -102
- package/dist/generator-templates/routes/graphiql.tsx +0 -10
- package/dist/generator-templates/routes/index.tsx +0 -40
- package/dist/generator-templates/routes/pages/$pageHandle.tsx +0 -112
- package/dist/generator-templates/routes/policies/$policyHandle.tsx +0 -140
- package/dist/generator-templates/routes/policies/index.tsx +0 -117
- package/dist/generator-templates/routes/products/$productHandle.tsx +0 -92
- package/dist/hooks/init.d.ts +0 -5
- package/dist/lib/admin-session.d.ts +0 -6
- package/dist/lib/admin-session.js +0 -16
- package/dist/lib/admin-session.test.d.ts +0 -1
- package/dist/lib/admin-session.test.js +0 -27
- package/dist/lib/admin-urls.d.ts +0 -8
- package/dist/lib/check-lockfile.d.ts +0 -3
- package/dist/lib/check-lockfile.test.d.ts +0 -1
- package/dist/lib/check-version.d.ts +0 -16
- package/dist/lib/check-version.test.d.ts +0 -1
- package/dist/lib/codegen.d.ts +0 -26
- package/dist/lib/combined-environment-variables.d.ts +0 -8
- package/dist/lib/combined-environment-variables.js +0 -57
- package/dist/lib/combined-environment-variables.test.d.ts +0 -1
- package/dist/lib/combined-environment-variables.test.js +0 -111
- package/dist/lib/config.d.ts +0 -20
- package/dist/lib/flags.d.ts +0 -27
- package/dist/lib/flags.test.d.ts +0 -1
- package/dist/lib/graphql/admin/create-storefront.d.ts +0 -17
- package/dist/lib/graphql/admin/fetch-job.d.ts +0 -23
- package/dist/lib/graphql/admin/link-storefront.d.ts +0 -14
- package/dist/lib/graphql/admin/list-environments.d.ts +0 -21
- package/dist/lib/graphql/admin/list-storefronts.d.ts +0 -25
- package/dist/lib/graphql/admin/pull-variables.d.ts +0 -21
- package/dist/lib/graphql.d.ts +0 -21
- package/dist/lib/graphql.js +0 -18
- package/dist/lib/graphql.test.d.ts +0 -1
- package/dist/lib/log.d.ts +0 -6
- package/dist/lib/mini-oxygen.d.ts +0 -22
- package/dist/lib/missing-routes.d.ts +0 -8
- package/dist/lib/missing-routes.test.d.ts +0 -1
- package/dist/lib/missing-storefronts.d.ts +0 -5
- package/dist/lib/missing-storefronts.js +0 -18
- package/dist/lib/process.d.ts +0 -6
- package/dist/lib/pull-environment-variables.d.ts +0 -20
- package/dist/lib/pull-environment-variables.js +0 -57
- package/dist/lib/pull-environment-variables.test.d.ts +0 -1
- package/dist/lib/pull-environment-variables.test.js +0 -174
- package/dist/lib/remix-version-interop.d.ts +0 -11
- package/dist/lib/remix-version-interop.test.d.ts +0 -1
- package/dist/lib/render-errors.d.ts +0 -16
- package/dist/lib/shell.d.ts +0 -11
- package/dist/lib/shell.test.d.ts +0 -1
- package/dist/lib/shop.d.ts +0 -7
- package/dist/lib/shop.js +0 -32
- package/dist/lib/shop.test.d.ts +0 -1
- package/dist/lib/shop.test.js +0 -78
- package/dist/lib/shopify-config.d.ts +0 -35
- package/dist/lib/shopify-config.test.d.ts +0 -1
- package/dist/lib/string.d.ts +0 -3
- package/dist/lib/string.test.d.ts +0 -1
- package/dist/lib/template-downloader.d.ts +0 -6
- package/dist/lib/transpile-ts.d.ts +0 -16
- package/dist/lib/user-errors.d.ts +0 -9
- package/dist/lib/user-errors.js +0 -11
- package/dist/lib/virtual-routes.d.ts +0 -7
- package/dist/lib/virtual-routes.test.d.ts +0 -1
- /package/dist/{commands/hydrogen/env/list.test.d.ts → lib/setups/css/common.js} +0 -0
- /package/dist/{commands/hydrogen/env/pull.test.d.ts → lib/setups/i18n/mock-i18n-types.js} +0 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import {
|
|
2
|
+
json,
|
|
3
|
+
redirect,
|
|
4
|
+
type ActionArgs,
|
|
5
|
+
type LoaderArgs,
|
|
6
|
+
type V2_MetaFunction,
|
|
7
|
+
} from '@shopify/remix-oxygen';
|
|
8
|
+
import {Form, Link, useActionData} from '@remix-run/react';
|
|
9
|
+
|
|
10
|
+
type ActionResponse = {
|
|
11
|
+
error: string | null;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const meta: V2_MetaFunction = () => {
|
|
15
|
+
return [{title: 'Login'}];
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export async function loader({context}: LoaderArgs) {
|
|
19
|
+
if (await context.session.get('customerAccessToken')) {
|
|
20
|
+
return redirect('/account');
|
|
21
|
+
}
|
|
22
|
+
return json({});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function action({request, context}: ActionArgs) {
|
|
26
|
+
const {session, storefront} = context;
|
|
27
|
+
|
|
28
|
+
if (request.method !== 'POST') {
|
|
29
|
+
return json({error: 'Method not allowed'}, {status: 405});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const form = await request.formData();
|
|
34
|
+
const email = String(form.has('email') ? form.get('email') : '');
|
|
35
|
+
const password = String(form.has('password') ? form.get('password') : '');
|
|
36
|
+
const validInputs = Boolean(email && password);
|
|
37
|
+
|
|
38
|
+
if (!validInputs) {
|
|
39
|
+
throw new Error('Please provide both an email and a password.');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const {customerAccessTokenCreate} = await storefront.mutate(
|
|
43
|
+
LOGIN_MUTATION,
|
|
44
|
+
{
|
|
45
|
+
variables: {
|
|
46
|
+
input: {email, password},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
if (!customerAccessTokenCreate?.customerAccessToken?.accessToken) {
|
|
52
|
+
throw new Error(customerAccessTokenCreate?.customerUserErrors[0].message);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const {customerAccessToken} = customerAccessTokenCreate;
|
|
56
|
+
session.set('customerAccessToken', customerAccessToken);
|
|
57
|
+
|
|
58
|
+
return redirect('/account', {
|
|
59
|
+
headers: {
|
|
60
|
+
'Set-Cookie': await session.commit(),
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
} catch (error: unknown) {
|
|
64
|
+
if (error instanceof Error) {
|
|
65
|
+
return json({error: error.message}, {status: 400});
|
|
66
|
+
}
|
|
67
|
+
return json({error}, {status: 400});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export default function Login() {
|
|
72
|
+
const data = useActionData<ActionResponse>();
|
|
73
|
+
const error = data?.error || null;
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<div className="login">
|
|
77
|
+
<h1>Sign in.</h1>
|
|
78
|
+
<Form method="POST">
|
|
79
|
+
<fieldset>
|
|
80
|
+
<label htmlFor="email">Email address</label>
|
|
81
|
+
<input
|
|
82
|
+
id="email"
|
|
83
|
+
name="email"
|
|
84
|
+
type="email"
|
|
85
|
+
autoComplete="email"
|
|
86
|
+
required
|
|
87
|
+
placeholder="Email address"
|
|
88
|
+
aria-label="Email address"
|
|
89
|
+
// eslint-disable-next-line jsx-a11y/no-autofocus
|
|
90
|
+
autoFocus
|
|
91
|
+
/>
|
|
92
|
+
<label htmlFor="password">Password</label>
|
|
93
|
+
<input
|
|
94
|
+
id="password"
|
|
95
|
+
name="password"
|
|
96
|
+
type="password"
|
|
97
|
+
autoComplete="current-password"
|
|
98
|
+
placeholder="Password"
|
|
99
|
+
aria-label="Password"
|
|
100
|
+
minLength={8}
|
|
101
|
+
required
|
|
102
|
+
/>
|
|
103
|
+
</fieldset>
|
|
104
|
+
{error ? (
|
|
105
|
+
<p>
|
|
106
|
+
<mark>
|
|
107
|
+
<small>{error}</small>
|
|
108
|
+
</mark>
|
|
109
|
+
</p>
|
|
110
|
+
) : (
|
|
111
|
+
<br />
|
|
112
|
+
)}
|
|
113
|
+
<button type="submit">Sign in</button>
|
|
114
|
+
</Form>
|
|
115
|
+
<br />
|
|
116
|
+
<div>
|
|
117
|
+
<p>
|
|
118
|
+
<Link to="/account/recover">Forgot password →</Link>
|
|
119
|
+
</p>
|
|
120
|
+
<p>
|
|
121
|
+
<Link to="/account/register">Register →</Link>
|
|
122
|
+
</p>
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// NOTE: https://shopify.dev/docs/api/storefront/latest/mutations/customeraccesstokencreate
|
|
129
|
+
const LOGIN_MUTATION = `#graphql
|
|
130
|
+
mutation login($input: CustomerAccessTokenCreateInput!) {
|
|
131
|
+
customerAccessTokenCreate(input: $input) {
|
|
132
|
+
customerUserErrors {
|
|
133
|
+
code
|
|
134
|
+
field
|
|
135
|
+
message
|
|
136
|
+
}
|
|
137
|
+
customerAccessToken {
|
|
138
|
+
accessToken
|
|
139
|
+
expiresAt
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
` as const;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {
|
|
2
|
+
json,
|
|
3
|
+
redirect,
|
|
4
|
+
type ActionArgs,
|
|
5
|
+
type V2_MetaFunction,
|
|
6
|
+
} from '@shopify/remix-oxygen';
|
|
7
|
+
|
|
8
|
+
export const meta: V2_MetaFunction = () => {
|
|
9
|
+
return [{title: 'Logout'}];
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export async function loader() {
|
|
13
|
+
return redirect('/account/login');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function action({request, context}: ActionArgs) {
|
|
17
|
+
const {session} = context;
|
|
18
|
+
session.unset('customerAccessToken');
|
|
19
|
+
|
|
20
|
+
if (request.method !== 'POST') {
|
|
21
|
+
return json({error: 'Method not allowed'}, {status: 405});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return redirect('/', {
|
|
25
|
+
headers: {
|
|
26
|
+
'Set-Cookie': await session.commit(),
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default function Logout() {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import {json, redirect, type LoaderArgs} from '@shopify/remix-oxygen';
|
|
2
|
+
import {Form, Link, useActionData} from '@remix-run/react';
|
|
3
|
+
|
|
4
|
+
type ActionResponse = {
|
|
5
|
+
error?: string;
|
|
6
|
+
resetRequested?: boolean;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export async function loader({context}: LoaderArgs) {
|
|
10
|
+
const customerAccessToken = await context.session.get('customerAccessToken');
|
|
11
|
+
if (customerAccessToken) {
|
|
12
|
+
return redirect('/account');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return json({});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function action({request, context}: LoaderArgs) {
|
|
19
|
+
const {storefront} = context;
|
|
20
|
+
const form = await request.formData();
|
|
21
|
+
const email = form.has('email') ? String(form.get('email')) : null;
|
|
22
|
+
|
|
23
|
+
if (request.method !== 'POST') {
|
|
24
|
+
return json({error: 'Method not allowed'}, {status: 405});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
if (!email) {
|
|
29
|
+
throw new Error('Please provide an email.');
|
|
30
|
+
}
|
|
31
|
+
await storefront.mutate(CUSTOMER_RECOVER_MUTATION, {
|
|
32
|
+
variables: {email},
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
return json({resetRequested: true});
|
|
36
|
+
} catch (error: unknown) {
|
|
37
|
+
const resetRequested = false;
|
|
38
|
+
if (error instanceof Error) {
|
|
39
|
+
return json({error: error.message, resetRequested}, {status: 400});
|
|
40
|
+
}
|
|
41
|
+
return json({error, resetRequested}, {status: 400});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export default function Recover() {
|
|
46
|
+
const action = useActionData<ActionResponse>();
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<div className="account-recover">
|
|
50
|
+
<div>
|
|
51
|
+
{action?.resetRequested ? (
|
|
52
|
+
<>
|
|
53
|
+
<h1>Request Sent.</h1>
|
|
54
|
+
<p>
|
|
55
|
+
If that email address is in our system, you will receive an email
|
|
56
|
+
with instructions about how to reset your password in a few
|
|
57
|
+
minutes.
|
|
58
|
+
</p>
|
|
59
|
+
<br />
|
|
60
|
+
<Link to="/account/login">Return to Login</Link>
|
|
61
|
+
</>
|
|
62
|
+
) : (
|
|
63
|
+
<>
|
|
64
|
+
<h1>Forgot Password.</h1>
|
|
65
|
+
<p>
|
|
66
|
+
Enter the email address associated with your account to receive a
|
|
67
|
+
link to reset your password.
|
|
68
|
+
</p>
|
|
69
|
+
<br />
|
|
70
|
+
<Form method="POST">
|
|
71
|
+
<fieldset>
|
|
72
|
+
<label htmlFor="email">Email</label>
|
|
73
|
+
<input
|
|
74
|
+
aria-label="Email address"
|
|
75
|
+
autoComplete="email"
|
|
76
|
+
// eslint-disable-next-line jsx-a11y/no-autofocus
|
|
77
|
+
autoFocus
|
|
78
|
+
id="email"
|
|
79
|
+
name="email"
|
|
80
|
+
placeholder="Email address"
|
|
81
|
+
required
|
|
82
|
+
type="email"
|
|
83
|
+
/>
|
|
84
|
+
</fieldset>
|
|
85
|
+
{action?.error ? (
|
|
86
|
+
<p>
|
|
87
|
+
<mark>
|
|
88
|
+
<small>{action.error}</small>
|
|
89
|
+
</mark>
|
|
90
|
+
</p>
|
|
91
|
+
) : (
|
|
92
|
+
<br />
|
|
93
|
+
)}
|
|
94
|
+
<button type="submit">Request Reset Link</button>
|
|
95
|
+
</Form>
|
|
96
|
+
<div>
|
|
97
|
+
<br />
|
|
98
|
+
<p>
|
|
99
|
+
<Link to="/account/login">Login →</Link>
|
|
100
|
+
</p>
|
|
101
|
+
</div>
|
|
102
|
+
</>
|
|
103
|
+
)}
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// NOTE: https://shopify.dev/docs/api/storefront/latest/mutations/customerrecover
|
|
110
|
+
const CUSTOMER_RECOVER_MUTATION = `#graphql
|
|
111
|
+
mutation customerRecover(
|
|
112
|
+
$email: String!,
|
|
113
|
+
$country: CountryCode,
|
|
114
|
+
$language: LanguageCode
|
|
115
|
+
) @inContext(country: $country, language: $language) {
|
|
116
|
+
customerRecover(email: $email) {
|
|
117
|
+
customerUserErrors {
|
|
118
|
+
code
|
|
119
|
+
field
|
|
120
|
+
message
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
` as const;
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import {
|
|
2
|
+
json,
|
|
3
|
+
redirect,
|
|
4
|
+
type ActionFunction,
|
|
5
|
+
type LoaderArgs,
|
|
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}: LoaderArgs) {
|
|
18
|
+
const customerAccessToken = await context.session.get('customerAccessToken');
|
|
19
|
+
if (customerAccessToken) {
|
|
20
|
+
return redirect('/account');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return json({});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const action: ActionFunction = async ({request, context}) => {
|
|
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;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import {type ActionArgs, json, redirect} from '@shopify/remix-oxygen';
|
|
2
|
+
import {Form, useActionData, type V2_MetaFunction} from '@remix-run/react';
|
|
3
|
+
|
|
4
|
+
type ActionResponse = {
|
|
5
|
+
error: string | null;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const meta: V2_MetaFunction = () => {
|
|
9
|
+
return [{title: 'Reset Password'}];
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export async function action({request, context, params}: ActionArgs) {
|
|
13
|
+
if (request.method !== 'POST') {
|
|
14
|
+
return json({error: 'Method not allowed'}, {status: 405});
|
|
15
|
+
}
|
|
16
|
+
const {id, resetToken} = params;
|
|
17
|
+
const {session, storefront} = context;
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
if (!id || !resetToken) {
|
|
21
|
+
throw new Error('customer token or id not found');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const form = await request.formData();
|
|
25
|
+
const password = form.has('password') ? String(form.get('password')) : '';
|
|
26
|
+
const passwordConfirm = form.has('passwordConfirm')
|
|
27
|
+
? String(form.get('passwordConfirm'))
|
|
28
|
+
: '';
|
|
29
|
+
const validInputs = Boolean(password && passwordConfirm);
|
|
30
|
+
if (validInputs && password !== passwordConfirm) {
|
|
31
|
+
throw new Error('Please provide matching passwords');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const {customerReset} = await storefront.mutate(CUSTOMER_RESET_MUTATION, {
|
|
35
|
+
variables: {
|
|
36
|
+
id: `gid://shopify/Customer/${id}`,
|
|
37
|
+
input: {password, resetToken},
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (customerReset?.customerUserErrors?.length) {
|
|
42
|
+
throw new Error(customerReset?.customerUserErrors[0].message);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!customerReset?.customerAccessToken) {
|
|
46
|
+
throw new Error('Access token not found. Please try again.');
|
|
47
|
+
}
|
|
48
|
+
session.set('customerAccessToken', customerReset.customerAccessToken);
|
|
49
|
+
|
|
50
|
+
return redirect('/account', {
|
|
51
|
+
headers: {
|
|
52
|
+
'Set-Cookie': await session.commit(),
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
} catch (error: unknown) {
|
|
56
|
+
if (error instanceof Error) {
|
|
57
|
+
return json({error: error.message}, {status: 400});
|
|
58
|
+
}
|
|
59
|
+
return json({error}, {status: 400});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export default function Reset() {
|
|
64
|
+
const action = useActionData<ActionResponse>();
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<div className="account-reset">
|
|
68
|
+
<h1>Reset Password.</h1>
|
|
69
|
+
<p>Enter a new password for your account.</p>
|
|
70
|
+
<Form method="POST">
|
|
71
|
+
<fieldset>
|
|
72
|
+
<label htmlFor="password">Password</label>
|
|
73
|
+
<input
|
|
74
|
+
aria-label="Password"
|
|
75
|
+
autoComplete="current-password"
|
|
76
|
+
// eslint-disable-next-line jsx-a11y/no-autofocus
|
|
77
|
+
autoFocus
|
|
78
|
+
id="password"
|
|
79
|
+
minLength={8}
|
|
80
|
+
name="password"
|
|
81
|
+
placeholder="Password"
|
|
82
|
+
required
|
|
83
|
+
type="password"
|
|
84
|
+
/>
|
|
85
|
+
<label htmlFor="passwordConfirm">Re-enter password</label>
|
|
86
|
+
<input
|
|
87
|
+
aria-label="Re-enter password"
|
|
88
|
+
autoComplete="current-password"
|
|
89
|
+
id="passwordConfirm"
|
|
90
|
+
minLength={8}
|
|
91
|
+
name="passwordConfirm"
|
|
92
|
+
placeholder="Re-enter password"
|
|
93
|
+
required
|
|
94
|
+
type="password"
|
|
95
|
+
/>
|
|
96
|
+
</fieldset>
|
|
97
|
+
{action?.error ? (
|
|
98
|
+
<p>
|
|
99
|
+
<mark>
|
|
100
|
+
<small>{action.error}</small>
|
|
101
|
+
</mark>
|
|
102
|
+
</p>
|
|
103
|
+
) : (
|
|
104
|
+
<br />
|
|
105
|
+
)}
|
|
106
|
+
<button type="submit">Reset</button>
|
|
107
|
+
</Form>
|
|
108
|
+
<br />
|
|
109
|
+
<p>
|
|
110
|
+
<a href="/account/login">Back to login →</a>
|
|
111
|
+
</p>
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// NOTE: https://shopify.dev/docs/api/storefront/latest/mutations/customerreset
|
|
117
|
+
const CUSTOMER_RESET_MUTATION = `#graphql
|
|
118
|
+
mutation customerReset(
|
|
119
|
+
$id: ID!,
|
|
120
|
+
$input: CustomerResetInput!
|
|
121
|
+
$country: CountryCode
|
|
122
|
+
$language: LanguageCode
|
|
123
|
+
) @inContext(country: $country, language: $language) {
|
|
124
|
+
customerReset(id: $id, input: $input) {
|
|
125
|
+
customerAccessToken {
|
|
126
|
+
accessToken
|
|
127
|
+
expiresAt
|
|
128
|
+
}
|
|
129
|
+
customerUserErrors {
|
|
130
|
+
code
|
|
131
|
+
field
|
|
132
|
+
message
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
` as const;
|