@@ -1,22 +1,14 @@
-import type {CustomerAddressInput} from '@shopify/hydrogen/customer-account-api-types';
-import type {
- AddressFragment,
- CustomerFragment,
-} from 'customer-accountapi.generated';
+import type {MailingAddressInput} from '@shopify/hydrogen/storefront-api-types';
+import type {AddressFragment, CustomerFragment} from 'storefrontapi.generated';
import {
data,
Form,
+ redirect,
useActionData,
useNavigation,
useOutletContext,
- type Fetcher,
} from 'react-router';
import type {Route} from './+types/account.addresses';
-import {
- UPDATE_ADDRESS_MUTATION,
- DELETE_ADDRESS_MUTATION,
- CREATE_ADDRESS_MUTATION,
-} from '~/graphql/customer-account/CustomerAddressMutations';
export type ActionResponse = {
addressId?: string | null;
@@ -32,13 +24,16 @@ export const meta: Route.MetaFunction = () => {
};
export async function loader({context}: Route.LoaderArgs) {
- context.customerAccount.handleAuthStatus();
-
+ const {session} = context;
+ const customerAccessToken = await session.get('customerAccessToken');
+ if (!customerAccessToken) {
+ return redirect('/account/login');
+ }
return {};
}
export async function action({request, context}: Route.ActionArgs) {
- const {customerAccount} = context;
+ const {storefront, session} = context;
try {
const form = await request.formData();
@@ -50,31 +45,26 @@ export async function action({request, context}: Route.ActionArgs) {
throw new Error('You must provide an address id.');
}
- // this will ensure redirecting to login never happen for mutatation
- const isLoggedIn = await customerAccount.isLoggedIn();
- if (!isLoggedIn) {
- return data(
- {error: {[addressId]: 'Unauthorized'}},
- {
- status: 401,
- },
- );
+ const customerAccessToken = await session.get('customerAccessToken');
+ if (!customerAccessToken) {
+ return data({error: {[addressId]: 'Unauthorized'}}, {status: 401});
}
+ const {accessToken} = customerAccessToken;
const defaultAddress = form.has('defaultAddress')
? String(form.get('defaultAddress')) === 'on'
- : false;
- const address: CustomerAddressInput = {};
- const keys: (keyof CustomerAddressInput)[] = [
+ : null;
+ const address: MailingAddressInput = {};
+ const keys: (keyof MailingAddressInput)[] = [
'address1',
'address2',
'city',
'company',
- 'territoryCode',
+ 'country',
'firstName',
'lastName',
- 'phoneNumber',
- 'zoneCode',
+ 'phone',
+ 'province',
'zip',
];
@@ -89,170 +79,134 @@ export async function action({request, context}: Route.ActionArgs) {
case 'POST': {
// handle new address creation
try {
- const {data, errors} = await customerAccount.mutate(
+ const {customerAddressCreate} = await storefront.mutate(
CREATE_ADDRESS_MUTATION,
{
- variables: {
- address,
- defaultAddress,
- language: customerAccount.i18n.language,
- },
+ variables: {customerAccessToken: accessToken, address},
},
);
- if (errors?.length) {
- throw new Error(errors[0].message);
+ if (customerAddressCreate?.customerUserErrors?.length) {
+ const error = customerAddressCreate.customerUserErrors[0];
+ throw new Error(error.message);
}
- if (data?.customerAddressCreate?.userErrors?.length) {
- throw new Error(data?.customerAddressCreate?.userErrors[0].message);
- }
-
- if (!data?.customerAddressCreate?.customerAddress) {
- throw new Error('Customer address create failed.');
- }
-
- return {
- error: null,
- createdAddress: data?.customerAddressCreate?.customerAddress,
- defaultAddress,
- };
- } catch (error: unknown) {
- if (error instanceof Error) {
- return data(
- {error: {[addressId]: error.message}},
- {
- status: 400,
- },
+ const createdAddress = customerAddressCreate?.customerAddress;
+ if (!createdAddress?.id) {
+ throw new Error(
+ 'Expected customer address to be created, but the id is missing',
);
}
- return data(
- {error: {[addressId]: error}},
- {
- status: 400,
- },
- );
+
+ if (defaultAddress) {
+ const createdAddressId = decodeURIComponent(createdAddress.id);
+ const {customerDefaultAddressUpdate} = await storefront.mutate(
+ UPDATE_DEFAULT_ADDRESS_MUTATION,
+ {
+ variables: {
+ customerAccessToken: accessToken,
+ addressId: createdAddressId,
+ },
+ },
+ );
+
+ if (customerDefaultAddressUpdate?.customerUserErrors?.length) {
+ const error = customerDefaultAddressUpdate.customerUserErrors[0];
+ throw new Error(error.message);
+ }
+ }
+
+ return {error: null, createdAddress, defaultAddress};
+ } catch (error: unknown) {
+ if (error instanceof Error) {
+ return data({error: {[addressId]: error.message}}, {status: 400});
+ }
+ return data({error: {[addressId]: error}}, {status: 400});
}
}
case 'PUT': {
// handle address updates
try {
- const {data, errors} = await customerAccount.mutate(
+ const {customerAddressUpdate} = await storefront.mutate(
UPDATE_ADDRESS_MUTATION,
{
variables: {
address,
- addressId: decodeURIComponent(addressId),
- defaultAddress,
- language: customerAccount.i18n.language,
+ customerAccessToken: accessToken,
+ id: decodeURIComponent(addressId),
},
},
);
- if (errors?.length) {
- throw new Error(errors[0].message);
+ const updatedAddress = customerAddressUpdate?.customerAddress;
+
+ if (customerAddressUpdate?.customerUserErrors?.length) {
+ const error = customerAddressUpdate.customerUserErrors[0];
+ throw new Error(error.message);
}
- if (data?.customerAddressUpdate?.userErrors?.length) {
- throw new Error(data?.customerAddressUpdate?.userErrors[0].message);
- }
-
- if (!data?.customerAddressUpdate?.customerAddress) {
- throw new Error('Customer address update failed.');
- }
-
- return {
- error: null,
- updatedAddress: address,
- defaultAddress,
- };
- } catch (error: unknown) {
- if (error instanceof Error) {
- return data(
- {error: {[addressId]: error.message}},
+ if (defaultAddress) {
+ const {customerDefaultAddressUpdate} = await storefront.mutate(
+ UPDATE_DEFAULT_ADDRESS_MUTATION,
{
- status: 400,
+ variables: {
+ customerAccessToken: accessToken,
+ addressId: decodeURIComponent(addressId),
+ },
},
);
+
+ if (customerDefaultAddressUpdate?.customerUserErrors?.length) {
+ const error = customerDefaultAddressUpdate.customerUserErrors[0];
+ throw new Error(error.message);
+ }
}
- return data(
- {error: {[addressId]: error}},
- {
- status: 400,
- },
- );
+
+ return {error: null, updatedAddress, defaultAddress};
+ } catch (error: unknown) {
+ if (error instanceof Error) {
+ return data({error: {[addressId]: error.message}}, {status: 400});
+ }
+ return data({error: {[addressId]: error}}, {status: 400});
}
}
case 'DELETE': {
// handles address deletion
try {
- const {data, errors} = await customerAccount.mutate(
+ const {customerAddressDelete} = await storefront.mutate(
DELETE_ADDRESS_MUTATION,
{
- variables: {
- addressId: decodeURIComponent(addressId),
- language: customerAccount.i18n.language,
- },
+ variables: {customerAccessToken: accessToken, id: addressId},
},
);
- if (errors?.length) {
- throw new Error(errors[0].message);
+ if (customerAddressDelete?.customerUserErrors?.length) {
+ const error = customerAddressDelete.customerUserErrors[0];
+ throw new Error(error.message);
}
-
- if (data?.customerAddressDelete?.userErrors?.length) {
- throw new Error(data?.customerAddressDelete?.userErrors[0].message);
- }
-
- if (!data?.customerAddressDelete?.deletedAddressId) {
- throw new Error('Customer address delete failed.');
- }
-
return {error: null, deletedAddress: addressId};
} catch (error: unknown) {
if (error instanceof Error) {
- return data(
- {error: {[addressId]: error.message}},
- {
- status: 400,
- },
- );
+ return data({error: {[addressId]: error.message}}, {status: 400});
}
- return data(
- {error: {[addressId]: error}},
- {
- status: 400,
- },
- );
+ return data({error: {[addressId]: error}}, {status: 400});
}
}
default: {
return data(
{error: {[addressId]: 'Method not allowed'}},
- {
- status: 405,
- },
+ {status: 405},
);
}
}
} catch (error: unknown) {
if (error instanceof Error) {
- return data(
- {error: error.message},
- {
- status: 400,
- },
- );
+ return data({error: error.message}, {status: 400});
}
- return data(
- {error},
- {
- status: 400,
- },
- );
+ return data({error}, {status: 400});
}
}
@@ -291,21 +245,17 @@ function NewAddressForm() {
address2: '',
city: '',
company: '',
- territoryCode: '',
+ country: '',
firstName: '',
id: 'new',
lastName: '',
- phoneNumber: '',
- zoneCode: '',
+ phone: '',
+ province: '',
zip: '',
- } as CustomerAddressInput;
+ } as AddressFragment;
return (
- <AddressForm
- addressId={'NEW_ADDRESS_ID'}
- address={newAddress}
- defaultAddress={null}
- >
+ <AddressForm address={newAddress} defaultAddress={null}>
{({stateForMethod}) => (
<div>
<button
@@ -331,7 +281,6 @@ function ExistingAddresses({
{addresses.nodes.map((address) => (
<AddressForm
key={address.id}
- addressId={address.id}
address={address}
defaultAddress={defaultAddress}
>
@@ -360,26 +309,26 @@ function ExistingAddresses({
}
export function AddressForm({
- addressId,
address,
defaultAddress,
children,
}: {
- addressId: AddressFragment['id'];
- address: CustomerAddressInput;
- defaultAddress: CustomerFragment['defaultAddress'];
children: (props: {
- stateForMethod: (method: 'PUT' | 'POST' | 'DELETE') => Fetcher['state'];
+ stateForMethod: (
+ method: 'PUT' | 'POST' | 'DELETE',
+ ) => ReturnType<typeof useNavigation>['state'];
}) => React.ReactNode;
+ defaultAddress: CustomerFragment['defaultAddress'];
+ address: AddressFragment;
}) {
const {state, formMethod} = useNavigation();
const action = useActionData<ActionResponse>();
- const error = action?.error?.[addressId];
- const isDefaultAddress = defaultAddress?.id === addressId;
+ const error = action?.error?.[address.id];
+ const isDefaultAddress = defaultAddress?.id === address.id;
return (
- <Form id={addressId}>
+ <Form id={address.id}>
<fieldset>
- <input type="hidden" name="addressId" defaultValue={addressId} />
+ <input type="hidden" name="addressId" defaultValue={address.id} />
<label htmlFor="firstName">First name*</label>
<input
aria-label="First name"
@@ -444,13 +393,13 @@ export function AddressForm({
required
type="text"
/>
- <label htmlFor="zoneCode">State / Province*</label>
+ <label htmlFor="province">State / Province*</label>
<input
- aria-label="State/Province"
+ aria-label="State"
autoComplete="address-level1"
- defaultValue={address?.zoneCode ?? ''}
- id="zoneCode"
- name="zoneCode"
+ defaultValue={address?.province ?? ''}
+ id="province"
+ name="province"
placeholder="State / Province"
required
type="text"
@@ -466,25 +415,24 @@ export function AddressForm({
required
type="text"
/>
- <label htmlFor="territoryCode">Country Code*</label>
+ <label htmlFor="country">Country*</label>
<input
- aria-label="territoryCode"
- autoComplete="country"
- defaultValue={address?.territoryCode ?? ''}
- id="territoryCode"
- name="territoryCode"
+ aria-label="Country"
+ autoComplete="country-name"
+ defaultValue={address?.country ?? ''}
+ id="country"
+ name="country"
placeholder="Country"
required
type="text"
- maxLength={2}
/>
- <label htmlFor="phoneNumber">Phone</label>
+ <label htmlFor="phone">Phone</label>
<input
- aria-label="Phone Number"
+ aria-label="Phone"
autoComplete="tel"
- defaultValue={address?.phoneNumber ?? ''}
- id="phoneNumber"
- name="phoneNumber"
+ defaultValue={address?.phone ?? ''}
+ id="phone"
+ name="phone"
placeholder="+16135551111"
pattern="^\+?[1-9]\d{3,14}$"
type="tel"
@@ -514,3 +462,98 @@ export function AddressForm({
</Form>
);
}
+
+// NOTE: https://shopify.dev/docs/api/storefront/2023-04/mutations/customeraddressupdate
+const UPDATE_ADDRESS_MUTATION = `#graphql
+ mutation customerAddressUpdate(
+ $address: MailingAddressInput!
+ $customerAccessToken: String!
+ $id: ID!
+ $country: CountryCode
+ $language: LanguageCode
+ ) @inContext(country: $country, language: $language) {
+ customerAddressUpdate(
+ address: $address
+ customerAccessToken: $customerAccessToken
+ id: $id
+ ) {
+ customerAddress {
+ id
+ }
+ customerUserErrors {
+ code
+ field
+ message
+ }
+ }
+ }
+` as const;
+
+// NOTE: https://shopify.dev/docs/api/storefront/latest/mutations/customerAddressDelete
+const DELETE_ADDRESS_MUTATION = `#graphql
+ mutation customerAddressDelete(
+ $customerAccessToken: String!,
+ $id: ID!,
+ $country: CountryCode,
+ $language: LanguageCode
+ ) @inContext(country: $country, language: $language) {
+ customerAddressDelete(customerAccessToken: $customerAccessToken, id: $id) {
+ customerUserErrors {
+ code
+ field
+ message
+ }
+ deletedCustomerAddressId
+ }
+ }
+` as const;
+
+// NOTE: https://shopify.dev/docs/api/storefront/latest/mutations/customerdefaultaddressupdate
+const UPDATE_DEFAULT_ADDRESS_MUTATION = `#graphql
+ mutation customerDefaultAddressUpdate(
+ $addressId: ID!
+ $customerAccessToken: String!
+ $country: CountryCode
+ $language: LanguageCode
+ ) @inContext(country: $country, language: $language) {
+ customerDefaultAddressUpdate(
+ addressId: $addressId
+ customerAccessToken: $customerAccessToken
+ ) {
+ customer {
+ defaultAddress {
+ id
+ }
+ }
+ customerUserErrors {
+ code
+ field
+ message
+ }
+ }
+ }
+` as const;
+
+// NOTE: https://shopify.dev/docs/api/storefront/latest/mutations/customeraddresscreate
+const CREATE_ADDRESS_MUTATION = `#graphql
+ mutation customerAddressCreate(
+ $address: MailingAddressInput!
+ $customerAccessToken: String!
+ $country: CountryCode
+ $language: LanguageCode
+ ) @inContext(country: $country, language: $language) {
+ customerAddressCreate(
+ address: $address
+ customerAccessToken: $customerAccessToken
+ ) {
+ customerAddress {
+ id
+ }
+ customerUserErrors {
+ code
+ field
+ message
+ }
+ }
+ }
+` as const;