diff --git a/docs/configuring-graphql-client-for-user-session.md b/docs/configuring-graphql-client-for-user-session.md
index 6437cdad..c5f851cb 100644
--- a/docs/configuring-graphql-client-for-user-session.md
+++ b/docs/configuring-graphql-client-for-user-session.md
@@ -7,9 +7,9 @@ author: "Geoff Taylor"
# Configuring a GraphQL Client for WooCommerce User Session Management
-In this comprehensive guide, we'll walk you through the process of configuring a GraphQL client to manage user sessions and credentials when working with WooGraphQL. By following the steps outlined in this tutorial, you'll learn how to create a GraphQL client that maintains a valid WooCommerce session in the `woocommerce_sessions` DB table. This knowledge will enable you to build robust applications that interact smoothly with WooCommerce while providing a seamless experience for your users and shortening development time.
+In this section, we'll walk you through the process of configuring a GraphQL client to manage user sessions and credentials when working with WooGraphQL. By following the steps outlined in this tutorial, you'll learn how to create a GraphQL client that maintains a valid WooCommerce session in the `woocommerce_sessions` DB table. This knowledge will enable you to build robust applications that interact smoothly with WooCommerce while providing a seamless experience for your users and shortening development time.
-By properly handling the session token, you can implement session pass-off functionality, allowing you to fallback on the cart page, my-account page, or any other page living in WordPress that relies on user sessions. Note that implementing the session pass-off functionality is out of the scope of this guide. So, let's dive in and explore the intricacies of setting up a GraphQL client that effectively manages user sessions for your e-commerce store!
+By properly handling the session token, you can implement session pass-off functionality, allowing you to fallback on the cart page, my-account page, or any other page living in WordPress that relies on user sessions. (Note that implementing the session pass-off functionality is out of the scope of this section.) So, let's dive in and explore the intricacies of setting up a GraphQL client that effectively manages user sessions for your e-commerce store!
## Sending the `woocommerce-session` HTTP request header
@@ -52,7 +52,7 @@ fetch(endpoint, {
This works for simple streamlined applications that don't rely heavily on cart functionality. Note that this example also does not retrieve the updated token from the `woocommerce-session` HTTP response header.
-And if you're using a library or framework like Apollo, configuring middleware and afterware layers are required, which makes things more confusing if not explained or demonstrated effectively. In this guide, we'll walk you through setting up the Apollo Client and its middleware/afterware to work with WooGraphQL.
+And if you're using a library or framework like Apollo, configuring middleware and afterware layers are required. In this section, we'll walk you through setting up the Apollo Client and its middleware/afterware to work with WooGraphQL.
## Creating the Apollo Client instance
@@ -74,7 +74,7 @@ const client = new ApolloClient({
});
```
-In the example you see the creation of our `client`. It include middleware/afterware callbacks managed by the `ApolloLink` class. For those not familiar with it, the `ApolloLink` class is allowed you to customize the flow of data by defining you networks behavior as a chain of link object. I'm stating this so you know the order of the callbacks is also important and it will be understood why as we define this callbacks themselves.
+In the example you see the creation of our `client`. It include middleware/afterware callbacks managed by the `ApolloLink` class. For those not familiar with it, the `ApolloLink` class allows you to customize the flow of data by defining your network's behavior as a chain of link objects. I'm stating this so you know the order of the callbacks is also important and it will be understood why as we define these callbacks themselves.
## Defining the `createSessionLink` function
@@ -104,18 +104,18 @@ function createSessionLink() {
And that's our callback for applying the our session token to each request made through our client. Note that I am using the shorthand method of importing the `setContext` function, however most examples you will find will use the `ApolloLink` class directly to define the link object.
```javascript
-mport { ApolloLink } from '@apollo/client';
+import { ApolloLink } from '@apollo/client';
const consoleLink = new ApolloLink((operation, forward) => {
return operation.setContext(/* our callback */);
});
```
-And this works fine too, but is more verbose and kinda overkill if your just make a stateless link like we are here. `Stateless` links are middleware callbacks that don't care to know anything about the context of the operation and just does it own thing regardless of what operation Apollo is about to execute.
+And this works fine too, but it's more verbose and kinda overkill if you're just making a stateless link like we are here. `Stateless` links are middleware callbacks that don't care to know anything about the context of the operation and just does it own thing regardless of what operation Apollo is about to execute.
## About the environment variables
-Before we dive into the guide, it's important to note that the `process.env.*` variables used throughout the tutorial are simply string values stored in an `.env` file and loaded using the [**dotenv**](https://www.npmjs.com/package/dotenv) package. As a reader, you can replace these variables with any values that suit your needs.
+Before we dive into the section, it's important to note that the `process.env.*` variables used throughout the tutorial are simply string values stored in an `.env` file and loaded using the [**dotenv**](https://www.npmjs.com/package/dotenv) package. As a reader, you can replace these variables with any values that suit your needs.
Here's a sample .env file to help you get started:
@@ -141,7 +141,7 @@ export async function getSessionToken(forceFetch = false) {
}
```
-The function is rather simple. It attempt to retrieve the `sessionToken` from `localStorage`, and if that fails or `forceFetch` is passed it fetches a new one using `fetchSessionToken()`. And `fetchSessionToken` is defined.
+The function is rather simple. It attempt to retrieve the `sessionToken` from `localStorage`, and if that fails or `forceFetch` is passed it fetches a new one using `fetchSessionToken()`. And now `fetchSessionToken` is defined.
```javascript
import { GraphQLClient } from 'graphql-request';
@@ -172,7 +172,7 @@ async function fetchSessionToken() {
```
-For this example this works for most case but typically you want the obscure the retrieval of the token and the endpoint from the end-user, especially if dealing with authenticated users. There are a number of a ways to do this like serverless functions or Next.js API routes and they should be doing exactly what is done here retrieve the sessionToken and/or user authentication tokens and nothing else. See the `GetCartDocument` below in `./graphql`.
+This works for most cases but typically you want the obscure the retrieval of the token and the endpoint from the end-user, especially if dealing with authenticated users. There are a number of a ways to do this like serverless functions or Next.js API routes and they should be doing exactly what is done here: retrieve the sessionToken and/or user authentication tokens and nothing else. See the `GetCartDocument` below in `./graphql`.
```javascript
import { gql } from '@apollo/client';
@@ -255,7 +255,7 @@ const targetErrors = [
];
```
-This our the error messages we are targeting. Each are exclusively results of an invalid tokens.
+These are the error messages we are targeting. Each are exclusively results of an invalid tokens.
```javascript
let observable;
@@ -281,10 +281,11 @@ let observable;
})
```
-This is the scary looking part if you are not familar with observables, but don't be. Observables are similar to Promises, but instead of handling a single asynchronous event, they handle multiple events over time. While Promises resolve only once and return a single value, Observables emit multiple values and can be canceled, providing greater control over asynchronous data streams.
-Our usage here is to tell Apollo to retry the last operation after we have retrieved a new token with `getSessionToken` if the current `graphQLError` matches any of our targetted errors, otherwise `observable` is left as a `undefined` value and Apollo continues as normal.
+This is the scary looking part if you are not familar with observables, so let me explain it briefly. Observables are similar to Promises, but instead of handling a single asynchronous event, they handle multiple events over time. While Promises resolve only once and return a single value, Observables emit multiple values and can be canceled, providing greater control over asynchronous data streams.
-Next is the `createUpdateLink` callback, responsible for retrieving an updated `sessionToken` from the `woocommerce-session` HTTP response token. The reason for this is the session token generated by WooGraphQL is self-managing and a new token with an updated expiration time of 14 days from the last action is generated on each request that a `woocommerce-session` HTTP request header is sent. To retrieve a store this updated token we use Apollo afterware.
+Our usage here is to tell Apollo to retry the last operation after we have retrieved a new token with `getSessionToken` if the current `graphQLError` matches any of our targetted errors, otherwise `observable` is left as an `undefined` value and Apollo continues as normal.
+
+Next is the `createUpdateLink` callback, responsible for retrieving an updated `sessionToken` from the `woocommerce-session` HTTP response token. The reason for this is the session token generated by WooGraphQL is self-managing and a new token with an updated expiration time of 14 days from the last action is generated on each request that a `woocommerce-session` HTTP request header is sent. To retrieve and store this updated token we use Apollo afterware.
## Defining the `createUpdateLink` function
@@ -313,16 +314,16 @@ function createUpdateLink(operation, forward) => {
}
```
-This is an our Apollo afterware callback, and if you are wondering how does this differ from Apollo middleware look at the following.
+This is an our Apollo afterware callback, and if you are wondering how does this differ from Apollo middleware, look at the following.
```javascript
return forward(operation).map((response) => {
```
-By calling `.map()` on the result of `forward()`, we're telling Apollo to execute this after operation completion, you can even take it a further by modifying the `response` object if necessary. It is not here, but I figured I should at least state that fact.
+By calling `.map()` on the result of `forward()`, we're telling Apollo to execute this after operation completion, you can even take it a further by modifying the `response` object if necessary. It is not necessary here, but I figured I should at least state that fact.
-We also put after the `createErrorLink` callback in our `from()` call when defining the `ApolloClient` to ensure it's never executed on a request failed due to an invalid token.
+We can also put the `createErrorLink` callback in our `from()` call when defining the `ApolloClient` to ensure it's never executed on a request failed due to an invalid token.
-And with the creation of the `createUpdateLink` link, we now have an Apollo Client that completely manages the WooCommerce session. Note that this doesn't account for all use cases, specifically dealing with registered WooCommerce customers. In such cases, you'll need to use a second JWT for identifying their WordPress account, called an Authentication Token or auth token for short. For handling user authentication, auth tokens, and refresh tokens, refer to the next guide.
+And with the creation of the `createUpdateLink` link, we now have an Apollo Client that completely manages the WooCommerce session. Note that this doesn't account for all use cases, specifically dealing with registered WooCommerce customers. In such cases, you'll need to use a second JWT for identifying their WordPress account, called an Authentication Token or auth token for short. For handling user authentication, auth tokens, and refresh tokens, refer to the next section.
This should provide you with a solid foundation for setting up a GraphQL client that effectively manages user sessions in your e-commerce application. By following the steps outlined, you'll be able to create a seamless experience for your users when interacting with both WooCommerce, ultimately saving development time and effort.
diff --git a/docs/handling-user-authentication.md b/docs/handling-user-authentication.md
index 1466c09a..16f965da 100644
--- a/docs/handling-user-authentication.md
+++ b/docs/handling-user-authentication.md
@@ -7,9 +7,9 @@ author: "Geoff Taylor"
# Handling User Authentication
-In this guide, we'll pick where the last one stopped and focus on handling user authentication, auth tokens, and refresh tokens. This will allow your application to not only manage WooCommerce sessions effectively but also handle WordPress authentication, providing a seamless experience for your users.
+In this section, we'll pick up where the last one stopped and focus on handling user authentication, auth tokens, and refresh tokens. This will allow your application to not only manage WooCommerce sessions effectively but also handle WordPress authentication, providing a seamless experience for your users.
-The execution of this part of the guide should be similar to the first part, with some additional steps to account for the different behavior around validation and renewal of auth tokens. We'll walk you through modifying the `createSessionLink`, `fetchSessionToken`,
+The execution of this section of the documentation should be similar to the [previous section](configuring-graphql-client-for-user-session.md), with some additional steps to account for the different behavior around validation and renewal of auth tokens. We'll walk you through modifying the `createSessionLink`, `fetchSessionToken`,
and `createErrorLink` functions, creating the `getAuthToken` function, and implementing the necessary steps to manage auth token renewal.
## Updating the `createSessionLink` function
@@ -40,11 +40,11 @@ function createSessionLink() {
}
```
-Not too much changing here it's still as simple as it was before except now we're set an `Authorization` header too.
+Not too much changing here - it's still as simple as it was before except now we're setting an `Authorization` header too.
## Creating the `getAuthToken` and `fetchAuthToken` functions.
-Next, we'll create a new function called getAuthToken. This function is similar to the getSessionToken function but has some key differences due to the way session tokens and auth tokens handle renewal. Starting with the following mutation.
+Next, we'll create a new function called `getAuthToken`. This function is similar to the `getSessionToken` function but has some key differences due to the way session tokens and auth tokens handle renewal. Start with the following mutation.
```javascript
import { gql } from '@apollo/client';
@@ -58,13 +58,13 @@ const RefreshAuthTokenDocument = gql`
`;
```
-To help you understand the differences, let's briefly discuss how the session token and auth token handle renewal. As stated in the previous guide session tokens are self-managed and renewed automatically by WooGraphQL when sent within the 14 day limit, and an updated session token is generated on every request. All you have to do is retrieve it. Auth tokens, on the other hand, require you to use the mutation above and the refresh token that's distributed with the auth token to get a new auth token before the auth token expires, which is approximately 15 minutes after creation 😅.
+To help you understand the differences, let's briefly discuss how the session token and auth token handle renewal. As stated in the previous section session tokens are self-managed and renewed automatically by WooGraphQL when sent within the 14 day limit, and an updated session token is generated on every request. All you have to do is retrieve it. Auth tokens, on the other hand, require you to use the mutation above and the refresh token that's distributed with the auth token to get a new auth token before the auth token expires, which is approximately 15 minutes after creation 😅.
```javascript
export function hasCredentials() {
- const authToken = sessionStorage.getItem(process.env.AUTH_TOKEN_LS_KEY);
+ const authToken = sessionStorage.getItem(process.env.AUTH_TOKEN_SS_KEY);
const refreshToken = localStorage.getItem(process.env.REFRESH_TOKEN_LS_KEY);
if (!!authToken && !!refreshToken) {
@@ -75,11 +75,11 @@ export function hasCredentials() {
}
```
-As the name states it all it confirms the existence of the auth and refresh tokens.
+As the name states, this confirms the existence of the auth and refresh tokens.
```javascript
export async function getAuthToken() {
- let authToken = sessionStorage.getItem(process.env.AUTH_TOKEN_LS_KEY );
+ let authToken = sessionStorage.getItem(process.env.AUTH_TOKEN_SS_KEY );
if (!authToken || !tokenSetter) {
authToken = await fetchAuthToken();
}
@@ -87,7 +87,7 @@ export async function getAuthToken() {
}
```
-This should look familiar if you read the previous guide, as it's almost identical `getSessionToken()`, only difference is there is no `forceFetch` option because it's simply not needed.
+This should look familiar if you read the previous section, as it's almost identical `getSessionToken()`, only difference is there is no `forceFetch` option because it's simply not needed.
```javascript
let tokenSetter;
@@ -112,7 +112,7 @@ async function fetchAuthToken() {
}
// Save token.
- sessionStorage.setItem(process.env.AUTH_TOKEN_LS_KEY, authToken);
+ sessionStorage.setItem(process.env.AUTH_TOKEN_SS_KEY, authToken);
if (tokenSetter) {
clearInterval(tokenSetter);
}
@@ -131,14 +131,14 @@ async function fetchAuthToken() {
}
```
-There is a lot going on here, but it's very similar to our `fetchSessionToken()` from the previous guide the different here is the auth token in sessionStorage instead of localStorage, which means it will be deleted when the user closes the browser. A new auth token will be needed every time the user opens the page after closing the browser. To better breakdown the function let's step through the possible outcomes.
+There is a lot going on here, but it's very similar to our `fetchSessionToken()` from the previous section. The difference here is the auth token is in `sessionStorage` instead of `localStorage`, which means it will be deleted when the user closes the browser. A new auth token will be needed every time the user opens the page after closing the browser. To better breakdown the function, let's step through the possible outcomes.
-1. The first being the quiet exit if no `refreshToken` is found. This is the scenario of an unauthenticated user. This is pretty much any new user that show up to your application.
-2. The next one is the error thrown if no `authToken` is returned. This is the scenario of an user with a invalid/expired refresh token, at which case you meant just want to delete the stored refresh token and quietly exit the function.
-3. The error handler is incase anything goes wrong during the `GraphQLClient.query()` call.
-4. And last if nothing goes wrong `tokenSetter` is assigned with a new recurring fetcher set for 5 minute interval and the `authToken` is returned.
+1. A quiet exit if no `refreshToken` is found. This is the scenario of an unauthenticated user. This is pretty much any new user that shows up to your application.
+2. An error thrown if no `authToken` is returned. This is the scenario of an user with a invalid/expired refresh token, in which case you may just want to delete the stored refresh token and quietly exit the function.
+3. The error handler is in case anything goes wrong during the `GraphQLClient.query()` call.
+4. Finally, if nothing goes wrong, `tokenSetter` is assigned with a new recurring fetcher set for 5 minute interval and the `authToken` is returned.
-The purpose of the `tokenSetter` fetcher is to address the short lifespan of the `authToken`. This also ensures that a invalid `authToken` is never sent, and because of this we don't have update the `createErrorLink` or `createUpdateLink` callbacks from the previous guide, but we do have to update our `fetchSessionToken()` function.
+The purpose of the `tokenSetter` fetcher is to address the short lifespan of the `authToken`. This also ensures that a invalid `authToken` is never sent, and because of this we don't have update the `createErrorLink` or `createUpdateLink` callbacks from the previous section, but we do have to update our `fetchSessionToken()` function.
## Updating the `fetchSessionToken()` function
@@ -196,7 +196,7 @@ We'll start by making a quick helper that'll sort our newly obtained credentials
```javascript
function saveCredentials(authToken, sessionToken, refreshToken = null) {
- sessionStorage.setItem(process.env.AUTH_TOKEN_LS_KEY, authToken);
+ sessionStorage.setItem(process.env.AUTH_TOKEN_SS_KEY, authToken);
sessionStorage.setItem(process.env.SESSION_TOKEN_LS_KEY, sessionToken);
if (refreshToken) {
localStorage.setItem(process.env.REFRESH_TOKEN_LS_KEY, refreshToken);
@@ -245,10 +245,10 @@ export async function login(username, password) {
}
```
-Just like with `fetchSessionToken()` is highly recommend the you obscure the API calls here by deferring the logic to something like a serverless function or Next.js API route. Note, we are also return the `customer` object here which could potentially be problematic if sensitive information like the user's email or phone number is being pulled.
+Just like with `fetchSessionToken()`, it is highly recommended that you obscure the API calls here by deferring the logic to something like a serverless function or Next.js API route. Note, we are also return the `customer` object here which could potentially be problematic if sensitive information like the user's email or phone number is being pulled.
## Conclusion
In summary, we demonstrated how to configure a GraphQL client to work with WooGraphQL, manage WooCommerce sessions, and handle WordPress authentication. With this setup, you should be able to create a robust and secure client that manages user authentication efficiently and seamlessly.
-The next guide will begin teaching how you best utilize the data received from WooGraphQL to create showstopping components.
+The next section will begin teaching how you best utilize the data received from WooGraphQL to create showstopping components.
diff --git a/docs/handling-user-session-and-using-cart-mutations.md b/docs/handling-user-session-and-using-cart-mutations.md
index f7509111..9ca63f7e 100644
--- a/docs/handling-user-session-and-using-cart-mutations.md
+++ b/docs/handling-user-session-and-using-cart-mutations.md
@@ -7,27 +7,20 @@ author: "Geoff Taylor"
# Handling User Session and Using Cart Mutations
-In this guide, we will demonstrate how to implement cart controls on the single product page, which will take into account the state of the cart stored in the user session. This guide builds upon the app created in the previous guides, so use the code samples from them as a starting point. The guide is broken down into three parts: The implementation and use of `UserSessionProvider.jsx`, `useCartMutations.js`, and `CartOptions.jsx`.
+In this section, we will demonstrate how to implement cart controls on the single product page, which will take into account the state of the cart stored in the user session. This section builds upon the app created in the previous sections, so use the code samples from those as a starting point. The section is broken down into three parts: The implementation and use of `UserSessionProvider.jsx`, `useCartMutations.js`, and `CartOptions.jsx`.
## Prerequisites
- Basic knowledge of React and React Router.
- Familiarity with GraphQL and WPGraphQL.
-- A setup WPGraphQL/WooGraphQL backend.
-- Read previous guides on [Routing By URI](routing-by-uri.md) and [Using Product Data](using-product.data.md)
+- A WPGraphQL/WooGraphQL backend.
+- Read previous sections on [Routing By URI](routing-by-uri.md) and [Using Product Data](using-product.data.md)
## Step 0: Create our `graphql.js` file
```javascript
import { gql } from '@apollo/client';
-export const CustomerContent = gql`
- fragment CustomerContent on Customer {
- id
- sessionToken
- }
-`;
-
export const ProductContentSlice = gql`
fragment ProductContentSlice on Product {
id
@@ -197,6 +190,8 @@ export const CartItemContent = gql`
value
}
}
+ ${ProductContentSlice}
+ ${ProductVariationContentSlice}
`;
export const CartContent = gql`
@@ -235,14 +230,111 @@ export const CartContent = gql`
discountTax
discountTotal
}
+ ${CartItemContent}
`;
+export const AddressFields = gql`
+ fragment AddressFields on CustomerAddress {
+ firstName
+ lastName
+ company
+ address1
+ address2
+ city
+ state
+ country
+ postcode
+ phone
+ }
+`;
+
+export const LineItemFields = gql`
+ fragment LineItemFields on LineItem {
+ databaseId
+ product {
+ node {
+ ...ProductContentSlice
+ }
+ }
+ orderId
+ quantity
+ subtotal
+ total
+ totalTax
+ }
+ ${ProductContentSlice}
+`;
+
+export const OrderFields = gql`
+ fragment OrderFields on Order {
+ id
+ databaseId
+ orderNumber
+ orderVersion
+ status
+ needsProcessing
+ subtotal
+ paymentMethodTitle
+ total
+ totalTax
+ date
+ dateCompleted
+ datePaid
+ billing {
+ ...AddressFields
+ }
+ shipping {
+ ...AddressFields
+ }
+ lineItems(first: 100) {
+ nodes {
+ ...LineItemFields
+ }
+ }
+ }
+ ${AddressFields}
+ ${LineItemFields}
+`;
+
+export const CustomerFields = gql`
+ fragment CustomerFields on Customer {
+ id
+ databaseId
+ firstName
+ lastName
+ displayName
+ billing {
+ ...AddressFields
+ }
+ shipping {
+ ...AddressFields
+ }
+ orders(first: 100) {
+ nodes {
+ ...OrderFields
+ }
+ }
+ }
+ ${AddressFields}
+ ${OrderFields}
+`;
+
+export const CustomerContent = gql`
+ fragment CustomerContent on Customer {
+ id
+ sessionToken
+ }
+`;
+
+
+
export const GetProduct = gql`
query GetProduct($id: ID!, $idType: ProductIdTypeEnum) {
product(id: $id, idType: $idType) {
...ProductContentFull
}
}
+ ${ProductContentFull}
`;
export const GetProductVariation = gql`
@@ -251,6 +343,7 @@ export const GetProductVariation = gql`
...VariationContent
}
}
+ ${VariationContent}
`;
export const GetCart = gql`
@@ -262,6 +355,8 @@ export const GetCart = gql`
...CustomerContent
}
}
+ ${CartContent}
+ ${CustomerContent}
`;
export const AddToCart = gql`
@@ -277,6 +372,8 @@ export const AddToCart = gql`
}
}
}
+ ${CartContent}
+ ${CartItemContent}
`;
export const UpdateCartItemQuantities = gql`
@@ -290,6 +387,8 @@ export const UpdateCartItemQuantities = gql`
}
}
}
+ ${CartContent}
+ ${CartItemContent}
`;
export const RemoveItemsFromCart = gql`
@@ -303,23 +402,47 @@ export const RemoveItemsFromCart = gql`
}
}
}
+ ${CartContent}
+ ${CartItemContent}
+`;
+export const Login = gql`
+ mutation Login($username: String!, $password: String!) {
+ login(input: { username: $username, password: $password }) {
+ authToken
+ refreshToken
+ customer {
+ ...CustomerFields
+ }
+ }
+ }
+ ${CustomerFields}
`;
+export const UpdateCustomer = gql`
+ mutation UpdateCustomer($input: UpdateCustomerInput!) {
+ updateCustomer(input: $input) {
+ customer {
+ ...CustomerFields
+ }
+ }
+ }
+ ${CustomerFields}
+`;
```
-We've included all the queries will be using going forward and leveraging some fragments here and there. Now we can move onto implementing the components sourcing these queries and mutations.
+We've included all the queries we'll be using going forward and leveraging some fragments here and there. Now we can move onto implementing the components sourcing these queries and mutations.
We won't go over them into much detail here but you can learn more about them in the [schema](/schema) docs.
## Step 1: UserSessionProvider.jsx
-`UserSessionProvider.jsx` is a state manager that queries and maintains the app's copy of the end-user's session state from WooCommerce on the backend. We'll also be implementing a helper hook called `useSession()` that will provide the user session state to components nested within the provider. In order for the `UserSessionProvider` code in our samples to work properly, the end user will have to implement an ApolloClient with a middleware layer configured to manage the WooCommerce session token, like the one demonstrated in our [**Configuring GraphQL Client for User Session**](configuring-graphql-client-for-user-session.md) guide.
+`UserSessionProvider.jsx` is a state manager that queries and maintains the app's copy of the end-user's session state from WooCommerce on the backend. We'll also be implementing a helper hook called `useSession()` that will provide the user session state to components nested within the provider. In order for the `UserSessionProvider` code in our samples to work properly, the end user will have to implement an ApolloClient with a middleware layer configured to manage the WooCommerce session token, like the one demonstrated in our [**Configuring GraphQL Client for User Session**](configuring-graphql-client-for-user-session.md) section.
Here is the code for `UserSessionProvider.jsx`:
```jsx
import { createContext, useContext, useEffect, useReducer } from 'react';
-import { useQuery } from '@apollo/client';
-import { GetCart } from './graphql';
+import { useQuery, useMutation } from '@apollo/client';
+import { GetCart, Login, UpdateCustomer } from './graphql';
const initialSession = {
cart: null,
@@ -351,6 +474,8 @@ export function SessionProvider({ children }) {
const [state, dispatch] = useReducer(reducer, initialSession);
const { data, loading: fetching } = useQuery(GetCart);
+ const [executeLogin, { data: loginData, errors: loginErrors }] = useMutation(Login);
+ const [executeUpdateCustomer, { data: updateCustomerData, errors: updateCustomerErrors }] = useMutation(UpdateCustomer);
useEffect(() => {
if (data?.cart) {
@@ -378,11 +503,44 @@ export function SessionProvider({ children }) {
payload: customer,
});
+ useEffect(() => {
+ if (loginData.login) {
+ const {
+ authToken,
+ refreshToken,
+ customer
+ } = loginData.login;
+
+ sessionStorage.getItem(process.env.AUTH_TOKEN_SS_KEY, authToken);
+ localStorage.getItem(process.env.REFRESH_TOKEN_LS_KEY, refreshToken);
+
+ setCustomer(customer);
+ }
+ }, [loginData]);
+
+ useEffect(() => {
+ if (updateCustomerData.updateCustomer) {
+ const { customer } = updateCustomerData.updateCustomer;
+
+ setCustomer(customer);
+ }
+ }, [updateCustomerData]);
+
+ const login = (username, password) => {
+ return executeLogin({ username, password });
+ }
+
+ const updateCustomer = (input) => {
+ return executeUpdateCustomer({ input });
+ }
+
const store = {
...state,
fetching,
setCart,
setCustomer,
+ login,
+ updateCustomer,
};
return (
{children}
@@ -392,7 +550,7 @@ export function SessionProvider({ children }) {
export const useSession = () => useContext(SessionContext);
```
-To use the `SessionProvider`, you should wrap your root app component with it and wrap the `SessionProvider` with an ApolloProvider set with our session token managing ApolloClient. Make sure to demonstrate this for the reader against our previous code samples from previous posts.
+To use the `SessionProvider`, you should wrap your root app component with it and wrap the `SessionProvider` with an ApolloProvider set with our session token managing ApolloClient.
## Step 2: useCartMutations.js
@@ -536,7 +694,7 @@ With the `useCartMutations` hook implemented, you can use it within your compone
You can now use this hook to create and manage cart interactions in your components. For instance, you can create an "Add to Cart" button that adds items to the cart, updates the quantity of an existing item, or removes an item from the cart.
-Here's an example of how you could use the useCartMutations hook within a React component use our SingleProduct component from the previous guide:
+Here's an example of how you could use the useCartMutations hook within a React component using our SingleProduct component from the previous section:
```jsx
import React, { useEffect, useState } from 'react';
@@ -622,7 +780,7 @@ In this example, we have our `SingleProduct` component that receives a `productI
The `handleAddOrUpdateAction` and `handleRemoveAction` functions call the `mutate` function returned by the `useCartMutations`. The `loading` flag is used to disable the buttons while any cart mutations are in progress.
-This is just an example of how you could use the `useCartMutations` hook and only using simple products, but as I'm sure you noticed it support a `variationId` as the second parameter. Implementing Variable product support in our `SingleProduct` component is out of the scope this guide, but with what has been provided you should have no problem implementing variable product support.
+This is just an example of how you could use the `useCartMutations` hook using simple products, but as I'm sure you noticed, it support a `variationId` as the second parameter. Implementing Variable product support in our `SingleProduct` component is out of the scope this section, but with what has been provided you should have no problem implementing variable product support.
## Conclusion
diff --git a/docs/harmonizing-with-wordpress.md b/docs/harmonizing-with-wordpress.md
index 2e7d093f..8a50e75d 100644
--- a/docs/harmonizing-with-wordpress.md
+++ b/docs/harmonizing-with-wordpress.md
@@ -7,7 +7,7 @@ author: "Geoff Taylor"
# Harmonizing with WordPress
-In our [previous guide](using-cart-data.md), we created a cart page for our WooCommerce store. Now, we are going to add a "Checkout" button to this page and discuss how to ensure the security of the process.
+In our [previous section](using-cart-data.md), we created a cart page for our WooCommerce store. Now, we are going to add a "Checkout" button to this page and discuss how to ensure the security of the process.
Our checkout button's `href` attribute will be a nonced URL that directs the user to a particular endpoint on the WP backend. The endpoint performs a set of operations such as validating the URL, loading the WooCommerce session, authenticating the registered user, and redirecting to the checkout page. If the URL validation fails, the user will be redirected to the WP homepage.
@@ -33,7 +33,7 @@ To create a checkout button in our CartPage component, we can use the `checkoutU
## Understanding URL Validation and Security
-Although our checkout button is now functional, it's important to note that the default URL validation process can be improved in terms of security. In the next part of this guide, we will discuss the security measures and how to enhance them.
+Although our checkout button is now functional, it's important to note that the default URL validation process can be improved in terms of security. In the next part of this section, we will discuss the security measures and how to enhance them.
### Improving Security with Client Session ID
@@ -72,11 +72,11 @@ const input = {
}
```
-When the `client_session_id` or `client_session_id_expiration` values become invalid or expired, WooGraphQL generates new values with an expiration time of one hour. To avoid this, we recommend you to periodically update these values from the client side and retrieve a new `checkoutUrl` each time.
+When the `client_session_id` or `client_session_id_expiration` values become invalid or expired, WooGraphQL generates new values with an expiration time of one hour. To avoid this, we recommend that you periodically update these values from the client side and retrieve a new `checkoutUrl` each time.
## Reinventing Security: The Client-Side Nonce
-Next we're going to explore an advanced approach to enhance the security of our checkout procedure by generating a nonce on the client side. By doing this, and not pulling the Nonces or Auth URLs from WooGraphQL we remove any risk of leakage thru GraphQL request and further protect the end-user's data and the WordPress backend. This process will involve recreating some of PHP and WordPress core functions in JavaScript.
+Next we're going to explore an advanced approach to enhance the security of our checkout procedure by generating a nonce on the client side. By doing this, and not pulling the Nonces or Auth URLs from WooGraphQL we remove any risk of leakage thru GraphQL request and further protect the end-user's data and the WordPress backend. This process will involve recreating some PHP and WordPress core functions in JavaScript.
1. **PHP `time` Function in JavaScript**
@@ -105,7 +105,7 @@ Next we're going to explore an advanced approach to enhance the security of our
3. **WordPress `wp_hash` Function in JavaScript**
- The `wp_hash` function, another WordPress core function, will be adapted to JavaScript as well. This function uses the `wp_salt` function to retrieve the salt from WordPress Salt constants, usually defined in the `wp-config.php` file. In our context, we only need the `nonce` salt. Therefore, it's crucial to ensure the `NONCE_KEY` and `NONCE_SALT` constants are set on the WordPress installation, and their values are accessible in our front-end application.
+ The `wp_hash` function, another WordPress core function, will be adapted to JavaScript as well. This function uses the `wp_salt` function to retrieve the salt from WordPress Salt constants, usually defined in the `wp-config.php` file. In our context, we only need the `nonce` salt. Therefore, it's crucial to ensure the `NONCE_KEY` and `NONCE_SALT` constants are set on the WordPress installation and their values are accessible in our front-end application.
`wp_hash` also uses `hash_hmac` and `md5` encryption to create the hash. For this, we'll utilize `crypto-js`, which you can install with `npm` using the command `npm install crypto-js`. Here's how to write `wp_hash` in JavaScript:
@@ -134,7 +134,7 @@ export function createNonce(action, uId, token) {
In the function above:
- The `action` parameter represents the nonce action name.
-- The `uId` parameter represents the end-user's session ID, either their WP User Database ID (if they are authenticated) or a random string (if they are a guest). To retrieve this value, we'll have to decode the WooCommerce Session Token used by ApolloClient.
+- The `uId` parameter represents the end-user's session ID - either their WP User Database ID (if they are authenticated) or a random string (if they are a guest). To retrieve this value, we'll have to decode the WooCommerce Session Token used by ApolloClient.
- The `token` is our Client Session ID mentioned earlier.
4. **Generating the URLs**
@@ -218,4 +218,4 @@ To confirm the validity of your URL, compare it with the Auth URLs generated by
## Conclusion
-With this guide, you should now be able to add a secure checkout button to your WooCommerce cart page using WooGraphQL. Keep in mind that even though we've improved security by setting a client-side session ID and expiration, these measures should be part of a broader security strategy. Always ensure to follow best practices to keep your application and user data safe.
+With this section, you should now be able to add a secure checkout button to your WooCommerce cart page using WooGraphQL. Keep in mind that even though we've improved security by setting a client-side session ID and expiration, these measures should be part of a broader security strategy. Always ensure to follow best practices to keep your application and user data safe.
diff --git a/docs/images/account-address-screenshot.png b/docs/images/account-address-screenshot.png
new file mode 100644
index 00000000..52380261
Binary files /dev/null and b/docs/images/account-address-screenshot.png differ
diff --git a/docs/images/account-dashboard-to-orders.gif b/docs/images/account-dashboard-to-orders.gif
new file mode 100644
index 00000000..20f256fb
Binary files /dev/null and b/docs/images/account-dashboard-to-orders.gif differ
diff --git a/docs/images/account-details-screenshot.png b/docs/images/account-details-screenshot.png
new file mode 100644
index 00000000..aa015a40
Binary files /dev/null and b/docs/images/account-details-screenshot.png differ
diff --git a/docs/images/login-to-account-dashboard.gif b/docs/images/login-to-account-dashboard.gif
new file mode 100644
index 00000000..6e4ff7fd
Binary files /dev/null and b/docs/images/login-to-account-dashboard.gif differ
diff --git a/docs/images/order-status-page-states.gif b/docs/images/order-status-page-states.gif
new file mode 100644
index 00000000..d9019707
Binary files /dev/null and b/docs/images/order-status-page-states.gif differ
diff --git a/docs/installation.md b/docs/installation.md
index 45b80a32..41d2b2c5 100644
--- a/docs/installation.md
+++ b/docs/installation.md
@@ -7,7 +7,7 @@ keywords: "WooGraphQL, WPGraphQL, WooCommerce, GraphQL, installation, setup, hea
# Installation
-This guide will walk you through the process of installing and configuring WooGraphQL for your WordPress website.
+This section will walk you through the process of installing and configuring WooGraphQL for your WordPress website.
## Prerequisites
diff --git a/docs/routing-by-uri.md b/docs/routing-by-uri.md
index e16757cc..32518682 100644
--- a/docs/routing-by-uri.md
+++ b/docs/routing-by-uri.md
@@ -7,7 +7,7 @@ author: "Geoff Taylor"
# Routing By URI
-In this guide, we will create a simple app that demonstrates routing with WPGraphQL's `nodeByUri` query. We will use this query to fetch data for a shop page that displays a list of products with their "name", "shortDescription", "price", and "image". The shop page will use the uri parameter to fetch the data and render the page accordingly.
+In this section, we will create a simple app that demonstrates routing with WPGraphQL's `nodeByUri` query. We will use this query to fetch data for a shop page that displays a list of products with their "name", "shortDescription", "price", and "image". The shop page will use the uri parameter to fetch the data and render the page accordingly.
## Prerequisites
@@ -120,7 +120,7 @@ The `ShopPage` component fetches the data using the `nodeByUri` query and update
The `ProductListing` component takes the products data and renders a list of products.
-_Notice that we are not checking to see if the fields that are nullable aren't empty values before rendering them and remember you should always do so, we just skipped it here to for readability._
+_Notice that we are not checking to see if the fields that are nullable aren't empty values before rendering them. You should always do so, but we skip it here for readability._
```jsx
import React from 'react';
@@ -148,7 +148,7 @@ export default ProductListing;
With the `ProductListing` component, we can display the product listing for both collection and single data object. This approach can also be applied to other pages such as `/product-category/*` or `/product-tag/*` pages, with the ability to change there slug names as well in the WP Dashboard.
-In the next section, we will focused further on rendering a product listing using the `nodeByUri` query by exploring adding features like pagination, sorting, and filtering to our shop page.
+In the next section, we will focus further on rendering a product listing using the `nodeByUri` query by exploring adding features like pagination, sorting, and filtering to our shop page.
## Pagination
@@ -486,7 +486,7 @@ const ShopPage = () => {
export default ShopPage;
```
-With these changes, you can now search for products by typing in the search input, and the products will be fetched and displayed based on the search query. This is far from complete it needs many more things, like CSS styling, field validation, and error handling to name few.
+With these changes, you can now search for products by typing in the search input, and the products will be fetched and displayed based on the search query. This is far from complete. It needs many more things, like CSS styling, field validation, and error handling to name a few.
## Conclusion
diff --git a/docs/settings.md b/docs/settings.md
index af6beff6..84bab0c6 100644
--- a/docs/settings.md
+++ b/docs/settings.md
@@ -23,13 +23,15 @@ The default WooCommerce User Session Handler is responsible for capturing cart a
## Enable Unsupported types
-The settings is simple to understand and likely to be enabled if your using a WC extension that uses a product type that isn't support by WooGraphQL out-of-box. When enabled it will substitute the missing type with the SimpleProduct type. This way you can still you the product type and possibly pull what extra data you need from the `Product`'s `metaData` field.
+The settings is simple to understand and likely to be enabled if you're using a WC extension that uses a product type that isn't support by WooGraphQL out-of-box. When enabled it will substitute the missing type with the SimpleProduct type. This way you can still use the product type and possibly pull what extra data you need from the `Product`'s `metaData` field.

## Enable User Session transferring URLs
-This settings when enabled activated the WooCommerce Session-backed nonce generator and transfer session endpoint for passing a user's session from a client to the WordPress installation, the primary use of these nonces are to create authorizing URLs that enable the user to travel to the backend as if it were a part of the front-end application. This setting is disabled if the QL Session Handler is disabled as it required for nonce generation to work. The next four settings are all about customizing the names of different parts of the authorizing URL..
+This setting, when activated, enables WooCommerce Session-backed nonce generator and transfer session endpoint for passing a user's session from a client to the WordPress installation. The primary use of these nonces is to create authorizing URLs that enable the user to travel to the backend as if it were a part of the front-end application. This setting is disabled if the QL Session Handler is disabled as it required for nonce generation to work.
+
+The next four settings are all about customizing the names of different parts of the authorizing URL..
### Endpoint for Authorizing URLs
@@ -37,9 +39,9 @@ The endpoint (path) for transferring user sessions on the site. Defaults to `tra
### Cart URL nonce name, Checkout URL nonce name, and Add Payment Method URL nonce name
-The name of the nonce param for each respective URLs. They have to be unique and cannot be identical.
+The name of the nonce param for each respective URL. They have to be unique and cannot be identical.
-Uses these settings alone is very insecure. It's highly recommended that specific measures be taken on the client to further secure the WP backend and end-user's data.
+Using these settings alone is very insecure. It's highly recommended that specific measures be taken on the client to further secure the WP backend and end-user's data.
## WooGraphQL Pro Settings
These settings allow you to enable or disable the GraphQL schema types, queries, and mutations for various WooCommerce extensions supported by WooGraphQL Pro. This is useful if you have one of the supported extensions installed and activated but don't need it exposed to the GraphQL API, keeping the schema lightweight.
diff --git a/docs/using-cart-data.md b/docs/using-cart-data.md
index b55308fd..11d610b0 100644
--- a/docs/using-cart-data.md
+++ b/docs/using-cart-data.md
@@ -7,14 +7,14 @@ author: "Geoff Taylor"
# Using Cart Data
-In this guide, we will create a "/cart" page that displays a table of the items in the cart. We will use the `UserSessionProvider` created in the last guide to pull the cart data using `useSession()`. The table will have four columns: `Product`, `Price`, `Quantity`, and `Total`. We will also create a cart totals table that displays the shipping totals, applied coupons, and the final total. Let's start by implementing the changes to the `useCartMutations` hook also created in the previous guide.
+In this section, we will create a "/cart" page that displays a table of the items in the cart. We will use the `UserSessionProvider` created in the last section to pull the cart data using `useSession()`. The table will have four columns: `Product`, `Price`, `Quantity`, and `Total`. We will also create a cart totals table that displays the shipping totals, applied coupons, and the final total. Let's start by implementing the changes to the `useCartMutations` hook also created in the previous section.
## Prerequisites
- Basic knowledge of React and React Router.
- Familiarity with GraphQL and WPGraphQL.
- A setup WPGraphQL/WooGraphQL backend.
-- Read previous guides on [Routing By URI](routing-by-uri.md), [Using Product Data](using-product-data.md), and [Handling User Session and Using Cart Mutations](handing-user-session-and-using-cart-mutations).
+- Read previous sections on [Routing By URI](routing-by-uri.md), [Using Product Data](using-product-data.md), and [Handling User Session and Using Cart Mutations](handing-user-session-and-using-cart-mutations).
## Step 0: Create `graphql.js` file
@@ -510,7 +510,7 @@ export const useOtherCartMutations = () => {
};
```
-This hook provides the helper callbacks for the other cart mutations. These mutations are the ones that affect the cart and not cart items, at least not directly, like `applyCoupon`.
+This hook provides the helper callbacks for the other cart mutations. These mutations are the ones that effect the cart and not the cart items, at least not directly, like `applyCoupon`.
## Step 2: Create the `/cart` page
@@ -524,7 +524,7 @@ import { ShippingInfo } from './ShippingInfo';
import { ApplyCouponForm } from './ApplyCouponForm';
```
-You should see two import that don't exist yet, let's create them. `ShippingInfo.js` and `ApplyCouponForm.js` are components that will be used in the `CartPage` component to handle two particular actions:
+You should see two imports that don't exist yet. Let's create them. `ShippingInfo.js` and `ApplyCouponForm.js` are components that will be used in the `CartPage` component to handle two particular actions:
First the `ShippingLocaleForm.js`.
@@ -619,7 +619,7 @@ const ShippingInfo = () => {
export default ShippingInfo;
```
-This component works by confirming the session shipping requirements and status before return the proper output. If shipping is needed and a shipping address is set for the customer, the shipping rates are displayed for selection. If shipping is needed and no address is set, then a shipping address form is displayed to set the customer shipping address. If no shipping is needed `null` is returned.
+This component works by confirming the session shipping requirements and status before returning the proper output. If shipping is needed and a shipping address is set for the customer, the shipping rates are displayed for selection. If shipping is needed and no address is set, then a shipping address form is displayed to set the customer shipping address. If no shipping is needed, `null` is returned.
Simple enough, now the `ApplyCoupon.js`
@@ -745,10 +745,10 @@ export default CartPage;
In the `CartPage` component, we first fetch the `cart` from the `SessionProvider`. If the cart is not available, we show a loading message. Once the cart is loaded, we display the cart items in a table format, allowing users to remove items or update the quantity.
-Lastly, we display the cart's subtotal, applied coupons with their respective discounts and removal buttons, and follow that up the cart's total.
+Lastly, we display the cart's subtotal, applied coupons with their respective discounts and removal buttons, and follow that up with the cart's total.
Now, you can use the `CartPage` component in your app, allowing users to interact with the cart, apply coupons, and manage shipping options.
## Conclusion
-With this you're essentially ready to develop a complete application. In the next couple guides we'll be exploring taking the user through checkout by passing the session back to WordPress.
+With this you're essentially ready to develop a complete application. In the next couple sections we'll be exploring taking the user through checkout.
diff --git a/docs/using-checkout-mutation-and-order-mutations.md b/docs/using-checkout-mutation-and-order-mutations.md
index e69de29b..63bdbc29 100644
--- a/docs/using-checkout-mutation-and-order-mutations.md
+++ b/docs/using-checkout-mutation-and-order-mutations.md
@@ -0,0 +1,97 @@
+---
+title: "Using Checkout Mutation + Order Mutations with WooGraphQL"
+description: "Learn how to handle repeat shoppers with existing payment methods using the `checkout` mutation, and side-stepping WooCommerce's management of the session completely by using the `createOrder` mutation."
+keywords: "WooGraphQL, WPGraphQL, WooCommerce, GraphQL, checkout mutation, createOrder mutation, session management"
+author: "Geoff Taylor"
+---
+
+# Using Checkout Mutation and Order Mutations
+
+In this section of the documentation, we will be building upon the knowledge gained from previous sections to explore more advanced functionalities provided by WooGraphQL. Specifically, we will delve into the `checkout` mutation and `createOrder` mutation, and how they can be used to handle repeat shoppers with existing payment methods and manage WooCommerce's session respectively.
+
+The `checkout` mutation allows us to handle repeat shoppers who have existing payment methods. This is particularly useful in creating a seamless shopping experience for your customers, as they do not have to re-enter their payment details every time they shop.
+
+On the other hand, the `createOrder` mutation allows us to bypass WooCommerce's session management completely. This is especially useful in scenarios where you want to have more control over the session management in your application.
+
+In this section, we will provide detailed examples and code snippets to demonstrate how these mutations can be used in a real-world application. We will also discuss potential use cases and best practices for using these mutations.
+
+Before proceeding, it is recommended that you have a good understanding of the basics of WooGraphQL and have gone through the previous sections of this documentation. This will ensure that you have the necessary background knowledge to fully understand the concepts and examples presented in this section.
+
+Let's get started!
+
+## Scenario 1: Handling Checkout for an Existing User
+
+In this scenario, we are dealing with a returning user who has previously made a purchase on our application and has a payment method already stored. The `checkout` mutation will be used to handle this process.
+
+### Using the `checkout` Mutation
+
+The `checkout` mutation allows us to process the checkout for a user with an existing payment method. This mutation takes the `input` argument which should contain the `paymentMethod` field. The `paymentMethod` field should be the ID of the payment method the customer wishes to use.
+
+Here is an example of how to use the `checkout` mutation:
+
+```graphql
+mutation {
+ checkout(input: { paymentMethod: "stripe" }) {
+ clientMutationId
+ order {
+ id
+ orderId
+ total
+ }
+ }
+}
+```
+
+In the case where the user wishes to use a new payment method, we have two options:
+
+1. Redirect them to the traditional checkout page.
+2. Redirect them to the "add new payment method" page.
+
+The `addPaymentMethodUrl` field, which can be retrieved from the `customer` query, provides the URL to the "add new payment method" page. Here is an example of how to retrieve this URL:
+
+```graphql
+query {
+ customer {
+ addPaymentMethodUrl
+ }
+}
+```
+
+## Scenario 2: Handling Checkout for a New or Existing User with Client-Side Session Management
+
+In this scenario, we are dealing with a new or existing user of our application, but we are not relying on WooCommerce to manage the session. Instead, all things pertaining to the cart are handled client-side until checkout. At this point, we use the `createOrder` mutation to generate the order on the backend.
+
+### Using the `createOrder` Mutation
+
+The `createOrder` mutation allows us to create an order on the backend without relying on WooCommerce's session management. This mutation takes the `input` argument which should contain the `paymentMethod` field and the `lineItems` field. The `lineItems` field should be an array of `LineItemInput` objects, each representing a product in the cart.
+
+Here is an example of how to use the `createOrder` mutation:
+
+```graphql
+mutation {
+ createOrder(input: {
+ paymentMethod: "stripe",
+ lineItems: [
+ {
+ productId: 1,
+ quantity: 2
+ },
+ {
+ productId: 3,
+ quantity: 1
+ }
+ ]
+ }) {
+ clientMutationId
+ order {
+ id
+ orderId
+ total
+ }
+ }
+}
+```
+
+This scenario is most common for clients that use a payment processor external to WooCommerce. By handling the cart client-side and only using WooCommerce for product data and order management, we can provide a seamless checkout experience for our users.
+
+In the next sections, we will delve deeper into how to utilize order data, customer data, and various product data types to further enhance our application.
diff --git a/docs/using-composite-product-data-and-mutations.md b/docs/using-composite-product-data-and-mutations.md
index e69de29b..47e90de6 100644
--- a/docs/using-composite-product-data-and-mutations.md
+++ b/docs/using-composite-product-data-and-mutations.md
@@ -0,0 +1,264 @@
+---
+title: "Using Composite Product Data + Mutations with WooGraphQL"
+description: "Learn how to use the Composite Product functionality with WooGraphQL by building upon the code from `Using Product Data` and `Creating Session Provider and using Cart Mutations`."
+keywords: "WooGraphQL, WPGraphQL, WooCommerce, GraphQL, Composite Product functionality, Product Data, Session Provider, Cart Mutations"
+author: "Geoff Taylor"
+---
+
+# Using Composite Product Data + Mutations
+
+In this section, we will be discussing how to use composite product data and mutations. We will be building on the code written in previous sections of the documentation, specifically the sections on [using product data](https://woographql.com/docs/using-product-data) and [handling user session and using cart mutations](https://woographql.com/docs/handling-user-session-and-using-cart-mutations).
+
+Composite products are a unique type of product in WooCommerce that allow store owners to build complex products by combining simple products. These products are designed to manage and provide a lot of visual context data, like the behavior for displaying certain parts of components or totals. It's up to the demands of the store and client application how much of this context should be used.
+
+When adding a composite product to the cart, all components must be provided to the `AddCompositeToCart`'s `configuration` field, even optional components.
+
+Here's the component we'll be using in the examples ahead.
+
+```jsx
+import React from 'react';
+import { useQuery } from '@apollo/client';
+import { GetProduct } from './graphql';
+import useCartMutations from './useCartMutations';
+
+const CompositeProduct = ({ productId }) => {
+ const [quantity, setQuantity] = React.useState(1);
+ const { data, loading, error } = useQuery(GetProduct, {
+ variables: { id: productId, idType: 'DATABASE_ID' },
+ });
+
+ const { quantityInCart: inCart, mutate, loading: cartLoading } = useCartMutations(productId);
+
+ React.useEffect(() => {
+ if (inCart) {
+ setQuantity(inCart);
+ }
+ }, [inCart]);
+
+ if (loading) return
+ );
+};
+
+export default CompositeProduct;
+```
+
+This `CompositeProduct` component receives a `productId` as a prop. It uses the `useQuery` hook from Apollo Client to fetch the product data from the GraphQL API. The product data includes the product's name, description, price, and attributes.
+
+The `useCartMutations` hook is used to manage the cart actions. It returns the quantity of the item currently in the cart, a `mutate` function that can be used to add, update, or remove items, and a `loading` flag indicating whether any cart mutations are in progress.
+
+The `handleAddOrUpdateAction` and `handleRemoveAction` functions call the `mutate` function returned by `useCartMutations`. The `loading` flag is used to disable the buttons while any cart mutations are in progress.
+
+The component renders the product information and provides buttons to add, update, or remove the item from the cart. The "Add to Cart" button's text changes to "Update" if the item is already in the cart. If the product is out of stock, a message is displayed instead of the cart options.
+
+This component is a good starting point. You can further customize and extend it to suit your specific needs.
+
+Alright, let's start by updating the `SingleProduct` component to support composite products. We'll use the `CompositeCard` component code provided, but we'll rename it to `CompositeProduct` for consistency.
+
+Here's the updated `SingleProduct` component:
+
+```jsx
+import React from 'react';
+import { CompositeProduct } from './CompositeProduct';
+import { Product as ProductType } from '@axis/graphql';
+
+export const SingleProduct = ({ product }) => {
+ if (product.__typename === 'CompositeProduct') {
+ return ;
+ }
+
+ // ...rest of the component
+};
+```
+
+In the code above, we're checking if the product type is `CompositeProduct`. If it is, we're rendering the `CompositeProduct` component. If it's not, we're rendering the rest of the `SingleProduct` component as usual.
+
+Next, let's update the `useCartMutations` hook to add support for `addCompositeToCart`. Here's the updated hook:
+
+```jsx
+import { useEffect, useMemo, useState } from 'react';
+
+import { useSession } from '@axis/components/SessionProvider';
+import {
+ useAddToCartMutation,
+ useAddCompositeToCartMutation,
+ useUpdateCartItemQuantitiesMutation,
+ useRemoveItemsFromCartMutation,
+ Cart,
+ GetCartDocument,
+ CartItem,
+} from '@axis/graphql';
+
+export interface CartMutationCompositeInput extends CartMutationInput {
+ configuration: {
+ componentId: string;
+ productId?: number;
+ hidden?: boolean;
+ quantity?: number;
+ variation?: {
+ attributeName: string;
+ attributeValue: string;
+ }[]
+ variationId?: number;
+ }[];
+}
+
+// ...rest of the hook
+
+const useCartMutations = (
+ productId: number,
+ variationId?: number,
+ extraData?: string,
+) => {
+ // ...rest of the hook
+
+ const [addCompositeToCart, { loading: addingComposite }] = useAddCompositeToCartMutation({
+ onCompleted({ addCompositeToCart: data }) {
+ if (data?.cart) {
+ setCart(data.cart as Cart);
+ }
+ },
+ notifyOnNetworkStatusChange: true,
+ });
+
+ // ...rest of the hook
+
+ async function mutate(values) {
+ const {
+ quantity = 1,
+ all = false,
+ mutation = 'update',
+ } = values;
+
+ if (!cart) {
+ return;
+ }
+
+ if (!productId) {
+ throw new Error('No item provided.');
+ // TODO: Send error to Sentry.IO.
+ }
+
+ let item: CartItem|undefined;
+ switch (mutation) {
+ // ...rest of the switch statement
+
+ case 'addComposite':
+ if (!values.configuration) {
+ throw new Error('No component configurations provided');
+ }
+ addCompositeToCart({
+ variables: {
+ productId,
+ quantity,
+ configuration: values.configuration,
+ extraData,
+ },
+ });
+ break;
+
+ // ...rest of the switch statement
+ }
+ }
+
+ // ...rest of the hook
+
+ return store;
+};
+
+export default useCartMutations;
+```
+
+In the updated hook, we've added a new mutation `addCompositeToCart` and a new case in the `mutate` function to handle adding composite products to the cart.
+
+Now before finishing up, let's revisit the facts touched upon at the started of section:
+
+1. Composite products are designed to manage and provide a lot of visual context data, like the behavior for displaying certain parts of components or totals. It's up to the demands of the store and client application how much of this context should be used. Some specific GraphQL fields that provide this optional context are the `CompositeProduct` type's `addToCartFormLocation` field, the `CompositeProductComponent` type's `optionsStyle` and `paginationStyle`.
+
+2. All components must be provided to the `AddCompositeToCart`'s `configuration` field, even optional components. Optional components should be set with a quantity of `0`.
+
+By following these steps and understanding these facts, you can effectively use composite products in your WooCommerce store with GraphQL.
+
+## Conclusion
+
+In this section, we have delved into the intricacies of working with Composite Product Data and Mutations. We have demonstrated how to adapt the code from the `Using Product Data` and `Creating Session Provider and using Cart Mutations` sections to handle the unique specifications of composite products.
+
+We've explored how to modify the `ProductListing` and `SingleProduct` components to support `CompositeProduct` types. We've also shown how to use the `addToCart` mutation to add composite products to the cart, taking into account the unique structure of these products.
+
+This exploration has highlighted the flexibility and power of WooGraphQL, demonstrating how it can be used to handle a wide range of product types in a WooCommerce store.
+
+As we wrap up this section, we hope that you now feel confident in your ability to work with composite product data and mutations. The skills and knowledge you've gained here will be invaluable as you continue to build and enhance your headless WooCommerce applications.
+
+In the upcoming sections, we will continue to explore other product types, including Product Bundles and Product Add-ons. Each of these product types presents its own unique challenges and opportunities, and we look forward to guiding you through them.
+
+As always, we encourage you to experiment with the concepts and code snippets provided in this section, applying them to your own projects. Happy coding!
diff --git a/docs/using-customer-data-and-mutations.md b/docs/using-customer-data-and-mutations.md
index e69de29b..8b2b9854 100644
--- a/docs/using-customer-data-and-mutations.md
+++ b/docs/using-customer-data-and-mutations.md
@@ -0,0 +1,427 @@
+---
+title: "Using Customer Data + Mutations with WooGraphQL"
+description: "Learn how to utilize the `customer` query and mutations by building a clone of a WooCommerce's user account pages in React.js."
+keywords: "WooGraphQL, WPGraphQL, WooCommerce, GraphQL, customer query, customer mutations, React.js, user account pages"
+author: "Geoff Taylor"
+---
+
+# Using Customer Data + Mutations
+
+This section of the documentation will guide you through the process of creating a clone of WooCommerce's user account pages using React.js and the WooGraphQL API. This demonstration will provide a comprehensive understanding of how to use the `customer` query and `updateCustomer` mutation.
+
+Before proceeding, it is assumed that you have already gone through and studied the code samples of the following documentation:
+- [Handling User Session and Using Cart Mutations](handling-user-session-and-using-cart-mutations.md)
+- [Using Order Data](using-order-data.md)
+
+Code samples through this section with reference components/files defined in one of the those two sections.
+
+## Application Overview
+
+The application we're going to build will start with a login page. Upon successful login, the user will be directed to the account dashboard. The account page will consist of a navigation bar and main content area. The navigation bar will include links to the following sections:
+
+- Dashboard
+- Orders
+- Addresses
+- Account Details
+- Logout
+
+Let's go over what each of these sections will do:
+
+- **Dashboard**: This page will display a welcome message to the authenticated user.
+- **Orders**: This page will display a list of the user's orders. When an order is selected, more details about the selected order will be displayed.
+- **Addresses**: This page will display forms for editing the user's billing and shipping addresses. Upon form submission, the `updateCustomer` mutation will be executed to save the changes on the server.
+- **Account Details**: This page will display a form for updating the user's `firstName`, `lastName`, `displayName`, `email`, and `password`. Upon form submission, the `updateCustomer` mutation will be executed to save the changes on the server.
+- **Logout**: Clicking this link will delete the user's credentials and return them to the login page.
+
+The next section will just jump right into defining the individual components while never really turning upon the application root. This is because you, the developer, actually have a lot of wiggle room when it comes to routing and application structure. The only requirement is that all the component used are wrapped by the same `SessionProvider` instance.
+
+## Login Page
+
+Our login page will be a simple form with fields for an email address and password. When the form is submitted, we'll use the `login` mutation to authenticate the user. If the login is successful, we'll store the user's credentials and redirect them to the dashboard.
+
+Here's what the code for our login page might look like:
+
+```jsx
+import React, { useState, useEffect } from 'react';
+import { useSession } from './SessionProvider';
+
+function LoginPage() {
+ const [username, setUserName] = useState('');
+ const [password, setPassword] = useState('');
+ const { login, customer } = useSession();
+
+ const handleLogin = (event) => {
+ event.preventDefault();
+ login(username, password);
+ }
+
+ useEffect(() => {
+ if (customer?.id && customer.id !== 'guest') {
+ // Redirect to account page.
+ window.location.href = `${process.env.APP_URL}/account`;
+ }
+ }, [customer])
+
+ return (
+
+ );
+}
+
+export default LoginPage;
+```
+
+In this code, we're using the `login` callback from the `SessionProvider` to execute our `login` mutation. When the form is submitted, we call the `login` function with our form values as variables. If the login is successful, we can save the user's credentials and redirect them to the dashboard. If there's an error, we display the error message.
+
+## Dashboard Page
+
+The dashboard page will display a welcome message to the authenticated user. We'll use the `customer` object, fetched in our `login` mutation and stored in the `SessionProvider`, to fetch the user's details. Here's what the code for our dashboard page might look like:
+
+```jsx
+import React, { useEffect } from 'react';
+import { useSession } from './SessionProvider';
+
+function DashboardPage() {
+ const { customer, logout, fetching } = useSession();
+ useEffect(() => {
+ if (fetching) {
+ return;
+ }
+
+ if (!customer?.id || customer.id === 'guest') {
+ // redirect to login
+ window.location.href = `${process.env.APP_URL}/login`;
+ }
+ }, [customer]);
+
+ if (fetching) {
+ return
+ >
+ );
+}
+
+export default DashboardPage;
+```
+
+
+
+This code is rather simple. We display a welcome message with links to the other account pages. If the user is not authenticated, they are redirected back to login.
+
+## Orders Page
+
+The orders page will display a list of the user's orders. When an order is selected, more details about the selected order will be displayed. We'll use the `orders` field on the `customer` type to fetch the user's orders. Here's what the code for our orders page might look like:
+
+```jsx
+import React, { useState, useEffect } from 'react';
+import { useSession } from './SessionProvider';
+
+function OrdersPage() {
+ const { customer, fetching } = useSession();
+ const [selectedOrder, setSelectedOrder] = useState(null);
+ const orders = (customer?.orders?.nodes || []) as Order[];
+
+ useEffect(() => {
+ if (fetching) {
+ return;
+ }
+ if (!customer?.id || customer.id === 'guest') {
+ // redirect to login
+ window.location.href = `${process.env.APP_URL}/login`;
+ }
+ }, [customer]);
+
+ if (fetching) {
+ return
Date: {new Date(selectedOrder.date as string).toLocaleDateString()}
+
+ )}
+
+ );
+}
+
+export default OrdersPage;
+```
+
+
+
+In this code, we're using the `customer` object again. We're displaying the customer's `orders` and if one is selected this order is highlighted and displayed in more detail below.
+
+## Addresses Page
+
+The addresses page will display forms for editing the user's billing and shipping addresses. Upon form submission, the `updateCustomer` mutation will be executed to save the changes on the server. Here's what the code for our addresses page might look like:
+
+```jsx
+import React, { useState, useEffect } from 'react';
+import { useSession } from './SessionProvider';
+
+function AddressesPage() {
+ const [billing, setBilling] = useState({});
+ const [shipping, setShipping] = useState({});
+ const { customer, updateCustomer, fetching } = useSession();
+
+ useEffect(() => {
+ if (!customer?.id || customer.id === 'guest') {
+ // redirect to login
+ window.location.href = `${process.env.APP_URL}/login`;
+ }
+ });
+
+ useEffect(() => {
+ customer?.billing && setBilling(customer.billing);
+ customer?.shipping && setShipping(customer.shipping);
+ }, [customer]);
+
+ const handleBillingChange = (event) => {
+ setBilling({ ...billing, [event.target.name]: event.target.value });
+ };
+
+ const handleShippingChange = (event) => {
+ setShipping({ ...shipping, [event.target.name]: event.target.value });
+ };
+
+ const handleUpdateCustomer = async (event) => {
+ event.preventDefault();
+
+ updateCustomer({ billing, shipping });
+ };
+
+ if (!customer) {
+ return null;
+ }
+
+ return (
+
+ );
+}
+
+export default AddressesPage;
+```
+
+
+
+Forgive me for skipping the other form fields but they should be pretty obvious from this point if you use the fields I created as reference. This is the first page with a form and you'll note the use of an `useEffect` to update the `shipping`, and `billing` state after changes to the `customer` object. This `useEffect` should capture both the initial values on mount, although they may not get displayed until the second render, and the changes after `updateCustomer` has been run.
+
+## Account Details Page
+
+The account details page will display a form for updating the user's `firstName`, `lastName`, `displayName`, `email`, and `password`. Upon form submission, the `updateCustomer` mutation will be executed by our `updateCustomer` callback to save the changes on the server. Here's what the code for our account details page might look like:
+
+```jsx
+import React, { useState, useEffect } from 'react';
+import { useSession } from './SessionProvider';
+
+function AccountDetailsPage() {
+ const [details, setDetails] = useState({});
+ const [confirmPassword, setConfirmPassword] = useState('');
+ const { customer, updateCustomer, fetching } = useSession();
+
+ useEffect(() => {
+ if (fetching) {
+ return;
+ }
+
+ if (!customer?.id || customer.id === 'guest') {
+ // redirect to login
+ window.location.href = `${process.env.APP_URL}/login`;
+ }
+ });
+
+ useEffect(() => {
+ setDetails({ ...customer })
+ }, [customer])
+
+ const handleChange = (event) => {
+ setDetails({ ...details, [event.target.name]: event.target.value });
+ };
+
+ const handleSubmit = async (event) => {
+ event.preventDefault();
+
+ if (!!details.password && details.password !== confirmPassword) {
+ alert('Passwords do not match');
+ return;
+ }
+
+ updateCustomer({ ...details });
+ };
+
+ if (!customer) {
+ return null;
+ }
+
+ return (
+
+ );
+}
+
+export default AccountDetailsPage
+
+;
+```
+
+
+
+In this code, we're using the `updateCustomer` to execute our `updateCustomer` mutation when the form is submitted again. The logic pattern here is pretty identical to the `Addresses` page. You'll notice the continued lack of sophistication, with no little to no error handling. This is done to keep noise out these code samples. You, the reader, should consider these samples incomplete until you've included proper error handling.
+
+## Where's the rest of the application?
+
+With this the account pages are feature-complete minus some error-handling. You'll note there is a lack of error handling in the `SessionProvider`, as well. Consider it homework.
+
+I'll also reiterate that I left out anything pertaining the application structure like navigation bars and the application root. That is because I wanted these code samples to be as framework-agnostic as possible.
+This is also not quite a WooCommerce clone because it's missing the `Add Payment Methods` page. Implementing this page would be identical to the `orders` page, except the fields to be mapped to the listing are `customer.availablePaymentMethodsCC` or `customer.availablePaymentMethodsEC` instead of `customer.orders`. You'd also have to include a button to the `Add Payment Method` page on the WP Backend. See [Harmonizing With Wordpress](harmonizing-with-wordpress.md) for more details on how to get this URL.
+You'll note these samples also don't take into account React server components. That is due to the common nature of most Account pages/components will have to be powered by client-side/runtime queries and not static generation/build-time queries - meaning they'll likely be executed after page load.
+
+## Conclusion
+
+In this guide, we've seen how to use the WooGraphQL API to create a clone of WooCommerce's user account pages. We've used the `customer` query to fetch the user's details, the `orders` field on the `customer` type to fetch the user's orders, and the `updateCustomer` mutation to update the user's details and addresses. We've also seen how to handle user authentication with the `login` mutation and how to log out the user. It should be noted that this was all made possible by proper usage our `SessionProvider`.
+
+With the completion of this section and all proceeding sections, you have been given an deep dive into the basic and intermediate usages of WooGraphQL. The following sections will be on WooGraphQL Pro functionality.
diff --git a/docs/using-order-data.md b/docs/using-order-data.md
index e69de29b..24300fa9 100644
--- a/docs/using-order-data.md
+++ b/docs/using-order-data.md
@@ -0,0 +1,139 @@
+---
+title: "Using Order Data with WooGraphQL"
+description: "Learn how to utilize the Order queries and type by building a basic order status page in React.js that works by taking an `email` and returning a list of all the orders connected to that `email`."
+keywords: "WooGraphQL, WPGraphQL, WooCommerce, GraphQL, Order queries, React.js, order status page"
+author: "Geoff Taylor"
+---
+
+# Using Order Data
+
+In this section, we will delve into how to utilize the Order queries and types provided by WooGraphQL. This tutorial assumes that you have already gone through the previous documentation pages and have a basic understanding of how to use GraphQL with WooCommerce.
+
+Our objective here is to demonstrate how to build a basic order status page using React.js. This page will allow users to input an email address and, in return, receive a list of all orders associated with that email. This demonstration aims to highlight both the possible and recommended ways of retrieving and utilizing order data.
+
+Before we start, it's important to note that while we will first show you how to fetch orders directly from the client-side, this is not the recommended approach due to privacy concerns. Instead, we suggest using a serverless function, such as a Next API route, with admin access provided by a WordPress Application Password. This method allows for secure querying of orders using the root-level `orders` queries with the `where.billingEmail` argument set.
+
+With these prerequisites and objectives in mind, let's dive into creating our order status page.
+
+## Creating the Order Status Page
+
+First, let's create a simple form that takes an email as input. When the form is submitted, a list of orders associated with the entered email will be displayed.
+
+```jsx
+import React, { useState } from 'react';
+import { useSession } from './SessionProvider';
+
+function OrderStatusPage() {
+ const { customer, updateCustomer, fetching } = useSession();
+ const [email, setEmail] = useState('');
+ const [selectedOrder, setSelectedOrder] = useState(null);
+
+ const handleSubmit = (event) => {
+ event.preventDefault();
+
+ updateCustomer({
+ billing: { email }
+ })
+ };
+
+ if (!customer) {
+ return null;
+ }
+
+ if (!customer?.billing?.email) {
+ return (
+
+ );
+ }
+
+ const orders = customer?.orders?.nodes || [];
+
+ return (
+
+ );
+}
+
+export default OrderStatusPage;
+```
+
+After the email has been submitted, we will use the `updateCustomer` callback from the `SessionProvider` to set the current viewer's `billingEmail` as the provided email address. Then, we will the `orders` field from the resulting `customer` object after it's saved in the `SessionProvider`.
+
+
+
+As mentioned above, this is not the ideal approach due to privacy concerns.
+
+Let's create a Next.js API route page that takes a billingEmail address and runs the query against the endpoint using `GraphQLClient` from the `graphql-request` library, and returns the orders.
+
+```jsx
+import { GraphQLClient } from 'graphql-request';
+
+export default async function handler(req, res) {
+ const { billingEmail } = req.body;
+
+ const endpoint = 'YOUR_GRAPHQL_ENDPOINT';
+ const graphQLClient = new GraphQLClient(endpoint, {
+ headers: {
+ authorization: 'Bearer YOUR_WORDPRESS_APPLICATION_PASSWORD',
+ },
+ });
+
+ const query = `
+ query ($billingEmail: String) {
+ orders(where: { billingEmail: $billingEmail }) {
+ nodes {
+ ... OrderFields
+ }
+ }
+ }
+ `;
+
+ const variables = {
+ billingEmail,
+ };
+
+ const data = await graphQLClient.request(query, variables);
+
+ res.status(200).json(data);
+}
+```
+
+Replace `'YOUR_GRAPHQL_ENDPOINT'` with your GraphQL endpoint and `'Bearer YOUR_WORDPRESS_APPLICATION_PASSWORD'` with your WordPress Application Password.
+
+For more information on WordPress Application Passwords, refer to the [official WordPress Application Password documentation](https://developer.wordpress.org/rest-api/using-the-rest-api/authentication/application-passwords/).
+
+## Conclusion
+
+Well done! You've successfully navigated through the process of using order data in a WooGraphQL context. We've explored how to build a basic order status page in React.js, which takes an email address and returns a list of all the orders associated with that email. We've also discussed the recommended method of retrieving and utilizing order data, which involves using a serverless function like a Next API route.
+
+Remember, while the method we initially demonstrated works, it's not the most secure or efficient way to handle order data. We recommend using a serverless function with admin access, provided by a WordPress Application Password, to query for the orders. This method is more secure and efficient, and it allows for greater flexibility and control over the data.
+
+As you continue to build your headless WooCommerce application, keep these principles in mind. Understanding how to effectively and securely handle order data is crucial for providing a smooth and secure user experience.
+
+In the next sections, we will delve deeper into the capabilities of WooGraphQL, exploring topics like customer data and mutations, subscription data and mutations, and more. Stay tuned!
diff --git a/docs/using-product-addons-data-and-mutations.md b/docs/using-product-addons-data-and-mutations.md
index e69de29b..8ef37fae 100644
--- a/docs/using-product-addons-data-and-mutations.md
+++ b/docs/using-product-addons-data-and-mutations.md
@@ -0,0 +1,6 @@
+---
+title: "Using Product Add-ons Data + Mutations with WooGraphQL"
+description: "Learn how to use the Product Add-on functionality with WooGraphQL by building upon the code from `Using Product Data` and `Creating Session Provider and using Cart Mutations`."
+keywords: "WooGraphQL, WPGraphQL, WooCommerce, GraphQL, Product Add-on functionality, Product Data, Session Provider, Cart Mutations"
+author: "Geoff Taylor"
+---
diff --git a/docs/using-product-bundle-data-and-mutations.md b/docs/using-product-bundle-data-and-mutations.md
index e69de29b..d385653f 100644
--- a/docs/using-product-bundle-data-and-mutations.md
+++ b/docs/using-product-bundle-data-and-mutations.md
@@ -0,0 +1,97 @@
+---
+title: "Using Product Bundle Data + Mutations with WooGraphQL"
+description: "Learn how to use the Product Bundle functionality with WooGraphQL by building upon the code from `Using Product Data` and `Creating Session Provider and using Cart Mutations`."
+keywords: "WooGraphQL, WPGraphQL, WooCommerce, GraphQL, Product Bundle functionality, Product Data, Session Provider, Cart Mutations"
+author: "Geoff Taylor"
+---
+
+# Using Product Bundle Data + Mutations
+
+In the previous sections, we have explored various aspects of using WooGraphQL, from handling user sessions and cart mutations to working with different product types. Now, we are going to delve into the world of product bundles. Product bundles are a powerful feature in WooCommerce that allows merchants to sell multiple products together as a set, often at a discounted price. This can be a great way to increase average order value and move more inventory.
+
+In this section, we will demonstrate how to use the WooGraphQL API to work with product bundle data and mutations. We will build upon the code from the previous sections, specifically the ones on "Using Product Data" and "Creating Session Provider and using Cart Mutations". This will involve fetching product bundle data, adding product bundles to the cart, and handling the unique specifications of product bundles.
+
+We will be using the `BundleProduct` component and updating the `SingleProduct` and `useCartMutations` components to handle product bundles. We will be using the code samples from the [Using Product Data](https://woographql.com/docs/using-product-data) and [Handling User Session and Using Cart Mutations](https://woographql.com/docs/handling-user-session-and-using-cart-mutations) as a starting point.
+
+Let's start by looking at the `BundleProduct` component:
+
+```jsx
+import React, { useState } from 'react';
+import useCartMutations from './useCartMutations';
+import { useSession } from './SessionProvider';
+import { LoadingSpinner } from './LoadingSpinner';
+import { CartCard } from './CartCard';
+
+// ... rest of the code from the provided sample ...
+
+export function BundleCard({ product }) {
+ // ... rest of the code from the provided sample ...
+}
+```
+
+Next, we will update the `SingleProduct` component to handle product bundles:
+
+```jsx
+import React from 'react';
+import BundleCard from './BundleCard';
+
+function SingleProduct({ product }) {
+ if (product.__typename === 'BundleProduct') {
+ return ;
+ }
+
+ // ... rest of the code for handling other product types ...
+}
+```
+
+Finally, we will update the `useCartMutations` hook to handle adding and removing product bundles from the cart:
+
+```jsx
+import { useState } from 'react';
+import { useSession } from './SessionProvider';
+import {
+ useAddToCartMutation,
+ useAddBundleToCartMutation,
+ useUpdateCartItemQuantitiesMutation,
+ useRemoveItemsFromCartMutation,
+} from '@axis/graphql';
+
+export default function useCartMutations(productId) {
+ // ... rest of the code from the provided sample ...
+
+ async function mutate(values) {
+ // ... rest of the code from the provided sample ...
+
+ switch (mutation) {
+ // ... rest of the code from the provided sample ...
+
+ case 'addBundle':
+ if (!values.bundleItems) {
+ throw new Error('No bundle items provided');
+ }
+ addBundleToCart({
+ variables: {
+ productId,
+ quantity,
+ bundleItems: values.bundleItems,
+ },
+ });
+ break;
+
+ // ... rest of the code from the provided sample ...
+ }
+ }
+
+ // ... rest of the code from the provided sample ...
+}
+```
+
+Now we have updated the `SingleProduct` and `useCartMutations` components to handle product bundles, and we have created a new `BundleProduct` component for displaying product bundles. With these updates, your application should now be able to handle product bundles effectively.
+
+## Conclusion
+
+Congratulations! You have now learned how to work with product bundle data and mutations using the WooGraphQL API. This includes fetching product bundle data, adding product bundles to the cart, and handling the unique specifications of product bundles.
+
+Remember, while product bundles are similar to other product types in many ways, they have unique specifications that require special handling. Therefore, it's important to understand these differences and how to work with them when building your headless WooCommerce application.
+
+In the next sections, we will continue to explore more advanced features of WooGraphQL, including working with product add-ons. Stay tuned!
diff --git a/docs/using-product-data.md b/docs/using-product-data.md
index f59f8859..66220994 100644
--- a/docs/using-product-data.md
+++ b/docs/using-product-data.md
@@ -7,14 +7,14 @@ author: "Geoff Taylor"
# Using Product Data
-In this guide, we will implement the Single Product page using the provided GraphQL query and the JSON result. We will display the product's `name`, `description`, `price`, `regularPrice`, `attributes`, `width`, `height`, `length`, and `weight`. Additionally, we will prepare a section for cart options like desired quantity and an Add to Cart button.
+In this section, we will implement the Single Product page using the provided GraphQL query and the JSON result. We will display the product's `name`, `description`, `price`, `regularPrice`, `attributes`, `width`, `height`, `length`, and `weight`. Additionally, we will prepare a section for cart options like desired quantity and an Add to Cart button.
## Prerequisites
- Basic knowledge of React and React Router.
- Familiarity with GraphQL and WPGraphQL.
- A setup WPGraphQL/WooGraphQL backend.
-- Read previous guides on [Routing By URI](routing-by-uri.md)
+- Read previous sections on [Routing By URI](routing-by-uri.md)
## Step 0: Create our `graphql.js` file.
@@ -191,7 +191,7 @@ dimensions {
weight
```
-Inside the SingleProduct component, after rendering the attributes
+Inside the SingleProduct component, render the attributes.
```jsx
@@ -204,7 +204,7 @@ Inside the SingleProduct component, after rendering the attributes
## Step 4: Add cart options section
-Finally, add a section for cart options like desired quantity and the Add to Cart button. Use the `soldIndividually` and `stockStatus` fields to control the state of the cart controls. Add this inside the SingleProduct component, after rendering the weight information
+Finally, add a section for cart options like desired quantity and the Add to Cart button. Use the `soldIndividually` and `stockStatus` fields to control the state of the cart controls. Add this inside the SingleProduct component after rendering the weight information.
```jsx
@@ -225,12 +225,12 @@ Finally, add a section for cart options like desired quantity and the Add to Car
);
```
-With this implementation, the Single Product page displays the product information, dimensions, and weight. Additionally, it includes a section for cart options, such as the desired quantity and an Add to Cart button. The availability of the cart options is dictated by the `soldIndividually` and `stockStatus` fields. If the product is not sold individually, the user can select a quantity. The Add to Cart button is only shown if the product is in stock; otherwise, an "Out of stock" message is displayed. We could also go a step further and use the product's `stockQuantity` to set a hard max quantity limit, but it's outta of the scope of this guide.
+With this implementation, the Single Product page displays the product information, dimensions, and weight. Additionally, it includes a section for cart options, such as the desired quantity and an Add to Cart button. The availability of the cart options is dictated by the `soldIndividually` and `stockStatus` fields. If the product is not sold individually, the user can select a quantity. The Add to Cart button is only shown if the product is in stock; otherwise, an "Out of stock" message is displayed. We could also go a step further and use the product's `stockQuantity` to set a hard max quantity limit, but that's out of the scope of this section.
## Conclusion
-In this guide, you learned how to implement a Single Product page using the provided GraphQL queries and the example JSON response. The Single Product page displays essential product information such as the name, description, price, attributes, dimensions, and weight. The Add to Cart controls are conditionally rendered based on the `soldIndividually` and `stockStatus` fields.
+In this section, you learned how to implement a Single Product page using the provided GraphQL queries and the example JSON response. The Single Product page displays essential product information such as the name, description, price, attributes, dimensions, and weight. The Add to Cart controls are conditionally rendered based on the `soldIndividually` and `stockStatus` fields.
-In the next guide, we will dive into implementing the functionality for adding a product to the cart and updating the cart's contents. We will explore how to manage the cart state and interact with the WooCommerce API to handle cart-related actions.
+In the next section, we will dive into implementing the functionality for adding a product to the cart and updating the cart's contents. We will explore how to manage the cart state and interact with the WooCommerce API to handle cart-related actions.
-By following these guides, you'll be well on your way to building a complete, functional e-commerce website using React and WooCommerce with GraphQL.
+By continuing to follow the documentation, you'll be well on your way to building a complete, functional e-commerce website using React and WooCommerce with GraphQL.
diff --git a/docs/using-subscription-data-and-mutations.md b/docs/using-subscription-data-and-mutations.md
index e69de29b..2d1a2ccd 100644
--- a/docs/using-subscription-data-and-mutations.md
+++ b/docs/using-subscription-data-and-mutations.md
@@ -0,0 +1,138 @@
+---
+title: "Using Subscription Data + Mutations with WooGraphQL"
+description: "Learn how to use the Subscription functionality provided by WooGraphQL Pro and build upon the code written in `Using Product Data` and `Routing by URI` by rewriting the `ProductListing` and `SingleProduct` components to support `SubscriptionProduct` types."
+keywords: "WooGraphQL, WPGraphQL, WooCommerce, GraphQL, Subscription functionality, ProductListing, SingleProduct, SubscriptionProduct types"
+author: "Geoff Taylor"
+---
+
+# Using Subscription Data + Mutations
+
+This section of the documentation will focus on how to use the Subscription functionality provided by WooGraphQL Pro. We will build upon the code written in `Using Product Data` and `Routing by URI` by rewriting the `ProductListing` and `SingleProduct` components to support `SubscriptionProduct` types.
+
+Before we start, ensure that the `changeSubPaymentMethodUrl`, `renewSubPaymentMethodUrl`, and `Enable Subscriptions` options from the WooGraphQL settings page are checked and enabled. These settings will allow us to handle subscription renewals, payment method changes, and enable subscription functionality respectively.
+
+## Understanding SubscriptionProduct Type
+
+The `SubscriptionProduct` type represents a product that can be purchased on a recurring basis. It includes fields for the subscription price, interval, length, and sign-up fee, among others. Here is an example of how to query for a subscription product:
+
+```javascript
+const SUBSCRIPTION_PRODUCT_QUERY = gql`
+ query SubscriptionProduct($id: ID!) {
+ product(id: $id) {
+ ... on SubscriptionProduct {
+ id
+ name
+ price
+ subscriptionPrice
+ subscriptionPeriod
+ subscriptionPeriodInterval
+ subscriptionLength
+ subscriptionSignUpee
+ }
+ }
+ }
+`;
+```
+
+## Modifying ProductListing and SingleProduct Components
+
+To support `SubscriptionProduct` types, we need to modify our `ProductListing` and `SingleProduct` components. These components should be able to display the subscription details and allow users to add subscription products to their cart.
+
+Here is an example of how to modify the `ProductListing` component:
+
+```javascript
+function ProductListing({ product }) {
+ // ... other code
+
+ return (
+
+
{product.name}
+
Price: {product.price}
+
Subscription Price: {product.subscriptionPrice}
+
Subscription Period: {product.subscriptionPeriod}
+ // ... other product details
+
+
+ );
+};
+```
+
+The `SingleProduct` component can be modified in a similar way.
+
+## Adding a Subscriptions Page
+
+We can add a `Subscriptions` page to the account page clone made in `Using Customer Data + Mutations`. This page will utilize the `subscriptions` field on the `Customer` type that returns a list of `SubscriptionOrder`.
+
+Here is an example of how to query for a customer's subscriptions:
+
+```javascript
+const CUSTOMER_SUBSCRIPTIONS_QUERY = gql`
+ query CustomerSubscriptions($id: ID!) {
+ customer(id: $id) {
+ subscriptions {
+ nodes {
+ id
+ status
+ total
+ lineItems {
+ nodes {
+ product {
+ ... on SubscriptionProduct {
+ name
+ subscriptionPrice
+ subscriptionPeriod
+ }
+ }
+ quantity
+ }
+ }
+ }
+ }
+ }
+ }
+`;
+```
+
+And here is an example of how to display the subscriptions on the `Subscriptions` page:
+
+```javascript
+function SubscriptionsPage({ customer }) {
+ return (
+