Michael Bromley 2 лет назад
Родитель
Сommit
39b19cc8b7

+ 0 - 109
docs/docs/guides/storefront/configuring-a-graphql-client.md

@@ -8,112 +8,3 @@ showtoc: true
 
 
 This guide provides examples of how to set up popular GraphQL clients to work with the Vendure Shop API. These examples are designed to work with both the `bearer` and `cookie` methods of [managing sessions]({{< relref "managing-sessions" >}}).
 This guide provides examples of how to set up popular GraphQL clients to work with the Vendure Shop API. These examples are designed to work with both the `bearer` and `cookie` methods of [managing sessions]({{< relref "managing-sessions" >}}).
 
 
-## Apollo Client
-
-Here's an example configuration for [Apollo Client](https://www.apollographql.com/docs/react/) with a React app.
-
-```ts
-import {
-  ApolloClient,
-  ApolloLink, 
-  HttpLink,
-  InMemoryCache,
-} from '@apollo/client'
-import { setContext } from '@apollo/client/link/context'
-
-const AUTH_TOKEN_KEY = 'auth_token';
-
-const httpLink = new HttpLink({
-  uri: `${process.env.NEXT_PUBLIC_URL_SHOP_API}/shop-api`,
-  withCredentials: true,
-});
-
-const afterwareLink = new ApolloLink((operation, forward) => {
-  return forward(operation).map((response) => {
-    const context = operation.getContext();
-    const authHeader = context.response.headers.get('vendure-auth-token');
-    if (authHeader) {
-      // If the auth token has been returned by the Vendure
-      // server, we store it in localStorage  
-      localStorage.setItem(AUTH_TOKEN_KEY, authHeader);
-    }
-    return response;
-  });
-});
-
-const client = new ApolloClient({
-  link: ApolloLink.from([
-    setContext(() => {
-      const authToken = localStorage.getItem(AUTH_TOKEN_KEY)
-      if (authToken) {
-        // If we have stored the authToken from a previous
-        // response, we attach it to all subsequent requests.  
-        return {
-          headers: {
-            authorization: `Bearer ${authToken}`,
-          },
-        }
-      }
-    }),
-    afterwareLink,
-    httpLink,
-  ]),
-  cache: new InMemoryCache(),
-})
-
-export default client;
-```
-
-## Urql
-
-Here's an example using the [urql](https://formidable.com/open-source/urql/) client:
-
-```tsx
-import * as React from "react"
-import { createClient, dedupExchange, fetchExchange, Provider } from "urql"
-import { cacheExchange} from "@urql/exchange-graphcache"
-import { makeOperation} from "@urql/core"
-
-const AUTH_TOKEN_KEY = "auth_token"
-
-const client = createClient({
-  fetch: (input, init) => {
-    const token = localStorage.getItem(AUTH_TOKEN_KEY)
-    if (token) {
-      const headers = input instanceof Request ? input.headers : init.headers;
-      headers['Authorization'] = `Bearer ${token}`;
-    }
-    return fetch(input, init).then(response => {
-      const token = response.headers.get("vendure-auth-token")
-      if (token) {
-        localStorage.setItem(AUTH_TOKEN_KEY, token)
-      }
-      return response
-    })
-  },
-  url: process.env.NEXT_PUBLIC_URL_SHOP_API,
-  exchanges: [
-    dedupExchange,
-    cacheExchange({
-      updates: {
-        Mutation: {
-          addItemToOrder: (parent, args, cache) => {
-            const activeOrder = cache.resolve('Query', 'activeOrder');
-            if (activeOrder == null) {
-              // The first time that the `addItemToOrder` mutation is called in a session,
-              // the `activeOrder` query needs to be manually updated to point to the newly-created
-              // Order type. From then on, the graphcache will handle keeping it up-to-date.
-              cache.link('Query', 'activeOrder', parent.addItemToOrder);
-            }
-          },
-        },
-      },
-    }),
-    fetchExchange,
-  ],
-})
-
-export const App = () => (
-  <Provider value={client}><YourRoutes /></Provider>
-)
-```

+ 318 - 2
docs/docs/guides/storefront/connect-api/index.mdx

@@ -2,6 +2,10 @@
 title: Connect to the API
 title: Connect to the API
 ---
 ---
 
 
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+import Stackblitz from '@site/src/components/Stackblitz';
+
 The first thing you'll need to do is to connect your storefront app to the **Shop API**. The Shop API is a GraphQL API
 The first thing you'll need to do is to connect your storefront app to the **Shop API**. The Shop API is a GraphQL API
 that provides access to the products, collections, customer data, and exposes mutations that allow you to add items to
 that provides access to the products, collections, customer data, and exposes mutations that allow you to add items to
 the cart, checkout, manage customer accounts, and more.
 the cart, checkout, manage customer accounts, and more.
@@ -19,18 +23,26 @@ GraphQL requests are made over HTTP, so you can use any HTTP client such as the
 * [Apollo Client](https://www.apollographql.com/docs/react): A full-featured client which includes a caching layer and React integration.
 * [Apollo Client](https://www.apollographql.com/docs/react): A full-featured client which includes a caching layer and React integration.
 * [urql](https://formidable.com/open-source/urql/): The highly customizable and versatile GraphQL client for React, Svelte, Vue, or plain JavaScript
 * [urql](https://formidable.com/open-source/urql/): The highly customizable and versatile GraphQL client for React, Svelte, Vue, or plain JavaScript
 * [graphql-request](https://github.com/jasonkuhrt/graphql-request): Minimal GraphQL client supporting Node and browsers for scripts or simple apps
 * [graphql-request](https://github.com/jasonkuhrt/graphql-request): Minimal GraphQL client supporting Node and browsers for scripts or simple apps
+* [TanStack Query](https://tanstack.com/query/latest): Powerful asynchronous state management for TS/JS, React, Solid, Vue and Svelte, which can be combined with `graphql-request`.
+
+Here are some examples of how to use these clients to connect to the Shop API:
 
 
-Here are some examples of how to use these clients to connect to the Shop API.
+## Examples
 
 
 ### Fetch
 ### Fetch
 
 
 First we'll look at a plain Fetch-based implementation, to show you that there's no special magic to a GraphQL request - it's just a POST request with a JSON body.
 First we'll look at a plain Fetch-based implementation, to show you that there's no special magic to a GraphQL request - it's just a POST request with a JSON body.
 
 
-```ts
+
+<Tabs>
+<TabItem value="client.ts" label="client.ts" default>
+
+```ts title="src/client.ts"
 export function query(document: string, variables: Record<string, any> = {}) {
 export function query(document: string, variables: Record<string, any> = {}) {
     return fetch('https://localhost:3000/shop-api', {
     return fetch('https://localhost:3000/shop-api', {
         method: 'POST',
         method: 'POST',
         headers: { 'content-type': 'application/json' },
         headers: { 'content-type': 'application/json' },
+        credentials: 'include',
         body: JSON.stringify({
         body: JSON.stringify({
           query: document,
           query: document,
           variables,
           variables,
@@ -40,3 +52,307 @@ export function query(document: string, variables: Record<string, any> = {}) {
       .catch((err) => console.log(err));
       .catch((err) => console.log(err));
 }
 }
 ```
 ```
+
+</TabItem>
+<TabItem value="index.ts" label="index.ts" default>
+
+This function can then be used to make a GraphQL query:
+
+
+```ts title="src/index.ts"
+import { query } from './client';
+
+const document = `
+    query GetProducts($options: ProductListOptions) {
+        products(options: $options) {
+            items {
+                id
+                name
+                slug
+                description
+                featuredAsset {
+                    preview
+                }
+            }
+        }
+    }
+`;
+
+query(document, {
+    options: {
+        take: 10,
+        skip: 0,
+    },
+}).then((res) => console.log(res));
+```
+
+</TabItem>
+<TabItem value="result" label="result">
+
+```json title="console log"
+{
+  "data": {
+    "products": {
+      "items": [
+        {
+          "id": "1",
+          "name": "Laptop",
+          "slug": "laptop",
+          "featuredAsset": {
+            "preview": "https://demo.vendure.io/assets/preview/71/derick-david-409858-unsplash__preview.jpg"
+          }
+        },
+        {
+          "id": "2",
+          "name": "Tablet",
+          "slug": "tablet",
+          "featuredAsset": {
+            "preview": "https://demo.vendure.io/assets/preview/b8/kelly-sikkema-685291-unsplash__preview.jpg"
+          }
+        },
+        {
+          "id": "3",
+          "name": "Wireless Optical Mouse",
+          "slug": "cordless-mouse",
+          "featuredAsset": {
+            "preview": "https://demo.vendure.io/assets/preview/a1/oscar-ivan-esquivel-arteaga-687447-unsplash__preview.jpg"
+          }
+        }
+      ]
+    }
+  }
+}
+```
+
+</TabItem>
+</Tabs>
+
+As you can see, the basic implementation with `fetch` is quite straightforward. It is also lacking some features that other,
+dedicated client libraries will provide.
+
+### Apollo Client
+
+Here's an example configuration for [Apollo Client](https://www.apollographql.com/docs/react/) with a React app.
+
+Follow the [getting started instructions](https://www.apollographql.com/docs/react/get-started) to install the required
+packages.
+
+
+<Tabs>
+<TabItem value="client.ts" label="client.ts" default>
+
+```ts title="src/client.ts"
+import {
+    ApolloClient,
+    ApolloLink,
+    HttpLink,
+    InMemoryCache,
+} from '@apollo/client'
+import { setContext } from '@apollo/client/link/context'
+
+// If using bearer-token based session management, we'll store the token
+// in localStorage using this key.
+const AUTH_TOKEN_KEY = 'auth_token';
+
+const httpLink = new HttpLink({
+    uri: `${process.env.NEXT_PUBLIC_URL_SHOP_API}/shop-api`,
+    // This is required if using cookie-based session management,
+    // so that any cookies get sent with the request.
+    credentials: 'include',
+});
+
+// This part is used to check for and store the session token
+// if it is returned by the server.
+const afterwareLink = new ApolloLink((operation, forward) => {
+    return forward(operation).map((response) => {
+        const context = operation.getContext();
+        const authHeader = context.response.headers.get('vendure-auth-token');
+        if (authHeader) {
+            // If the auth token has been returned by the Vendure
+            // server, we store it in localStorage
+            localStorage.setItem(AUTH_TOKEN_KEY, authHeader);
+        }
+        return response;
+    });
+});
+
+export const client = new ApolloClient({
+    link: ApolloLink.from([
+        // If we have stored the authToken from a previous
+        // response, we attach it to all subsequent requests.
+        setContext(() => {
+            const authToken = localStorage.getItem(AUTH_TOKEN_KEY)
+            if (authToken) {
+                return {
+                    headers: {
+                        authorization: `Bearer ${authToken}`,
+                    },
+                }
+            }
+        }),
+        afterwareLink,
+        httpLink,
+    ]),
+    cache: new InMemoryCache(),
+})
+```
+
+</TabItem>
+<TabItem value='index.tsx' label='index.tsx'>
+
+```tsx title="src/index.tsx"
+import React from 'react';
+import * as ReactDOM from 'react-dom/client';
+import { ApolloProvider } from '@apollo/client';
+import App from './App';
+import { client } from './client';
+
+// Supported in React 18+
+const root = ReactDOM.createRoot(document.getElementById('root'));
+
+root.render(
+    <ApolloProvider client={client}>
+        <App />
+    </ApolloProvider>,
+);
+```
+
+</TabItem>
+<TabItem value="App.tsx" label="App.tsx">
+
+```tsx title="src/App.tsx"
+import { useQuery, gql } from '@apollo/client';
+
+const GET_PRODUCTS = gql`
+    query GetProducts($options: ProductListOptions) {
+        products(options: $options) {
+            items {
+                id
+                name
+                slug
+                featuredAsset {
+                    preview
+                }
+            }
+        }
+    }
+`;
+
+export default function App() {
+    const { loading, error, data } = useQuery(GET_PRODUCTS, {
+        variables: { options: { take: 3 } },
+    });
+
+    if (loading) return <p>Loading...</p>;
+    if (error) return <p>Error : {error.message}</p>;
+
+    return data.products.items.map(({ id, name, slug, featuredAsset }) => (
+        <div key={id}>
+            <h3>{name}</h3>
+            <img width="400" height="250" alt="location-reference" src={`${featuredAsset.preview}?preset=medium`} />
+        </div>
+    ));
+}
+```
+
+</TabItem>
+</Tabs>
+
+
+Here's a live version of this example:
+
+<Stackblitz id='vendure-docs-apollo-client' />
+
+### TanStack Query
+
+Here's an example using [@tanstack/query](https://tanstack.com/query/latest) in combination with [graphql-request](https://github.com/jasonkuhrt/graphql-request) based on [this guide](https://tanstack.com/query/v4/docs/react/graphql):
+
+
+<Tabs>
+<TabItem value="App.tsx" label="App.tsx" default>
+
+```tsx title="src/App.tsx"
+import * as React from 'react';
+import { gql, request } from 'graphql-request';
+import { useQuery } from '@tanstack/react-query';
+
+const GET_PRODUCTS = gql`
+    query GetProducts($options: ProductListOptions) {
+        products(options: $options) {
+            items {
+                id
+                name
+                slug
+                featuredAsset {
+                    preview
+                }
+            }
+        }
+    }
+`;
+
+export default function App() {
+    const { data } = useQuery({
+        queryKey: ['products'],
+        queryFn: async () =>
+            request(
+                'https://demo.vendure.io/shop-api',
+                GET_PRODUCTS,
+                { options: { take: 3 } }
+            ),
+    });
+
+    return data ? (
+        data.products.items.map(({ id, name, slug, featuredAsset }) => (
+            <div key={id}>
+                <h3>{name}</h3>
+                <img
+                    width="400"
+                    height="250"
+                    alt="location-reference"
+                    src={`${featuredAsset.preview}?preset=medium`}
+                />
+            </div>
+        ))
+    ) : (
+        <>Loading...</>
+    );
+}
+
+```
+
+</TabItem>
+
+
+<TabItem value="index.tsx" label="index.tsx" default>
+
+```ts title="src/index.tsx"
+import * as React from 'react';
+import { StrictMode } from 'react';
+import { createRoot } from 'react-dom/client';
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+
+import App from './App';
+
+// Create a client
+const queryClient = new QueryClient();
+
+const rootElement = document.getElementById('root');
+const root = createRoot(rootElement);
+
+root.render(
+    <StrictMode>
+        <QueryClientProvider client={queryClient}>
+            <App />
+        </QueryClientProvider>
+    </StrictMode>
+);
+```
+
+</TabItem>
+</Tabs>
+
+Here's a live version of this example:
+
+<Stackblitz id='vendure-docs-tanstack-query' />
+

+ 1 - 1
docs/sidebars.js

@@ -90,10 +90,10 @@ const sidebars = {
         {
         {
             type: 'category',
             type: 'category',
             label: 'Building a Storefront',
             label: 'Building a Storefront',
-            items: [{ type: 'autogenerated', dirName: 'guides/storefront' }],
             customProps: {
             customProps: {
                 icon: icon.shoppingBag,
                 icon: icon.shoppingBag,
             },
             },
+            items: ['guides/storefront/storefront-starters/index', 'guides/storefront/connect-api/index'],
         },
         },
         {
         {
             type: 'category',
             type: 'category',

+ 13 - 0
docs/src/components/Stackblitz/index.tsx

@@ -0,0 +1,13 @@
+import React from 'react';
+
+export default function Stackblitz(props: { id: string }) {
+    return (
+        <iframe
+            style={{
+                width: '100%',
+                minHeight: '500px',
+            }}
+            src={`https://stackblitz.com/edit/${props.id}?ctl=1&embed=1`}
+        />
+    );
+}