Merge branch 'master' of https://github.com/medusajs/medusa into docs/shipping-overview

This commit is contained in:
Shahed Nasser
2022-04-12 22:02:26 +03:00
30 changed files with 5972 additions and 3298 deletions
+1 -1
View File
@@ -1512,7 +1512,7 @@
"/store/carts/{id}": {
"post": {
"operationId": "PostCartsCartPaymentMethodUpdate",
"summary": "Update a Cart\"",
"summary": "Update a Cart",
"description": "Updates a Cart.",
"parameters": [
{
-9
View File
@@ -1,9 +0,0 @@
# SendGrid (Documentation coming soon)
[View plugin here](https://github.com/medusajs/medusa/tree/master/packages/medusa-plugin-sendgrid)
<div>
<video width="100%" height="100%" playsinline autoplay muted controls>
<source src="https://user-images.githubusercontent.com/59018053/154807282-1e72671f-1936-411d-b914-e05c6597693a.mp4" type="video/mp4" />
</video>
</div>
File diff suppressed because it is too large Load Diff
+327 -74
View File
@@ -1,6 +1,10 @@
# Stripe
[View plugin here](https://github.com/medusajs/medusa/tree/master/packages/medusa-payment-stripe)
This document guides you through setting up Stripe payments in your Medusa server, admin, and storefront using the [Stripe Plugin](https://github.com/medusajs/medusa/tree/master/packages/medusa-payment-stripe).
## Video Guide
You can also follow our video guide to learn how the setup works:
<div>
<video width="100%" height="100%" playsinline autoplay muted controls>
@@ -8,115 +12,364 @@
</video>
</div>
## Overview
### Introduction
[Stripe](https://stripe.com/) is a battle-tested and unified platform for transaction handling. Stripe supplies you with the technical components needed to handle transactions safely and all the analytical features necessary to gain insight into your sales. These features are also available in a safe test environment which allows for a concern-free development process.
Handling payments is at the core of every commerce system; it allows us to run our businesses. Consequently, a vast landscape of payment providers has developed, each with varying cost models, implementational specifications, and analytical capabilities.
Using the `medusa-payment-stripe` plugin, this guide shows you how to set up your Medusa project with Stripe as a payment provider.
As a consequence, one might ask, which one(s) should I choose? Medusa makes exchanging enabled payment providers easy through its unified payment API. Here, one may select payment provider plugins already existing ([PayPal](https://docs.medusajs.com/add-plugins/paypal), [Klarna](https://docs.medusajs.com/add-plugins/klarna), and Stripe), or develop new ones.
## Prerequisites
Using the `medusa-payment-stripe` plugin, this guide will show you how to set up your Medusa project with Stripe as a payment provider.
Before you proceed with this guide, make sure you create a [Stripe account](https://stripe.com). Youll later retrieve the API Keys and secrets from your account to connect Medusa to your Stripe account.
[Stripe](https://stripe.com) is a battle-tested and unified platform for transaction handling. Stripe supplies you with the technical components needed to handle transactions safely and all the analytical features necessary to gain insight into your sales. These features are also available in a safe test environment which allows for a concern free development process.
## Medusa Server
### Prerequisites
This section guides you over the steps necessary to add Stripe as a payment provider to your Medusa server.
This guide assumes that you have set up a medusa project (See [this guide](https://docs.medusajs.com/tutorial/creating-your-medusa-server)). Furthermore, this guide will be using the Gatsby starter as our storefront (See [this guide](https://docs.medusajs.com/starters/gatsby-medusa-starter)) and the Admin panel to manage our store (See the github installation guide [here](https://github.com/medusajs/admin)).
If you dont have a Medusa server installed yet, you must follow our [quickstart guide](../quickstart/quick-start) first.
### Installation
### Plugin Installation
The first step is to install the `medusa-payment-stripe` plugin in your Medusa project using your favorite package manager:
In the root of your Medusa server, run the following command to install the stripe plugin:
```bash
# yarn
yarn add medusa-payment-stripe
# npm
npm install medusa-payment-stripe
```
Then in your `medusa-config.js` , add the plugin to your `plugins` array:
### Plugin Configuration
```javascript
module.exports = {
// ... other options
plugins: [
// ... other plugins
{
resolve: `medusa-payment-stripe`,
options: {
api_key: STRIPE_API_KEY,
webhook_secret: STRIPE_WEBHOOK_SECRET,
},
},
];
Next, you need to add configurations for your stripe plugin.
In `medusa-config.js` add the following at the end of the `plugins` array:
```jsx
const plugins = [
...,
{
resolve: `medusa-payment-stripe`,
options: {
api_key: STRIPE_API_KEY,
webhook_secret: STRIPE_WEBHOOK_SECRET,
},
},
];
```
:::note
You might find that this code is already available but commented out. You can proceed with removing the comments instead of adding the code again.
:::
The Stripe plugin uses 2 configuration options. The `api_key` is essential to both your development and production environments. As for the `webhook_secret`, its essential for your production environment. So, if youre only using Stripe for development you can skip adding the value for this option at the moment.
### Retrieving The Keys
On the [dashboard](https://dashboard.stripe.com) of your Stripe account click on the Developers link at the top right. This will take you to the developer dashboard.
Youll first retrieve the API key. You can find it by choosing API Keys from the sidebar and copying the Secret key.
Next, you need to add the key to your environment variables. In your Medusa server, create `.env` if it doesnt already exist and add the Stripe key:
```jsx
STRIPE_API_KEY=sk_...
```
:::note
If you store environment variables differently on your server, for example, using the hosting providers UI, then you dont need to add it in `.env`. Add the environment variables in a way relevant to your server.
:::
Next, if youre installing this plugin for production use, you need to retrieve the Webhook secret. Webhooks allows you to track different events on your Medusa server, such as failed payments.
Go to Webhooks on Stripes developer dashboard. Then, choose the Add an Endpoint button.
The endpoint for Stripes webhook on your Medusa server is `{SERVER_URL}/stripe/hooks`. So, add that endpoint in its field. Make sure to replace `{SERVER_URL}` with the URL to your server.
Then, you can add a description. You must select at least one event to listen to. Once youre done, click “Add endpoint”.
After the Webhook is created, youll see a key at the top right that starts with `we_...`. Copy that key and in your Medusa server add the Webhook secret environment variable:
```jsx
STRIPE_WEBHOOK_SECRET=we_...
```
## Admin Setup
This section will guide you through adding Stripe as a payment provider in a region using your Medusa admin dashboard.
This step is required for you to be able to use Stripe as a payment provider in your storefront.
### Prerequisites
If you dont have a Medusa admin installed, make sure to follow along with [the guide on how to install it](https://github.com/medusajs/admin#-quickstart) before continuing with this section.
### Adding Stripe
First, make sure that both your Medusa server and Medusa Admin are running.
Then, in your Medusa Admin, log in and choose Settings from the Sidebar. Then, choose Regions.
![Settings](https://i.imgur.com/wRkmbLY.png)
Then, choose the regions you want to add Stripe as a payment provider. In the right-side settings, scroll down to “Payment Providers” and choose Stripe.
![Choose Stripe](https://i.imgur.com/FH5vgWh.png)
Once youre done, click Save. Stripe is now a payment provider in your store in the regions you selected.
## Storefront Setup
This guide will take you through how to set up Stripe payments in your Medusa storefront. It includes the steps necessary when using one of Medusas official storefronts as well as your own custom React-based storefront.
### Prerequisites
All storefronts require that you obtain your Stripes Publishable Key. You can retrieve it from your Stripes developer dashboard by choosing API Keys and then copying the Publishable Key.
### Next.js Storefront
Medusa has a Next.js storefront that you can easily use with your Medusa server. If you dont have the storefront installed, you can follow [this quickstart guide](../starters/nextjs-medusa-starter).
In your `.env` file, add the following variable with its value set to the Publishable Key:
```jsx
NEXT_PUBLIC_STRIPE_KEY=pk_...
```
:::note
This variable might be available in your `.env` file but commented out. You can instead remove the comment and change its value.
:::
Now, if you run your Medusa server and your storefront, on checkout youll be able to use Stripe.
![Next.js Stripe Form](https://i.imgur.com/1XvW776.png)
### Gatsby Storefront
Medusa also has a Gatsby storefront that you can use as your ecommerce store. If you dont have the storefront installed, you can follow [this quickstart guide](../starters/gatsby-medusa-starter).
In your `.env.development` file (or the file youre using for your environment variables) add the following variable with the value set to the Publishable Key:
```jsx
GATSBY_STRIPE_KEY=pk_
```
:::note
You might find this environment variable already available so you can just replace its value with your Publishable Key.
:::
Now, if you run your Medusa server and your storefront, on checkout youll be able to use Stripe.
![Gatsby Stripe Form](https://i.imgur.com/1XvW776.png)
### Custom Storefront
This section will go over how to add Stripe into a React-based framework. The instructions are general instructions that you can use in your storefront.
#### Workflow Overview
The integration with stripe must have the following workflow:
1. During checkout when the user reaches the payment section, you should [create payment sessions](https://docs.medusajs.com/api/store/cart/initialize-payment-sessions). This will initialize the `payment_sessions` array in the `cart` object received. The `payment_sessions` is an array of available payment providers.
2. If Stripe is available as a payment provider, you should select Stripe as [the payment session](https://docs.medusajs.com/api/store/cart/select-a-payment-session) for the current cart. This will initialize the `payment_session` object in the `cart` object to include data related to Stripe and the current payment session. This includes the payment intent and client secret.
3. After the user enters their card details and submits the form, confirm the payment with Stripe.
4. If the payment is confirmed successfully, [complete the order](https://docs.medusajs.com/api/store/cart/complete-a-cart) in Medusa. Otherwise show an error.
#### Installing Dependencies
Before you start the implementations you need to install the necessary dependencies. Youll be using Stripes React libraries to show the UI and handle the payment confirmation:
```bash
npm install --save @stripe/react-stripe-js @stripe/stripe-js
```
Youll also use Medusas JS Client to easily call Medusas REST APIs:
```bash
npm i @medusajs/medusa-js
```
#### Initialize Stripe
In this section, youll initialize Stripe without Medusas checkout workflow. Please note that this is one approach to add Stripe into your React project. You can check out [Stripes React documentation](https://stripe.com/docs/stripe-js/react) for other methods or components.
Create a container component that will hold the payment card component:
```jsx
import { useState } from 'react';
import {Elements} from '@stripe/react-stripe-js';
import Form from './Form';
import {loadStripe} from '@stripe/stripe-js';
const stripePromise = loadStripe('pk_...');
export default function Container() {
const [clientSecret, setClientSecret] = useState()
//TODO set clientSecret
return (
<div>
{clientSecret && (
<Elements stripe={stripePromise} options={{
clientSecret
}}>
<Form clientSecret={clientSecret} cartId={cartId} />
</Elements>
)}
</div>
);
};
```
In this component, you need to use Stripes `loadStripe` function outside of the components implementation to ensure that Stripe doesnt re-load with every change. The function accepts the Publishable Key.
:::note
Youll probably store this Publishable Key in an environment variable depending on your framework. Its hard-coded here for simplicity.
:::
Then, inside the components implementation, you add a state variable `clientSecret` which youll retrieve in the next section.
Once the clientSecret is set, the `Elements` Stripe component will wrap a `Form` component youll create next. This is necessary because the `Elements` component allows child elements to get access to the cards inputs and their data using Stripes `useElements` hook.
Create a new file for the `Form` component with the following content:
```jsx
import {CardElement, useElements, useStripe} from '@stripe/react-stripe-js';
export default function Form({clientSecret, cartId}) {
const stripe = useStripe();
const elements = useElements();
async function handlePayment(e) {
e.preventDefault()
//TODO handle payment
}
return (
<form>
<CardElement />
<button onClick={handlePayment}>Submit</button>
</form>
);
};
```
This component shows a CardElement component from Stripes React library. You can use `stripe` to be able to confirm the payment later. The `elements` variable will be used to retrieve the entered card details safely.
#### Implement the Workflow
Youll now implement the workflow explained earlier. Youll use Medusas JS Client, so make sure to import it and initialize it in your `Container` component:
```jsx
import Medusa from "@medusajs/medusa-js"
export default function Container() {
const client = new Medusa();
...
}
```
Now head over to [Stripe](https://stripe.com/) and create your account. You can then click API Keys on your dashboard, and here you will see two keys. We suggest using the test environment during development, and therefore you should make sure that you are seeing the keys to the test environment (These keys start with `pk_test_` and `sk_test_` respectively).
:::note
Now open your `.env` file for the Medusa project and store your _secret key_ in the `STRIPE_API_KEY` variable:
In your storefront, youll probably be managing the Medusa client through a context for better performance.
```bash
# .env
STRIPE_API_KEY=<your key here>
:::
Then, in the place of the `//TODO` inside the `Container` element, initialize the payment sessions and create a payment session if Stripe is available:
```jsx
client.carts.createPaymentSessions(cart.id)
.then(({cart}) => {
//check if stripe is selected
const isStripeAvailable = cart.payment_sessions?.some((session) => session.provider_id === 'stripe');
if (!isStripeAvailable) {
return;
}
//select stripe payment session
client.carts.setPaymentSession(cart.id, {
provider_id: 'stripe'
}).then(({cart}) => {
setClientSecret(cart.payment_session.data.client_secret);
});
})
```
> Note: For production you should also create a webhook on Stripe (also available on your dashboard) and store its secret in the `STRIPE_WEBHOOK_SECRET` variable. We will go into detail with this in a later guide.
:::note
Then navigate to your Gatsby starter project and open the `.env.development` file and store your _publishable key_ in the `GATSBY_STRIPE_KEY` variable:
Notice that here its assumed you have access to the `cart` object throughout your storefront. Ideally, the `cart` should be managed through a context. So, every time the cart is updated, for example, when the `createPaymentSessions` or `setPaymentSession` are called, the cart should be updated in the context to be accessed from other elements. In this case, you probably wouldnt need a `clientSecret` state variable as you can use the client secret directly from the `cart` object.
```bash
# .env
GATSBY_STRIPE_KEY=<your key here>
:::
Once the client secret is set, the form will be shown to the user.
The last step in the workflow is confirming the payment with Stripe and if its done successfully, completing the users order. This part is done in the `Form` component.
As youll use Medusas client again make sure to import it and initialize it:
```jsx
import Medusa from "@medusajs/medusa-js"
export default function Form() {
const client = new Medusa();
...
}
```
### Whats next?
Then, replace the `//TODO` in the `handlePayment` function with the following content:
At this point we have set everything up, and the Stripe payment provider is now enabled in your Medusa project. So, go ahead and start up your medusa project, the gatsby starter, and the admin panel!
```jsx
return stripe.confirmCardPayment(clientSecret, {
payment_method: {
card: elements.getElement(CardElement),
billing_details: {
name,
email,
phone,
address: {
city,
country,
line1,
line2,
postal_code,
}
}
}
}).then(({ error, paymentIntent }) => {
//TODO handle errors
client.carts.complete(cartId).then(resp => console.log(resp))
})
```
However, as Medusa allows for different payment providers in different regions (and multiple providers in each) we should first make Stripe a valid payment option in our default region. To do so, open the admin panel (`http://localhost:7000`) login, and navigate to `Settings > Region settings > Edit Default Region`. Here you should now be able to select Stripe as a payment provider:
You use the `confirmCardPayment` method in the `stripe` object. Youll need to pass it the client secret, which you can have access to from the cart object if its available through the context.
<center>
This method also requires the customers information like `name`, `email`, and their address. Make sure to place the values for each based on your implementation.
![Change payment provider](https://i.imgur.com/mVIDYz4.png)
Once the promise resolves you can handle the errors, if there are any. If not, you can complete the customers order using `complete` from Medusas client. This request expects the cart ID which you should have access to as well.
</center>
If you run your server and storefront now, youll see the Stripe UI element and youll be able to make orders.
After doing this, and clicking save, we are ready to accept payments using Stripe. So, navigate to your storefront (`http://localhost:8000`) and go through the checkout process:
![Stripe Form](https://i.imgur.com/NOi8THw.png)
<center>
## Capturing Payment
![Checkout process](https://i.imgur.com/qhanISL.gif)
After the customer places an order, youll be able to see the order on the admin panel. In the payment information under the “Payment” section, you should see a “Capture” button.
</center>
After doing so, you should be able to see an uncaptured payment in Stripe. Here, you navigate to the payments tab, where you should see the following (depending on your choices during the checkout process):
![Capture Payment](https://i.imgur.com/Iz55PVZ.png)
<center>
Clicking this button allows you to capture the payment for an order. You can also refund payments if an order has captured payments.
![Uncaptured payment](https://i.imgur.com/LX6UR40.png)
Refunding or Capturing payments is reflected in your Stripes dashboard as well. This gives you access to all of Stripes analytical capabilities.
</center>
## Whats Next 🚀
To then capture the payment, navigate back to the admin panel (`http://localhost:7000/`), and dig into the relevant order, and capture the payment:
<center>
![Capture payment](https://i.imgur.com/y5UxxpS.gif)
</center>
The capture is then reflected in the payment overview in Stripe as well, giving you access to all of Stripe's analytical capabilities:
<center>
![Captured payment](https://i.imgur.com/edv84Nq.png)
</center>
### Summary
In this guide we have setup Stripe as a payment provider giving you a fully functioning ecommerce experience! Interested in learning more? Check out the other guides and tutorials or head over to our [Discord channel](https://discord.gg/xpCwq3Kfn8) if you have any questions or want to become part of our community!
[View plugin here](https://github.com/medusajs/medusa/tree/master/packages/medusa-payment-stripe)
- Check out [more plugins](https://github.com/medusajs/medusa/tree/master/packages) you can add to your store.
+141
View File
@@ -0,0 +1,141 @@
---
title: Medusa Admin Quickstart
---
# Admin Quickstart
This document will guide you through setting up the Medusa admin in minutes, as well as some of its features.
## Prerequisites
The Medusa admin is connected to the Medusa server. So, make sure to install the Medusa server first before proceeding with the admin. You can check out the [quickstart guide to install the Medusa server](../quickstart/quick-start).
:::tip
If youre not very familiar with Medusas architecture, you can learn more about it in the [Architecture Overview](../introduction#architecture-overview).
:::
## Install the Admin
Start by cloning the [Admin GitHub repository](https://github.com/medusajs/admin):
```bash
git clone https://github.com/medusajs/admin medusa-admin
```
Then, change to the cloned directory and install the dependencies:
```bash
cd medusa-admin
npm install
```
## Test it Out
Before running your Medusa admin, make sure that your Medusa server is running.
:::tip
To run your Medusa server, go to the directory holding the server and run `npm start`.
:::
Then, in the directory holding your Medusa admin, run the following to start the development server:
```bash
npm start
```
By default, the admin runs on port 7000. So, in your browser, go to `localhost:7000` to view your admin.
![Admin Log In](https://i.imgur.com/XYqMCo9.png)
Use your Medusa admins user credentials to log in.
:::tip
If you installed the demo data when you installed the Medusa server by using the `--seed` option or running `npm run seed`, you can use the email `admin@medusa-test.com` and password `supersecret` to log in.
:::
## Create a New Admin User
To create a new admin user from the command line, run the following command in the directory holding your Medusa server:
```bash
medusa user -e some@email.com -p some-password
```
This will create a new user that you can use to log into your admin panel.
## Changing the Default Port
The default port is set in `package.json` in the `develop` script:
```json
"develop": "gatsby develop -p 7000",
```
If you wish to change the port you can simply change the `7000` to your desired port.
However, if you change your Medusa admin port, you need to change it in your Medusa server. The Medusa server has the Medusa admin and store URLs set in the configurations to avoid CORS issues.
To change the URL of the Medusa admin in the server, add a new environment variable `ADMIN_CORS` or modify it if you already have it to your Admin URL:
```bash
ADMIN_CORS=<YOUR_ADMIN_URL>
```
Make sure to replace `<YOUR_ADMIN_URL>` with your URL.
## Admin Features Overview
### Order Management
In the Medusa admin, you can view all orders in your store and their details. You can fulfill orders, capture payments, and track order history. You can also create and manage return requests, swaps, and claims.
![Order Management](https://i.imgur.com/aE0wOHA.png)
### Product Management
In the Medusa admin, you can manage your stores products. You can create products, add their description and images, create variants with multiple options, set different prices for different currencies, and manage inventory.
:::note
To upload images and save products, you need to integrate a file storage plugin. You can use [DigitalOcean Spaces](../add-plugins/spaces), [S3](../add-plugins/s3), or [MinIO](../add-plugins/minio).
:::
![Product Management](https://i.imgur.com/hgqqv4p.png)
### Customer Management
In the Medusa admin, you can manage your stores customers. You can manage their information and get a close-up on their orders.
![Customer Management](https://i.imgur.com/bPAImGY.png)
### Discounts Management
In the Medusa admin, you can manage your stores discounts. You can create and manage discounts created for all products or specific products. Discounts can also be of a fixed amount or free shipping. You can also customize the discount code, its expiry date, its description, and more.
![Discounts Management](https://i.imgur.com/CUUcLba.png)
### Gift Cards Management
In the Medusa admin, you can manage your stores gift card products. You can create a gift card product with images and descriptions. You can specify unlimited denominations as well.
![Gift Cards Management](https://i.imgur.com/243IhXA.png)
### Settings Management
In the Medusa admin, you can manage your stores overall settings. These include your stores regions, fulfillment providers, payment providers, your stores users, and more.
![Settings Management](https://i.imgur.com/MJc92CU.png)
## Whats Next 🚀
- Install the [Next.js](../starters/nextjs-medusa-starter.md) or [Gatsby](../starters/gatsby-medusa-starter.md) storefront starters.
- [Learn how you can use `create-medusa-app` to install all of Medusas 3 components.](../how-to/create-medusa-app.md)
@@ -36,7 +36,11 @@ This exports a function that returns an Express router. In that function, you ca
Now, if you run your server and send a request to `/admin/hello`, you will receive a JSON response message.
> Custom endpoints are compiled into the `dist` directory of your Backend when you run your server using `medusa develop`, while its running, and when you run `npm run build`.
:::note
Custom endpoints are compiled into the `dist` directory of your Backend when you run your server using `medusa develop`, while its running, and when you run `npm run build`.
:::
## Accessing Endpoints from Admin
@@ -36,7 +36,11 @@ This exports a function that returns an Express router. In that function, you ca
Now, if you run your server and send a request to `/store/hello`, you will receive a JSON response message.
> Custom endpoints are compiled into the `dist` directory of your Backend when you run your server using `medusa develop`, while its running, and when you run `npm run build`.
:::note
Custom endpoints are compiled into the `dist` directory of your Backend when you run your server using `medusa develop`, while its running, and when you run `npm run build`.
:::
## Multiple Endpoints
@@ -0,0 +1,92 @@
---
title: Create a Subscriber
---
# Create a Subscriber
In this document, youll learn how you create a subscriber in your Medusa server that listens to events to perform an action.
## Overview
In Medusa, there are events that are emitted when a certain action occurs. For example, if a customer places an order, the `order.placed` event is emitted with the order data.
The purpose of these events is to allow other parts of the platform, or third-party integrations, to listen to those events and perform a certain action. That is done by creating subscribers.
Subscribers register handlers for an events and allows you to perform an action when that event occurs. For example, if you want to send your customer an email when they place an order, then you can listen to the `order.placed` event and send the email when the event is emitted.
Natively in Medusa there are subscribers to handle different events. However, you can also create your own custom subscribers.
Custom subscribers reside in your project's `src/subscribers` directory. Files here should export classes, which will be treated as subscribers by Medusa. By convention, the class name should end with `Subscriber` and the file name should be the camel-case version of the class name without `Subscriber`. For example, the `WelcomeSubscriber` class is in the file `src/subscribers/welcome.js`.
Whenever an event is emitted, the subscribers registered handler method is executed. The handler method receives as a parameter an object that holds data related to the event. For example, if an order is placed the `order.placed` event will be emitted and all the handlers will receive the order id in the parameter object.
## Prerequisites
Medusa's event system works by pushing data to a Queue that each handler then gets notified of. The queuing system is based on Redis and you will therefore need to make sure that [Redis](https://redis.io) is installed and configured for your Medusa project.
Then, you need to set your Redis URL in your Medusa server. By default, the Redis URL is `redis://localhost:6379`. If you use a different one, set the following environment variable in `.env`:
```bash
REDIS_URL=<YOUR_REDIS_URL>
```
After that, in `medusa-config.js`, youll need to comment out the following line:
```jsx
module.exports = {
projectConfig: {
redis_url: REDIS_URL, //this line is commented out
...
}
}
```
After that, you are able to listen to events on your server.
## Implementation
After creating the file under `src/subscribers`, in the constructor of your subscriber, you should listen to events using `eventBusService.subscribe` , where `eventBusService` is a service injected into your subscribers constructor.
The `eventBusService.subscribe` method receives the name of the event as a first parameter and as a second parameter a method in your subscriber that will handle this event.
For example, here is the `OrderNotifierSubscriber` class which is created in `src/subscribers/orderNotifier.js`:
```jsx
class OrderNotifierSubscriber {
constructor({ eventBusService }) {
eventBusService.subscribe("order.placed", this.handleOrder);
}
handleOrder = async (data) => {
console.log("New Order: " + data.id)
};
}
export default OrderNotifierSubscriber;
```
This subscriber will register the method `handleOrder` as one of the handlers of the `order.placed` event. The method `handleOrder` will be executed every time an order is placed, and it will receive the order ID in the `data` parameter. You can then use the orders details to perform any kind of task you need.
> The `data` object will not contain other order data. Only the ID of the order. You can retrieve the order information using the `orderService`.
## Using Services in Subscribers
You can access any service through the dependencies injected to your subscribers constructor.
For example:
```jsx
constructor({ productService, eventBusService }) {
this.productService = productService;
eventBusService.subscribe("order.placed", this.handleOrder);
}
```
You can then use `this.productService` anywhere in your subscribers methods.
## Whats Next 🚀
- [Learn how to create a service.](/advanced/backend/services/create-service)
@@ -45,7 +45,7 @@ The default build and deploy settings fit the needs of a Gatsby application, so
#### Add an environment variable
```shell=
netlify env:set GATSBY_STORE_URL "https://your-medusa-server.com"
netlify env:set GATSBY_MEDUSA_BACKEND_URL "https://your-medusa-server.com"
```
The above environment variable should point to your Medusa server.
@@ -0,0 +1,7 @@
# Documentation Error
If you have installed the dependencies in the root of this repository (i.e., if you have a `node_modules` directory at the root of this repository), this will cause an error when running this documentation website. This is because the content resides in `docs/content` and when that content is being imported from there, a mix up can happen between the dependencies which will cause an `invalid hook call` error.
For that reason, we added a `clean-node-modules` script that deletes the `node_modules` directory, and we call that script before the `start` and `build` scripts are ran.
So, everytime you run these 2 scripts, the `node_modules` directory at the root will be deleted.
+8
View File
@@ -1,3 +1,11 @@
# Website
This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator.
## Note Before Development
If you have installed the dependencies in the root of this repository (i.e., if you have a `node_modules` directory at the root of this repository), this will cause an error when running this documentation website. This is because the content resides in `docs/content` and when that content is being imported from there, a mix up can happen between the dependencies which will cause an `invalid hook call` error.
For that reason, we added a `clean-node-modules` script that deletes the `node_modules` directory, and we call that script before the `start` and `build` scripts are ran.
So, everytime you run these 2 scripts, the `node_modules` directory at the root will be deleted.
+14 -9
View File
@@ -6,7 +6,7 @@ const algoliaApiKey = process.env.ALGOLIA_API_KEY || "temp"
/** @type {import('@docusaurus/types').DocusaurusConfig} */
module.exports = {
title: "Medusa Commerce",
title: "Medusa",
tagline: "Explore and learn how to use Medusa",
url: "https://docs.medusajs.com",
baseUrl: "/",
@@ -25,18 +25,22 @@ module.exports = {
],
],
themeConfig: {
disableSwitch: true,
colorMode: {
defaultMode: 'light',
disableSwitch: true,
},
algolia: {
apiKey: algoliaApiKey,
indexName: "medusa-commerce",
placeholder: "Search docs...",
appId: algoliaAppId,
contextualSearch: false,
},
prism: {
defaultLanguage: "js",
plugins: ["line-numbers", "show-language"],
theme: require("@kiwicopple/prism-react-renderer/themes/vsDark"),
darkTheme: require("@kiwicopple/prism-react-renderer/themes/vsDark"),
theme: require("prism-react-renderer/themes/vsDark"),
darkTheme: require("prism-react-renderer/themes/vsDark"),
},
navbar: {
hideOnScroll: true,
@@ -44,6 +48,7 @@ module.exports = {
alt: "Medusa Commerce",
src: "img/logo.svg",
srcDark: "img/logo.svg",
width: 100
},
items: [
{
@@ -98,12 +103,12 @@ module.exports = {
title: "More",
items: [
{
label: "Contact",
href: "https://medusa-commere.com",
label: "Medusa Home",
href: "https://medusajs.com",
},
{
label: "Privacy & Terms",
href: "https://medusa-commere.com",
label: "Contact",
href: "https://ky5eo2x1u81.typeform.com/get-in-touch",
},
{
label: "GitHub",
@@ -112,7 +117,7 @@ module.exports = {
],
},
],
copyright: `© ${new Date().getFullYear()} Medusa Commerce`,
copyright: `© ${new Date().getFullYear()} Medusa`,
},
},
presets: [
+8 -8
View File
@@ -4,7 +4,10 @@
"private": true,
"scripts": {
"docusaurus": "docusaurus",
"clean-node-modules": "rm -rf ../../node_modules",
"prestart": "yarn clean-node-modules",
"start": "docusaurus clear && docusaurus start",
"prebuild": "yarn clean-node-modules",
"build": "docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
@@ -14,17 +17,14 @@
"write-heading-ids": "docusaurus write-heading-ids"
},
"dependencies": {
"@docusaurus/core": "2.0.0-beta.3",
"@docusaurus/preset-classic": "2.0.0-beta.3",
"@docusaurus/theme-search-algolia": "^2.0.0-beta.3",
"@kiwicopple/prism-react-renderer": "github:kiwicopple/prism-react-renderer",
"@mdx-js/react": "^1.6.21",
"@svgr/webpack": "^5.5.0",
"@docusaurus/core": "2.0.0-beta.17",
"@docusaurus/preset-classic": "2.0.0-beta.17",
"@svgr/webpack": "6.2.1",
"clsx": "^1.1.1",
"docusaurus2-dotenv": "^1.4.0",
"file-loader": "^6.2.0",
"lodash": "^4.17.21",
"prism-react-renderer": "^1.2.1",
"prism-react-renderer": "^1.3.1",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"url-loader": "^4.1.1"
@@ -44,4 +44,4 @@
"devDependencies": {
"prettier": "^2.3.2"
}
}
}
+123 -60
View File
@@ -63,6 +63,10 @@ module.exports = {
},
],
},
{
type: "doc",
id: "admin/quickstart",
},
{
type: "doc",
id: "how-to/create-medusa-app",
@@ -130,7 +134,7 @@ module.exports = {
{
type: "category",
label: 'Services',
collapsed: true,
collapsed: false,
items: [
{
type: "doc",
@@ -139,6 +143,18 @@ module.exports = {
},
]
},
{
type: "category",
label: 'Subscribers',
collapsed: false,
items: [
{
type: "doc",
id: "advanced/backend/subscribers/create-subscriber",
label: "Create a Subscriber"
},
]
},
{
type: "category",
label: 'Shipping',
@@ -187,82 +203,124 @@ module.exports = {
items: [
{
type: "category",
label: "Gatsby + Contentful + Medusa",
label: "Analytics",
items: [
{
type: "doc",
id: "how-to/headless-ecommerce-store-with-gatsby-contentful-medusa",
},
{
type: "doc",
id: "how-to/making-your-store-more-powerful-with-contentful",
id: "add-plugins/segment",
label: "Segment",
},
],
},
{
type: "doc",
id: "add-plugins/contentful",
label: "CMS: Contentful",
type: "category",
label: "Bot",
items: [
{
type: "doc",
id: "add-plugins/slack",
label: "Slack",
},
],
},
{
type: "doc",
id: "add-plugins/strapi",
label: "CMS: Strapi",
type: "category",
label: "CMS",
items: [
{
type: "doc",
id: "add-plugins/contentful",
label: "Contentful",
},
{
type: "doc",
id: "add-plugins/strapi",
label: "Strapi",
},
{
type: "category",
label: "Gatsby + Contentful + Medusa",
items: [
{
type: "doc",
id: "how-to/headless-ecommerce-store-with-gatsby-contentful-medusa",
},
{
type: "doc",
id: "how-to/making-your-store-more-powerful-with-contentful",
},
],
},
],
},
{
type: "doc",
id: "add-plugins/segment",
label: "Analytics: Segment",
type: "category",
label: "Notifications",
items: [
{
type: "doc",
id: "add-plugins/sendgrid",
label: "SendGrid",
},
],
},
{
type: "doc",
id: "add-plugins/meilisearch",
label: "Search: MeiliSearch",
type: "category",
label: "Payment",
items: [
{
type: "doc",
id: "add-plugins/klarna",
label: "Klarna",
},
{
type: "doc",
id: "add-plugins/paypal",
label: "PayPal",
},
{
type: "doc",
id: "add-plugins/stripe",
label: "Stripe",
},
],
},
{
type: "doc",
id: "add-plugins/algolia",
label: "Search: Algolia",
type: "category",
label: "Search",
items: [
{
type: "doc",
id: "add-plugins/algolia",
label: "Algolia",
},
{
type: "doc",
id: "add-plugins/meilisearch",
label: "MeiliSearch",
},
],
},
{
type: "doc",
id: "add-plugins/spaces",
label: "File: Spaces",
},
{
type: "doc",
id: "add-plugins/s3",
label: "File: S3",
},
{
type: "doc",
id: "add-plugins/minio",
label: "File: MinIO",
},
{
type: "doc",
id: "add-plugins/stripe",
label: "Payment: Stripe",
},
{
type: "doc",
id: "add-plugins/klarna",
label: "Payment: Klarna",
},
{
type: "doc",
id: "add-plugins/paypal",
label: "Payment: PayPal",
},
{
type: "doc",
id: "add-plugins/sendgrid",
label: "Notification: SendGrid",
},
{
type: "doc",
id: "add-plugins/slack",
label: "Bot: Slack",
type: "category",
label: "Storage",
items: [
{
type: "doc",
id: "add-plugins/minio",
label: "MinIO",
},
{
type: "doc",
id: "add-plugins/s3",
label: "S3",
},
{
type: "doc",
id: "add-plugins/spaces",
label: "Spaces",
},
],
},
],
},
@@ -295,6 +353,11 @@ module.exports = {
id: "troubleshooting/signing-in-to-admin",
label: "Signing in to Medusa Admin",
},
{
type: "doc",
id: "troubleshooting/documentation-error",
label: "Documentation Error",
},
],
},
],
+5 -4
View File
@@ -1,13 +1,14 @@
import React, { useEffect, useState } from "react"
import CloseIcon from "../close-icon"
import styles from "./banner.module.css"
import clsx from "clsx"
import useThemeContext from "@theme/hooks/useThemeContext"
import ConfLogo from "../../../static/img/logo.svg"
import clsx from "clsx"
import styles from "./banner.module.css"
import {useColorMode} from '@docusaurus/theme-common';
const Banner = (props) => {
const [isBannerVisible, setIsBannerVisible] = useState(true)
const { isDarkTheme } = useThemeContext()
const { isDarkTheme } = useColorMode()
const handleDismissBanner = () => {
setIsBannerVisible(false)
+12 -10
View File
@@ -62,12 +62,19 @@ p {
/* DocSearch */
html[data-theme="light"] .DocSearch-Button {
/* html[data-theme="light"] .DocSearch-Button {
--docsearch-searchbox-background: #fff;
}
html[data-theme="dark"] .DocSearch-Button {
--docsearch-searchbox-background: #1f1f1f;
} */
.DocSearch-Button {
width: 100%;
max-width: 175px;
border-radius: 8px !important;
--docsearch-container-background: #f5f6f7;
}
span.DocSearch-Button-Key {
@@ -90,11 +97,6 @@ html[data-theme="dark"] .docusaurus-highlight-code-line {
font-size: 14px;
}
/* Medusa logo */
.navbar__brand {
width: 100px;
}
.navbar-github-link:before {
content: "";
width: 24px;
@@ -203,10 +205,6 @@ footer .footer__items svg {
display: none;
}
footer .footer__item a:hover {
color: white;
}
@media screen and (min-width: 966px) {
footer .footer__col {
display: flex;
@@ -255,3 +253,7 @@ footer {
.prism-code div:active {
outline: none !important;
}
details summary {
cursor: pointer;
}
+5 -244
View File
@@ -1,249 +1,10 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import { DocSearchButton, useDocSearchKeyboardEvents } from "@docsearch/react"
import Head from "@docusaurus/Head"
import Link from "@docusaurus/Link"
import { useHistory } from "@docusaurus/router"
import { translate } from "@docusaurus/Translate"
import { useBaseUrlUtils } from "@docusaurus/useBaseUrl"
import useDocusaurusContext from "@docusaurus/useDocusaurusContext"
import useAlgoliaContextualFacetFilters from "@theme/hooks/useAlgoliaContextualFacetFilters"
import useSearchQuery from "@theme/hooks/useSearchQuery"
import React, { useCallback, useMemo, useRef, useState } from "react"
import { createPortal } from "react-dom"
import styles from "./styles.module.css"
let DocSearchModal = null
const convertToKebabCase = (string) => {
return string
.replace(/\s+/g, "-")
.replace("'", "")
.replace(".", "")
.replace('"', "")
.toLowerCase()
}
const replaceUrl = (item) => {
let { url, hierarchy } = item
if (url.includes("api/store") || url.includes("/api/admin")) {
url = url.replace("#", "")
if (hierarchy.lvl2) {
const index = url.lastIndexOf("/")
url =
url.substring(0, index) +
`/${convertToKebabCase(hierarchy.lvl1)}` +
url.substring(index)
}
}
return url
}
function Hit({ hit, children }) {
if (hit.url.includes("/api/store") || hit.url.includes("/api/admin")) {
const url = replaceUrl(hit)
return <a href={url}>{children}</a>
}
return <Link to={hit.url}>{children}</Link>
}
function ResultsFooter({ state, onClose }) {
const { generateSearchPageLink } = useSearchQuery()
return (
<Link to={generateSearchPageLink(state.query)} onClick={onClose}>
See all {state.context.nbHits} results
</Link>
)
}
function DocSearch({ contextualSearch, ...props }) {
const { siteMetadata } = useDocusaurusContext()
const contextualSearchFacetFilters = useAlgoliaContextualFacetFilters()
const configFacetFilters = props.searchParameters?.facetFilters ?? []
const facetFilters = contextualSearch
? // Merge contextual search filters with config filters
[...contextualSearchFacetFilters, ...configFacetFilters]
: // ... or use config facetFilters
configFacetFilters
// we let user override default searchParameters if he wants to
const searchParameters = {
...props.searchParameters,
facetFilters,
}
const { withBaseUrl } = useBaseUrlUtils()
const history = useHistory()
const searchContainer = useRef(null)
const searchButtonRef = useRef(null)
const [isOpen, setIsOpen] = useState(false)
const [initialQuery, setInitialQuery] = useState(null)
const importDocSearchModalIfNeeded = useCallback(() => {
if (DocSearchModal) {
return Promise.resolve()
}
return Promise.all([
import("@docsearch/react/modal"),
import("@docsearch/react/style"),
import("./styles.css"),
]).then(([{ DocSearchModal: Modal }]) => {
DocSearchModal = Modal
})
}, [])
const onOpen = useCallback(() => {
importDocSearchModalIfNeeded().then(() => {
searchContainer.current = document.createElement("div")
document.body.insertBefore(
searchContainer.current,
document.body.firstChild
)
setIsOpen(true)
})
}, [importDocSearchModalIfNeeded, setIsOpen])
const onClose = useCallback(() => {
setIsOpen(false)
searchContainer.current.remove()
}, [setIsOpen])
const onInput = useCallback(
(event) => {
importDocSearchModalIfNeeded().then(() => {
setIsOpen(true)
setInitialQuery(event.key)
})
},
[importDocSearchModalIfNeeded, setIsOpen, setInitialQuery]
)
const navigator = useRef({
navigate({ item }) {
const url = replaceUrl(item)
// Need to type out the entire URL to prevent it from attempting to open the page
// as part of the docusaurus project. Which will fail.
window.location = `https://docs.medusajs.com${url}`
},
navigateNewTab({ item }) {
const url = replaceUrl(item)
const windowReference = window.open(url, "_blank", "noopener")
if (windowReference) {
windowReference.focus()
}
},
navigateNewWindow({ item }) {
const url = replaceUrl(item)
window.open(url, "_blank", "noopener")
},
}).current
const transformItems = useRef((items) => {
return items.map((item) => {
// We transform the absolute URL into a relative URL.
// Alternatively, we can use `new URL(item.url)` but it's not
// supported in IE.
const a = document.createElement("a")
a.href = item.url
return {
...item,
url: withBaseUrl(`${a.pathname}${a.hash}`),
}
})
}).current
const resultsFooterComponent = useMemo(
() => (footerProps) => <ResultsFooter {...footerProps} onClose={onClose} />,
[onClose]
)
const transformSearchClient = useCallback(
(searchClient) => {
searchClient.addAlgoliaAgent("docusaurus", siteMetadata.docusaurusVersion)
return searchClient
},
[siteMetadata.docusaurusVersion]
)
useDocSearchKeyboardEvents({
isOpen,
onOpen,
onClose,
onInput,
searchButtonRef,
})
const translatedSearchLabel = translate({
id: "theme.SearchBar.label",
message: "Search",
description: "The ARIA label and placeholder for search button",
})
import React from 'react';
import SearchBar from '@theme-original/SearchBar';
export default function SearchBarWrapper(props) {
return (
<>
<Head>
{/* This hints the browser that the website will load data from Algolia,
and allows it to preconnect to the DocSearch cluster. It makes the first
query faster, especially on mobile. */}
<link
rel="preconnect"
href={`https://${props.appId}-dsn.algolia.net`}
crossOrigin="anonymous"
/>
</Head>
<div className={styles.searchBox}>
<DocSearchButton
onTouchStart={importDocSearchModalIfNeeded}
onFocus={importDocSearchModalIfNeeded}
onMouseOver={importDocSearchModalIfNeeded}
onClick={onOpen}
ref={searchButtonRef}
translations={{
buttonText: translatedSearchLabel,
buttonAriaLabel: translatedSearchLabel,
}}
/>
</div>
{isOpen &&
createPortal(
<DocSearchModal
onClose={onClose}
initialScrollY={window.scrollY}
initialQuery={initialQuery}
navigator={navigator}
transformItems={transformItems}
hitComponent={Hit}
resultsFooterComponent={resultsFooterComponent}
transformSearchClient={transformSearchClient}
{...props}
searchParameters={searchParameters}
/>,
searchContainer.current
)}
<SearchBar {...props} />
</>
)
);
}
function SearchBar() {
const { siteConfig } = useDocusaurusContext()
return <DocSearch {...siteConfig.themeConfig.algolia} />
}
export default SearchBar
+1092 -2783
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -331,7 +331,7 @@ const createAllPages = (sections, api, siteData, createPage, template) => {
api: api,
title: edge.section.section_name,
description: edge.section.schema ? edge.section.schema.description : "",
to: { section: baseURL, method: null },
to: { section: baseURL, method: null, sectionObj: edge.section },
},
})
edge.section.paths.forEach(p => {
@@ -345,7 +345,7 @@ const createAllPages = (sections, api, siteData, createPage, template) => {
api: api,
title: method.summary,
description: method.description || "",
to: { section: baseURL, method: methodURL },
to: { section: baseURL, method: methodURL, sectionObj: edge.section },
},
})
})
@@ -1,9 +1,10 @@
import React from "react"
import { Box, Flex } from "theme-ui"
import Topbar from "../topbar"
import Section from "./section"
const Content = ({ data, api }) => {
import React from "react"
import Section from "./section"
import Topbar from "../topbar"
const Content = ({ data, currentSection, api }) => {
return (
<Flex
sx={{
@@ -20,11 +21,7 @@ const Content = ({ data, api }) => {
},
}}
>
<main className="DocSearch-content">
{data.sections.map((s, i) => {
return <Section key={i} data={s} api={api} />
})}
</main>
<Section data={currentSection} api={api} />
</Box>
</Flex>
)
@@ -13,7 +13,7 @@ import { formatMethodParams } from "../../utils/format-parameters"
import { formatRoute } from "../../utils/format-route"
import useInView from "../../hooks/use-in-view"
const Method = ({ data, section, pathname, api }) => {
const Method = ({ data, section, sectionData, pathname, api }) => {
const { parameters, requestBody, description, method, summary } = data
const jsonResponse = data.responses[0].content?.[0].json
const { updateHash, updateMetadata } = useContext(NavigationContext)
@@ -27,7 +27,7 @@ const Method = ({ data, section, pathname, api }) => {
useEffect(() => {
if (isInView) {
updateHash(section, convertToKebabCase(summary))
updateHash(section, convertToKebabCase(summary), sectionData)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isInView])
+22 -14
View File
@@ -1,21 +1,22 @@
import React, { useState, useRef, useEffect, useContext } from "react"
import { Flex, Box, Heading, Text, Button } from "theme-ui"
import Method from "./method"
import Parameters from "./parameters"
import { convertToKebabCase } from "../../utils/convert-to-kebab-case"
import EndpointContainer from "./endpoint-container"
import Markdown from "react-markdown"
import JsonContainer from "./json-container"
import ResponsiveContainer from "./responsive-container"
import Description from "./description"
import NavigationContext from "../../context/navigation-context"
import { Box, Button, Flex, Heading, Text } from "theme-ui"
import React, { useContext, useEffect, useRef, useState } from "react"
import ChevronDown from "../icons/chevron-down"
import Description from "./description"
import EndpointContainer from "./endpoint-container"
import JsonContainer from "./json-container"
import Markdown from "react-markdown"
import Method from "./method"
import NavigationContext from "../../context/navigation-context"
import Parameters from "./parameters"
import ResponsiveContainer from "./responsive-container"
import { convertToKebabCase } from "../../utils/convert-to-kebab-case"
import useInView from "../../hooks/use-in-view"
const Section = ({ data, api }) => {
const { section } = data
const section = data;
const [isExpanded, setIsExpanded] = useState(false)
const { openSections, updateSection, updateMetadata } = useContext(
const { openSections, updateSection, updateMetadata, updateHash } = useContext(
NavigationContext
)
@@ -61,6 +62,12 @@ const Section = ({ data, api }) => {
}
}, [section.section_name, openSections, openSections.length])
useEffect(() => {
if (section.section_name) {
updateHash(convertToKebabCase(section.section_name), section.paths && section.paths.length ? (section.paths[0].methods[0].path || '') : '', section)
}
}, [section.section_name])
const [containerRef, isInView] = useInView({
root: null,
rootMargin: "0px 0px -80% 0px",
@@ -70,7 +77,7 @@ const Section = ({ data, api }) => {
useEffect(() => {
const handleInView = () => {
if (isInView) {
updateSection(convertToKebabCase(section.section_name))
updateSection({id: convertToKebabCase(section.section_name), section})
}
}
handleInView()
@@ -188,6 +195,7 @@ const Section = ({ data, api }) => {
key={i}
data={m}
section={convertToKebabCase(section.section_name)}
sectionData={section}
pathname={p.name}
/>
)
+24 -4
View File
@@ -1,10 +1,30 @@
import { Box, Flex } from "theme-ui"
import React from "react"
import { Flex, Box } from "theme-ui"
import Sidebar from "./side-bar"
import styled from "@emotion/styled"
const LayoutContainer = styled(Flex)`
--side-bar-width: 220px;
@media screen and (min-width: 1680px) {
--side-bar-width: 280px;
}
`
const ContentBox = styled(Box)`
@media screen and (min-width: 849px) {
width: calc(100% - var(--side-bar-width));
}
@media screen and (max-width: 848px) {
width: 100%;
}
`
const Layout = ({ data, api, children }) => {
return (
<Flex sx={{ p: "0", m: "0", overflow: "hidden" }}>
<LayoutContainer sx={{ p: "0", m: "0", overflow: "hidden" }}>
<Flex
sx={{
position: "absolute",
@@ -17,9 +37,9 @@ const Layout = ({ data, api, children }) => {
}}
>
<Sidebar data={data} api={api} />
<Box>{children}</Box>
<ContentBox>{children}</ContentBox>
</Flex>
</Flex>
</LayoutContainer>
)
}
@@ -1,9 +1,10 @@
import React, { useContext } from "react"
import { convertToKebabCase } from "../../utils/convert-to-kebab-case"
import NavigationContext from "../../context/navigation-context"
import { convertToKebabCase } from "../../utils/convert-to-kebab-case"
import { navigate } from "gatsby"
const HitComponent = ({ hit, children }) => {
const HitComponent = ({ hit, children, data }) => {
const { goTo, api } = useContext(NavigationContext)
let { url, type, hierarchy } = hit
@@ -20,13 +21,17 @@ const HitComponent = ({ hit, children }) => {
*/
const goToHierarchy = e => {
e.preventDefault()
//find section
let section = data.sections.find((s) => s.section.section_name == hierarchy.lvl1);
section = section ? section.section : {}
if (hierarchy.lvl2) {
goTo({
section: convertToKebabCase(hierarchy.lvl1),
method: convertToKebabCase(hierarchy.lvl2),
sectionObj: section
})
} else {
goTo({ section: convertToKebabCase(hierarchy.lvl1) })
goTo({ section: convertToKebabCase(hierarchy.lvl1), sectionObj: section })
}
}
+11 -5
View File
@@ -1,6 +1,8 @@
import React, { useContext } from "react"
import { DocSearch } from "@docsearch/react"
import "../../medusa-plugin-themes/docsearch/theme.css"
import React, { useContext } from "react"
import { DocSearch } from "@docsearch/react"
import HitComponent from "./hit-component"
import NavigationContext from "../../context/navigation-context"
import { convertToKebabCase } from "../../utils/convert-to-kebab-case"
@@ -8,7 +10,7 @@ import { navigate } from "gatsby-link"
const algoliaApiKey = process.env.ALGOLIA_API_KEY || "temp"
const Search = () => {
const Search = ({data}) => {
const { goTo, api } = useContext(NavigationContext)
const getOtherAPI = () => {
@@ -36,13 +38,17 @@ const Search = () => {
*/
const goToHierarchy = item => {
const { hierarchy } = item
//find section
let section = data.sections.find((s) => s.section.section_name == hierarchy.lvl1);
section = section ? section.section : {}
if (hierarchy.lvl2) {
goTo({
section: convertToKebabCase(hierarchy.lvl1),
method: convertToKebabCase(hierarchy.lvl2),
sectionObj: section
})
} else {
goTo({ section: convertToKebabCase(hierarchy.lvl1) })
goTo({ section: convertToKebabCase(hierarchy.lvl1), sectionObj: section })
}
}
@@ -67,7 +73,7 @@ const Search = () => {
<DocSearch
apiKey={algoliaApiKey}
indexName="medusa-commerce"
hitComponent={HitComponent}
hitComponent={({hit, children}) => <HitComponent data={data} hit={hit} children={children} />}
navigator={{
navigate({ item }) {
if (item.url.includes(`api/${api}`)) {
@@ -1,19 +1,14 @@
import { Box, Flex, Image } from "theme-ui"
import React, { useEffect, useState } from "react"
import { Flex, Image, Box } from "theme-ui"
import styled from "@emotion/styled"
import Logo from "../../assets/logo.svg"
import LogoMuted from "../../assets/logo-muted.svg"
import SideBarItem from "./sidebar-item"
import SideBarSelector from "./sidebar-selector"
import { navigate } from "gatsby"
import styled from "@emotion/styled"
const SideBarContainer = styled(Flex)`
--side-bar-width: 220px;
@media screen and (min-width: 1680px) {
--side-bar-width: 280px;
}
@media screen and (max-width: 848px) {
display: none;
}
@@ -1,10 +1,11 @@
import React, { useContext } from "react"
import Collapsible from "react-collapsible"
import { Flex, Box, Text } from "theme-ui"
import styled from "@emotion/styled"
import { convertToKebabCase } from "../../utils/convert-to-kebab-case"
import { Box, Flex, Text } from "theme-ui"
import React, { useContext, useEffect, useState } from "react"
import ChevronDown from "../icons/chevron-down"
import Collapsible from "react-collapsible"
import NavigationContext from "../../context/navigation-context"
import { convertToKebabCase } from "../../utils/convert-to-kebab-case"
import styled from "@emotion/styled"
const StyledCollapsible = styled(Collapsible)`
margin-bottom: 10px;
@@ -26,6 +27,7 @@ const SideBarItem = ({ item }) => {
currentSection,
goTo,
} = useContext(NavigationContext)
const [isOpen, setIsOpen] = useState(false);
const { section } = item
const subItems = section.paths
.map(p => {
@@ -47,16 +49,21 @@ const SideBarItem = ({ item }) => {
if (element) {
element.scrollIntoView()
if (!openSections.includes(id)) {
openSection(id)
openSection({id, section})
}
}
}
const handleSubClick = path => {
const id = convertToKebabCase(section.section_name)
goTo({ section: id, method: path })
goTo({ section: id, method: path, sectionObj: section })
}
useEffect(() => {
setIsOpen(currentSection === convertToKebabCase(section.section_name) ||
openSections.includes(convertToKebabCase(section.section_name)));
}, [section.section_name, currentSection, openSections])
return (
<Container id={`nav-${convertToKebabCase(section.section_name)}`}>
<StyledCollapsible
@@ -86,10 +93,7 @@ const SideBarItem = ({ item }) => {
{section.section_name} <ChevronDown />
</Flex>
}
open={
currentSection === convertToKebabCase(section.section_name) ||
openSections.includes(convertToKebabCase(section.section_name))
}
open={isOpen}
onTriggerOpening={handleClick}
transitionTime={1}
>
+9 -6
View File
@@ -1,12 +1,12 @@
import { Box, Flex, Link, Select } from "@theme-ui/components"
import { navigate } from "gatsby-link"
import React, { useContext } from "react"
import ChevronDown from "./icons/chevron-down"
import GitHub from "../components/icons/github"
import NavigationContext from "../context/navigation-context"
import { convertToKebabCase } from "../utils/convert-to-kebab-case"
import ChevronDown from "./icons/chevron-down"
import Search from "./search"
import { convertToKebabCase } from "../utils/convert-to-kebab-case"
import { navigate } from "gatsby-link"
const Topbar = ({ data, api }) => {
const { goTo, reset, currentSection } = useContext(NavigationContext)
@@ -15,7 +15,10 @@ const Topbar = ({ data, api }) => {
const parts = e.target.value.split(" ")
if (parts[0] === api) {
goTo({ section: parts[1] })
//find section
let sectionObj = data.sections.find((s) => convertToKebabCase(s.section.section_name) === parts[1]);
sectionObj = sectionObj ? sectionObj.section : {};
goTo({ section: parts[1], sectionObj })
} else {
reset()
navigate(`/api/${api === "admin" ? "store" : "admin"}`)
@@ -112,7 +115,7 @@ const Topbar = ({ data, api }) => {
>
<GitHub />
</Link>
<Search />
<Search data={data} />
</Flex>
</Flex>
)
+43 -24
View File
@@ -1,4 +1,5 @@
import React, { useReducer } from "react"
import { checkDisplay } from "../utils/check-display"
import scrollParent from "../utils/scroll-parent"
import types from "./types"
@@ -7,6 +8,11 @@ export const defaultNavigationContext = {
api: "null",
setApi: () => {},
currentSection: null,
currentSectionObj: {
section_name: "",
paths: [],
schema: {}
},
updateSection: () => {},
currentHash: null,
updateHash: () => {},
@@ -21,6 +27,7 @@ const NavigationContext = React.createContext(defaultNavigationContext)
export default NavigationContext
const reducer = (state, action) => {
let obj = []
switch (action.type) {
case types.SET_API: {
return {
@@ -29,23 +36,30 @@ const reducer = (state, action) => {
}
}
case types.UPDATE_HASH:
return {
...state,
currentSection: action.payload.section,
currentHash: action.payload.method,
}
case types.UPDATE_SECTION:
return {
...state,
currentSection: action.payload,
currentHash: null,
}
case types.OPEN_SECTION:
const obj = state.openSections
obj.push(action.payload)
obj.push(action.payload.section)
return {
...state,
openSections: obj,
currentSection: action.payload.section,
currentHash: action.payload.method,
currentSectionObj: action.payload.sectionObj
}
case types.UPDATE_SECTION:
obj.push(action.payload.id)
return {
...state,
openSections: obj,
currentSection: action.payload.id,
currentHash: null,
currentSectionObj: action.payload.section
}
case types.OPEN_SECTION:
obj.push(action.payload.id)
return {
...state,
openSections: obj,
currentSection: action.payload.id,
currentSectionObj: action.payload.section
}
case types.RESET:
return {
@@ -53,6 +67,11 @@ const reducer = (state, action) => {
openSections: [],
currentSection: null,
currentHash: null,
currentSectionObj: {
section_name: "",
paths: [],
schema: {}
}
}
case types.UPDATE_METADATA:
return {
@@ -108,10 +127,10 @@ export const NavigationProvider = ({ children }) => {
dispatch({ type: types.UPDATE_METADATA, payload: metadata })
}
const updateHash = (section, method) => {
const updateHash = (section, method, sectionObj) => {
dispatch({
type: types.UPDATE_HASH,
payload: { method: method, section: section },
payload: { method: method, section: section, sectionObj },
})
window.history.replaceState(
null,
@@ -121,21 +140,21 @@ export const NavigationProvider = ({ children }) => {
scrollNav(method)
}
const updateSection = section => {
dispatch({ type: types.UPDATE_SECTION, payload: section })
window.history.replaceState(null, "", `/api/${state.api}/${section}`)
scrollNav(section)
const updateSection = ({id, section}) => {
dispatch({ type: types.UPDATE_SECTION, payload: {id, section} })
window.history.replaceState(null, "", `/api/${state.api}/${id}`)
scrollNav(id)
}
const openSection = sectionName => {
dispatch({ type: types.OPEN_SECTION, payload: sectionName })
const openSection = ({id, section}) => {
dispatch({ type: types.OPEN_SECTION, payload: {id, section} })
}
const goTo = to => {
const { section, method } = to
const { section, method, sectionObj } = to
if (!state.openSections.includes(section)) {
openSection(section)
openSection({id: section, section: sectionObj})
}
scrollToElement(method || section)
}
+14 -5
View File
@@ -1,14 +1,15 @@
import React, { useContext, useEffect, useState } from "react"
import { Helmet } from "react-helmet"
import Layout from "../components/layout"
import Content from "../components/content"
import { Helmet } from "react-helmet"
import Layout from "../components/layout"
import NavigationContext from "../context/navigation-context"
import { convertToKebabCase } from "../utils/convert-to-kebab-case"
export default function ReferencePage({
pageContext: { data, api, title, description, to },
}) {
const { setApi, goTo, metadata } = useContext(NavigationContext)
const { setApi, goTo, metadata, currentSection, currentSectionObj } = useContext(NavigationContext)
const [siteData, setSiteData] = useState({
title: title,
description: description,
@@ -18,6 +19,14 @@ export default function ReferencePage({
setApi(api)
if (to) {
goTo(to)
} else if (data.sections && data.sections.length) {
//go to the first section
const firstSection = data.sections[0].section;
goTo({
section: convertToKebabCase(firstSection.section_name),
method: firstSection.paths && firstSection.paths.length ? firstSection.paths[0].methods[0].method : '',
sectionObj: firstSection
})
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
@@ -34,10 +43,10 @@ export default function ReferencePage({
return (
<Layout data={data} api={api}>
<Helmet>
<title>{`API | Medusa Commerce API Reference`}</title>
<title>{`API | Medusa API Reference`}</title>
<meta name="description" content={siteData.description} />
</Helmet>
<Content data={data} api={api} />
<Content data={data} currentSection={currentSectionObj} api={api} />
</Layout>
)
}