@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.
Files changed (108) hide show
  1. package/dist/commands/hydrogen/build.js +40 -78
  2. package/dist/commands/hydrogen/codegen.js +8 -3
  3. package/dist/commands/hydrogen/deploy.js +173 -37
  4. package/dist/commands/hydrogen/deploy.test.js +192 -20
  5. package/dist/commands/hydrogen/dev.js +56 -31
  6. package/dist/commands/hydrogen/init.js +1 -1
  7. package/dist/commands/hydrogen/init.test.js +155 -53
  8. package/dist/commands/hydrogen/link.js +5 -21
  9. package/dist/commands/hydrogen/link.test.js +10 -10
  10. package/dist/commands/hydrogen/preview.js +22 -11
  11. package/dist/commands/hydrogen/setup.js +0 -4
  12. package/dist/commands/hydrogen/setup.test.js +0 -1
  13. package/dist/commands/hydrogen/shortcut.js +1 -0
  14. package/dist/commands/hydrogen/upgrade.js +720 -0
  15. package/dist/commands/hydrogen/upgrade.test.js +786 -0
  16. package/dist/generator-templates/starter/.graphqlrc.yml +12 -1
  17. package/dist/generator-templates/starter/CHANGELOG.md +126 -0
  18. package/dist/generator-templates/starter/README.md +23 -0
  19. package/dist/generator-templates/starter/app/components/Cart.tsx +1 -1
  20. package/dist/generator-templates/starter/app/components/Footer.tsx +3 -1
  21. package/dist/generator-templates/starter/app/components/Header.tsx +5 -1
  22. package/dist/generator-templates/starter/app/components/Layout.tsx +14 -11
  23. package/dist/generator-templates/starter/app/components/Search.tsx +1 -1
  24. package/dist/generator-templates/starter/app/graphql/customer-account/CustomerAddressMutations.ts +61 -0
  25. package/dist/generator-templates/starter/app/graphql/customer-account/CustomerDetailsQuery.ts +39 -0
  26. package/dist/generator-templates/starter/app/graphql/customer-account/CustomerOrderQuery.ts +87 -0
  27. package/dist/generator-templates/starter/app/graphql/customer-account/CustomerOrdersQuery.ts +58 -0
  28. package/dist/generator-templates/starter/app/graphql/customer-account/CustomerUpdateMutation.ts +24 -0
  29. package/dist/generator-templates/starter/app/lib/fragments.ts +102 -0
  30. package/dist/generator-templates/starter/app/lib/session.ts +67 -0
  31. package/dist/generator-templates/starter/app/root.tsx +11 -45
  32. package/dist/generator-templates/starter/app/routes/[robots.txt].tsx +0 -27
  33. package/dist/generator-templates/starter/app/routes/account.$.tsx +8 -4
  34. package/dist/generator-templates/starter/app/routes/account._index.tsx +5 -0
  35. package/dist/generator-templates/starter/app/routes/account.addresses.tsx +215 -206
  36. package/dist/generator-templates/starter/app/routes/account.orders.$id.tsx +56 -163
  37. package/dist/generator-templates/starter/app/routes/account.orders._index.tsx +32 -109
  38. package/dist/generator-templates/starter/app/routes/account.profile.tsx +40 -180
  39. package/dist/generator-templates/starter/app/routes/account.tsx +20 -135
  40. package/dist/generator-templates/starter/app/routes/account_.authorize.tsx +5 -0
  41. package/dist/generator-templates/starter/app/routes/account_.login.tsx +3 -140
  42. package/dist/generator-templates/starter/app/routes/account_.logout.tsx +5 -24
  43. package/dist/generator-templates/starter/app/routes/cart.tsx +7 -5
  44. package/dist/generator-templates/starter/app/routes/collections.$handle.tsx +1 -1
  45. package/dist/generator-templates/starter/app/routes/products.$handle.tsx +2 -2
  46. package/dist/generator-templates/starter/app/routes/search.tsx +1 -1
  47. package/dist/generator-templates/starter/customer-accountapi.generated.d.ts +506 -0
  48. package/dist/generator-templates/starter/package.json +11 -10
  49. package/dist/generator-templates/starter/remix.config.js +4 -0
  50. package/dist/generator-templates/starter/remix.env.d.ts +6 -11
  51. package/dist/generator-templates/starter/server.ts +24 -167
  52. package/dist/generator-templates/starter/storefrontapi.generated.d.ts +104 -881
  53. package/dist/hooks/init.js +4 -4
  54. package/dist/lib/auth.js +5 -10
  55. package/dist/lib/build.js +6 -1
  56. package/dist/lib/bundle/analyzer.js +36 -26
  57. package/dist/lib/check-lockfile.js +1 -0
  58. package/dist/lib/codegen.js +59 -18
  59. package/dist/lib/defer.js +12 -0
  60. package/dist/lib/file.js +52 -3
  61. package/dist/lib/flags.js +27 -9
  62. package/dist/lib/get-oxygen-deployment-data.test.js +4 -2
  63. package/dist/lib/graphql/admin/client.test.js +2 -2
  64. package/dist/lib/graphql/admin/get-oxygen-data.js +1 -0
  65. package/dist/lib/log.js +32 -14
  66. package/dist/lib/mini-oxygen/assets.js +118 -0
  67. package/dist/lib/mini-oxygen/common.js +2 -1
  68. package/dist/lib/mini-oxygen/index.js +7 -5
  69. package/dist/lib/mini-oxygen/mini-oxygen.test.js +214 -0
  70. package/dist/lib/mini-oxygen/node.js +19 -5
  71. package/dist/lib/mini-oxygen/workerd-inspector-logs.js +227 -0
  72. package/dist/lib/mini-oxygen/workerd-inspector-proxy.js +200 -0
  73. package/dist/lib/mini-oxygen/workerd-inspector.js +62 -235
  74. package/dist/lib/mini-oxygen/workerd.js +74 -50
  75. package/dist/lib/missing-routes.js +6 -3
  76. package/dist/lib/onboarding/common.js +40 -9
  77. package/dist/lib/onboarding/local.js +19 -11
  78. package/dist/lib/onboarding/remote.js +48 -28
  79. package/dist/lib/render-errors.js +2 -0
  80. package/dist/lib/request-events.js +65 -31
  81. package/dist/lib/setups/css/assets.js +1 -46
  82. package/dist/lib/setups/css/css-modules.js +3 -2
  83. package/dist/lib/setups/css/postcss.js +4 -2
  84. package/dist/lib/setups/css/tailwind.js +4 -2
  85. package/dist/lib/setups/css/vanilla-extract.js +3 -2
  86. package/dist/lib/setups/i18n/replacers.test.js +56 -38
  87. package/dist/lib/shell.js +1 -1
  88. package/dist/lib/template-diff.js +89 -0
  89. package/dist/lib/template-downloader.js +3 -2
  90. package/dist/lib/transpile/project.js +1 -1
  91. package/dist/virtual-routes/assets/debug-network.css +592 -0
  92. package/dist/virtual-routes/assets/favicon-dark.svg +20 -0
  93. package/dist/virtual-routes/components/FlameChartWrapper.jsx +8 -10
  94. package/dist/virtual-routes/components/IconClose.jsx +38 -0
  95. package/dist/virtual-routes/components/IconDiscard.jsx +44 -0
  96. package/dist/virtual-routes/components/RequestDetails.jsx +179 -0
  97. package/dist/virtual-routes/components/RequestTable.jsx +92 -0
  98. package/dist/virtual-routes/components/RequestWaterfall.jsx +151 -0
  99. package/dist/virtual-routes/lib/useDebugNetworkServer.jsx +176 -0
  100. package/dist/virtual-routes/routes/subrequest-profiler.jsx +243 -0
  101. package/oclif.manifest.json +134 -59
  102. package/package.json +18 -26
  103. package/dist/generator-templates/starter/app/routes/account_.activate.$id.$activationToken.tsx +0 -161
  104. package/dist/generator-templates/starter/app/routes/account_.recover.tsx +0 -129
  105. package/dist/generator-templates/starter/app/routes/account_.register.tsx +0 -207
  106. package/dist/generator-templates/starter/app/routes/account_.reset.$id.$resetToken.tsx +0 -136
  107. package/dist/virtual-routes/routes/debug-network.jsx +0 -289
  108. /package/dist/generator-templates/starter/app/{utils.ts → lib/variants.ts} +0 -0
@@ -1,5 +1,6 @@
1
- import type {CustomerFragment} from 'storefrontapi.generated';
2
- import type {CustomerUpdateInput} from '@shopify/hydrogen/storefront-api-types';
1
+ import type {CustomerFragment} from 'customer-accountapi.generated';
2
+ import type {CustomerUpdateInput} from '@shopify/hydrogen/customer-account-api-types';
3
+ import {CUSTOMER_UPDATE_MUTATION} from '~/graphql/customer-account/CustomerUpdateMutation';
3
4
  import {
4
5
  json,
5
6
  redirect,
@@ -24,86 +25,78 @@ export const meta: MetaFunction = () => {
24
25
  };
25
26
 
26
27
  export async function loader({context}: LoaderFunctionArgs) {
27
- const customerAccessToken = await context.session.get('customerAccessToken');
28
- if (!customerAccessToken) {
29
- return redirect('/account/login');
30
- }
31
- return json({});
28
+ await context.customerAccount.handleAuthStatus();
29
+
30
+ return json(
31
+ {},
32
+ {
33
+ headers: {
34
+ 'Set-Cookie': await context.session.commit(),
35
+ },
36
+ },
37
+ );
32
38
  }
33
39
 
34
40
  export async function action({request, context}: ActionFunctionArgs) {
35
- const {session, storefront} = context;
41
+ const {customerAccount} = context;
36
42
 
37
43
  if (request.method !== 'PUT') {
38
44
  return json({error: 'Method not allowed'}, {status: 405});
39
45
  }
40
46
 
41
47
  const form = await request.formData();
42
- const customerAccessToken = await session.get('customerAccessToken');
43
- if (!customerAccessToken) {
44
- return json({error: 'Unauthorized'}, {status: 401});
45
- }
46
48
 
47
49
  try {
48
- const password = getPassword(form);
49
50
  const customer: CustomerUpdateInput = {};
50
- const validInputKeys = [
51
- 'firstName',
52
- 'lastName',
53
- 'email',
54
- 'password',
55
- 'phone',
56
- ] as const;
51
+ const validInputKeys = ['firstName', 'lastName'] as const;
57
52
  for (const [key, value] of form.entries()) {
58
53
  if (!validInputKeys.includes(key as any)) {
59
54
  continue;
60
55
  }
61
- if (key === 'acceptsMarketing') {
62
- customer.acceptsMarketing = value === 'on';
63
- }
64
56
  if (typeof value === 'string' && value.length) {
65
57
  customer[key as (typeof validInputKeys)[number]] = value;
66
58
  }
67
59
  }
68
60
 
69
- if (password) {
70
- customer.password = password;
71
- }
72
-
73
61
  // update customer and possibly password
74
- const updated = await storefront.mutate(CUSTOMER_UPDATE_MUTATION, {
75
- variables: {
76
- customerAccessToken: customerAccessToken.accessToken,
77
- customer,
62
+ const {data, errors} = await customerAccount.mutate(
63
+ CUSTOMER_UPDATE_MUTATION,
64
+ {
65
+ variables: {
66
+ customer,
67
+ },
78
68
  },
79
- });
69
+ );
80
70
 
81
- // check for mutation errors
82
- if (updated.customerUpdate?.customerUserErrors?.length) {
83
- return json(
84
- {error: updated.customerUpdate?.customerUserErrors[0]},
85
- {status: 400},
86
- );
71
+ if (errors?.length) {
72
+ throw new Error(errors[0].message);
87
73
  }
88
74
 
89
- // update session with the updated access token
90
- if (updated.customerUpdate?.customerAccessToken?.accessToken) {
91
- session.set(
92
- 'customerAccessToken',
93
- updated.customerUpdate?.customerAccessToken,
94
- );
75
+ if (!data?.customerUpdate?.customer) {
76
+ throw new Error('Customer profile update failed.');
95
77
  }
96
78
 
97
79
  return json(
98
- {error: null, customer: updated.customerUpdate?.customer},
80
+ {
81
+ error: null,
82
+ customer: data?.customerUpdate?.customer,
83
+ },
99
84
  {
100
85
  headers: {
101
- 'Set-Cookie': await session.commit(),
86
+ 'Set-Cookie': await context.session.commit(),
102
87
  },
103
88
  },
104
89
  );
105
90
  } catch (error: any) {
106
- return json({error: error.message, customer: null}, {status: 400});
91
+ return json(
92
+ {error: error.message, customer: null},
93
+ {
94
+ status: 400,
95
+ headers: {
96
+ 'Set-Cookie': await context.session.commit(),
97
+ },
98
+ },
99
+ );
107
100
  }
108
101
  }
109
102
 
@@ -142,75 +135,6 @@ export default function AccountProfile() {
142
135
  defaultValue={customer.lastName ?? ''}
143
136
  minLength={2}
144
137
  />
145
- <label htmlFor="phone">Mobile</label>
146
- <input
147
- id="phone"
148
- name="phone"
149
- type="tel"
150
- autoComplete="tel"
151
- placeholder="Mobile"
152
- aria-label="Mobile"
153
- defaultValue={customer.phone ?? ''}
154
- />
155
- <label htmlFor="email">Email address</label>
156
- <input
157
- id="email"
158
- name="email"
159
- type="email"
160
- autoComplete="email"
161
- required
162
- placeholder="Email address"
163
- aria-label="Email address"
164
- defaultValue={customer.email ?? ''}
165
- />
166
- <div className="account-profile-marketing">
167
- <input
168
- id="acceptsMarketing"
169
- name="acceptsMarketing"
170
- type="checkbox"
171
- placeholder="Accept marketing"
172
- aria-label="Accept marketing"
173
- defaultChecked={customer.acceptsMarketing}
174
- />
175
- <label htmlFor="acceptsMarketing">
176
- &nbsp; Subscribed to marketing communications
177
- </label>
178
- </div>
179
- </fieldset>
180
- <br />
181
- <legend>Change password (optional)</legend>
182
- <fieldset>
183
- <label htmlFor="currentPassword">Current password</label>
184
- <input
185
- id="currentPassword"
186
- name="currentPassword"
187
- type="password"
188
- autoComplete="current-password"
189
- placeholder="Current password"
190
- aria-label="Current password"
191
- minLength={8}
192
- />
193
-
194
- <label htmlFor="newPassword">New password</label>
195
- <input
196
- id="newPassword"
197
- name="newPassword"
198
- type="password"
199
- placeholder="New password"
200
- aria-label="New password"
201
- minLength={8}
202
- />
203
-
204
- <label htmlFor="newPasswordConfirm">New password (confirm)</label>
205
- <input
206
- id="newPasswordConfirm"
207
- name="newPasswordConfirm"
208
- type="password"
209
- placeholder="New password (confirm)"
210
- aria-label="New password confirm"
211
- minLength={8}
212
- />
213
- <small>Passwords must be at least 8 characters.</small>
214
138
  </fieldset>
215
139
  {action?.error ? (
216
140
  <p>
@@ -228,67 +152,3 @@ export default function AccountProfile() {
228
152
  </div>
229
153
  );
230
154
  }
231
-
232
- function getPassword(form: FormData): string | undefined {
233
- let password;
234
- const currentPassword = form.get('currentPassword');
235
- const newPassword = form.get('newPassword');
236
- const newPasswordConfirm = form.get('newPasswordConfirm');
237
-
238
- let passwordError;
239
- if (newPassword && !currentPassword) {
240
- passwordError = new Error('Current password is required.');
241
- }
242
-
243
- if (newPassword && newPassword !== newPasswordConfirm) {
244
- passwordError = new Error('New passwords must match.');
245
- }
246
-
247
- if (newPassword && currentPassword && newPassword === currentPassword) {
248
- passwordError = new Error(
249
- 'New password must be different than current password.',
250
- );
251
- }
252
-
253
- if (passwordError) {
254
- throw passwordError;
255
- }
256
-
257
- if (currentPassword && newPassword) {
258
- password = newPassword;
259
- } else {
260
- password = currentPassword;
261
- }
262
-
263
- return String(password);
264
- }
265
-
266
- const CUSTOMER_UPDATE_MUTATION = `#graphql
267
- # https://shopify.dev/docs/api/storefront/latest/mutations/customerUpdate
268
- mutation customerUpdate(
269
- $customerAccessToken: String!,
270
- $customer: CustomerUpdateInput!
271
- $country: CountryCode
272
- $language: LanguageCode
273
- ) @inContext(language: $language, country: $country) {
274
- customerUpdate(customerAccessToken: $customerAccessToken, customer: $customer) {
275
- customer {
276
- acceptsMarketing
277
- email
278
- firstName
279
- id
280
- lastName
281
- phone
282
- }
283
- customerAccessToken {
284
- accessToken
285
- expiresAt
286
- }
287
- customerUserErrors {
288
- code
289
- field
290
- message
291
- }
292
- }
293
- }
294
- ` as const;
@@ -1,104 +1,34 @@
1
- import {Form, NavLink, Outlet, useLoaderData} from '@remix-run/react';
2
1
  import {json, redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
3
- import type {CustomerFragment} from 'storefrontapi.generated';
2
+ import {Form, NavLink, Outlet, useLoaderData} from '@remix-run/react';
3
+ import {CUSTOMER_DETAILS_QUERY} from '~/graphql/customer-account/CustomerDetailsQuery';
4
4
 
5
5
  export function shouldRevalidate() {
6
6
  return true;
7
7
  }
8
8
 
9
- export async function loader({request, context}: LoaderFunctionArgs) {
10
- const {session, storefront} = context;
11
- const {pathname} = new URL(request.url);
12
- const customerAccessToken = await session.get('customerAccessToken');
13
- const isLoggedIn = !!customerAccessToken?.accessToken;
14
- const isAccountHome = pathname === '/account' || pathname === '/account/';
15
- const isPrivateRoute =
16
- /^\/account\/(orders|orders\/.*|profile|addresses|addresses\/.*)$/.test(
17
- pathname,
18
- );
9
+ export async function loader({context}: LoaderFunctionArgs) {
10
+ const {data, errors} = await context.customerAccount.query(
11
+ CUSTOMER_DETAILS_QUERY,
12
+ );
19
13
 
20
- if (!isLoggedIn) {
21
- if (isPrivateRoute || isAccountHome) {
22
- session.unset('customerAccessToken');
23
- return redirect('/account/login', {
24
- headers: {
25
- 'Set-Cookie': await session.commit(),
26
- },
27
- });
28
- } else {
29
- // public subroute such as /account/login...
30
- return json({
31
- isLoggedIn: false,
32
- isAccountHome,
33
- isPrivateRoute,
34
- customer: null,
35
- });
36
- }
37
- } else {
38
- // loggedIn, default redirect to the orders page
39
- if (isAccountHome) {
40
- return redirect('/account/orders');
41
- }
14
+ if (errors?.length || !data?.customer) {
15
+ throw new Error('Customer not found');
42
16
  }
43
17
 
44
- try {
45
- const {customer} = await storefront.query(CUSTOMER_QUERY, {
46
- variables: {
47
- customerAccessToken: customerAccessToken.accessToken,
48
- country: storefront.i18n.country,
49
- language: storefront.i18n.language,
50
- },
51
- cache: storefront.CacheNone(),
52
- });
53
-
54
- if (!customer) {
55
- throw new Error('Customer not found');
56
- }
57
-
58
- return json(
59
- {isLoggedIn, isPrivateRoute, isAccountHome, customer},
60
- {
61
- headers: {
62
- 'Cache-Control': 'no-cache, no-store, must-revalidate',
63
- },
64
- },
65
- );
66
- } catch (error) {
67
- // eslint-disable-next-line no-console
68
- console.error('There was a problem loading account', error);
69
- session.unset('customerAccessToken');
70
- return redirect('/account/login', {
18
+ return json(
19
+ {customer: data.customer},
20
+ {
71
21
  headers: {
72
- 'Set-Cookie': await session.commit(),
22
+ 'Cache-Control': 'no-cache, no-store, must-revalidate',
23
+ 'Set-Cookie': await context.session.commit(),
73
24
  },
74
- });
75
- }
76
- }
77
-
78
- export default function Acccount() {
79
- const {customer, isPrivateRoute, isAccountHome} =
80
- useLoaderData<typeof loader>();
81
-
82
- if (!isPrivateRoute && !isAccountHome) {
83
- return <Outlet context={{customer}} />;
84
- }
85
-
86
- return (
87
- <AccountLayout customer={customer as CustomerFragment}>
88
- <br />
89
- <br />
90
- <Outlet context={{customer}} />
91
- </AccountLayout>
25
+ },
92
26
  );
93
27
  }
94
28
 
95
- function AccountLayout({
96
- customer,
97
- children,
98
- }: {
99
- customer: CustomerFragment;
100
- children: React.ReactNode;
101
- }) {
29
+ export default function AccountLayout() {
30
+ const {customer} = useLoaderData<typeof loader>();
31
+
102
32
  const heading = customer
103
33
  ? customer.firstName
104
34
  ? `Welcome, ${customer.firstName}`
@@ -110,7 +40,9 @@ function AccountLayout({
110
40
  <h1>{heading}</h1>
111
41
  <br />
112
42
  <AccountMenu />
113
- {children}
43
+ <br />
44
+ <br />
45
+ <Outlet context={{customer}} />
114
46
  </div>
115
47
  );
116
48
  }
@@ -155,50 +87,3 @@ function Logout() {
155
87
  </Form>
156
88
  );
157
89
  }
158
-
159
- export const CUSTOMER_FRAGMENT = `#graphql
160
- fragment Customer on Customer {
161
- acceptsMarketing
162
- addresses(first: 6) {
163
- nodes {
164
- ...Address
165
- }
166
- }
167
- defaultAddress {
168
- ...Address
169
- }
170
- email
171
- firstName
172
- lastName
173
- numberOfOrders
174
- phone
175
- }
176
- fragment Address on MailingAddress {
177
- id
178
- formatted
179
- firstName
180
- lastName
181
- company
182
- address1
183
- address2
184
- country
185
- province
186
- city
187
- zip
188
- phone
189
- }
190
- ` as const;
191
-
192
- // NOTE: https://shopify.dev/docs/api/storefront/latest/queries/customer
193
- const CUSTOMER_QUERY = `#graphql
194
- query Customer(
195
- $customerAccessToken: String!
196
- $country: CountryCode
197
- $language: LanguageCode
198
- ) @inContext(country: $country, language: $language) {
199
- customer(customerAccessToken: $customerAccessToken) {
200
- ...Customer
201
- }
202
- }
203
- ${CUSTOMER_FRAGMENT}
204
- ` as const;
@@ -0,0 +1,5 @@
1
+ import type {LoaderFunctionArgs} from '@shopify/remix-oxygen';
2
+
3
+ export async function loader({context}: LoaderFunctionArgs) {
4
+ return context.customerAccount.authorize();
5
+ }
@@ -1,142 +1,5 @@
1
- import {
2
- json,
3
- redirect,
4
- type ActionFunctionArgs,
5
- type LoaderFunctionArgs,
6
- } from '@shopify/remix-oxygen';
7
- import {Form, Link, useActionData, type MetaFunction} from '@remix-run/react';
1
+ import type {LoaderFunctionArgs} from '@shopify/remix-oxygen';
8
2
 
9
- type ActionResponse = {
10
- error: string | null;
11
- };
12
-
13
- export const meta: MetaFunction = () => {
14
- return [{title: 'Login'}];
15
- };
16
-
17
- export async function loader({context}: LoaderFunctionArgs) {
18
- if (await context.session.get('customerAccessToken')) {
19
- return redirect('/account');
20
- }
21
- return json({});
3
+ export async function loader({request, context}: LoaderFunctionArgs) {
4
+ return context.customerAccount.login();
22
5
  }
23
-
24
- export async function action({request, context}: ActionFunctionArgs) {
25
- const {session, storefront} = context;
26
-
27
- if (request.method !== 'POST') {
28
- return json({error: 'Method not allowed'}, {status: 405});
29
- }
30
-
31
- try {
32
- const form = await request.formData();
33
- const email = String(form.has('email') ? form.get('email') : '');
34
- const password = String(form.has('password') ? form.get('password') : '');
35
- const validInputs = Boolean(email && password);
36
-
37
- if (!validInputs) {
38
- throw new Error('Please provide both an email and a password.');
39
- }
40
-
41
- const {customerAccessTokenCreate} = await storefront.mutate(
42
- LOGIN_MUTATION,
43
- {
44
- variables: {
45
- input: {email, password},
46
- },
47
- },
48
- );
49
-
50
- if (!customerAccessTokenCreate?.customerAccessToken?.accessToken) {
51
- throw new Error(customerAccessTokenCreate?.customerUserErrors[0].message);
52
- }
53
-
54
- const {customerAccessToken} = customerAccessTokenCreate;
55
- session.set('customerAccessToken', customerAccessToken);
56
-
57
- return redirect('/account', {
58
- headers: {
59
- 'Set-Cookie': await session.commit(),
60
- },
61
- });
62
- } catch (error: unknown) {
63
- if (error instanceof Error) {
64
- return json({error: error.message}, {status: 400});
65
- }
66
- return json({error}, {status: 400});
67
- }
68
- }
69
-
70
- export default function Login() {
71
- const data = useActionData<ActionResponse>();
72
- const error = data?.error || null;
73
-
74
- return (
75
- <div className="login">
76
- <h1>Sign in.</h1>
77
- <Form method="POST">
78
- <fieldset>
79
- <label htmlFor="email">Email address</label>
80
- <input
81
- id="email"
82
- name="email"
83
- type="email"
84
- autoComplete="email"
85
- required
86
- placeholder="Email address"
87
- aria-label="Email address"
88
- // eslint-disable-next-line jsx-a11y/no-autofocus
89
- autoFocus
90
- />
91
- <label htmlFor="password">Password</label>
92
- <input
93
- id="password"
94
- name="password"
95
- type="password"
96
- autoComplete="current-password"
97
- placeholder="Password"
98
- aria-label="Password"
99
- minLength={8}
100
- required
101
- />
102
- </fieldset>
103
- {error ? (
104
- <p>
105
- <mark>
106
- <small>{error}</small>
107
- </mark>
108
- </p>
109
- ) : (
110
- <br />
111
- )}
112
- <button type="submit">Sign in</button>
113
- </Form>
114
- <br />
115
- <div>
116
- <p>
117
- <Link to="/account/recover">Forgot password →</Link>
118
- </p>
119
- <p>
120
- <Link to="/account/register">Register →</Link>
121
- </p>
122
- </div>
123
- </div>
124
- );
125
- }
126
-
127
- // NOTE: https://shopify.dev/docs/api/storefront/latest/mutations/customeraccesstokencreate
128
- const LOGIN_MUTATION = `#graphql
129
- mutation login($input: CustomerAccessTokenCreateInput!) {
130
- customerAccessTokenCreate(input: $input) {
131
- customerUserErrors {
132
- code
133
- field
134
- message
135
- }
136
- customerAccessToken {
137
- accessToken
138
- expiresAt
139
- }
140
- }
141
- }
142
- ` as const;
@@ -1,29 +1,10 @@
1
- import {json, redirect, type ActionFunctionArgs} from '@shopify/remix-oxygen';
2
- import {type MetaFunction} from '@remix-run/react';
3
-
4
- export const meta: MetaFunction = () => {
5
- return [{title: 'Logout'}];
6
- };
1
+ import {redirect, type ActionFunctionArgs} from '@shopify/remix-oxygen';
7
2
 
3
+ // if we dont implement this, /account/logout will get caught by account.$.tsx to do login
8
4
  export async function loader() {
9
- return redirect('/account/login');
10
- }
11
-
12
- export async function action({request, context}: ActionFunctionArgs) {
13
- const {session} = context;
14
- session.unset('customerAccessToken');
15
-
16
- if (request.method !== 'POST') {
17
- return json({error: 'Method not allowed'}, {status: 405});
18
- }
19
-
20
- return redirect('/', {
21
- headers: {
22
- 'Set-Cookie': await session.commit(),
23
- },
24
- });
5
+ return redirect('/');
25
6
  }
26
7
 
27
- export default function Logout() {
28
- return null;
8
+ export async function action({context}: ActionFunctionArgs) {
9
+ return context.customerAccount.logout();
29
10
  }
@@ -1,6 +1,6 @@
1
1
  import {Await, type MetaFunction} from '@remix-run/react';
2
2
  import {Suspense} from 'react';
3
- import type {CartQueryData} from '@shopify/hydrogen';
3
+ import type {CartQueryDataReturn} from '@shopify/hydrogen';
4
4
  import {CartForm} from '@shopify/hydrogen';
5
5
  import {json, type ActionFunctionArgs} from '@shopify/remix-oxygen';
6
6
  import {CartMain} from '~/components/Cart';
@@ -11,11 +11,11 @@ export const meta: MetaFunction = () => {
11
11
  };
12
12
 
13
13
  export async function action({request, context}: ActionFunctionArgs) {
14
- const {session, cart} = context;
14
+ const {cart} = context;
15
15
 
16
16
  const [formData, customerAccessToken] = await Promise.all([
17
17
  request.formData(),
18
- session.get('customerAccessToken'),
18
+ await context.customerAccount.getAccessToken(),
19
19
  ]);
20
20
 
21
21
  const {action, inputs} = CartForm.getFormInput(formData);
@@ -25,7 +25,7 @@ export async function action({request, context}: ActionFunctionArgs) {
25
25
  }
26
26
 
27
27
  let status = 200;
28
- let result: CartQueryData;
28
+ let result: CartQueryDataReturn;
29
29
 
30
30
  switch (action) {
31
31
  case CartForm.ACTIONS.LinesAdd:
@@ -54,7 +54,7 @@ export async function action({request, context}: ActionFunctionArgs) {
54
54
  case CartForm.ACTIONS.BuyerIdentityUpdate: {
55
55
  result = await cart.updateBuyerIdentity({
56
56
  ...inputs.buyerIdentity,
57
- customerAccessToken: customerAccessToken?.accessToken,
57
+ customerAccessToken,
58
58
  });
59
59
  break;
60
60
  }
@@ -72,6 +72,8 @@ export async function action({request, context}: ActionFunctionArgs) {
72
72
  headers.set('Location', redirectTo);
73
73
  }
74
74
 
75
+ headers.append('Set-Cookie', await context.session.commit());
76
+
75
77
  return json(
76
78
  {
77
79
  cart: cartResult,