Skip to content

Commit

Permalink
💥 get customer login flow and account/orders landing page working
Browse files Browse the repository at this point in the history
  • Loading branch information
michenly committed Dec 14, 2023
1 parent a58d436 commit 589937e
Show file tree
Hide file tree
Showing 12 changed files with 189 additions and 769 deletions.
2 changes: 1 addition & 1 deletion examples/customer-api/app/routes/authorize.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ export async function action({context}: ActionFunctionArgs) {
}

export async function loader({context}: LoaderFunctionArgs) {
return context.customer.authorize('/');
return context.customer.authorize('/account');
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// https://shopify.dev/docs/api/customer/latest/queries/customer
export const CUSTOMER_DETAILS_QUERY = `#graphql:customer
query Customer {
customer {
firstName
lastName
}
}
` as const;
51 changes: 51 additions & 0 deletions templates/skeleton/app/graphql/customer/CustomerOrdersQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
const ORDER_ITEM_FRAGMENT = `#graphql:customer
fragment OrderItem on Order {
totalPrice {
amount
currencyCode
}
financialStatus
id
number
processedAt
}
` as const;

export const CUSTOMER_FRAGMENT = `#graphql:customer
fragment CustomerOrders on Customer {
orders(
sortKey: PROCESSED_AT,
reverse: true,
first: $first,
last: $last,
before: $startCursor,
after: $endCursor
) {
nodes {
...OrderItem
}
pageInfo {
hasPreviousPage
hasNextPage
endCursor
startCursor
}
}
}
${ORDER_ITEM_FRAGMENT}
` as const;

// https://shopify.dev/docs/api/customer/latest/queries/customer
export const CUSTOMER_ORDERS_QUERY = `#graphql:customer
${CUSTOMER_FRAGMENT}
query CustomerOrders(
$endCursor: String
$first: Int
$last: Int
$startCursor: String
) {
customer {
...CustomerOrders
}
}
` as const;
14 changes: 11 additions & 3 deletions templates/skeleton/app/routes/account.$.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import {redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen';

// fallback wild card for all unauthenticated rouutes in account sectiion
export async function loader({context}: LoaderFunctionArgs) {
if (await context.session.get('customerAccessToken')) {
return redirect('/account');
console.log('\x1b[45m%s\x1b[0m', `-------Account Fallback---------`);

if (!(await context.customerClient.isLoggedIn())) {
console.log(
'\x1b[36m%s\x1b[0m',
`-------Account Fallback login check fail---------`,
);
return redirect('/account/login');
}
return redirect('/account/login');

return redirect('/account');
}
5 changes: 5 additions & 0 deletions templates/skeleton/app/routes/account._index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import {redirect} from '@shopify/remix-oxygen';

export async function loader() {
return redirect('/account/orders');
}
138 changes: 33 additions & 105 deletions templates/skeleton/app/routes/account.orders._index.tsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,47 @@
import {Link, useLoaderData, type MetaFunction} from '@remix-run/react';
import {Money, Pagination, getPaginationVariables} from '@shopify/hydrogen';
import {json, redirect, type LoaderFunctionArgs} from '@shopify/remix-oxygen';
import type {
CustomerOrdersFragment,
OrderItemFragment,
} from 'storefrontapi.generated';
import {CUSTOMER_ORDERS_QUERY} from 'app/graphql/customer/CustomerOrdersQuery';

export const meta: MetaFunction = () => {
return [{title: 'Orders'}];
};

export async function loader({request, context}: LoaderFunctionArgs) {
const {session, storefront} = context;
console.log('\x1b[45m%s\x1b[0m', `-------Account Orders loader---------`);

const customerAccessToken = await session.get('customerAccessToken');
if (!customerAccessToken?.accessToken) {
// TODO: update pagination doc to included storefornt & customer account API
const paginationVariables = getPaginationVariables(request, {
pageBy: 20,
});

if (!(await context.customerClient.isLoggedIn())) {
console.log(
'\x1b[36m%s\x1b[0m',
`-------Account Orders login check fail---------`,
);
return redirect('/account/login');
}

try {
const paginationVariables = getPaginationVariables(request, {
pageBy: 20,
});

const {customer} = await storefront.query(CUSTOMER_ORDERS_QUERY, {
variables: {
customerAccessToken: customerAccessToken.accessToken,
country: storefront.i18n.country,
language: storefront.i18n.language,
...paginationVariables,
console.log(
'\x1b[35m%s\x1b[0m',
`-------use customerClient from account/orders ---------`,
);
const {data, errors} = await context.customerClient.query(
CUSTOMER_ORDERS_QUERY,
{
variables: {
...paginationVariables,
},
},
cache: storefront.CacheNone(),
});
);

if (!customer) {
if (errors?.length || !data?.customer) {
throw new Error('Customer not found');
}

return json({customer});
return json({customer: data.customer});
} catch (error: unknown) {
if (error instanceof Error) {
return json({error: error.message}, {status: 400});
Expand All @@ -47,20 +51,17 @@ export async function loader({request, context}: LoaderFunctionArgs) {
}

export default function Orders() {
const {customer} = useLoaderData<{customer: CustomerOrdersFragment}>();
const {orders, numberOfOrders} = customer;
//TODO add customer account API type
const {customer} = useLoaderData<any>();
const {orders} = customer;
return (
<div className="orders">
<h2>
Orders <small>({numberOfOrders})</small>
</h2>
<br />
{orders.nodes.length ? <OrdersTable orders={orders} /> : <EmptyOrders />}
</div>
);
}

function OrdersTable({orders}: Pick<CustomerOrdersFragment, 'orders'>) {
function OrdersTable({orders}: Pick<any, 'orders'>) {
return (
<div className="acccount-orders">
{orders?.nodes.length ? (
Expand All @@ -71,7 +72,7 @@ function OrdersTable({orders}: Pick<CustomerOrdersFragment, 'orders'>) {
<PreviousLink>
{isLoading ? 'Loading...' : <span>↑ Load previous</span>}
</PreviousLink>
{nodes.map((order) => {
{nodes.map((order: any) => {
return <OrderItem key={order.id} order={order} />;
})}
<NextLink>
Expand Down Expand Up @@ -100,92 +101,19 @@ function EmptyOrders() {
);
}

function OrderItem({order}: {order: OrderItemFragment}) {
function OrderItem({order}: {order: any}) {
return (
<>
<fieldset>
<Link to={`/account/orders/${order.id}`}>
<strong>#{order.orderNumber}</strong>
<strong>#{order.number}</strong>
</Link>
<p>{new Date(order.processedAt).toDateString()}</p>
<p>{order.financialStatus}</p>
<p>{order.fulfillmentStatus}</p>
<Money data={order.currentTotalPrice} />
<Money data={order.totalPrice} />
<Link to={`/account/orders/${btoa(order.id)}`}>View Order →</Link>
</fieldset>
<br />
</>
);
}

const ORDER_ITEM_FRAGMENT = `#graphql
fragment OrderItem on Order {
currentTotalPrice {
amount
currencyCode
}
financialStatus
fulfillmentStatus
id
lineItems(first: 10) {
nodes {
title
variant {
image {
url
altText
height
width
}
}
}
}
orderNumber
customerUrl
statusUrl
processedAt
}
` as const;

export const CUSTOMER_FRAGMENT = `#graphql
fragment CustomerOrders on Customer {
numberOfOrders
orders(
sortKey: PROCESSED_AT,
reverse: true,
first: $first,
last: $last,
before: $startCursor,
after: $endCursor
) {
nodes {
...OrderItem
}
pageInfo {
hasPreviousPage
hasNextPage
endCursor
startCursor
}
}
}
${ORDER_ITEM_FRAGMENT}
` as const;

// NOTE: https://shopify.dev/docs/api/storefront/latest/queries/customer
const CUSTOMER_ORDERS_QUERY = `#graphql
${CUSTOMER_FRAGMENT}
query CustomerOrders(
$country: CountryCode
$customerAccessToken: String!
$endCursor: String
$first: Int
$language: LanguageCode
$last: Int
$startCursor: String
) @inContext(country: $country, language: $language) {
customer(customerAccessToken: $customerAccessToken) {
...CustomerOrders
}
}
` as const;
Loading

0 comments on commit 589937e

Please sign in to comment.