Skip to content

Commit

Permalink
♻️ the api to pass customerAccount into cart from the server
Browse files Browse the repository at this point in the history
  • Loading branch information
michenly committed Feb 7, 2024
1 parent d63e5a5 commit 64466bb
Show file tree
Hide file tree
Showing 12 changed files with 186 additions and 90 deletions.
2 changes: 1 addition & 1 deletion .changeset/soft-sloths-double.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
---

🐛 Fix issue where customer login does not persist to checkout
✨ Add `customerLoggedIn` option to `cart.get()`. Where a `?logged_in=true` will be added to the checkoutUrl if a customer is logged in.
✨ Add `customerAccount` option to `createCartHandler`. Where a `?logged_in=true` will be added to the checkoutUrl for cart query if a customer is logged in.
132 changes: 109 additions & 23 deletions packages/hydrogen/docs/generated/generated_docs_data.json

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions packages/hydrogen/src/cart/createCartHandler.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {Storefront} from '../storefront';
import type {CustomerAccount} from '../customer/types';
import {type CartGetFunction, cartGetDefault} from './queries/cartGetDefault';
import {
type CartCreateFunction,
Expand Down Expand Up @@ -47,6 +48,7 @@ import {

export type CartHandlerOptions = {
storefront: Storefront;
customerAccount?: CustomerAccount;
getCartId: () => string | undefined;
setCartId: (cartId: string) => Headers;
cartQueryFragment?: string;
Expand Down Expand Up @@ -97,6 +99,7 @@ export function createCartHandler<TCustomMethods extends CustomMethodsBase>(
getCartId,
setCartId,
storefront,
customerAccount,
cartQueryFragment,
cartMutateFragment,
} = options;
Expand All @@ -113,6 +116,7 @@ export function createCartHandler<TCustomMethods extends CustomMethodsBase>(
const methods: HydrogenCart = {
get: cartGetDefault({
storefront,
customerAccount,
getCartId,
cartFragment: cartQueryFragment,
}),
Expand Down
57 changes: 19 additions & 38 deletions packages/hydrogen/src/cart/queries/cartGetDefault.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {describe, it, expect} from 'vitest';
import {cartGetDefault} from './cartGetDefault';
import {CART_ID, mockCreateStorefrontClient} from '../cart-test-helper';
import type {CustomerAccount} from '../../customer/types';

describe('cartGetDefault', () => {
it('should return a default cart get implementation', async () => {
Expand Down Expand Up @@ -41,61 +42,41 @@ describe('cartGetDefault', () => {
expect(result.query).toContain(cartFragment);
});

describe('run with cart options', () => {
it('should return a cartId passed in', async () => {
const cartGet = cartGetDefault({
storefront: mockCreateStorefrontClient(),
getCartId: () => CART_ID,
});

const result = await cartGet({cartId: 'gid://shopify/Cart/c1-456'});

expect(result).toHaveProperty('id', 'gid://shopify/Cart/c1-456');
});

it('should add logged_in search param to checkout link if customerLoggedIn = true', async () => {
const cartGet = cartGetDefault({
storefront: mockCreateStorefrontClient(),
getCartId: () => CART_ID,
});

const result = await cartGet({customerLoggedIn: true});

expect(result?.checkoutUrl).toContain('logged_in=true');
it('should return a cartId passed in', async () => {
const cartGet = cartGetDefault({
storefront: mockCreateStorefrontClient(),
getCartId: () => CART_ID,
});

it('should NOT add logged_in search param to checkout link if customerLoggedIn = false', async () => {
const cartGet = cartGetDefault({
storefront: mockCreateStorefrontClient(),
getCartId: () => CART_ID,
});

const result = await cartGet({customerLoggedIn: false});
const result = await cartGet({cartId: 'gid://shopify/Cart/c1-456'});

expect(result?.checkoutUrl).not.toContain('logged_in=true');
});
expect(result).toHaveProperty('id', 'gid://shopify/Cart/c1-456');
});

it('should add logged_in search param to checkout link if customerLoggedIn resolved to true', async () => {
describe('run with customerAccount option', () => {
it('should add logged_in search param to checkout link if customer is logged in', async () => {
const cartGet = cartGetDefault({
storefront: mockCreateStorefrontClient(),
customerAccount: {
isLoggedIn: () => Promise.resolve(true),
} as CustomerAccount,
getCartId: () => CART_ID,
});

const result = await cartGet({
customerLoggedIn: Promise.resolve(true),
});

const result = await cartGet();
expect(result?.checkoutUrl).toContain('logged_in=true');
});

it('should NOT add logged_in search param to checkout link if customerLoggedIn resolved to false', async () => {
it('should NOT add logged_in search param to checkout link if customer is NOT logged in', async () => {
const cartGet = cartGetDefault({
storefront: mockCreateStorefrontClient(),
customerAccount: {
isLoggedIn: () => Promise.resolve(false),
} as CustomerAccount,
getCartId: () => CART_ID,
});

const result = await cartGet({customerLoggedIn: Promise.resolve(false)});

const result = await cartGet();
expect(result?.checkoutUrl).not.toContain('logged_in=true');
});
});
Expand Down
46 changes: 25 additions & 21 deletions packages/hydrogen/src/cart/queries/cartGetDefault.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {StorefrontApiErrors, formatAPIResult} from '../../storefront';
import type {CustomerAccount} from '../../customer/types';
import type {CartQueryOptions, CartReturn} from './cart-types';
import type {
Cart,
Expand Down Expand Up @@ -29,43 +30,46 @@ type CartGetProps = {
numCartLines?: number;
};

type CartGetOptions = CartGetProps & {
customerLoggedIn?: Promise<boolean> | boolean;
};

export type CartGetFunction = (
cartGetOptions?: CartGetOptions,
cartInput?: CartGetProps,
) => Promise<CartReturn | null>;

export function cartGetDefault(options: CartQueryOptions): CartGetFunction {
return async (cartGetOptions?: CartGetOptions) => {
const cartId = options.getCartId();
type CartGetOptions = CartQueryOptions & {
/**
* The customer account client instance created by [`createCustomerAccountClient`](docs/api/hydrogen/latest/utilities/createcustomeraccountclient).
*/
customerAccount?: CustomerAccount;
};

export function cartGetDefault({
storefront,
customerAccount,
getCartId,
cartFragment,
}: CartGetOptions): CartGetFunction {
return async (cartInput?: CartGetProps) => {
const cartId = getCartId();

if (!cartId) return null;

const {customerLoggedIn, ...cartInput} = cartGetOptions || {};

const [isCustomerLoggedIn, {cart, errors}] = await Promise.all([
customerLoggedIn,
options.storefront.query<{
customerAccount ? customerAccount.isLoggedIn() : false,
storefront.query<{
cart: Cart;
errors: StorefrontApiErrors;
}>(CART_QUERY(options.cartFragment), {
}>(CART_QUERY(cartFragment), {
variables: {
cartId,
...cartInput,
},
cache: options.storefront.CacheNone(),
cache: storefront.CacheNone(),
}),
]);

const formattedResult = formatAPIResult(cart, errors);

if (isCustomerLoggedIn) {
return addCustomerLoggedInParam(isCustomerLoggedIn, formattedResult);
}

return formattedResult;
return formatAPIResult(
addCustomerLoggedInParam(isCustomerLoggedIn, cart),
errors,
);
};
}

Expand Down
2 changes: 1 addition & 1 deletion templates/demo-store/app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export async function loader({request, context}: LoaderFunctionArgs) {
const layout = await getLayoutData(context);

const isLoggedInPromise = customerAccount.isLoggedIn();
const cartPromise = cart.get({customerLoggedIn: isLoggedInPromise});
const cartPromise = cart.get();

const seo = seoPayload.root({shop: layout.shop, url: request.url});

Expand Down
6 changes: 1 addition & 5 deletions templates/demo-store/app/routes/($locale).cart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@ import {useRootLoaderData} from '~/root';
export async function action({request, context}: ActionFunctionArgs) {
const {cart} = context;

const [formData, customerAccessToken] = await Promise.all([
request.formData(),
context.customerAccount.getAccessToken(),
]);
const formData = await request.formData();

const {action, inputs} = CartForm.getFormInput(formData);
invariant(action, 'No cartAction defined');
Expand Down Expand Up @@ -51,7 +48,6 @@ export async function action({request, context}: ActionFunctionArgs) {
case CartForm.ACTIONS.BuyerIdentityUpdate:
result = await cart.updateBuyerIdentity({
...inputs.buyerIdentity,
customerAccessToken,
});
break;
default:
Expand Down
1 change: 1 addition & 0 deletions templates/demo-store/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export default {

const cart = createCartHandler({
storefront,
customerAccount,
getCartId: cartGetIdDefault(request.headers),
setCartId: cartSetIdDefault(),
});
Expand Down
12 changes: 12 additions & 0 deletions templates/skeleton/app/lib/fragments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,18 @@ export const CART_QUERY_FRAGMENT = `#graphql
id
checkoutUrl
totalQuantity
buyerIdentity {
countryCode
customer {
id
email
firstName
lastName
displayName
}
email
phone
}
lines(first: $numCartLines) {
nodes {
...CartLine
Expand Down
2 changes: 1 addition & 1 deletion templates/skeleton/app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export async function loader({context}: LoaderFunctionArgs) {
const publicStoreDomain = context.env.PUBLIC_STORE_DOMAIN;

const isLoggedInPromise = customerAccount.isLoggedIn();
const cartPromise = cart.get({customerLoggedIn: isLoggedInPromise});
const cartPromise = cart.get();

// defer the footer query (below the fold)
const footerPromise = storefront.query(FOOTER_QUERY, {
Expand Down
1 change: 1 addition & 0 deletions templates/skeleton/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export default {
*/
const cart = createCartHandler({
storefront,
customerAccount,
getCartId: cartGetIdDefault(request.headers),
setCartId: cartSetIdDefault(),
cartQueryFragment: CART_QUERY_FRAGMENT,
Expand Down
11 changes: 11 additions & 0 deletions templates/skeleton/storefrontapi.generated.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,17 @@ export type CartApiQueryFragment = Pick<
StorefrontAPI.Cart,
'id' | 'checkoutUrl' | 'totalQuantity' | 'note'
> & {
buyerIdentity: Pick<
StorefrontAPI.CartBuyerIdentity,
'countryCode' | 'email' | 'phone'
> & {
customer?: StorefrontAPI.Maybe<
Pick<
StorefrontAPI.Customer,
'id' | 'email' | 'firstName' | 'lastName' | 'displayName'
>
>;
};
lines: {
nodes: Array<
Pick<StorefrontAPI.CartLine, 'id' | 'quantity'> & {
Expand Down

0 comments on commit 64466bb

Please sign in to comment.