Skip to content

Commit

Permalink
Add a customer account api example (#1126)
Browse files Browse the repository at this point in the history
  • Loading branch information
blittle authored Jul 25, 2023
1 parent 2c3f9e7 commit 9482bc5
Show file tree
Hide file tree
Showing 19 changed files with 19,279 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .changeset/rich-ants-vanish.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@shopify/hydrogen': patch
---

Add an example using the new [Customer Account API](https://shopify.dev/docs/api/customer)
38 changes: 38 additions & 0 deletions examples/customer-api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Hydrogen Customer API Example

**Caution: The Customer API and this example are both in an unstable pre-release state and may have breaking changes in a future release.**

This is an example using the new Shopify [Customer Accounts API](https://shopify.dev/docs/api/customer). Note this functionality is in dev preview, the API is subject to change, and should not be used in production.

## Requirements

1. Hydrogen 2023.7 or later
2. [Ngrok](https://ngrok.com/) for pointing a public https domain to your local machine required for oAuth

## Environment variables

Create a `.env` file at the root of your project

```toml
PUBLIC_CUSTOMER_ACCOUNT_ID=shp_<your-id>
PUBLIC_CUSTOMER_ACCOUNT_URL=https://shopify.com/<your-url-id>
PUBLIC_STOREFRONT_API_TOKEN=<your-storefront-api-token>
PUBLIC_STORE_DOMAIN=<your-store>.myshopify.com
SESSION_SECRET=foobar
```

## Setup

1. Setup a [ngrok](https://ngrok.com/) account and add a permanent domain.
2. Add the `Hydrogen` or `Headless` app/channel to your store via the Shopify admin
3. Create a storefront if one doesn't exist
4. Access the `Customer Account API` settings via the storefront settings page
5. Copy the permanent domain from ngrok and add it as a `callback URI`: `https://your-ngrok-domain.app/authorize`
6. Add a `JavaScript origin` with your ngrok domain: `https://your-ngrok-domain.app`
7. Add a logout URI to your ngrok domain: `https://your-ngrok-domain.app`
8. Copy the `Client ID` from the Customer Account API credentials to the `.env` `PUBLIC_CUSTOMER_ACCOUNT_ID` variable
9. Copy the Customer Account API url to the `.env` `PUBLIC_CUSTOMER_ACCOUNT_URL` variable
10. Update the ngrok npm script within `package.json` to use your ngrok domain
11. Install the [ngrok CLI](https://ngrok.com/download)
12. In a terminal start ngrok with `npm run ngrok`
13. In another terminal, start hydrogen with `npm run dev`
12 changes: 12 additions & 0 deletions examples/customer-api/app/entry.client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import {RemixBrowser} from '@remix-run/react';
import {startTransition, StrictMode} from 'react';
import {hydrateRoot} from 'react-dom/client';

startTransition(() => {
hydrateRoot(
document,
<StrictMode>
<RemixBrowser />
</StrictMode>,
);
});
33 changes: 33 additions & 0 deletions examples/customer-api/app/entry.server.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type {EntryContext} from '@shopify/remix-oxygen';
import {RemixServer} from '@remix-run/react';
import isbot from 'isbot';
import {renderToReadableStream} from 'react-dom/server';

export default async function handleRequest(
request: Request,
responseStatusCode: number,
responseHeaders: Headers,
remixContext: EntryContext,
) {
const body = await renderToReadableStream(
<RemixServer context={remixContext} url={request.url} />,
{
signal: request.signal,
onError(error) {
// eslint-disable-next-line no-console
console.error(error);
responseStatusCode = 500;
},
},
);

if (isbot(request.headers.get('user-agent'))) {
await body.allReady;
}

responseHeaders.set('Content-Type', 'text/html');
return new Response(body, {
headers: responseHeaders,
status: responseStatusCode,
});
}
67 changes: 67 additions & 0 deletions examples/customer-api/app/root.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import {type LinksFunction, type LoaderArgs} from '@shopify/remix-oxygen';
import {
Links,
Meta,
Outlet,
Scripts,
ScrollRestoration,
useLoaderData,
} from '@remix-run/react';
import type {Shop} from '@shopify/hydrogen/storefront-api-types';
import styles from './styles/app.css';
import favicon from '../public/favicon.svg';

export const links: LinksFunction = () => {
return [
{rel: 'stylesheet', href: styles},
{
rel: 'preconnect',
href: 'https://cdn.shopify.com',
},
{
rel: 'preconnect',
href: 'https://shop.app',
},
{rel: 'icon', type: 'image/svg+xml', href: favicon},
];
};

export async function loader({context}: LoaderArgs) {
const layout = await context.storefront.query<{shop: Shop}>(LAYOUT_QUERY);
return {layout};
}

export default function App() {
const data = useLoaderData<typeof loader>();

const {name} = data.layout.shop;

return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<Meta />
<Links />
</head>
<body>
<h1 style={{marginBottom: 14}}>Customer API Example</h1>
<p style={{marginBottom: 14}}>
This is an example of Hydrogen using the Customer API
</p>
<Outlet />
<ScrollRestoration />
<Scripts />
</body>
</html>
);
}

const LAYOUT_QUERY = `#graphql
query layout {
shop {
name
description
}
}
`;
65 changes: 65 additions & 0 deletions examples/customer-api/app/routes/_index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import {Form, useLoaderData, useRouteError} from '@remix-run/react';
import {type LoaderArgs, json} from '@shopify/remix-oxygen';

export async function loader({context}: LoaderArgs) {
if (await context.customer.isLoggedIn()) {
const user = await context.customer.query(`
{
personalAccount {
firstName
lastName
}
}
`);

return json({
user,
});
}
return json({user: null});
}

export function ErrorBoundary() {
const error = useRouteError() as Error;
return (
<>
<h2>
<b>Error loading the user:</b>
</h2>
<p>{error.message}</p>

<Form method="post" action="/logout" style={{marginTop: 24}}>
<button>Logout</button>
</Form>
</>
);
}

export default function () {
const {user} = useLoaderData();

return (
<div style={{marginTop: 24}}>
{user ? (
<>
<div style={{marginBottom: 24}}>
<b>
Welcome {user.personalAccount.firstName}{' '}
{user.personalAccount.lastName}
</b>
</div>
<div>
<Form method="post" action="/logout">
<button>Logout</button>
</Form>
</div>
</>
) : null}
{!user ? (
<Form method="post" action="/authorize">
<button>Login</button>
</Form>
) : null}
</div>
);
}
9 changes: 9 additions & 0 deletions examples/customer-api/app/routes/authorize.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {ActionArgs, LoaderArgs} from '@shopify/remix-oxygen';

export async function action({context}: ActionArgs) {
return context.customer.login();
}

export async function loader({context}: LoaderArgs) {
return context.customer.authorize('/');
}
5 changes: 5 additions & 0 deletions examples/customer-api/app/routes/logout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import {ActionArgs} from '@shopify/remix-oxygen';

export async function action({context}: ActionArgs) {
return context.customer.logout();
}
31 changes: 31 additions & 0 deletions examples/customer-api/app/styles/app.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
body {
margin: 0;
background: #ffffff;
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
padding: 20px;
}

h1,
h2,
p {
margin: 0;
padding: 0;
}

h1 {
font-size: 3rem;
font-weight: 700;
line-height: 1.4;
}

h2 {
font-size: 1.2rem;
font-weight: 700;
line-height: 1.4;
}

p {
font-size: 1rem;
line-height: 1.4;
}
Loading

0 comments on commit 9482bc5

Please sign in to comment.