diff --git a/app/graphql/customer-account/CustomerAddressMutations.ts b/app/graphql/customer-account/CustomerAddressMutations.ts new file mode 100644 index 0000000..b6c6c53 --- /dev/null +++ b/app/graphql/customer-account/CustomerAddressMutations.ts @@ -0,0 +1,58 @@ +// NOTE: https://shopify.dev/docs/api/customer/latest/mutations/customerAddressUpdate +export const UPDATE_ADDRESS_MUTATION = `#graphql + mutation customerAddressUpdate( + $address: CustomerAddressInput! + $addressId: ID! + $defaultAddress: Boolean + ) { + customerAddressUpdate( + address: $address + addressId: $addressId + defaultAddress: $defaultAddress + ) { + userErrors { + code + field + message + } + } + } +` as const; + +// NOTE: https://shopify.dev/docs/api/customer/latest/mutations/customerAddressDelete +export const DELETE_ADDRESS_MUTATION = `#graphql + mutation customerAddressDelete( + $addressId: ID!, + ) { + customerAddressDelete(addressId: $addressId) { + deletedAddressId + userErrors { + code + field + message + } + } + } +` as const; + +// NOTE: https://shopify.dev/docs/api/customer/latest/mutations/customerAddressCreate +export const CREATE_ADDRESS_MUTATION = `#graphql + mutation customerAddressCreate( + $address: CustomerAddressInput! + $defaultAddress: Boolean + ) { + customerAddressCreate( + address: $address + defaultAddress: $defaultAddress + ) { + customerAddress { + id + } + userErrors { + code + field + message + } + } + } +` as const; diff --git a/app/graphql/customer-account/CustomerDetailsQuery.tsx b/app/graphql/customer-account/CustomerDetailsQuery.tsx new file mode 100644 index 0000000..05ed3a4 --- /dev/null +++ b/app/graphql/customer-account/CustomerDetailsQuery.tsx @@ -0,0 +1,83 @@ +const CUSTOMER_FRAGMENT = `#graphql + fragment OrderCard on Order { + id + number + processedAt + financialStatus + fulfillments(first: 1) { + nodes { + status + } + } + totalPrice { + amount + currencyCode + } + lineItems(first: 2) { + edges { + node { + title + image { + altText + height + url + width + } + } + } + } + } + + fragment AddressPartial on CustomerAddress { + id + formatted + firstName + lastName + company + address1 + address2 + territoryCode + zoneCode + city + zip + phoneNumber + } + + fragment CustomerDetails on Customer { + firstName + lastName + phoneNumber { + phoneNumber + } + emailAddress { + emailAddress + } + defaultAddress { + ...AddressPartial + } + addresses(first: 6) { + edges { + node { + ...AddressPartial + } + } + } + orders(first: 250, sortKey: PROCESSED_AT, reverse: true) { + edges { + node { + ...OrderCard + } + } + } + } +` as const; + +// NOTE: https://shopify.dev/docs/api/customer/latest/queries/customer +export const CUSTOMER_DETAILS_QUERY = `#graphql + query CustomerDetails { + customer { + ...CustomerDetails + } + } + ${CUSTOMER_FRAGMENT} +` as const; diff --git a/app/graphql/customer-account/CustomerOrderQuery.ts b/app/graphql/customer-account/CustomerOrderQuery.ts new file mode 100644 index 0000000..a340520 --- /dev/null +++ b/app/graphql/customer-account/CustomerOrderQuery.ts @@ -0,0 +1,87 @@ +// NOTE: https://shopify.dev/docs/api/customer/latest/queries/order +export const CUSTOMER_ORDER_QUERY = `#graphql + fragment OrderMoney on MoneyV2 { + amount + currencyCode + } + fragment DiscountApplication on DiscountApplication { + value { + __typename + ... on MoneyV2 { + ...OrderMoney + } + ... on PricingPercentageValue { + percentage + } + } + } + fragment OrderLineItemFull on LineItem { + id + title + quantity + price { + ...OrderMoney + } + discountAllocations { + allocatedAmount { + ...OrderMoney + } + discountApplication { + ...DiscountApplication + } + } + totalDiscount { + ...OrderMoney + } + image { + altText + height + url + id + width + } + variantTitle + } + fragment Order on Order { + id + name + statusPageUrl + processedAt + fulfillments(first: 1) { + nodes { + status + } + } + totalTax { + ...OrderMoney + } + totalPrice { + ...OrderMoney + } + subtotal { + ...OrderMoney + } + shippingAddress { + name + formatted(withName: true) + formattedArea + } + discountApplications(first: 100) { + nodes { + ...DiscountApplication + } + } + lineItems(first: 100) { + nodes { + ...OrderLineItemFull + } + } + } + query Order($orderId: ID!) { + order(id: $orderId) { + ... on Order { + ...Order + } + } + } +` as const; diff --git a/app/graphql/customer-account/CustomerUpdateMutation.tsx b/app/graphql/customer-account/CustomerUpdateMutation.tsx new file mode 100644 index 0000000..02e2f7f --- /dev/null +++ b/app/graphql/customer-account/CustomerUpdateMutation.tsx @@ -0,0 +1,11 @@ +export const CUSTOMER_UPDATE_MUTATION = `#graphql +mutation customerUpdate($customer: CustomerUpdateInput!) { + customerUpdate(input: $customer) { + userErrors { + code + field + message + } + } +} +`; diff --git a/app/routes/($locale).account.$.tsx b/app/routes/($locale).account.$.tsx new file mode 100644 index 0000000..acda30e --- /dev/null +++ b/app/routes/($locale).account.$.tsx @@ -0,0 +1,13 @@ +import {redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; + +// fallback wild card for all unauthenticated routes in account section +export async function loader({context, params}: LoaderFunctionArgs) { + await context.customerAccount.handleAuthStatus(); + + const locale = params.locale; + return redirect(locale ? `/${locale}/account` : '/account', { + headers: { + 'Set-Cookie': await context.session.commit(), + }, + }); +} diff --git a/app/routes/($locale).account_.authorize.ts b/app/routes/($locale).account_.authorize.ts new file mode 100644 index 0000000..492e429 --- /dev/null +++ b/app/routes/($locale).account_.authorize.ts @@ -0,0 +1,5 @@ +import type {LoaderFunctionArgs} from '@shopify/remix-oxygen'; + +export async function loader({context, params}: LoaderFunctionArgs) { + return context.customerAccount.authorize(); +} diff --git a/app/routes/($locale).account_.login.tsx b/app/routes/($locale).account_.login.tsx new file mode 100644 index 0000000..87682e5 --- /dev/null +++ b/app/routes/($locale).account_.login.tsx @@ -0,0 +1,5 @@ +import type {LoaderFunctionArgs} from '@shopify/remix-oxygen'; + +export async function loader({params, request, context}: LoaderFunctionArgs) { + return context.customerAccount.login(); +} diff --git a/app/routes/($locale).account_.logout.ts b/app/routes/($locale).account_.logout.ts new file mode 100644 index 0000000..9eb0203 --- /dev/null +++ b/app/routes/($locale).account_.logout.ts @@ -0,0 +1,20 @@ +import { + redirect, + type ActionFunction, + type AppLoadContext, + type LoaderFunctionArgs, + type ActionFunctionArgs, +} from '@shopify/remix-oxygen'; + +export async function doLogout(context: AppLoadContext) { + return context.customerAccount.logout(); +} + +export async function loader({params}: LoaderFunctionArgs) { + const locale = params.locale; + return redirect(locale ? `/${locale}` : '/'); +} + +export const action: ActionFunction = async ({context}: ActionFunctionArgs) => { + return doLogout(context); +};