feat: Update to API references look and feel (#343)
Co-authored-by: Vadim Smirnov <smirnou.vadzim@gmail.com> Co-authored-by: zakariasaad <zakaria.elas@gmail.com> Co-authored-by: Vilfred Sikker <vilfredsikker@gmail.com> Co-authored-by: olivermrbl <oliver@mrbltech.com>
This commit is contained in:
committed by
GitHub
parent
40400b483c
commit
143f06aa39
@@ -5308,7 +5308,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/returns/{id}receive": {
|
||||
"/returns/{id}/receive": {
|
||||
"post": {
|
||||
"operationId": "PostReturnsReturnReceive",
|
||||
"summary": "Receive a Return",
|
||||
@@ -5881,7 +5881,7 @@
|
||||
},
|
||||
"delete": {
|
||||
"operationId": "DeleteStoreCurrenciesCode",
|
||||
"summary": "Remvoe a Currency Code",
|
||||
"summary": "Remove a Currency Code",
|
||||
"description": "Removes a Currency Code from the available currencies.",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -6085,7 +6085,7 @@
|
||||
"/variants": {
|
||||
"get": {
|
||||
"operationId": "GetVariants",
|
||||
"summary": "List Product Variants.",
|
||||
"summary": "List Product Variants",
|
||||
"description": "Retrieves a list of Product Variants",
|
||||
"tags": [
|
||||
"Product Variant"
|
||||
@@ -7546,7 +7546,7 @@
|
||||
"swaps": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/refund"
|
||||
"$ref": "#/components/schemas/swap"
|
||||
}
|
||||
},
|
||||
"gift_card_transactions": {
|
||||
|
||||
@@ -955,4 +955,4 @@
|
||||
"idempotency_key": "10804103-2f4f-41ef-b44e-7049459f157d"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
41
docs/content/nav.yml
Normal file
41
docs/content/nav.yml
Normal file
@@ -0,0 +1,41 @@
|
||||
- title: Tutorials
|
||||
id: tutorials
|
||||
items:
|
||||
- id: overview
|
||||
title: Overview
|
||||
- id: getting-started
|
||||
title: Getting started
|
||||
- id: adding-plugins
|
||||
title: Adding plugins
|
||||
- id: creating-a-custom-endpoint
|
||||
title: Creating a custom endpoint
|
||||
- id: linking-medusa-cloud
|
||||
title: Linking Medusa Cloud
|
||||
- title: Guides
|
||||
id: guides
|
||||
items:
|
||||
- id: overview
|
||||
title: Overview
|
||||
- id: local-development
|
||||
title: Local development
|
||||
items:
|
||||
- id: overview
|
||||
title: Overview
|
||||
- id: create-project-from-starter
|
||||
title: Create project from starter
|
||||
items:
|
||||
- id: medusa-starter
|
||||
title: Medusa starter
|
||||
- id: gatsby-starter-medusa
|
||||
title: Gatsby starter
|
||||
- id: environment-variables
|
||||
title: Environment variables
|
||||
- title: Reference
|
||||
id: reference
|
||||
items:
|
||||
- id: admin
|
||||
title: admin
|
||||
- id: store
|
||||
title: store
|
||||
- title: Contributing
|
||||
id: contributing
|
||||
89
docs/content/tutorials/getting-started.md
Normal file
89
docs/content/tutorials/getting-started.md
Normal file
@@ -0,0 +1,89 @@
|
||||
---
|
||||
title: Getting started
|
||||
---
|
||||
# Quick Start w. Docker
|
||||
|
||||
This quick start is intended for developers, that have already configured their local development environment and familiarised them selves with all the technologies and frameworks used throughout the Medusa eco-system.
|
||||
|
||||
If this is not the case, please head over to our Getting Started tutorial for a thorough walkthrough.
|
||||
|
||||
## Introduction
|
||||
|
||||
With all the tools and technologies in place, let's get started by setting up a default project. Our starter is shipped with a very basic configuration, that includes the following plugins:
|
||||
|
||||
- Stripe as payment provider
|
||||
- SendGrid as email notification provider
|
||||
- Manual fulfilment as fulfilment provider
|
||||
|
||||
Additionally, we will spin up a PostgreSQL database and a Redis server, both required for Medusa to run. In this quick start, we will use docker to seamlessly set up these resources.
|
||||
|
||||
## Get started
|
||||
|
||||
1. Clone our starter project from Github
|
||||
|
||||
```bash
|
||||
git clone https://github.com/medusajs/medusa-starter-default.git my-medusa-starter
|
||||
```
|
||||
|
||||
2. Once cloned, we will jump into our project directory and get started with our configuration.
|
||||
|
||||
```bash
|
||||
cd my-medusa-starter
|
||||
```
|
||||
|
||||
3. Get your environment variables ready using our template
|
||||
|
||||
```bash
|
||||
mv .env.template .env
|
||||
```
|
||||
|
||||
4. Setup accounts for included plugins. This step is optional but required for placing orders.
|
||||
|
||||
Create a Stripe account and add your API key and webhook secret to `.env`
|
||||
Create a SendGrid account and add your API key to `.env`
|
||||
|
||||
```bash
|
||||
...
|
||||
STRIPE_API_KEY="some_stripe_key"
|
||||
STRIPE_WEBHOOK_SECRET="some_webhook_secret"
|
||||
SENDGRID_API_KEY="some_sendgrid_key"
|
||||
..
|
||||
```
|
||||
|
||||
5. Start your server
|
||||
|
||||
```bash
|
||||
docker-compose up --build
|
||||
```
|
||||
|
||||
We will use docker-compose and Docker to start up our development environment. Running the above command will do the following:
|
||||
|
||||
1. Build images for our Medusa project, a PostgreSQL database and a Redis server
|
||||
2. Run migrations for our newly created database
|
||||
3. Seed our database with some entities, that will allow us to easily get started.
|
||||
|
||||
These include:
|
||||
|
||||
- A user with email `admin@medusa-test.com` and password `supersecret`
|
||||
- A region called Default Region with a small subset of countries
|
||||
- A shipping option called Standard Shipping, that costs 10 EUR
|
||||
- A product called Cool Test Product
|
||||
- A variant of that product that costs 195 EUR
|
||||
|
||||
Once done, our server will be accessible at `http://localhost:9000`.
|
||||
|
||||
## Try it out
|
||||
|
||||
Let's try out our Medusa server by fetching some products.
|
||||
|
||||
```bash
|
||||
curl -X GET localhost:9000/store/products | python -m json.tool
|
||||
```
|
||||
|
||||
## What's next?
|
||||
|
||||
Add custom endpoint to your Medusa project (Insert link)
|
||||
|
||||
Install and configure additional plugins (Insert link)
|
||||
|
||||
Build a storefront using our Gatsby starter (Insert link)
|
||||
8
docs/pages/index-page.md
Normal file
8
docs/pages/index-page.md
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
templateKey: index-page
|
||||
pageTitle: Medusa docs
|
||||
introTtitle: Documentation
|
||||
introSubtitle: Explore and learn how to use Medusa.
|
||||
introDescriptionTitle: Quickstart
|
||||
introDescriptionSubtitle: Get up and running within 5 minutes, with helpful starters that lay the foundation for growth.
|
||||
---
|
||||
@@ -33,7 +33,6 @@ describe("/store/customers", () => {
|
||||
describe("POST /store/customers", () => {
|
||||
beforeEach(async () => {
|
||||
const manager = dbConnection.manager;
|
||||
|
||||
await manager.insert(Customer, {
|
||||
id: "test_customer",
|
||||
first_name: "John",
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
"test:fixtures": "jest --config=docs-util/jest.config.js --runInBand"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/theme-search-algolia": "^2.0.0-beta.3",
|
||||
"global": "^4.4.0",
|
||||
"import-from": "^3.0.0",
|
||||
"oas-normalize": "^2.3.1",
|
||||
|
||||
5207
packages/medusa-payment-manual/yarn.lock
Normal file
5207
packages/medusa-payment-manual/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,6 +0,0 @@
|
||||
import React from "react"
|
||||
import { ThemeProvider as Provider } from "./src/theme"
|
||||
|
||||
export const wrapPageElement = ({ element }) => {
|
||||
return <Provider>{element}</Provider>
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* Configure your Gatsby site with this file.
|
||||
*
|
||||
* See: https://www.gatsbyjs.com/docs/gatsby-config/
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
plugins: [
|
||||
`gatsby-plugin-react-helmet`,
|
||||
{
|
||||
resolve: `gatsby-plugin-anchor-links`,
|
||||
options: {
|
||||
offset: -300,
|
||||
},
|
||||
},
|
||||
`gatsby-transformer-json`,
|
||||
`gatsby-plugin-emotion`,
|
||||
{
|
||||
resolve: `gatsby-source-filesystem`,
|
||||
options: {
|
||||
path: `${__dirname}/../docs/api/store`,
|
||||
},
|
||||
},
|
||||
{
|
||||
resolve: `gatsby-source-filesystem`,
|
||||
options: {
|
||||
path: `${__dirname}/../docs/api`,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import React from "react"
|
||||
import { ThemeProvider as Provider } from "./src/theme"
|
||||
|
||||
export const wrapPageElement = ({ element }) => {
|
||||
return <Provider>{element}</Provider>
|
||||
}
|
||||
13
www/reference/.babelrc
Normal file
13
www/reference/.babelrc
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"babel-preset-gatsby",
|
||||
{
|
||||
"reactRuntime": "automatic",
|
||||
"targets": {
|
||||
"browsers": [">0.25%", "not dead"]
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
16
www/reference/gatsby-browser.js
Normal file
16
www/reference/gatsby-browser.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import React from "react"
|
||||
import { NavigationProvider } from "./src/context/navigation-context"
|
||||
|
||||
export const onServiceWorkerUpdateReady = () => {
|
||||
const answer = window.confirm(
|
||||
"We have updated the docs site. Reload to display the latest version?"
|
||||
)
|
||||
|
||||
if (answer) {
|
||||
window.location.reload()
|
||||
}
|
||||
}
|
||||
|
||||
export const wrapPageElement = ({ element }) => {
|
||||
return <NavigationProvider>{element}</NavigationProvider>
|
||||
}
|
||||
86
www/reference/gatsby-config.js
Normal file
86
www/reference/gatsby-config.js
Normal file
@@ -0,0 +1,86 @@
|
||||
module.exports = {
|
||||
siteMetadata: {
|
||||
title: "Medusa",
|
||||
description: "Open-source headless commerce engine",
|
||||
author: "Medusa core team",
|
||||
},
|
||||
plugins: [
|
||||
`gatsby-plugin-react-helmet`,
|
||||
{
|
||||
resolve: `gatsby-plugin-anchor-links`,
|
||||
options: {
|
||||
offset: -100,
|
||||
duration: 1000,
|
||||
},
|
||||
},
|
||||
`gatsby-transformer-json`,
|
||||
`gatsby-plugin-emotion`,
|
||||
|
||||
{
|
||||
resolve: `gatsby-source-filesystem`,
|
||||
options: {
|
||||
name: "store-api",
|
||||
path: `${__dirname}/../../docs/api/store-spec3.json`,
|
||||
},
|
||||
},
|
||||
{
|
||||
resolve: `gatsby-source-filesystem`,
|
||||
options: {
|
||||
name: "admin-api",
|
||||
path: `${__dirname}/../../docs/api/admin-spec3.json`,
|
||||
},
|
||||
},
|
||||
{
|
||||
resolve: `gatsby-source-filesystem`,
|
||||
options: {
|
||||
name: `docs`,
|
||||
path: `${__dirname}/../../docs/content/`,
|
||||
},
|
||||
},
|
||||
{
|
||||
resolve: `gatsby-source-filesystem`,
|
||||
options: {
|
||||
path: `${__dirname}/../../docs/pages`,
|
||||
name: `pages`,
|
||||
},
|
||||
},
|
||||
{
|
||||
resolve: `gatsby-transformer-remark`,
|
||||
options: {
|
||||
plugins: [
|
||||
{
|
||||
resolve: `gatsby-remark-autolink-headers`,
|
||||
options: {
|
||||
elements: [`h2`, `h3`, `h4`],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
`gatsby-plugin-theme-ui`,
|
||||
{
|
||||
resolve: `gatsby-plugin-algolia-docsearch`,
|
||||
options: {
|
||||
apiKey: process.env.ALGOLIA_DOCSEARCH_API_KEY, // required
|
||||
indexName: process.env.ALGOLIA_DOCSEARCH_INDEX_NAME, // required
|
||||
inputSelector: "#algolia-doc-search", // required
|
||||
debug: false, // (bool) Optional. Default `false`
|
||||
},
|
||||
},
|
||||
// `gatsby-plugin-preact`,
|
||||
// {
|
||||
// resolve: `gatsby-source-openapi-aggregate`,
|
||||
// options: {
|
||||
// specs: [
|
||||
// {
|
||||
// name: "admin-spec",
|
||||
// resolve: () =>
|
||||
// fromJson(
|
||||
// path.resolve(__dirname, "../../docs/api/admin-spec3.json")
|
||||
// ),
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// },
|
||||
],
|
||||
}
|
||||
551
www/reference/gatsby-node.js
Normal file
551
www/reference/gatsby-node.js
Normal file
@@ -0,0 +1,551 @@
|
||||
const path = require(`path`)
|
||||
const { createFilePath } = require(`gatsby-source-filesystem`)
|
||||
const fixtures = require("../../docs/api/fixtures.json")
|
||||
|
||||
const convertToKebabCase = string => {
|
||||
return string
|
||||
.replace(/\s+/g, "-")
|
||||
.replace("'", "")
|
||||
.replace(".", "")
|
||||
.replace('"', "")
|
||||
.toLowerCase()
|
||||
}
|
||||
|
||||
const createCustomNode = ({ name, node, createNode }) => {
|
||||
const tags = node.tags
|
||||
const nodePaths = Object.entries(node.paths).map(([path, values]) => {
|
||||
return {
|
||||
name: path,
|
||||
methods: Object.entries(values).map(([method, values]) => {
|
||||
const requestBodyValues =
|
||||
values?.requestBody?.content?.["application/json"]?.schema
|
||||
|
||||
return {
|
||||
method: method,
|
||||
...values,
|
||||
requestBody: {
|
||||
required: requestBodyValues?.required,
|
||||
type: requestBodyValues?.type,
|
||||
properties: Object.entries(requestBodyValues?.properties || {}).map(
|
||||
([property, values]) => {
|
||||
let ref = null
|
||||
let component_id = null
|
||||
let nestedModelNode = null
|
||||
const { items, anyOf, oneOf, ...rest } = values
|
||||
if (items || anyOf || oneOf) {
|
||||
if (items?.anyOf) {
|
||||
component_id = items.anyOf[0]["$ref"].substring(
|
||||
items.anyOf[0]["$ref"].lastIndexOf("/") + 1
|
||||
)
|
||||
ref = node.components.schemas[component_id]
|
||||
} else if (items) {
|
||||
const refPath = items["$ref"]
|
||||
if (refPath) {
|
||||
component_id = refPath.substring(
|
||||
items["$ref"].lastIndexOf("/") + 1
|
||||
)
|
||||
ref = node.components.schemas[component_id]
|
||||
}
|
||||
} else if (anyOf) {
|
||||
component_id = anyOf[0]["$ref"].substring(
|
||||
anyOf[0]["$ref"].lastIndexOf("/") + 1
|
||||
)
|
||||
ref = node.components.schemas[component_id]
|
||||
} else if (oneOf) {
|
||||
component_id = oneOf[0]["$ref"].substring(
|
||||
oneOf[0]["$ref"].lastIndexOf("/") + 1
|
||||
)
|
||||
ref = node.components.schemas[component_id]
|
||||
}
|
||||
}
|
||||
if (ref) {
|
||||
const { properties, ...rest } = ref
|
||||
let nestedModelProperties = []
|
||||
if (properties) {
|
||||
Object.entries(properties).map(([key, values]) => {
|
||||
nestedModelProperties.push({
|
||||
property: key,
|
||||
...values,
|
||||
})
|
||||
})
|
||||
nestedModelNode = {
|
||||
properties: nestedModelProperties,
|
||||
...rest,
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
property: property,
|
||||
nestedModel: nestedModelNode,
|
||||
...rest,
|
||||
}
|
||||
}
|
||||
),
|
||||
},
|
||||
|
||||
responses: Object.entries(values.responses).map(
|
||||
([response, values]) => {
|
||||
const properties =
|
||||
values.content?.["application/json"]?.schema?.properties
|
||||
return {
|
||||
status: response,
|
||||
content: properties
|
||||
? Object.entries(properties).map(([property, values]) => {
|
||||
const toSet = {}
|
||||
let concept
|
||||
if (values.items && values.items["$ref"]) {
|
||||
const [, ...path] = values.items["$ref"].split("/")
|
||||
concept = path[path.length - 1]
|
||||
if (values.type === "array") {
|
||||
if (concept in fixtures.resources) {
|
||||
//make the array key plural
|
||||
let prop
|
||||
if (property.slice(-1) !== "s") {
|
||||
prop = property + "s"
|
||||
} else {
|
||||
prop = property
|
||||
}
|
||||
|
||||
toSet[prop] = [fixtures.resources[concept]]
|
||||
}
|
||||
} else if (concept in fixtures.resources) {
|
||||
toSet[property] = fixtures.resources[concept]
|
||||
}
|
||||
} else {
|
||||
if (fixtures.resources[property]) {
|
||||
toSet[property] = fixtures.resources[property]
|
||||
} else if (values["$ref"]) {
|
||||
const [, ...path] = values["$ref"].split("/")
|
||||
toSet[property] =
|
||||
fixtures.resources[path[path.length - 1]]
|
||||
}
|
||||
}
|
||||
|
||||
const json =
|
||||
Object.keys(toSet).length > 0
|
||||
? JSON.stringify(toSet, undefined, 2)
|
||||
: null
|
||||
|
||||
return {
|
||||
property: property,
|
||||
json: json,
|
||||
...values,
|
||||
}
|
||||
})
|
||||
: null,
|
||||
description: values.description,
|
||||
}
|
||||
}
|
||||
),
|
||||
}
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
const nodeSections = nodePaths.reduce((acc, current) => {
|
||||
// Not bulleproof and kind of naive
|
||||
const section = current.methods.find(method => method.tags).tags[0]
|
||||
|
||||
if (!section) {
|
||||
return acc
|
||||
}
|
||||
|
||||
const existingSection = acc.find(s => s.section.section_name === section)
|
||||
|
||||
//Because section_name does not always equal the resourceId
|
||||
const tag = tags.find(
|
||||
tag => tag.name.toLowerCase() === section.toLowerCase()
|
||||
)
|
||||
|
||||
let resourceId
|
||||
if (tag) resourceId = tag["x-resourceId"]
|
||||
|
||||
const schema =
|
||||
node.components.schemas[section.toLowerCase()] ||
|
||||
node.components.schemas[resourceId] ||
|
||||
null
|
||||
|
||||
//Create a schemaNode so we can access descriptions and attributes of objects
|
||||
let schemaNode
|
||||
if (schema) {
|
||||
let props = []
|
||||
|
||||
Object.entries(schema.properties).map(([key, values]) => {
|
||||
let ref = null
|
||||
let component_id = null
|
||||
let nestedModelNode = null
|
||||
|
||||
let { items, anyOf, oneOf, ...rest } = values
|
||||
if (items || anyOf || oneOf) {
|
||||
if (items?.anyOf) {
|
||||
component_id = items.anyOf[0]["$ref"].substring(
|
||||
items.anyOf[0]["$ref"].lastIndexOf("/") + 1
|
||||
)
|
||||
ref = node.components.schemas[component_id]
|
||||
} else if (items) {
|
||||
const refPath = items["$ref"]
|
||||
if (refPath) {
|
||||
component_id = refPath.substring(
|
||||
items["$ref"].lastIndexOf("/") + 1
|
||||
)
|
||||
ref = node.components.schemas[component_id]
|
||||
}
|
||||
} else if (anyOf) {
|
||||
component_id = anyOf[0]["$ref"].substring(
|
||||
anyOf[0]["$ref"].lastIndexOf("/") + 1
|
||||
)
|
||||
ref = node.components.schemas[component_id]
|
||||
}
|
||||
if (ref) {
|
||||
const { properties, ...rest } = ref
|
||||
let nestedModelProperties = []
|
||||
if (properties) {
|
||||
Object.entries(properties).map(([key, values]) => {
|
||||
nestedModelProperties.push({
|
||||
property: key,
|
||||
...values,
|
||||
})
|
||||
})
|
||||
nestedModelNode = {
|
||||
properties: nestedModelProperties,
|
||||
...rest,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
props.push({
|
||||
property: key,
|
||||
nestedModel: nestedModelNode,
|
||||
...rest,
|
||||
})
|
||||
})
|
||||
|
||||
let object = fixtures.resources[resourceId] || null
|
||||
|
||||
if (object) object = JSON.stringify(object, undefined, 2)
|
||||
|
||||
schemaNode = {
|
||||
object: object,
|
||||
description: schema.description,
|
||||
properties: props,
|
||||
}
|
||||
}
|
||||
|
||||
/** We want the query to return the following:
|
||||
*
|
||||
* sections [
|
||||
* section: {
|
||||
* section_name: Orders
|
||||
* paths: [<array-of-paths>]
|
||||
* }
|
||||
* ]
|
||||
*
|
||||
* The reason that we wrap it in a section is so the query returns Section type, the alternative is
|
||||
* sections [
|
||||
* Order: {...},
|
||||
* Customer: {...}
|
||||
* ...
|
||||
* ]
|
||||
*
|
||||
* Which isn't structured as a reuseable type
|
||||
*/
|
||||
if (!existingSection) {
|
||||
acc.push({
|
||||
section: {
|
||||
section_name: section,
|
||||
paths: [{ ...current }],
|
||||
schema: schemaNode,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
existingSection.section.paths.push({ ...current })
|
||||
}
|
||||
|
||||
return acc
|
||||
}, [])
|
||||
|
||||
const result = {
|
||||
name: name,
|
||||
paths: nodePaths,
|
||||
sections: nodeSections,
|
||||
rawNode: node,
|
||||
|
||||
// required fields
|
||||
id: name,
|
||||
parent: null, // or null if it's a source node without a parent
|
||||
children: [],
|
||||
internal: {
|
||||
type: name,
|
||||
contentDigest: `store-api-${node.internal.contentDigest}`,
|
||||
mediaType: node.internal.mediaType, // optional
|
||||
content: node.internal.content, // optional
|
||||
description: `A cleaned version of file-system-api json files`, // optional
|
||||
},
|
||||
}
|
||||
|
||||
createNode(result)
|
||||
}
|
||||
|
||||
exports.onCreateNode = ({ node, getNode, actions }) => {
|
||||
const { createNodeField, createNode } = actions
|
||||
if (node.internal.type === `MarkdownRemark`) {
|
||||
const slug = createFilePath({ node, getNode })
|
||||
createNodeField({
|
||||
node,
|
||||
name: `slug`,
|
||||
value: slug,
|
||||
})
|
||||
}
|
||||
|
||||
if (node.internal.type === "ApiJson" && node.components) {
|
||||
if (node.info.title === "Medusa Storefront API") {
|
||||
createCustomNode({ name: "Store", node: node, createNode: createNode })
|
||||
}
|
||||
if (node.info.title === "Medusa Admin API") {
|
||||
createCustomNode({ name: "Admin", node: node, createNode: createNode })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const createAllPages = (sections, api, siteData, createPage, template) => {
|
||||
sections.forEach(edge => {
|
||||
const baseURL = convertToKebabCase(edge.section.section_name)
|
||||
createPage({
|
||||
path: `api/${api}/${baseURL}`,
|
||||
component: template,
|
||||
context: {
|
||||
data: siteData.data[api],
|
||||
api: api,
|
||||
title: edge.section.section_name,
|
||||
description: edge.section.schema?.description || "",
|
||||
to: { section: baseURL, method: null },
|
||||
},
|
||||
})
|
||||
edge.section.paths.forEach(p => {
|
||||
p.methods.forEach(method => {
|
||||
const methodURL = convertToKebabCase(method.summary)
|
||||
createPage({
|
||||
path: `api/${api}/${baseURL}/${methodURL}`,
|
||||
component: template,
|
||||
context: {
|
||||
data: siteData.data[api],
|
||||
api: api,
|
||||
title: method.summary,
|
||||
description: method.description || "",
|
||||
to: { section: baseURL, method: methodURL },
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
exports.createPages = ({ graphql, actions }) => {
|
||||
const { createPage } = actions
|
||||
const template = path.resolve(`src/templates/reference-page.js`)
|
||||
// Query for markdown nodes to use in creating pages.
|
||||
// You can query for whatever data you want to create pages for e.g.
|
||||
// products, portfolio items, landing pages, etc.
|
||||
// Variables can be added as the second function parameter
|
||||
return graphql(
|
||||
`
|
||||
query Pages {
|
||||
admin {
|
||||
sections {
|
||||
section {
|
||||
section_name
|
||||
paths {
|
||||
name
|
||||
methods {
|
||||
tags
|
||||
summary
|
||||
description
|
||||
method
|
||||
operationId
|
||||
responses {
|
||||
status
|
||||
description
|
||||
content {
|
||||
_ref
|
||||
type
|
||||
property
|
||||
description
|
||||
json
|
||||
items {
|
||||
type
|
||||
_ref
|
||||
}
|
||||
}
|
||||
}
|
||||
requestBody {
|
||||
type
|
||||
required
|
||||
properties {
|
||||
description
|
||||
enum
|
||||
format
|
||||
property
|
||||
type
|
||||
nestedModel {
|
||||
title
|
||||
properties {
|
||||
property
|
||||
type
|
||||
description
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parameters {
|
||||
description
|
||||
in
|
||||
name
|
||||
required
|
||||
schema {
|
||||
type
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
schema {
|
||||
object
|
||||
description
|
||||
properties {
|
||||
property
|
||||
type
|
||||
description
|
||||
format
|
||||
nestedModel {
|
||||
title
|
||||
properties {
|
||||
property
|
||||
type
|
||||
description
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
store {
|
||||
sections {
|
||||
section {
|
||||
section_name
|
||||
paths {
|
||||
name
|
||||
methods {
|
||||
tags
|
||||
summary
|
||||
description
|
||||
method
|
||||
operationId
|
||||
responses {
|
||||
status
|
||||
description
|
||||
content {
|
||||
_ref
|
||||
property
|
||||
description
|
||||
json
|
||||
items {
|
||||
_ref
|
||||
}
|
||||
}
|
||||
}
|
||||
requestBody {
|
||||
type
|
||||
required
|
||||
properties {
|
||||
description
|
||||
property
|
||||
type
|
||||
nestedModel {
|
||||
title
|
||||
properties {
|
||||
property
|
||||
type
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
parameters {
|
||||
description
|
||||
in
|
||||
name
|
||||
required
|
||||
schema {
|
||||
type
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
schema {
|
||||
object
|
||||
description
|
||||
properties {
|
||||
property
|
||||
type
|
||||
description
|
||||
format
|
||||
nestedModel {
|
||||
title
|
||||
properties {
|
||||
property
|
||||
type
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{ limit: 1000 }
|
||||
).then(result => {
|
||||
if (result.errors) {
|
||||
throw result.errors
|
||||
}
|
||||
|
||||
//create entrypoint
|
||||
createPage({
|
||||
path: `api`,
|
||||
component: template,
|
||||
context: {
|
||||
data: result.data.store,
|
||||
api: "store",
|
||||
title: "Store",
|
||||
description: "Storefront API",
|
||||
},
|
||||
})
|
||||
|
||||
const apis = [
|
||||
{ title: "Store", description: "Storefront API", slug: "store" },
|
||||
{ title: "Admin", description: "Admin API", slug: "admin" },
|
||||
]
|
||||
|
||||
apis.forEach(api => {
|
||||
//create main page for API
|
||||
createPage({
|
||||
path: `api/${api.slug}`,
|
||||
component: template,
|
||||
context: {
|
||||
data: result.data[api.slug],
|
||||
api: api.slug,
|
||||
title: api.title,
|
||||
description: api.description,
|
||||
},
|
||||
})
|
||||
//create pages for all sections and methods
|
||||
createAllPages(
|
||||
result.data[api.slug].sections,
|
||||
api.slug,
|
||||
result,
|
||||
createPage,
|
||||
template
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
6
www/reference/gatsby-ssr.js
Normal file
6
www/reference/gatsby-ssr.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import React from "react"
|
||||
import { NavigationProvider } from "./src/context/navigation-context"
|
||||
|
||||
export const wrapPageElement = ({ element }) => {
|
||||
return <NavigationProvider>{element}</NavigationProvider>
|
||||
}
|
||||
@@ -18,13 +18,23 @@
|
||||
"@emotion/styled": "^11.0.0",
|
||||
"@rebass/forms": "^4.0.6",
|
||||
"emotion-theming": "^10.0.27",
|
||||
"gatsby": "^2.24.66",
|
||||
"gatsby": "^3.9.1",
|
||||
"gatsby-plugin-algolia-docsearch": "^1.0.5",
|
||||
"gatsby-plugin-anchor-links": "^1.1.1",
|
||||
"gatsby-plugin-emotion": "^5.0.0",
|
||||
"gatsby-plugin-preact": "^5.9.0",
|
||||
"gatsby-plugin-react-helmet": "^3.3.12",
|
||||
"gatsby-source-filesystem": "^2.3.31",
|
||||
"gatsby-plugin-theme-ui": "^0.10.1",
|
||||
"gatsby-remark-autolink-headers": "^4.6.0",
|
||||
"gatsby-source-filesystem": "^3.9.0",
|
||||
"gatsby-source-openapi-aggregate": "^0.3.0",
|
||||
"gatsby-transformer-json": "^3.0.0",
|
||||
"gatsby-transformer-remark": "^4.6.0",
|
||||
"gatsby-transformer-yaml": "^2.4.13",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"preact": "^10.5.14",
|
||||
"preact-render-to-string": "^5.1.19",
|
||||
"prismjs": "^1.24.1",
|
||||
"react": "^16.12.0",
|
||||
"react-collapsible": "^2.8.1",
|
||||
"react-dom": "^16.12.0",
|
||||
@@ -32,7 +42,8 @@
|
||||
"react-highlight.js": "^1.0.7",
|
||||
"react-intersection-observer": "^8.29.0",
|
||||
"react-markdown": "^5.0.3",
|
||||
"rebass": "^4.0.7"
|
||||
"react-virtualized": "^9.22.3",
|
||||
"theme-ui": "^0.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "2.1.2"
|
||||
9
www/reference/src/assets/github.svg
Normal file
9
www/reference/src/assets/github.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="33px" height="33px" viewBox="0 0 33 33" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>GitHub</title>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="GitHub-Mark" transform="translate(-136.000000, -331.000000)" fill="#000">
|
||||
<path d="M152.608,331.455 C143.614,331.455 136.32,338.748 136.32,347.745 C136.32,354.942 140.987,361.047 147.46,363.201 C148.275,363.351 148.572,362.848 148.572,362.416 C148.572,362.029 148.558,361.005 148.55,359.646 C144.019,360.63 143.063,357.462 143.063,357.462 C142.322,355.58 141.254,355.079 141.254,355.079 C139.775,354.069 141.366,354.089 141.366,354.089 C143.001,354.204 143.861,355.768 143.861,355.768 C145.314,358.257 147.674,357.538 148.602,357.121 C148.75,356.069 149.171,355.351 149.636,354.944 C146.019,354.533 142.216,353.135 142.216,346.893 C142.216,345.115 142.851,343.66 143.893,342.522 C143.725,342.11 143.166,340.453 144.053,338.211 C144.053,338.211 145.42,337.773 148.532,339.881 C149.831,339.519 151.225,339.339 152.61,339.332 C153.994,339.339 155.387,339.519 156.688,339.881 C159.798,337.773 161.163,338.211 161.163,338.211 C162.052,340.453 161.493,342.11 161.326,342.522 C162.37,343.66 163,345.115 163,346.893 C163,353.151 159.191,354.528 155.563,354.931 C156.147,355.434 156.668,356.428 156.668,357.947 C156.668,360.125 156.648,361.882 156.648,362.416 C156.648,362.852 156.942,363.359 157.768,363.2 C164.236,361.041 168.899,354.94 168.899,347.745 C168.899,338.748 161.605,331.455 152.608,331.455" id="Fill-51"></path>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
9
www/reference/src/assets/logo-muted.svg
Normal file
9
www/reference/src/assets/logo-muted.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg width="602" height="122" viewBox="0 0 602 122" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="602" height="122" fill="white"/>
|
||||
<path d="M95.0625 0H129.431V119.437H108.713V18.0375L75.5625 119.437H53.8688L20.7188 19.2562V119.437H0V0H34.6125L64.8375 95.7937L95.0625 0Z" fill="#89959C"/>
|
||||
<path d="M233.756 92.3826C228.881 110.42 213.525 121.633 192.319 121.633C165.75 121.633 148.688 103.595 148.688 76.5388C148.688 49.4826 165.75 31.4451 192.806 31.4451C222.788 31.4451 238.387 53.8701 230.1 81.9014H169.406C171.112 96.0389 178.913 104.326 192.562 104.326C202.069 104.326 209.625 99.9389 212.55 92.1389H233.756V92.3826ZM169.894 68.2514H211.819C213.037 56.0639 205.969 48.0201 192.562 48.0201C179.888 48.0201 171.844 55.0889 169.894 68.2514Z" fill="#89959C"/>
|
||||
<path d="M312.244 0.000396729H331.744V119.438H312.244V106.763C305.663 116.269 295.669 121.875 283.238 121.875C260.813 121.875 244.725 103.838 244.725 76.7816C244.725 49.7254 260.325 31.6879 283.238 31.6879C295.669 31.6879 305.663 37.0504 312.244 46.8004V0.000396729V0.000396729ZM312.975 76.7816C312.975 59.9629 303.469 48.7504 289.088 48.7504C274.463 48.7504 264.956 59.9629 264.956 76.7816C264.956 93.6004 274.463 104.813 289.088 104.813C303.469 104.569 312.975 93.6004 312.975 76.7816Z" fill="#89959C"/>
|
||||
<path d="M403.894 34.1264H423.394V119.439H403.894V104.083C397.8 115.539 388.294 121.389 376.35 121.389C352.706 121.389 348.806 97.7451 348.806 82.6326V33.8826H368.306V82.3889C368.306 93.6014 370.987 104.326 383.175 104.326C395.85 104.326 403.894 93.1139 403.894 77.2701V34.1264Z" fill="#89959C"/>
|
||||
<path d="M437.775 88.7263H456.544C455.325 99.2076 463.125 106.033 476.531 106.033C487.013 106.033 493.838 101.889 493.838 95.7951C493.838 78.7326 438.994 90.9201 438.994 58.9889C438.994 41.9264 454.838 31.4451 475.556 31.4451C498.469 31.4451 515.044 44.1201 511.387 63.6201H492.375C494.813 53.8701 487.013 47.2888 475.313 47.2888C465.319 47.2888 458.981 51.6764 458.981 57.7701C458.981 75.0764 514.069 61.9139 514.069 94.3326C514.069 111.639 498.469 121.633 476.287 121.633C452.156 121.633 436.313 109.445 437.775 88.7263Z" fill="#89959C"/>
|
||||
<path d="M601.331 70.9316V119.438H583.293V104.569C576.956 115.538 566.475 121.875 552.581 121.875C534.544 121.875 523.331 111.394 523.331 96.5254C523.331 79.2191 538.444 68.9816 562.331 68.9816C568.912 68.9816 575.737 69.7129 581.831 71.1754C581.831 58.2566 577.2 48.7504 563.55 48.7504C553.312 48.7504 546.975 53.8691 548.925 63.3754H529.181C524.55 44.3629 541.369 31.6879 563.55 31.6879C588.656 31.6879 601.331 47.0441 601.331 70.9316ZM582.562 85.8004C576.956 83.8504 570.131 82.8754 564.037 82.8754C550.144 82.8754 543.319 87.9941 543.319 95.7941C543.319 102.619 548.437 106.275 556.725 106.275C569.156 106.275 579.394 98.2316 582.562 85.8004Z" fill="#89959C"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
16
www/reference/src/assets/logo.svg
Normal file
16
www/reference/src/assets/logo.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
<svg width="905" height="234" viewBox="0 0 905 234" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0)">
|
||||
<path d="M189.638 38.0267L136.744 7.55792C119.438 -2.43583 98.2313 -2.43583 80.925 7.55792L27.7875 38.0267C10.725 48.0204 0 66.5454 0 86.2892V147.47C0 167.458 10.725 185.739 27.7875 195.733L80.6812 226.445C97.9875 236.439 119.194 236.439 136.5 226.445L189.394 195.733C206.7 185.739 217.181 167.458 217.181 147.47V86.2892C217.669 66.5454 206.944 48.0204 189.638 38.0267ZM108.712 171.358C78.7312 171.358 54.3562 146.983 54.3562 117.002C54.3562 87.0204 78.7312 62.6454 108.712 62.6454C138.694 62.6454 163.312 87.0204 163.312 117.002C163.312 146.983 138.938 171.358 108.712 171.358Z" fill="#0A3149"/>
|
||||
<path d="M398.808 55.3289H433.176V174.766H412.458V73.3664L379.308 174.766H357.614L324.464 74.5852V174.766H303.745V55.3289H338.358L368.583 151.123L398.808 55.3289Z" fill="#0A3149"/>
|
||||
<path d="M537.501 147.712C532.626 165.749 517.27 176.962 496.064 176.962C469.495 176.962 452.433 158.924 452.433 131.868C452.433 104.812 469.495 86.774 496.552 86.774C526.533 86.774 542.133 109.199 533.845 137.23H473.151C474.858 151.368 482.658 159.655 496.308 159.655C505.814 159.655 513.37 155.268 516.295 147.468H537.501V147.712ZM473.639 123.58H515.564C516.783 111.393 509.714 103.349 496.308 103.349C483.633 103.349 475.589 110.418 473.639 123.58Z" fill="#0A3149"/>
|
||||
<path d="M615.989 55.3293H635.489V174.767H615.989V162.092C609.408 171.598 599.414 177.204 586.983 177.204C564.558 177.204 548.47 159.167 548.47 132.111C548.47 105.054 564.07 87.0168 586.983 87.0168C599.414 87.0168 609.408 92.3793 615.989 102.129V55.3293ZM616.72 132.111C616.72 115.292 607.214 104.079 592.833 104.079C578.208 104.079 568.701 115.292 568.701 132.111C568.701 148.929 578.208 160.142 592.833 160.142C607.214 159.898 616.72 148.929 616.72 132.111Z" fill="#0A3149"/>
|
||||
<path d="M707.639 89.4553H727.139V174.768H707.639V159.412C701.545 170.868 692.039 176.718 680.095 176.718C656.451 176.718 652.552 153.074 652.552 137.962V89.2115H672.052V137.718C672.052 148.93 674.733 159.655 686.92 159.655C699.595 159.655 707.639 148.443 707.639 132.599V89.4553Z" fill="#0A3149"/>
|
||||
<path d="M741.52 144.055H760.289C759.07 154.537 766.87 161.362 780.277 161.362C790.758 161.362 797.583 157.218 797.583 151.124C797.583 134.062 742.739 146.249 742.739 114.318C742.739 97.2553 758.583 86.774 779.302 86.774C802.214 86.774 818.789 99.449 815.133 118.949H796.12C798.558 109.199 790.758 102.618 779.058 102.618C769.064 102.618 762.726 107.005 762.726 113.099C762.726 130.405 817.814 117.243 817.814 149.662C817.814 166.968 802.214 176.962 780.033 176.962C755.901 176.962 740.058 164.774 741.52 144.055Z" fill="#0A3149"/>
|
||||
<path d="M905.077 126.261V174.767H887.039V159.898C880.702 170.867 870.22 177.204 856.327 177.204C838.289 177.204 827.077 166.723 827.077 151.854C827.077 134.548 842.189 124.311 866.077 124.311C872.658 124.311 879.483 125.042 885.577 126.504C885.577 113.586 880.945 104.079 867.295 104.079C857.058 104.079 850.72 109.198 852.67 118.704H832.927C828.295 99.6918 845.114 87.0168 867.295 87.0168C892.402 87.0168 905.077 102.373 905.077 126.261ZM886.308 141.129C880.702 139.179 873.877 138.204 867.783 138.204C853.889 138.204 847.064 143.323 847.064 151.123C847.064 157.948 852.183 161.604 860.47 161.604C872.902 161.604 883.139 153.561 886.308 141.129Z" fill="#0A3149"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0">
|
||||
<rect width="904.8" height="234" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.4 KiB |
109
www/reference/src/components/Sidebar/index.js
Normal file
109
www/reference/src/components/Sidebar/index.js
Normal file
@@ -0,0 +1,109 @@
|
||||
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"
|
||||
|
||||
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;
|
||||
}
|
||||
`
|
||||
|
||||
const SideBarFade = styled(Box)`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: calc(var(--side-bar-width) - 1px);
|
||||
height: 50px;
|
||||
pointer-events: none;
|
||||
box-shadow: inset 0 50px 25px calc(-1 * 25px) white;
|
||||
`
|
||||
|
||||
const Sidebar = ({ data, api }) => {
|
||||
const [scrollPos, setScrollPos] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
const nav = document.querySelector("#nav")
|
||||
|
||||
const handleScroll = e => {
|
||||
const pos = e.srcElement.scrollTop / 50
|
||||
if (pos < 1) {
|
||||
setScrollPos(pos)
|
||||
}
|
||||
}
|
||||
|
||||
nav.addEventListener("scroll", handleScroll)
|
||||
return () => nav.removeEventListener("scroll", handleScroll)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<SideBarContainer
|
||||
sx={{
|
||||
position: "sticky",
|
||||
top: "0",
|
||||
bottom: "0",
|
||||
height: "100vh",
|
||||
backgroundColor: "light",
|
||||
boxShadow: "sidebarShadow",
|
||||
minWidth: "var(--side-bar-width)",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
sx={{
|
||||
px: "4",
|
||||
pt: "3",
|
||||
background: "light",
|
||||
width: "calc(var(--side-bar-width) - 1px)",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<Flex>
|
||||
<Image
|
||||
src={Logo}
|
||||
alt="Medusa logo"
|
||||
sx={{
|
||||
height: "28px",
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex py={4}>
|
||||
<SideBarSelector api={api} />
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex
|
||||
id="nav"
|
||||
sx={{
|
||||
flex: 1,
|
||||
position: "relative",
|
||||
px: "3",
|
||||
pb: "3",
|
||||
mr: "1px",
|
||||
flexDirection: "column",
|
||||
overflowY: "scroll",
|
||||
pr: "6px",
|
||||
scrollbarColor: "faded light",
|
||||
}}
|
||||
>
|
||||
<SideBarFade opacity={scrollPos} />
|
||||
{data.sections.map((s, i) => {
|
||||
return <SideBarItem item={s} key={i} />
|
||||
})}
|
||||
</Flex>
|
||||
<Flex sx={{ py: 4, px: 4, borderTop: "1px solid #efefef" }}>
|
||||
<Image src={LogoMuted} alt="Medusa Type" sx={{ height: "10px" }} />
|
||||
</Flex>
|
||||
</SideBarContainer>
|
||||
)
|
||||
}
|
||||
|
||||
export default Sidebar
|
||||
138
www/reference/src/components/Sidebar/sidebar-item.js
Normal file
138
www/reference/src/components/Sidebar/sidebar-item.js
Normal file
@@ -0,0 +1,138 @@
|
||||
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 ChevronDown from "../icons/chevron-down"
|
||||
import NavigationContext from "../../context/navigation-context"
|
||||
|
||||
const StyledCollapsible = styled(Collapsible)`
|
||||
margin-bottom: 10px;
|
||||
`
|
||||
|
||||
const Container = styled(Box)`
|
||||
div.Collapsible span.Collapsible__trigger.is-open {
|
||||
svg {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const SideBarItem = ({ item }) => {
|
||||
const {
|
||||
openSection,
|
||||
openSections,
|
||||
currentHash,
|
||||
currentSection,
|
||||
goTo,
|
||||
} = useContext(NavigationContext)
|
||||
const { section } = item
|
||||
const subItems = section.paths
|
||||
.map(p => {
|
||||
return p.methods
|
||||
})
|
||||
.reduce((pre, cur) => {
|
||||
return pre.concat(cur)
|
||||
})
|
||||
.map(m => {
|
||||
return {
|
||||
title: m.summary,
|
||||
path: convertToKebabCase(m.summary),
|
||||
}
|
||||
})
|
||||
|
||||
const handleClick = () => {
|
||||
const id = convertToKebabCase(section.section_name)
|
||||
const element = document.querySelector(`#${id}`)
|
||||
if (element) {
|
||||
element.scrollIntoView()
|
||||
if (!openSections.includes(id)) {
|
||||
openSection(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubClick = path => {
|
||||
const id = convertToKebabCase(section.section_name)
|
||||
goTo({ section: id, method: path })
|
||||
}
|
||||
|
||||
return (
|
||||
<Container id={`nav-${convertToKebabCase(section.section_name)}`}>
|
||||
<StyledCollapsible
|
||||
trigger={
|
||||
<Flex
|
||||
sx={{
|
||||
fontSize: "1",
|
||||
pl: "16px",
|
||||
pr: "10px",
|
||||
alignItems: "center",
|
||||
borderRadius: "small",
|
||||
cursor: "pointer",
|
||||
mr: "4px",
|
||||
mb: "5px",
|
||||
height: "25px",
|
||||
justifyContent: "space-between",
|
||||
"&:hover, &.active": {
|
||||
backgroundColor: "faded",
|
||||
},
|
||||
}}
|
||||
className={
|
||||
currentSection === convertToKebabCase(section.section_name)
|
||||
? "active"
|
||||
: null
|
||||
}
|
||||
>
|
||||
{section.section_name} <ChevronDown />
|
||||
</Flex>
|
||||
}
|
||||
open={
|
||||
currentSection === convertToKebabCase(section.section_name) ||
|
||||
openSections.includes(convertToKebabCase(section.section_name))
|
||||
}
|
||||
onTriggerOpening={handleClick}
|
||||
transitionTime={1}
|
||||
>
|
||||
{subItems.map((si, i) => {
|
||||
const path = convertToKebabCase(si.path)
|
||||
return (
|
||||
<Flex
|
||||
key={i}
|
||||
className={currentHash === path ? "active" : null}
|
||||
onClick={() => handleSubClick(path)}
|
||||
id={`nav-${path}`}
|
||||
sx={{
|
||||
ml: "10px",
|
||||
pl: "10px",
|
||||
pr: "10px",
|
||||
alignItems: "center",
|
||||
borderRadius: "small",
|
||||
cursor: "pointer",
|
||||
mb: "8px",
|
||||
textDecoration: "none",
|
||||
color: "black",
|
||||
height: "25px",
|
||||
"&:hover": {
|
||||
backgroundColor: "faded",
|
||||
},
|
||||
"&.active": {
|
||||
backgroundColor: "faded",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
sx={{
|
||||
fontSize: "0",
|
||||
}}
|
||||
>
|
||||
{si.title}
|
||||
</Text>
|
||||
</Flex>
|
||||
)
|
||||
})}
|
||||
</StyledCollapsible>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default SideBarItem
|
||||
56
www/reference/src/components/Sidebar/sidebar-selector.js
Normal file
56
www/reference/src/components/Sidebar/sidebar-selector.js
Normal file
@@ -0,0 +1,56 @@
|
||||
import React, { useContext } from "react"
|
||||
import { Flex, Select } from "theme-ui"
|
||||
import { navigate } from "gatsby-link"
|
||||
import NavigationContext from "../../context/navigation-context"
|
||||
import ChevronDown from "../icons/chevron-down"
|
||||
|
||||
const SideBarSelector = ({ api }) => {
|
||||
const { reset } = useContext(NavigationContext)
|
||||
|
||||
const handleSelect = e => {
|
||||
reset()
|
||||
navigate(`/api/${e.target.value}`)
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
marginLeft: "-16px",
|
||||
marginRight: "-10px",
|
||||
width: "calc(100% + 26px)",
|
||||
"& div": {
|
||||
width: "100%",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Select
|
||||
arrow={<ChevronDown fill={"dark"} styles={{ ml: "-28px" }} />}
|
||||
sx={{
|
||||
paddingLeft: "16px",
|
||||
marginRight: "-5px",
|
||||
borderRadius: "small",
|
||||
borderColor: "faded",
|
||||
width: "100%",
|
||||
fontSize: "1",
|
||||
fontFamily: "body",
|
||||
transition: "all .1s ease-in-out",
|
||||
"&:focus": {
|
||||
outline: "none !important",
|
||||
},
|
||||
boxShadow: "ctaBoxShadow",
|
||||
|
||||
"&:hover": {
|
||||
boxShadow: "buttonBoxShadowHover",
|
||||
},
|
||||
}}
|
||||
value={api}
|
||||
onChange={handleSelect}
|
||||
>
|
||||
<option value="admin">Admin</option>
|
||||
<option value="store">Storefront</option>
|
||||
</Select>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
export default SideBarSelector
|
||||
54
www/reference/src/components/content/code-box.js
Normal file
54
www/reference/src/components/content/code-box.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import React from "react"
|
||||
import { Flex, Box, Text } from "theme-ui"
|
||||
|
||||
const CodeBox = ({ header, children }) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
background: "fadedContrast",
|
||||
borderRadius: "small",
|
||||
boxShadow: "0 0 0 1px rgb(0 0 0 / 7%)",
|
||||
alignSelf: "flex-start",
|
||||
marginLeft: "auto",
|
||||
marginRight: "auto",
|
||||
width: "100%",
|
||||
mb: "4",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
bg: "faded",
|
||||
p: "8px 10px",
|
||||
letterSpacing: "0.01em",
|
||||
borderRadius: "8px 8px 0 0",
|
||||
}}
|
||||
>
|
||||
<Text variant="small" sx={{ fontWeight: "400" }}>
|
||||
{header}
|
||||
</Text>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
position: "relative",
|
||||
boxSizing: "content-box",
|
||||
maxHeight: "calc(90vh - 20px)",
|
||||
minHeight: "10px",
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
sx={{
|
||||
flexDirection: "column",
|
||||
position: "relative",
|
||||
minHeight: "inherit",
|
||||
maxHeight: "inherit",
|
||||
overflowY: "auto",
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Flex>
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default CodeBox
|
||||
130
www/reference/src/components/content/collapsible.js
Normal file
130
www/reference/src/components/content/collapsible.js
Normal file
@@ -0,0 +1,130 @@
|
||||
import React, { useState } from "react"
|
||||
import Collapsible from "react-collapsible"
|
||||
import { Flex, Box, Text, Heading } from "theme-ui"
|
||||
import Markdown from "react-markdown"
|
||||
import Description from "./description"
|
||||
|
||||
const NestedCollapsible = ({ properties, title }) => {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
return (
|
||||
<Box
|
||||
mt={2}
|
||||
sx={{
|
||||
"& .child-attrs": {
|
||||
cursor: "pointer",
|
||||
fontSize: "12px",
|
||||
p: "6px 10px",
|
||||
boxSizing: "border-box",
|
||||
width: "max-content",
|
||||
borderRadius: "small",
|
||||
border: "1px solid",
|
||||
borderColor: "faded",
|
||||
},
|
||||
|
||||
"& .child-attrs.is-open": {
|
||||
width: "100%",
|
||||
borderBottom: "none",
|
||||
borderBottomLeftRadius: "0",
|
||||
borderBottomRightRadius: "0",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Collapsible
|
||||
transitionTime={50}
|
||||
triggerClassName={"child-attrs"}
|
||||
triggerOpenedClassName={"child-attrs"}
|
||||
triggerTagName="div"
|
||||
trigger={
|
||||
<Flex
|
||||
sx={{
|
||||
alignItems: "baseline",
|
||||
fontSize: "13px",
|
||||
fontWeight: "400",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
mr={1}
|
||||
className={isOpen ? "rotated" : null}
|
||||
sx={{
|
||||
userSelect: "none",
|
||||
transition: "all 0.2s ease",
|
||||
transform: "rotate(45deg)",
|
||||
"&.rotated": {
|
||||
transform: "rotate(0deg)",
|
||||
},
|
||||
}}
|
||||
>
|
||||
×
|
||||
</Box>{" "}
|
||||
<Text
|
||||
sx={{ fontSize: "0", fontFamily: "body", userSelect: "none" }}
|
||||
>{`${isOpen ? "Hide" : "Show"} child attributes`}</Text>
|
||||
</Flex>
|
||||
}
|
||||
onTriggerOpening={() => setIsOpen(true)}
|
||||
onTriggerClosing={() => setIsOpen(false)}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
padding: "2",
|
||||
borderRadius: "small",
|
||||
borderTopLeftRadius: 0,
|
||||
borderTopRightRadius: 0,
|
||||
border: "hairline",
|
||||
borderColor: "faded",
|
||||
}}
|
||||
mb="2"
|
||||
>
|
||||
<Heading as="h3" p={2} sx={{ fontFamily: "body", fontWeight: "500" }}>
|
||||
{title}
|
||||
</Heading>
|
||||
{properties.map((param, i) => {
|
||||
return (
|
||||
<Box
|
||||
p={2}
|
||||
sx={{
|
||||
borderTop: "hairline",
|
||||
}}
|
||||
key={i}
|
||||
>
|
||||
<Flex
|
||||
sx={{
|
||||
fontSize: "0",
|
||||
alignItems: "baseline",
|
||||
pb: "1",
|
||||
fontFamily: "monospace",
|
||||
}}
|
||||
>
|
||||
<Box mr={2} fontSize={"12px"}>
|
||||
{param.property}
|
||||
</Box>
|
||||
<Text color={"gray"} fontSize={"10px"}>
|
||||
{param.type}
|
||||
</Text>
|
||||
{param.required && (
|
||||
<Text ml={1} fontSize={"10px"} variant="labels.required">
|
||||
required
|
||||
</Text>
|
||||
)}
|
||||
</Flex>
|
||||
<Description>
|
||||
<Text
|
||||
sx={{
|
||||
fontSize: "0",
|
||||
lineHeight: "26px",
|
||||
fontFamily: "body",
|
||||
}}
|
||||
>
|
||||
<Markdown>{param.description}</Markdown>
|
||||
</Text>
|
||||
</Description>
|
||||
</Box>
|
||||
)
|
||||
})}
|
||||
</Box>
|
||||
</Collapsible>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default NestedCollapsible
|
||||
20
www/reference/src/components/content/description.js
Normal file
20
www/reference/src/components/content/description.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import React from "react"
|
||||
import { Box } from "theme-ui"
|
||||
|
||||
const Description = ({ children }) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
code: {
|
||||
backgroundColor: "faded",
|
||||
borderRadius: "4px",
|
||||
p: "3px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default Description
|
||||
45
www/reference/src/components/content/endpoint-container.js
Normal file
45
www/reference/src/components/content/endpoint-container.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import React from "react"
|
||||
import { Flex, Text } from "theme-ui"
|
||||
import CodeBox from "./code-box"
|
||||
|
||||
const EndpointContainer = ({ endpoints }) => {
|
||||
if (!endpoints) return null
|
||||
|
||||
return (
|
||||
<CodeBox header="ENDPOINTS">
|
||||
<Flex
|
||||
py={2}
|
||||
sx={{
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
{endpoints.map((e, i) => {
|
||||
const method = e.method.toUpperCase()
|
||||
const endpoint = e.endpoint
|
||||
return (
|
||||
<Flex
|
||||
key={i}
|
||||
sx={{ fontSize: "0", fontFamily: "monospace", px: "3", py: "2" }}
|
||||
>
|
||||
<Text
|
||||
variant={`labels.${method}`}
|
||||
sx={{
|
||||
width: "55px",
|
||||
textAlign: "right",
|
||||
}}
|
||||
mr={2}
|
||||
>
|
||||
{method}
|
||||
</Text>
|
||||
<Text sx={{ color: "dark" }}>
|
||||
{endpoint.replace(/{(.*?)}/g, ":$1")}
|
||||
</Text>
|
||||
</Flex>
|
||||
)
|
||||
})}
|
||||
</Flex>
|
||||
</CodeBox>
|
||||
)
|
||||
}
|
||||
|
||||
export default EndpointContainer
|
||||
31
www/reference/src/components/content/index.js
Normal file
31
www/reference/src/components/content/index.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import React from "react"
|
||||
import { Box, Flex } from "theme-ui"
|
||||
import Topbar from "../topbar"
|
||||
import Section from "./section"
|
||||
|
||||
const Content = ({ data, api }) => {
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<Topbar data={data} api={api} />
|
||||
<Box
|
||||
sx={{
|
||||
maxHeight: "calc(100vh - 50px)",
|
||||
overflowY: "scroll",
|
||||
"@media screen and (max-width: 848px)": {
|
||||
mt: "50px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{data.sections.map((s, i) => {
|
||||
return <Section key={i} data={s} />
|
||||
})}
|
||||
</Box>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
export default Content
|
||||
31
www/reference/src/components/content/json-container.js
Normal file
31
www/reference/src/components/content/json-container.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import React, { useRef, useEffect } from "react"
|
||||
import { Box } from "theme-ui"
|
||||
import "../../prism-medusa-theme/prism.css"
|
||||
import Prism from "prismjs"
|
||||
import "prismjs/components/prism-json"
|
||||
import CodeBox from "./code-box"
|
||||
|
||||
const JsonContainer = ({ json, header }) => {
|
||||
const jsonRef = useRef()
|
||||
|
||||
//INVESTIGATE: @theme-ui/prism might be a better solution
|
||||
useEffect(() => {
|
||||
if (jsonRef.current) {
|
||||
Prism.highlightAllUnder(jsonRef.current)
|
||||
}
|
||||
}, [])
|
||||
|
||||
if (typeof json !== "string" || json === "{}") return null
|
||||
|
||||
return (
|
||||
<Box ref={jsonRef} sx={{ position: "sticky", top: "20px" }}>
|
||||
<CodeBox header={header}>
|
||||
<pre>
|
||||
<code className={"language-json"}>{json}</code>
|
||||
</pre>
|
||||
</CodeBox>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default JsonContainer
|
||||
110
www/reference/src/components/content/method.js
Normal file
110
www/reference/src/components/content/method.js
Normal file
@@ -0,0 +1,110 @@
|
||||
import React, { useContext, useEffect, useRef } from "react"
|
||||
import Markdown from "react-markdown"
|
||||
import { Flex, Text, Box, Heading } from "theme-ui"
|
||||
import { convertToKebabCase } from "../../utils/convert-to-kebab-case"
|
||||
import Parameters from "./parameters"
|
||||
import Route from "./route"
|
||||
import JsonContainer from "./json-container"
|
||||
import Description from "./description"
|
||||
import ResponsiveContainer from "./responsive-container"
|
||||
import { formatMethodParams } from "../../utils/format-parameters"
|
||||
import useInView from "../../hooks/use-in-view"
|
||||
import NavigationContext from "../../context/navigation-context"
|
||||
|
||||
const Method = ({ data, section, pathname }) => {
|
||||
const { parameters, requestBody, description, method, summary } = data
|
||||
const jsonResponse = data.responses[0].content?.[0].json
|
||||
const { updateHash, updateMetadata } = useContext(NavigationContext)
|
||||
const methodRef = useRef(null)
|
||||
|
||||
const [containerRef, isInView] = useInView({
|
||||
root: null,
|
||||
rootMargin: "0px 0px -80% 0px",
|
||||
threshold: 0,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (isInView) {
|
||||
updateHash(section, convertToKebabCase(summary))
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isInView])
|
||||
|
||||
const handleMetaChange = () => {
|
||||
updateMetadata({
|
||||
title: summary,
|
||||
description: description,
|
||||
})
|
||||
if (methodRef.current) {
|
||||
methodRef.current.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex
|
||||
py={"5vw"}
|
||||
sx={{
|
||||
borderTop: "hairline",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
id={convertToKebabCase(summary)}
|
||||
ref={methodRef}
|
||||
>
|
||||
<Flex>
|
||||
<Heading
|
||||
as="h2"
|
||||
mb={4}
|
||||
sx={{
|
||||
fontSize: "4",
|
||||
fontWeight: "500",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
ref={containerRef}
|
||||
onClick={() => handleMetaChange()}
|
||||
>
|
||||
{summary}
|
||||
</Heading>
|
||||
</Flex>
|
||||
<ResponsiveContainer>
|
||||
<Flex
|
||||
className="info"
|
||||
sx={{
|
||||
flexDirection: "column",
|
||||
pr: "5",
|
||||
"@media screen and (max-width: 848px)": {
|
||||
pr: "0",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Route path={pathname} method={method} />
|
||||
<Description>
|
||||
<Text
|
||||
sx={{
|
||||
lineHeight: "26px",
|
||||
}}
|
||||
mt={3}
|
||||
>
|
||||
<Markdown>{description}</Markdown>
|
||||
</Text>
|
||||
</Description>
|
||||
<Box mt={4}>
|
||||
<Parameters
|
||||
params={formatMethodParams({ parameters, requestBody })}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
<Box className="code">
|
||||
<JsonContainer
|
||||
json={jsonResponse}
|
||||
header={"RESPONSE"}
|
||||
method={convertToKebabCase(summary)}
|
||||
/>
|
||||
</Box>
|
||||
</ResponsiveContainer>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
export default Method
|
||||
68
www/reference/src/components/content/parameters.js
Normal file
68
www/reference/src/components/content/parameters.js
Normal file
@@ -0,0 +1,68 @@
|
||||
import React from "react"
|
||||
import { Flex, Text, Box } from "theme-ui"
|
||||
import Markdown from "react-markdown"
|
||||
import NestedCollapsible from "./collapsible"
|
||||
import Description from "./description"
|
||||
|
||||
const Parameters = ({ params, type }) => {
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<Text pb="2">{type === "attr" ? "Attributes" : "Parameters"}</Text>
|
||||
{params.properties.length > 0 ? (
|
||||
params.properties.map((prop, i) => {
|
||||
const nested = prop.nestedModel || prop.items?.properties || null
|
||||
return (
|
||||
<Box
|
||||
py={2}
|
||||
sx={{
|
||||
borderTop: "hairline",
|
||||
fontFamily: "monospace",
|
||||
fontSize: "0",
|
||||
}}
|
||||
key={i}
|
||||
>
|
||||
<Flex sx={{ alignItems: "baseline", fontSize: "0" }}>
|
||||
<Text mr={2}>{prop.property || prop.name}</Text>
|
||||
<Text color={"gray"}>
|
||||
{prop.type || prop.schema?.type || nested?.title}
|
||||
</Text>
|
||||
{prop.required ? (
|
||||
<Text ml={1} variant="labels.required">
|
||||
required
|
||||
</Text>
|
||||
) : null}
|
||||
</Flex>
|
||||
<Description>
|
||||
<Text
|
||||
sx={{
|
||||
fontSize: "0",
|
||||
lineHeight: "26px",
|
||||
fontFamily: "body",
|
||||
}}
|
||||
>
|
||||
<Markdown>{prop.description}</Markdown>
|
||||
</Text>
|
||||
</Description>
|
||||
{nested?.properties && (
|
||||
<NestedCollapsible
|
||||
properties={nested.properties}
|
||||
title={nested.title}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
)
|
||||
})
|
||||
) : (
|
||||
<Text sx={{ fontSize: "0", py: "3", fontFamily: "monospace" }}>
|
||||
No parameters
|
||||
</Text>
|
||||
)}
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
export default Parameters
|
||||
29
www/reference/src/components/content/responsive-container.js
Normal file
29
www/reference/src/components/content/responsive-container.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import React from "react"
|
||||
import styled from "@emotion/styled"
|
||||
import { Flex } from "theme-ui"
|
||||
|
||||
const ResponsiveFlex = styled(Flex)`
|
||||
.info {
|
||||
width: 55%;
|
||||
}
|
||||
|
||||
.code {
|
||||
width: 45%;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 848px) {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.info,
|
||||
.code {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const ResponsiveContainer = ({ children }) => {
|
||||
return <ResponsiveFlex>{children}</ResponsiveFlex>
|
||||
}
|
||||
|
||||
export default ResponsiveContainer
|
||||
17
www/reference/src/components/content/route.js
Normal file
17
www/reference/src/components/content/route.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import React from "react"
|
||||
import { Flex, Text } from "theme-ui"
|
||||
import { formatRoute } from "../../utils/format-route"
|
||||
|
||||
const Route = ({ method, path }) => {
|
||||
const fixedMethod = method.toUpperCase()
|
||||
return (
|
||||
<Flex sx={{ fontFamily: "monospace" }}>
|
||||
<Text variant={`labels.${fixedMethod}`} mr={1}>
|
||||
{fixedMethod}
|
||||
</Text>
|
||||
<Text>{formatRoute(path)}</Text>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
export default Route
|
||||
205
www/reference/src/components/content/section.js
Normal file
205
www/reference/src/components/content/section.js
Normal file
@@ -0,0 +1,205 @@
|
||||
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 ChevronDown from "../icons/chevron-down"
|
||||
import useInView from "../../hooks/use-in-view"
|
||||
|
||||
const Section = ({ data }) => {
|
||||
const { section } = data
|
||||
const [isExpanded, setIsExpanded] = useState(false)
|
||||
const { openSections, updateSection, updateMetadata } = useContext(
|
||||
NavigationContext
|
||||
)
|
||||
|
||||
const endpoints = section.paths
|
||||
.map(p => {
|
||||
let path = p.name
|
||||
let ep = []
|
||||
|
||||
p.methods.forEach(m => {
|
||||
ep.push({ method: m.method, endpoint: path })
|
||||
})
|
||||
|
||||
return ep
|
||||
})
|
||||
.flat()
|
||||
|
||||
const sectionRef = useRef(null)
|
||||
|
||||
const scrollIntoView = () => {
|
||||
if (sectionRef.current) {
|
||||
sectionRef.current.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleExpand = () => {
|
||||
updateMetadata({
|
||||
title: section.section_name,
|
||||
description: section.schema?.description,
|
||||
})
|
||||
setIsExpanded(true)
|
||||
scrollIntoView()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (isExpanded) {
|
||||
scrollIntoView()
|
||||
}
|
||||
}, [isExpanded])
|
||||
|
||||
useEffect(() => {
|
||||
const shouldOpen = openSections.includes(
|
||||
convertToKebabCase(section.section_name)
|
||||
)
|
||||
|
||||
if (shouldOpen) {
|
||||
setIsExpanded(true)
|
||||
}
|
||||
}, [section.section_name, openSections, openSections.length])
|
||||
|
||||
const [containerRef, isInView] = useInView({
|
||||
root: null,
|
||||
rootMargin: "0px 0px -80% 0px",
|
||||
threshold: 1.0,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
const handleInView = () => {
|
||||
if (isInView) {
|
||||
updateSection(convertToKebabCase(section.section_name))
|
||||
}
|
||||
}
|
||||
handleInView()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isInView])
|
||||
|
||||
return (
|
||||
<section ref={sectionRef} id={convertToKebabCase(section.section_name)}>
|
||||
<Box
|
||||
sx={{
|
||||
borderBottom: "hairline",
|
||||
padding: "5vw",
|
||||
backgroundColor: isExpanded ? "transparent" : "fadedContrast",
|
||||
}}
|
||||
>
|
||||
<Flex>
|
||||
<Heading
|
||||
as="h1"
|
||||
sx={{
|
||||
fontWeight: "500",
|
||||
fontSize: "22",
|
||||
mb: "3",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
ref={containerRef}
|
||||
className={`header-${convertToKebabCase(section.section_name)}`}
|
||||
onClick={handleExpand}
|
||||
>
|
||||
{section.section_name}
|
||||
</Heading>
|
||||
</Flex>
|
||||
<Flex
|
||||
sx={{
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<ResponsiveContainer>
|
||||
<Flex
|
||||
sx={{
|
||||
flexDirection: "column",
|
||||
lineHeight: "26px",
|
||||
pr: "5",
|
||||
"@media screen and (max-width: 848px)": {
|
||||
pr: "0",
|
||||
},
|
||||
}}
|
||||
className="info"
|
||||
>
|
||||
<Description>
|
||||
<Text mb={4}>
|
||||
<Markdown>{section.schema?.description}</Markdown>
|
||||
</Text>
|
||||
</Description>
|
||||
{isExpanded && section.schema ? (
|
||||
<Parameters params={section.schema} type={"attr"} />
|
||||
) : null}
|
||||
</Flex>
|
||||
<Flex
|
||||
className="code"
|
||||
sx={{
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<EndpointContainer endpoints={endpoints} />
|
||||
{isExpanded ? (
|
||||
<JsonContainer
|
||||
json={section.schema?.object}
|
||||
header={`${section.section_name.toUpperCase()} OBJECT`}
|
||||
/>
|
||||
) : null}
|
||||
</Flex>
|
||||
</ResponsiveContainer>
|
||||
{isExpanded ? (
|
||||
<Box mt={4}>
|
||||
{section.paths.map((p, i) => {
|
||||
return (
|
||||
<Flex
|
||||
key={i}
|
||||
sx={{
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
{p.methods.map((m, i) => {
|
||||
return (
|
||||
<Method
|
||||
key={i}
|
||||
data={m}
|
||||
section={convertToKebabCase(section.section_name)}
|
||||
pathname={p.name}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</Flex>
|
||||
)
|
||||
})}
|
||||
</Box>
|
||||
) : (
|
||||
<Flex
|
||||
sx={{
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
}}
|
||||
mt={4}
|
||||
>
|
||||
<Button
|
||||
onClick={handleExpand}
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
borderRadius: "24px",
|
||||
bg: "light",
|
||||
fontWeight: "500",
|
||||
}}
|
||||
>
|
||||
SHOW <ChevronDown fill={"dark"} styles={{ mr: "-10px" }} />
|
||||
</Button>
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
</Box>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export default Section
|
||||
24
www/reference/src/components/icons/chevron-down.js
Normal file
24
www/reference/src/components/icons/chevron-down.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import React from "react"
|
||||
import { Box } from "theme-ui"
|
||||
|
||||
const ChevronDown = ({ fill = "darkContrast", styles }) => {
|
||||
return (
|
||||
<Box
|
||||
as="svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
sx={{
|
||||
...styles,
|
||||
alignSelf: "center",
|
||||
pointerEvents: "none",
|
||||
fill: `${fill}`,
|
||||
}}
|
||||
>
|
||||
<path d="M7.41 7.84l4.59 4.58 4.59-4.58 1.41 1.41-6 6-6-6z" />
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default ChevronDown
|
||||
18
www/reference/src/components/icons/github.js
Normal file
18
www/reference/src/components/icons/github.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Image } from "@theme-ui/components"
|
||||
import React from "react"
|
||||
|
||||
import Logo from "../../assets/github.svg"
|
||||
|
||||
const GitHub = () => {
|
||||
return (
|
||||
<Image
|
||||
src={Logo}
|
||||
sx={{
|
||||
height: "20px",
|
||||
fill: "#000",
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default GitHub
|
||||
26
www/reference/src/components/layout.js
Normal file
26
www/reference/src/components/layout.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import React from "react"
|
||||
import { Flex, Box } from "theme-ui"
|
||||
import Sidebar from "./sidebar"
|
||||
|
||||
const Layout = ({ data, api, children }) => {
|
||||
return (
|
||||
<Flex sx={{ p: "0", m: "0", overflow: "hidden" }}>
|
||||
<Flex
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
fontFamily: "body",
|
||||
flexGrow: "1",
|
||||
}}
|
||||
>
|
||||
<Sidebar data={data} api={api} />
|
||||
<Box>{children}</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
export default Layout
|
||||
115
www/reference/src/components/topbar.js
Normal file
115
www/reference/src/components/topbar.js
Normal file
@@ -0,0 +1,115 @@
|
||||
import { Flex, Box, Link, Select } from "@theme-ui/components"
|
||||
import React, { useContext } from "react"
|
||||
import { navigate } from "gatsby-link"
|
||||
|
||||
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"
|
||||
|
||||
const Topbar = ({ data, api }) => {
|
||||
const { goTo, reset, currentSection } = useContext(NavigationContext)
|
||||
|
||||
const handleChange = e => {
|
||||
const parts = e.target.value.split(" ")
|
||||
|
||||
if (parts[0] === api) {
|
||||
goTo({ section: parts[1] })
|
||||
} else {
|
||||
reset()
|
||||
navigate(`/api/${api === "admin" ? "store" : "admin"}`)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
px: "5vw",
|
||||
height: "headerHeight",
|
||||
boxShadow: "topbarShadow",
|
||||
"@media screen and (max-width: 848px)": {
|
||||
position: "fixed",
|
||||
top: "0",
|
||||
left: "0",
|
||||
right: "0",
|
||||
zIndex: "9999",
|
||||
backgroundColor: "light",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "none",
|
||||
alignItems: "center",
|
||||
"@media screen and (max-width: 848px)": {
|
||||
display: "block",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Select
|
||||
arrow={<ChevronDown fill={"dark"} />}
|
||||
sx={{
|
||||
border: "none",
|
||||
width: "100%",
|
||||
fontSize: "2",
|
||||
fontFamily: "body",
|
||||
fontWeight: "500",
|
||||
flexGrow: "1",
|
||||
px: "0",
|
||||
transition: "all .1s ease-in-out",
|
||||
"&:focus": {
|
||||
outline: "none !important",
|
||||
},
|
||||
}}
|
||||
onChange={handleChange}
|
||||
value={`${api} ${currentSection}`}
|
||||
>
|
||||
<optgroup label={`${api.slice(0, 1).toUpperCase()}${api.slice(1)}`}>
|
||||
{data.sections.map((s, i) => {
|
||||
return (
|
||||
<option
|
||||
key={i}
|
||||
value={`${api} ${convertToKebabCase(
|
||||
s.section.section_name
|
||||
)}`}
|
||||
>
|
||||
{s.section.section_name}
|
||||
</option>
|
||||
)
|
||||
})}
|
||||
</optgroup>
|
||||
<optgroup label={api === "admin" ? "Store" : "Admin"}>
|
||||
<option>{`Go to ${
|
||||
api === "admin" ? "Storefront API" : "Admin API"
|
||||
}`}</option>
|
||||
</optgroup>
|
||||
</Select>
|
||||
</Box>
|
||||
</Box>
|
||||
<Flex
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Link variant="topbar" mr={3} href="https://docs.medusa-commerce.com">
|
||||
Docs
|
||||
</Link>
|
||||
<Link
|
||||
sx={{
|
||||
pt: "4px",
|
||||
}}
|
||||
variant="topbar"
|
||||
href="https://github.com/medusajs/medusa"
|
||||
>
|
||||
<GitHub />
|
||||
</Link>
|
||||
</Flex>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
export default Topbar
|
||||
157
www/reference/src/context/navigation-context.js
Normal file
157
www/reference/src/context/navigation-context.js
Normal file
@@ -0,0 +1,157 @@
|
||||
import React, { useReducer } from "react"
|
||||
import scrollParent from "../utils/scroll-parent"
|
||||
import types from "./types"
|
||||
|
||||
export const defaultNavigationContext = {
|
||||
api: "null",
|
||||
setApi: () => {},
|
||||
currentSection: null,
|
||||
updateSection: () => {},
|
||||
currentHash: null,
|
||||
updateHash: () => {},
|
||||
openSections: [],
|
||||
openSection: () => {},
|
||||
metadata: null,
|
||||
updateMetadata: () => {},
|
||||
reset: () => {},
|
||||
}
|
||||
|
||||
const NavigationContext = React.createContext(defaultNavigationContext)
|
||||
export default NavigationContext
|
||||
|
||||
const reducer = (state, action) => {
|
||||
switch (action.type) {
|
||||
case types.SET_API: {
|
||||
return {
|
||||
...state,
|
||||
api: action.payload,
|
||||
}
|
||||
}
|
||||
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)
|
||||
return {
|
||||
...state,
|
||||
openSections: obj,
|
||||
}
|
||||
case types.RESET:
|
||||
return {
|
||||
...state,
|
||||
openSections: [],
|
||||
currentSection: null,
|
||||
currentHash: null,
|
||||
}
|
||||
case types.UPDATE_METADATA:
|
||||
return {
|
||||
...state,
|
||||
metadata: {
|
||||
title: action.payload.title,
|
||||
description: action.payload.description,
|
||||
},
|
||||
}
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
const scrollNav = id => {
|
||||
const nav = document.querySelector("#nav")
|
||||
if (nav) {
|
||||
const element = nav.querySelector(`#nav-${id}`)
|
||||
if (element) {
|
||||
scrollParent(nav, element)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const scrollToElement = async id => {
|
||||
const element = document.querySelector(`#${id}`)
|
||||
if (element) {
|
||||
element.scrollIntoView({
|
||||
block: "start",
|
||||
inline: "nearest",
|
||||
})
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
scrollToElement(id)
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
|
||||
export const NavigationProvider = ({ children }) => {
|
||||
const [state, dispatch] = useReducer(reducer, defaultNavigationContext)
|
||||
|
||||
const setApi = api => {
|
||||
dispatch({ type: types.SET_API, payload: api })
|
||||
}
|
||||
|
||||
const updateMetadata = metadata => {
|
||||
dispatch({ type: types.UPDATE_METADATA, payload: metadata })
|
||||
}
|
||||
|
||||
const updateHash = (section, method) => {
|
||||
dispatch({
|
||||
type: types.UPDATE_HASH,
|
||||
payload: { method: method, section: section },
|
||||
})
|
||||
window.history.replaceState(
|
||||
null,
|
||||
"",
|
||||
`/api/${state.api}/${section}/${method}`
|
||||
)
|
||||
scrollNav(method)
|
||||
}
|
||||
|
||||
const updateSection = section => {
|
||||
dispatch({ type: types.UPDATE_SECTION, payload: section })
|
||||
window.history.replaceState(null, "", `/api/${state.api}/${section}`)
|
||||
scrollNav(section)
|
||||
}
|
||||
|
||||
const openSection = sectionName => {
|
||||
dispatch({ type: types.OPEN_SECTION, payload: sectionName })
|
||||
}
|
||||
|
||||
const goTo = to => {
|
||||
const { section, method } = to
|
||||
|
||||
if (!state.openSections.includes(section)) {
|
||||
openSection(section)
|
||||
}
|
||||
scrollToElement(method || section)
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
dispatch({ type: types.RESET })
|
||||
}
|
||||
|
||||
return (
|
||||
<NavigationContext.Provider
|
||||
value={{
|
||||
...state,
|
||||
openSection,
|
||||
updateSection,
|
||||
updateHash,
|
||||
setApi,
|
||||
goTo,
|
||||
reset,
|
||||
updateMetadata,
|
||||
dispatch,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</NavigationContext.Provider>
|
||||
)
|
||||
}
|
||||
10
www/reference/src/context/types.js
Normal file
10
www/reference/src/context/types.js
Normal file
@@ -0,0 +1,10 @@
|
||||
const types = {
|
||||
SET_API: "SET_API",
|
||||
UPDATE_HASH: "UPDATE_HASH",
|
||||
UPDATE_SECTION: "UPDATE_SECTION",
|
||||
OPEN_SECTION: "OPEN_SECTION",
|
||||
RESET: "RESET",
|
||||
UPDATE_METADATA: "UPDATE_METADATA",
|
||||
}
|
||||
|
||||
export default types
|
||||
2
www/reference/src/gatsby-plugin-theme-ui/breakpoints.js
Normal file
2
www/reference/src/gatsby-plugin-theme-ui/breakpoints.js
Normal file
@@ -0,0 +1,2 @@
|
||||
// eslint-disable-next-line import/no-anonymous-default-export
|
||||
export default ["40em", "52em", "64em"]
|
||||
@@ -163,6 +163,12 @@ export const buttons = {
|
||||
boxShadow: "ctaBoxShadowHover",
|
||||
},
|
||||
},
|
||||
trigger: {
|
||||
bg: "faded",
|
||||
alignItems: "baseline",
|
||||
fontSize: "13px",
|
||||
fontWeight: "400",
|
||||
},
|
||||
}
|
||||
|
||||
export default buttons
|
||||
@@ -1,3 +1,4 @@
|
||||
// eslint-disable-next-line import/no-anonymous-default-export
|
||||
export default {
|
||||
invalidInput: {
|
||||
color: "dark",
|
||||
@@ -1,6 +1,3 @@
|
||||
import React from "react"
|
||||
import { ThemeProvider as Provider } from "emotion-theming"
|
||||
|
||||
import "../fonts/index.css"
|
||||
|
||||
import breakpoints from "./breakpoints"
|
||||
@@ -10,19 +7,67 @@ import shadows from "./shadows"
|
||||
import forms from "./forms"
|
||||
import labels from "./labels"
|
||||
|
||||
export const theme = {
|
||||
labels,
|
||||
// eslint-disable-next-line import/no-anonymous-default-export
|
||||
export default {
|
||||
sizes: {
|
||||
headerHeight: "50px",
|
||||
},
|
||||
links: {
|
||||
topbar: {
|
||||
fontFamily: "body",
|
||||
fontSize: "2",
|
||||
textDecoration: "none",
|
||||
color: "dark",
|
||||
},
|
||||
},
|
||||
text: {
|
||||
labels,
|
||||
largest: {
|
||||
fontFamily: "body",
|
||||
fontSize: "22px",
|
||||
fontweight: "300",
|
||||
lineHeight: "1.5",
|
||||
letterSpacing: "normal",
|
||||
},
|
||||
large: {
|
||||
fontFamily: "body",
|
||||
fontSize: "18px",
|
||||
fontWeight: "300",
|
||||
lineHeight: "1.22",
|
||||
letterSpacing: "-0.5px",
|
||||
},
|
||||
medium: {
|
||||
fontFamily: "body",
|
||||
fontSize: "16px",
|
||||
fontWeight: "300",
|
||||
lineHeight: "1.22",
|
||||
letterSpacing: "normal",
|
||||
},
|
||||
base: {
|
||||
fontFamily: "body",
|
||||
fontsize: "14px",
|
||||
fontWeight: "300",
|
||||
lineHeight: "1.22",
|
||||
letterSpacing: "-0.25px",
|
||||
},
|
||||
small: {
|
||||
fontFamily: "body",
|
||||
fontSize: "12px",
|
||||
fontWeight: "300",
|
||||
lineHeight: "12px",
|
||||
letterSpacing: "0px",
|
||||
},
|
||||
},
|
||||
colors: {
|
||||
primary: "#53725D",
|
||||
secondary: "#79B28A",
|
||||
medusaGreen: "#53725D",
|
||||
danger: "#FF7675",
|
||||
muted: "#E3E8EE",
|
||||
lightest: "#fefefe",
|
||||
light: "#f0f0f0",
|
||||
dark: "#454545",
|
||||
darkest: "#212121",
|
||||
placeholder: "#a3acb9",
|
||||
dark: "#0a3149",
|
||||
darkContrast: "#0a314940",
|
||||
light: "#ffffff",
|
||||
faded: "#eef0f5",
|
||||
fadedContrast: "#eef0f540",
|
||||
},
|
||||
borders: {
|
||||
hairline: "1px solid #E3E8EE",
|
||||
@@ -80,8 +125,7 @@ export const theme = {
|
||||
},
|
||||
forms,
|
||||
buttons,
|
||||
radii: {
|
||||
small: "8px",
|
||||
},
|
||||
}
|
||||
|
||||
export const ThemeProvider = ({ children }) => (
|
||||
<Provider theme={theme}>{children}</Provider>
|
||||
)
|
||||
@@ -1,3 +1,4 @@
|
||||
// eslint-disable-next-line import/no-anonymous-default-export
|
||||
export default {
|
||||
pill: `
|
||||
rgba(0, 0, 0, 0) 0px 0px 0px 0px,
|
||||
@@ -108,4 +109,11 @@ export default {
|
||||
rgba(62, 207, 142, 0.25) 0px 3px 9px 0px;
|
||||
rgba(62, 207, 142, 0.25) 0px 2px 5px 0px;
|
||||
`,
|
||||
|
||||
sidebarShadow: `
|
||||
inset -1px 0 0 0 #eef0f5
|
||||
`,
|
||||
topbarShadow: `
|
||||
0 2px 5px 0 rgb(59 65 94 / 10%), 0 1px 1px 0 rgb(0 0 0 / 7%)
|
||||
`,
|
||||
}
|
||||
2
www/reference/src/gatsby-plugin-theme-ui/spacing.js
Normal file
2
www/reference/src/gatsby-plugin-theme-ui/spacing.js
Normal file
@@ -0,0 +1,2 @@
|
||||
// eslint-disable-next-line import/no-anonymous-default-export
|
||||
export default [0, 4, 8, 16, 32, 64]
|
||||
29
www/reference/src/hooks/use-in-view.js
Normal file
29
www/reference/src/hooks/use-in-view.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import { useEffect, useRef, useState } from "react"
|
||||
|
||||
const useInView = options => {
|
||||
const containerRef = useRef(null)
|
||||
const [isInView, setIsInView] = useState(false)
|
||||
|
||||
const callback = entries => {
|
||||
const [entry] = entries
|
||||
setIsInView(entry.isIntersecting)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const ref = containerRef.current
|
||||
const observer = new IntersectionObserver(callback, options)
|
||||
if (ref) {
|
||||
observer.observe(ref)
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (ref) {
|
||||
observer.unobserve(ref)
|
||||
}
|
||||
}
|
||||
}, [containerRef, options])
|
||||
|
||||
return [containerRef, isInView]
|
||||
}
|
||||
|
||||
export default useInView
|
||||
123
www/reference/src/prism-medusa-theme/prism.css
Normal file
123
www/reference/src/prism-medusa-theme/prism.css
Normal file
@@ -0,0 +1,123 @@
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
color: #0a3149;
|
||||
background: none;
|
||||
font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
|
||||
font-size: 12px;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
line-height: 1.5;
|
||||
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
pre[class*="language-"] {
|
||||
padding: 1em;
|
||||
margin: 0;
|
||||
overflow: auto;
|
||||
border-radius: 0.3em;
|
||||
}
|
||||
|
||||
:not(pre) > code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
:not(pre) > code[class*="language-"] {
|
||||
padding: 0.1em;
|
||||
border-radius: 0.3em;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.token.comment,
|
||||
.token.prolog,
|
||||
.token.doctype,
|
||||
.token.cdata {
|
||||
color: blue;
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
color: #0a3149;
|
||||
}
|
||||
|
||||
.token.namespace {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.token.property,
|
||||
.token.tag,
|
||||
.token.constant,
|
||||
.token.symbol,
|
||||
.token.deleted {
|
||||
color: #0a3149;
|
||||
}
|
||||
|
||||
.token.number {
|
||||
color: #e56f4a;
|
||||
}
|
||||
|
||||
.token.boolean {
|
||||
color: #3a97d4;
|
||||
}
|
||||
|
||||
.token.selector,
|
||||
.token.attr-name,
|
||||
.token.string,
|
||||
.token.char,
|
||||
.token.builtin,
|
||||
.token.inserted {
|
||||
color: #09825d;
|
||||
}
|
||||
|
||||
.token.operator,
|
||||
.token.entity,
|
||||
.token.url,
|
||||
.language-css .token.string,
|
||||
.style .token.string,
|
||||
.token.variable {
|
||||
color: #0a3149;
|
||||
}
|
||||
|
||||
.token.atrule,
|
||||
.token.attr-value,
|
||||
.token.function,
|
||||
.token.class-name {
|
||||
color: #e6db74;
|
||||
}
|
||||
|
||||
.token.keyword {
|
||||
color: #3a97d4;
|
||||
}
|
||||
|
||||
.token.keyword.null {
|
||||
color: grey;
|
||||
}
|
||||
|
||||
.token.regex,
|
||||
.token.important {
|
||||
color: #fd971f;
|
||||
}
|
||||
|
||||
.token.important,
|
||||
.token.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
.token.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.token.entity {
|
||||
cursor: help;
|
||||
}
|
||||
43
www/reference/src/templates/reference-page.js
Normal file
43
www/reference/src/templates/reference-page.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import React, { useContext, useEffect, useState } from "react"
|
||||
import { Helmet } from "react-helmet"
|
||||
|
||||
import Layout from "../components/layout"
|
||||
import Content from "../components/content"
|
||||
import NavigationContext from "../context/navigation-context"
|
||||
|
||||
export default function ReferencePage({
|
||||
pageContext: { data, api, title, description, to },
|
||||
}) {
|
||||
const { setApi, goTo, metadata } = useContext(NavigationContext)
|
||||
const [siteData, setSiteData] = useState({
|
||||
title: title,
|
||||
description: description,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
setApi(api)
|
||||
if (to) {
|
||||
goTo(to)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (metadata) {
|
||||
setSiteData({
|
||||
title: metadata.title,
|
||||
description: metadata.description,
|
||||
})
|
||||
}
|
||||
}, [metadata])
|
||||
|
||||
return (
|
||||
<Layout data={data} api={api}>
|
||||
<Helmet>
|
||||
<title>{`${siteData.title} | Medusa Commerce API Reference`}</title>
|
||||
<meta name="description" content={siteData.description} />
|
||||
</Helmet>
|
||||
<Content data={data} api={api} />
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
8
www/reference/src/utils/convert-to-kebab-case.js
Normal file
8
www/reference/src/utils/convert-to-kebab-case.js
Normal file
@@ -0,0 +1,8 @@
|
||||
export const convertToKebabCase = string => {
|
||||
return string
|
||||
.replace(/\s+/g, "-")
|
||||
.replace("'", "")
|
||||
.replace(".", "")
|
||||
.replace('"', "")
|
||||
.toLowerCase()
|
||||
}
|
||||
29
www/reference/src/utils/format-parameters.js
Normal file
29
www/reference/src/utils/format-parameters.js
Normal file
@@ -0,0 +1,29 @@
|
||||
export const formatMethodParams = method => {
|
||||
const { parameters, requestBody } = method
|
||||
|
||||
const params = []
|
||||
if (parameters && parameters.length > 0) {
|
||||
parameters.map(p => {
|
||||
return params.push({
|
||||
property: p.name,
|
||||
description: p.description,
|
||||
required: p.required,
|
||||
type: p.schema.type,
|
||||
})
|
||||
})
|
||||
}
|
||||
if (requestBody) {
|
||||
const { required, properties } = requestBody
|
||||
properties.map(p => {
|
||||
return params.push({
|
||||
property: p.property,
|
||||
description: p.description,
|
||||
required: required ? required.some(req => req === p.property) : false,
|
||||
type: p.type,
|
||||
nestedModel: p.nestedModel,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return { properties: params }
|
||||
}
|
||||
3
www/reference/src/utils/format-route.js
Normal file
3
www/reference/src/utils/format-route.js
Normal file
@@ -0,0 +1,3 @@
|
||||
export const formatRoute = string => {
|
||||
return string.replace(/{(.*?)}/g, ":$1")
|
||||
}
|
||||
215
www/reference/src/utils/fragments.js
Normal file
215
www/reference/src/utils/fragments.js
Normal file
@@ -0,0 +1,215 @@
|
||||
import { graphql } from "gatsby"
|
||||
|
||||
export const StoreSections = graphql`
|
||||
fragment StoreSections on StoreSections {
|
||||
sections {
|
||||
...StoreSection
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const StoreSection = graphql`
|
||||
fragment StoreSection on StoreSectionsSection {
|
||||
section_name
|
||||
paths {
|
||||
...StorePath
|
||||
}
|
||||
schema {
|
||||
object
|
||||
description
|
||||
properties {
|
||||
property
|
||||
type
|
||||
description
|
||||
format
|
||||
nestedModel {
|
||||
title
|
||||
properties {
|
||||
property
|
||||
type
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const StorePath = graphql`
|
||||
fragment StorePath on StoreSectionsSectionPaths {
|
||||
name
|
||||
methods {
|
||||
...StoreMethod
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const StoreMethod = graphql`
|
||||
fragment StoreMethod on StoreSectionsSectionPathsMethods {
|
||||
tags
|
||||
summary
|
||||
description
|
||||
method
|
||||
operationId
|
||||
responses {
|
||||
...StoreResponse
|
||||
}
|
||||
requestBody {
|
||||
...StoreRequestBody
|
||||
}
|
||||
|
||||
parameters {
|
||||
description
|
||||
in
|
||||
name
|
||||
required
|
||||
schema {
|
||||
type
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const StoreResponse = graphql`
|
||||
fragment StoreResponse on StoreSectionsSectionPathsMethodsResponses {
|
||||
status
|
||||
description
|
||||
content {
|
||||
_ref
|
||||
property
|
||||
description
|
||||
json
|
||||
items {
|
||||
_ref
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const StoreRequestBody = graphql`
|
||||
fragment StoreRequestBody on StoreSectionsSectionPathsMethodsRequestBody {
|
||||
type
|
||||
required
|
||||
properties {
|
||||
description
|
||||
property
|
||||
type
|
||||
nestedModel {
|
||||
title
|
||||
properties {
|
||||
property
|
||||
type
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const AdminSections = graphql`
|
||||
fragment AdminSections on AdminSections {
|
||||
sections {
|
||||
...AdminSection
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const AdminSection = graphql`
|
||||
fragment AdminSection on AdminSectionsSection {
|
||||
section_name
|
||||
paths {
|
||||
...AdminPath
|
||||
}
|
||||
schema {
|
||||
object
|
||||
description
|
||||
properties {
|
||||
property
|
||||
type
|
||||
description
|
||||
format
|
||||
nestedModel {
|
||||
title
|
||||
properties {
|
||||
property
|
||||
type
|
||||
description
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const AdminPath = graphql`
|
||||
fragment AdminPath on AdminSectionsSectionPaths {
|
||||
name
|
||||
methods {
|
||||
...AdminMethod
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const AdminMethod = graphql`
|
||||
fragment AdminMethod on AdminSectionsSectionPathsMethods {
|
||||
tags
|
||||
summary
|
||||
description
|
||||
method
|
||||
operationId
|
||||
responses {
|
||||
...AdminResponse
|
||||
}
|
||||
requestBody {
|
||||
...AdminRequestBody
|
||||
}
|
||||
|
||||
parameters {
|
||||
description
|
||||
in
|
||||
name
|
||||
required
|
||||
schema {
|
||||
type
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const AdminResponse = graphql`
|
||||
fragment AdminResponse on AdminSectionsSectionPathsMethodsResponses {
|
||||
status
|
||||
description
|
||||
content {
|
||||
_ref
|
||||
type
|
||||
property
|
||||
description
|
||||
json
|
||||
items {
|
||||
type
|
||||
_ref
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const AdminRequestBody = graphql`
|
||||
fragment AdminRequestBody on AdminSectionsSectionPathsMethodsRequestBody {
|
||||
type
|
||||
required
|
||||
properties {
|
||||
description
|
||||
enum
|
||||
format
|
||||
property
|
||||
type
|
||||
nestedModel {
|
||||
title
|
||||
properties {
|
||||
property
|
||||
type
|
||||
description
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
20
www/reference/src/utils/scroll-parent.js
Normal file
20
www/reference/src/utils/scroll-parent.js
Normal file
@@ -0,0 +1,20 @@
|
||||
const scrollParent = (parent, child) => {
|
||||
const parentRect = parent.getBoundingClientRect()
|
||||
|
||||
const parentViewableArea = {
|
||||
height: parent.clientHeight,
|
||||
width: parent.clientWidth,
|
||||
}
|
||||
|
||||
const childRect = child.getBoundingClientRect()
|
||||
const isViewable =
|
||||
childRect.top >= parentRect.top &&
|
||||
childRect.top <= parentRect.top + parentViewableArea.height
|
||||
|
||||
if (!isViewable) {
|
||||
const pos = childRect.top + parent.scrollTop - parentRect.top
|
||||
parent.scrollTop = pos > 0 ? pos : 0
|
||||
}
|
||||
}
|
||||
|
||||
export default scrollParent
|
||||
|
Before Width: | Height: | Size: 225 B After Width: | Height: | Size: 225 B |
File diff suppressed because it is too large
Load Diff
@@ -1,92 +0,0 @@
|
||||
import React from "react"
|
||||
import { Flex, Text } from "rebass"
|
||||
import styled from "@emotion/styled"
|
||||
import Markdown from "react-markdown"
|
||||
|
||||
import RouteSection from "./route-section"
|
||||
import JsonBox from "./json-box"
|
||||
import EndpointOverview from "./endpoint-overview"
|
||||
import Parameters from "./parameters"
|
||||
|
||||
const convertToKebabCase = string => {
|
||||
return string.replace(/\s+/g, "-").toLowerCase()
|
||||
}
|
||||
|
||||
const EndpointContainer = styled(Flex)`
|
||||
min-height: 90vh;
|
||||
position: relative;
|
||||
border-top: hairline;
|
||||
|
||||
code {
|
||||
background-color: #e3e8ee;
|
||||
border-radius: 5px;
|
||||
padding: 4px;
|
||||
}
|
||||
`
|
||||
|
||||
const DocsReader = ({ tags, spec }) => {
|
||||
return (
|
||||
<Flex flexDirection="column" width="100%">
|
||||
{Object.entries(tags).map(([tagName, endpoints]) => (
|
||||
<EndpointContainer id={convertToKebabCase(tagName)} p={4}>
|
||||
<Flex flexDirection="row" width="100%">
|
||||
<Flex py={5} flexDirection="column" p={4} width="100%">
|
||||
<EndpointOverview
|
||||
title={tagName}
|
||||
description={""}
|
||||
routes={endpoints}
|
||||
spec={spec}
|
||||
/>
|
||||
<Flex
|
||||
flexDirection="row"
|
||||
width={"100%"}
|
||||
sx={{
|
||||
position: "relative",
|
||||
borderTop: "hairline",
|
||||
}}
|
||||
>
|
||||
<Flex width={"100%"} flexDirection="column">
|
||||
{endpoints.map((endpoint, i) => (
|
||||
<Flex
|
||||
id={convertToKebabCase(endpoint.summary)}
|
||||
py={4}
|
||||
flexDirection="row"
|
||||
width="100%"
|
||||
>
|
||||
<Flex
|
||||
pr={5}
|
||||
width={"55%"}
|
||||
flexDirection="column"
|
||||
sx={{ lineHeight: "26px" }}
|
||||
>
|
||||
<Text mb={3} fontSize={3}>
|
||||
{endpoint.summary}
|
||||
</Text>
|
||||
<RouteSection
|
||||
basePath={""}
|
||||
method={endpoint.method}
|
||||
path={endpoint.path}
|
||||
/>
|
||||
<Markdown>{endpoint.description}</Markdown>
|
||||
<Parameters spec={spec} endpoint={endpoint} />
|
||||
</Flex>
|
||||
<Flex py={5} width="45%" flex="1">
|
||||
<JsonBox
|
||||
name={tagName}
|
||||
endpoint={endpoint}
|
||||
spec={spec}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</EndpointContainer>
|
||||
))}
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
export default DocsReader
|
||||
@@ -1,65 +0,0 @@
|
||||
import React from "react"
|
||||
import { Flex, Box, Text } from "rebass"
|
||||
|
||||
import RoutesOverview from "./routes-overview"
|
||||
import ParamSection from "./param-section"
|
||||
import JsonBox from "./json-box"
|
||||
|
||||
const EndpointOverview = ({ title, description, routes, spec }) => {
|
||||
let schema = {}
|
||||
let attrs = []
|
||||
let resourceId
|
||||
|
||||
if (spec) {
|
||||
const tag = spec.tags.find(t => t.name === title)
|
||||
|
||||
if (tag && tag["x-resourceId"]) {
|
||||
resourceId = tag["x-resourceId"]
|
||||
schema = spec.components.schemas[tag["x-resourceId"]]
|
||||
|
||||
for (const [name, details] of Object.entries(schema.properties)) {
|
||||
if (!attrs.find(a => a.name === name)) {
|
||||
attrs.push({
|
||||
name,
|
||||
...details,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex flexDirection="row" pb={4}>
|
||||
<Flex
|
||||
flexDirection="column"
|
||||
width="55%"
|
||||
pr={5}
|
||||
sx={{ lineHeight: "26px" }}
|
||||
>
|
||||
<Text mb={3} fontSize={4}>
|
||||
{title}
|
||||
</Text>
|
||||
<Text mb={4}>{description || schema.description}</Text>
|
||||
{attrs.length > 0 && (
|
||||
<Box
|
||||
sx={{
|
||||
borderBottom: "hairline",
|
||||
}}
|
||||
my="2"
|
||||
>
|
||||
<Text my={2}>Attributes</Text>
|
||||
{attrs.map(p => (
|
||||
<ParamSection param={p} spec={spec} />
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
</Flex>
|
||||
<Flex width={"45%"} flexDirection="column">
|
||||
{routes && <RoutesOverview content={routes} />}
|
||||
{resourceId && <JsonBox text={"OBJECT"} resourceId={resourceId} />}
|
||||
</Flex>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
export default EndpointOverview
|
||||
@@ -1,102 +0,0 @@
|
||||
import React from "react"
|
||||
import { Flex, Box, Text } from "rebass"
|
||||
import styled from "@emotion/styled"
|
||||
import Highlight from "react-highlight.js"
|
||||
|
||||
import "highlight.js/styles/a11y-light.css"
|
||||
|
||||
import fixtures from "../../../docs/api/fixtures.json"
|
||||
|
||||
import { deref } from "../utils/deref"
|
||||
|
||||
export const ResponseContainer = styled(Flex)`
|
||||
border: 1px solid #e3e8ee;
|
||||
border-radius: 5px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
width: 100%;
|
||||
max-height: calc(90vh - 20px);
|
||||
overflow-y: scroll;
|
||||
align-self: flex-start;
|
||||
font-size: 1;
|
||||
position: sticky;
|
||||
top: 20px;
|
||||
|
||||
code {
|
||||
background: #f7fafc !important;
|
||||
}
|
||||
`
|
||||
|
||||
const JsonBox = ({ text, resourceId, endpoint, spec }) => {
|
||||
let json = {}
|
||||
|
||||
const toSet = {}
|
||||
if (endpoint) {
|
||||
const props =
|
||||
endpoint?.responses?.["200"]?.content?.["application/json"]?.schema
|
||||
?.properties
|
||||
|
||||
if (props) {
|
||||
for (const [name, details] of Object.entries(props)) {
|
||||
let cleanDets = details
|
||||
if (details.$ref) {
|
||||
const [, ...path] = details.$ref.split("/")
|
||||
cleanDets = deref(path, spec)
|
||||
}
|
||||
|
||||
if (
|
||||
cleanDets["x-resourceId"] &&
|
||||
cleanDets["x-resourceId"] in fixtures.resources
|
||||
) {
|
||||
toSet[name] = fixtures.resources[cleanDets["x-resourceId"]]
|
||||
} else if (cleanDets.type === "array") {
|
||||
toSet[name] = cleanDets
|
||||
if (cleanDets.items.$ref) {
|
||||
const [, ...path] = cleanDets.items.$ref.split("/")
|
||||
let cleanObj = deref(path, spec)
|
||||
if (
|
||||
cleanObj["x-resourceId"] &&
|
||||
cleanObj["x-resourceId"] in fixtures.resources
|
||||
) {
|
||||
cleanObj = fixtures.resources[cleanObj["x-resourceId"]]
|
||||
}
|
||||
toSet[name] = [cleanObj]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (resourceId) {
|
||||
json = fixtures.resources[resourceId]
|
||||
} else {
|
||||
json = toSet
|
||||
}
|
||||
|
||||
return (
|
||||
<ResponseContainer flexDirection="column" as="pre">
|
||||
<Text
|
||||
fontSize={0}
|
||||
fontFamily="body"
|
||||
py={2}
|
||||
px={3}
|
||||
color="#4f566b"
|
||||
backgroundColor="#e3e8ee"
|
||||
>
|
||||
{text || "RESPONSE"}
|
||||
</Text>
|
||||
<Box
|
||||
w={1}
|
||||
flex="1"
|
||||
sx={{ overflowY: "scroll" }}
|
||||
backgroundColor="#f7fafc"
|
||||
>
|
||||
<Highlight language="json">
|
||||
{JSON.stringify(json, undefined, 2)}
|
||||
</Highlight>
|
||||
</Box>
|
||||
</ResponseContainer>
|
||||
)
|
||||
}
|
||||
|
||||
export default JsonBox
|
||||
@@ -1,24 +0,0 @@
|
||||
import React from "react"
|
||||
import { Flex, Box } from "rebass"
|
||||
|
||||
const Layout = ({ children }) => {
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
overflowY: "scroll",
|
||||
}}
|
||||
fontFamily={"body"}
|
||||
flexDirection="column"
|
||||
flexGrow="1"
|
||||
>
|
||||
<Box>{children}</Box>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
export default Layout
|
||||
@@ -1,161 +0,0 @@
|
||||
import React from "react"
|
||||
import styled from "@emotion/styled"
|
||||
import { Flex, Box, Text } from "rebass"
|
||||
import Markdown from "react-markdown"
|
||||
import Collapsible from "react-collapsible"
|
||||
|
||||
import { deref } from "../utils/deref"
|
||||
|
||||
const ExpandContainer = styled.div`
|
||||
.child-attrs {
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
box-sizing: border-box;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
width: max-content;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #e3e8ee;
|
||||
color: #afafaf;
|
||||
|
||||
&:hover {
|
||||
color: #212121;
|
||||
}
|
||||
}
|
||||
.child-attrs.is-open {
|
||||
width: 100%;
|
||||
border-bottom: none;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
`
|
||||
|
||||
const Expand = ({ schema, spec }) => {
|
||||
if (schema.$ref) {
|
||||
const [, ...path] = schema.$ref.split("/")
|
||||
schema = deref(path, spec)
|
||||
}
|
||||
|
||||
const properties = schema.properties
|
||||
|
||||
let aggregated = []
|
||||
for (const [name, details] of Object.entries(properties)) {
|
||||
let cleanDets = details
|
||||
|
||||
if (name === "$ref") {
|
||||
const [, ...path] = details.split("/")
|
||||
cleanDets = deref(path, spec)
|
||||
}
|
||||
|
||||
if (!aggregated.find(a => a.name === name)) {
|
||||
aggregated.push({
|
||||
name,
|
||||
...cleanDets,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<ExpandContainer>
|
||||
<Collapsible
|
||||
transitionTime={50}
|
||||
triggerClassName={"child-attrs"}
|
||||
triggerOpenedClassName={"child-attrs"}
|
||||
triggerTagName="div"
|
||||
trigger={"Show nested attributes"}
|
||||
triggerWhenOpen={"Hide"}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
padding: "10px",
|
||||
borderRadius: "5px",
|
||||
borderTopLeftRadius: 0,
|
||||
borderTopRightRadius: 0,
|
||||
border: "hairline",
|
||||
}}
|
||||
mb="2"
|
||||
>
|
||||
<Text my={2}>{schema.title}</Text>
|
||||
{aggregated.map(param => {
|
||||
let type = param.type
|
||||
if (!type && param.schema) {
|
||||
type = param.schema.type
|
||||
}
|
||||
return (
|
||||
<Box
|
||||
py={2}
|
||||
sx={{
|
||||
borderTop: "hairline",
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
fontSize="1"
|
||||
alignItems="baseline"
|
||||
pb={1}
|
||||
fontFamily="monospace"
|
||||
>
|
||||
<Box mr={2} fontSize={"12px"}>
|
||||
{param.name}
|
||||
</Box>
|
||||
<Text color={"gray"} fontSize={"10px"}>
|
||||
{type}
|
||||
</Text>
|
||||
{param.required && (
|
||||
<Text ml={1} fontSize={"10px"} variant="labels.required">
|
||||
required
|
||||
</Text>
|
||||
)}
|
||||
</Flex>
|
||||
<Text fontSize={0}>
|
||||
<Markdown>{param.description}</Markdown>
|
||||
</Text>
|
||||
</Box>
|
||||
)
|
||||
})}
|
||||
</Box>
|
||||
</Collapsible>
|
||||
</ExpandContainer>
|
||||
)
|
||||
}
|
||||
|
||||
const ParamSection = ({ routeParam, param, spec }) => {
|
||||
let type = param.type
|
||||
if (!type && param.schema) {
|
||||
type = param.schema.type
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
py={2}
|
||||
sx={{
|
||||
borderTop: "hairline",
|
||||
}}
|
||||
>
|
||||
<Flex fontSize="1" alignItems="baseline" pb={1} fontFamily="monospace">
|
||||
<Box mr={2} fontSize={"12px"}>
|
||||
{param.name}
|
||||
</Box>
|
||||
<Text color={"gray"} fontSize={"10px"}>
|
||||
{type}
|
||||
</Text>
|
||||
{(param.required || routeParam) && (
|
||||
<Text ml={1} fontSize={"10px"} variant="labels.required">
|
||||
required
|
||||
</Text>
|
||||
)}
|
||||
</Flex>
|
||||
<Text fontSize={0}>
|
||||
<Markdown>{param.description}</Markdown>
|
||||
</Text>
|
||||
{param.anyOf &&
|
||||
param.anyOf.map(schema => <Expand schema={schema} spec={spec} />)}
|
||||
{param.oneOf &&
|
||||
param.oneOf.map(schema => <Expand schema={schema} spec={spec} />)}
|
||||
{param.type === "array" && param.items?.properties && (
|
||||
<Expand schema={param.items} spec={spec} />
|
||||
)}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default ParamSection
|
||||
@@ -1,49 +0,0 @@
|
||||
import React from "react"
|
||||
import { Box, Text } from "rebass"
|
||||
|
||||
import ParamSection from "./param-section"
|
||||
|
||||
import { deref } from "../utils/deref"
|
||||
|
||||
const Parameters = ({ endpoint, spec }) => {
|
||||
const aggregated = endpoint.parameters || []
|
||||
|
||||
const reqBody = endpoint.requestBody || {}
|
||||
const props = reqBody.content?.["application/json"]?.schema?.properties
|
||||
if (props) {
|
||||
for (const [name, details] of Object.entries(props)) {
|
||||
let cleanDets = details
|
||||
if (name === "$ref") {
|
||||
const [, ...path] = details.split("/")
|
||||
cleanDets = deref(path, spec)
|
||||
}
|
||||
|
||||
if (!aggregated.find(a => a.name === name)) {
|
||||
aggregated.push({
|
||||
name,
|
||||
...cleanDets,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!aggregated.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
borderBottom: "hairline",
|
||||
}}
|
||||
my="2"
|
||||
>
|
||||
<Text my={2}>Parameters</Text>
|
||||
{aggregated.map(p => (
|
||||
<ParamSection param={p} spec={spec} />
|
||||
))}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default Parameters
|
||||
@@ -1,19 +0,0 @@
|
||||
import React from "react"
|
||||
import { Flex, Box, Text } from "rebass"
|
||||
|
||||
const RouteSection = ({ basePath, path, method }) => {
|
||||
path = path.replace(/{(.*?)}/g, ":$1")
|
||||
|
||||
return (
|
||||
<Box py={2}>
|
||||
<Flex fontFamily="monospace">
|
||||
<Text mr={2} variant={`labels.${method}`}>
|
||||
{method}
|
||||
</Text>
|
||||
<Text>{`${basePath}${path === "/" ? "" : path}`}</Text>
|
||||
</Flex>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
export default RouteSection
|
||||
@@ -1,64 +0,0 @@
|
||||
import React from "react"
|
||||
import styled from "@emotion/styled"
|
||||
import { Flex, Box, Text } from "rebass"
|
||||
|
||||
const StyledRoutesOverview = styled(Flex)`
|
||||
border: 1px solid #e3e8ee;
|
||||
border-radius: 5px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
width: 100%;
|
||||
max-height: calc(90vh - 20px);
|
||||
overflow-y: scroll;
|
||||
align-self: flex-start;
|
||||
font-size: 1;
|
||||
top: 20px;
|
||||
bottom: 20px;
|
||||
`
|
||||
|
||||
const RoutesOverview = ({ content }) => {
|
||||
if (!content) return null
|
||||
|
||||
return (
|
||||
<StyledRoutesOverview mb={3} flexDirection="column">
|
||||
<Text
|
||||
fontSize={0}
|
||||
fontFamily="body"
|
||||
py={2}
|
||||
px={3}
|
||||
color="#4f566b"
|
||||
backgroundColor="#e3e8ee"
|
||||
>
|
||||
ENDPOINTS
|
||||
</Text>
|
||||
<Box
|
||||
w={1}
|
||||
px={4}
|
||||
py={2}
|
||||
flex="1"
|
||||
sx={{ overflowY: "scroll" }}
|
||||
backgroundColor="#f7fafc"
|
||||
>
|
||||
<Flex fontSize={1} flexDirection="column">
|
||||
{content.map(route => (
|
||||
<Flex mb={2} width="100%">
|
||||
<Text
|
||||
width="55px"
|
||||
mr={2}
|
||||
variant={`labels.${route.method}`}
|
||||
textAlign="right"
|
||||
>
|
||||
{route.method}
|
||||
</Text>
|
||||
<Text color="#4f566b">
|
||||
{route.path?.replace(/{(.*?)}/g, ":$1")}
|
||||
</Text>
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
</Box>
|
||||
</StyledRoutesOverview>
|
||||
)
|
||||
}
|
||||
|
||||
export default RoutesOverview
|
||||
@@ -1,107 +0,0 @@
|
||||
import React from "react"
|
||||
import { Flex } from "rebass"
|
||||
import { Label, Select as RebassSelect } from "@rebass/forms"
|
||||
import styled from "@emotion/styled"
|
||||
|
||||
import Typography from "./typography"
|
||||
|
||||
const StyledSelect = styled(RebassSelect)`
|
||||
${Typography.Base}
|
||||
padding-right: 28px;
|
||||
|
||||
${props =>
|
||||
props.isCurrencyInput &&
|
||||
`
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
|
||||
&:hover {
|
||||
box-shadow: none;
|
||||
}
|
||||
`}
|
||||
`
|
||||
|
||||
const StyledLabel = styled.div`
|
||||
${Typography.Base}
|
||||
${props =>
|
||||
props.inline
|
||||
? `
|
||||
text-align: right;
|
||||
padding-right: 15px;
|
||||
`
|
||||
: `
|
||||
padding-bottom: 10px;
|
||||
`}
|
||||
|
||||
${props =>
|
||||
props.required &&
|
||||
`
|
||||
&:after {
|
||||
color: rgba(255, 0, 0, 0.5);
|
||||
content: " *";
|
||||
}
|
||||
`}
|
||||
`
|
||||
|
||||
const Select = React.forwardRef(
|
||||
(
|
||||
{
|
||||
name = "",
|
||||
label = "",
|
||||
defaultValue = "",
|
||||
options = [],
|
||||
placeholder = "",
|
||||
value,
|
||||
onChange,
|
||||
inline,
|
||||
required,
|
||||
selectHeight,
|
||||
isCurrencyInput,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
return (
|
||||
<Flex
|
||||
alignItems={inline && "center"}
|
||||
flexDirection={inline ? "row" : "column"}
|
||||
{...props}
|
||||
>
|
||||
{label && (
|
||||
<Label
|
||||
flex={"30% 0 0"}
|
||||
maxWidth={"200px"}
|
||||
htmlFor={name}
|
||||
display={props.start ? "flex" : inline && "inline !important"}
|
||||
>
|
||||
<StyledLabel required={required} inline={inline}>
|
||||
{label}
|
||||
</StyledLabel>
|
||||
</Label>
|
||||
)}
|
||||
<StyledSelect
|
||||
isCurrencyInput={isCurrencyInput}
|
||||
flex="50% 0 0"
|
||||
variant="buttons.primary"
|
||||
name={name}
|
||||
height={selectHeight || "inherit"}
|
||||
minWidth={"unset"}
|
||||
width={"unset"}
|
||||
ref={ref}
|
||||
value={value}
|
||||
defaultValue={defaultValue}
|
||||
onChange={onChange}
|
||||
>
|
||||
{placeholder && <option>{placeholder}</option>}
|
||||
{options.map((option, index) => (
|
||||
<option key={index} value={option.value}>
|
||||
{option.label || option.value}
|
||||
</option>
|
||||
))}
|
||||
</StyledSelect>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
export default Select
|
||||
@@ -1,130 +0,0 @@
|
||||
import React, { useState, useEffect } from "react"
|
||||
import { navigate } from "gatsby"
|
||||
import { Flex, Box, Text } from "rebass"
|
||||
import { AnchorLink } from "gatsby-plugin-anchor-links"
|
||||
import styled from "@emotion/styled"
|
||||
import Collapsible from "react-collapsible"
|
||||
|
||||
import Select from "./select"
|
||||
|
||||
const convertToKebabCase = string => {
|
||||
return string.replace(/\s+/g, "-").toLowerCase()
|
||||
}
|
||||
|
||||
const StyledNavItem = styled(Flex)`
|
||||
padding-left: 16px;
|
||||
padding-right: 10px;
|
||||
align-items: center;
|
||||
border-radius: 5pt;
|
||||
cursor: pointer;
|
||||
margin-bottom: 5px;
|
||||
height: 25px;
|
||||
|
||||
&:hover {
|
||||
background-color: #e0e0e059;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledAnchorLink = styled(AnchorLink)`
|
||||
display: flex;
|
||||
margin-left: 10px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
align-items: center;
|
||||
border-radius: 5pt;
|
||||
cursor: pointer;
|
||||
margin-bottom: 5px;
|
||||
text-decoration: none;
|
||||
align-items: center;
|
||||
color: black;
|
||||
height: 25px;
|
||||
[fill*="red"] {
|
||||
fill: #454545;
|
||||
}
|
||||
&:hover {
|
||||
${props =>
|
||||
!props.active &&
|
||||
`
|
||||
background-color: #e0e0e059;
|
||||
`}
|
||||
}
|
||||
&.active {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
`
|
||||
|
||||
const SideBarContainer = styled(Flex)`
|
||||
position: sticky;
|
||||
top: 0;
|
||||
height: 100vh;
|
||||
overflow-y: scroll;
|
||||
background-color: #f0f0f0;
|
||||
min-width: 250px;
|
||||
flex-direction: column;
|
||||
`
|
||||
|
||||
const SideBar = ({ tags }) => {
|
||||
const [api, setApi] = useState("store")
|
||||
|
||||
useEffect(() => {
|
||||
const pathname = window.location.pathname
|
||||
const matches = pathname.match(/api\/(store|admin)/)
|
||||
if (pathname.length > 1) {
|
||||
setApi(matches[1])
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<SideBarContainer>
|
||||
<Flex
|
||||
width="100%"
|
||||
alignContent="center"
|
||||
justifyContent={"center"}
|
||||
sx={{ borderBottom: "hairline" }}
|
||||
px={4}
|
||||
py={3}
|
||||
mb={3}
|
||||
flexDirection="column"
|
||||
>
|
||||
<Flex width={"100%"} alignContent="center">
|
||||
<Text
|
||||
fontFamily="Medusa"
|
||||
fontSize="26px"
|
||||
color="#454b54"
|
||||
fontWeight={300}
|
||||
>
|
||||
medusa
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex pt={3} justifyContent="space-between">
|
||||
<Select
|
||||
value={api}
|
||||
onChange={e => navigate(`/api/${e.target.value}`)}
|
||||
options={[
|
||||
{ value: "admin", label: "Admin" },
|
||||
{ value: "store", label: "Storefront" },
|
||||
]}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
{Object.entries(tags).map(([tag, details]) => {
|
||||
return (
|
||||
<Box pt={1} px={3}>
|
||||
<Collapsible
|
||||
transitionTime={50}
|
||||
trigger={<StyledNavItem fontSize={1}>{tag}</StyledNavItem>}
|
||||
>
|
||||
{details.map(e => (
|
||||
<StyledAnchorLink to={`#${convertToKebabCase(e.summary)}`}>
|
||||
<Text fontSize={0}>{e.summary}</Text>
|
||||
</StyledAnchorLink>
|
||||
))}
|
||||
</Collapsible>
|
||||
</Box>
|
||||
)
|
||||
})}
|
||||
</SideBarContainer>
|
||||
)
|
||||
}
|
||||
|
||||
export default SideBar
|
||||
@@ -1,54 +0,0 @@
|
||||
import { css } from "@emotion/core"
|
||||
|
||||
const Largest = props => css`
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
|
||||
Helvetica Neue, Ubuntu, sans-serif;
|
||||
font-size: 22px;
|
||||
font-weight: 300;
|
||||
line-height: 1.5;
|
||||
letter-spacing: normal;
|
||||
`
|
||||
|
||||
const Large = props => css`
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
|
||||
Helvetica Neue, Ubuntu, sans-serif;
|
||||
font-size: 18px;
|
||||
font-weight: 300;
|
||||
line-height: 1.22;
|
||||
letter-spacing: -0.5px;
|
||||
`
|
||||
|
||||
const Medium = props => css`
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
|
||||
Helvetica Neue, Ubuntu, sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: 300;
|
||||
line-height: 1.22;
|
||||
letter-spacing: normal;
|
||||
`
|
||||
|
||||
const Base = props => css`
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
|
||||
Helvetica Neue, Ubuntu, sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: 300;
|
||||
line-height: 1.22;
|
||||
letter-spacing: -0.25px;
|
||||
`
|
||||
|
||||
const Small = props => css`
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
|
||||
Helvetica Neue, Ubuntu, sans-serif;
|
||||
font-size: 12px;
|
||||
font-weight: 300;
|
||||
line-height: 12px;
|
||||
letter-spacing: 0px;
|
||||
`
|
||||
|
||||
export default {
|
||||
Largest,
|
||||
Large,
|
||||
Medium,
|
||||
Base,
|
||||
Small,
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
const useSpec = raw => {
|
||||
let tags = {}
|
||||
|
||||
for (const [path, methods] of Object.entries(raw.paths)) {
|
||||
for (const [method, specification] of Object.entries(methods)) {
|
||||
for (const t of specification.tags) {
|
||||
if (t in tags) {
|
||||
tags = {
|
||||
...tags,
|
||||
[t]: [
|
||||
...tags[t],
|
||||
{
|
||||
method: method.toUpperCase(),
|
||||
path,
|
||||
...specification,
|
||||
},
|
||||
],
|
||||
}
|
||||
} else {
|
||||
tags = {
|
||||
...tags,
|
||||
[t]: [
|
||||
{
|
||||
method: method.toUpperCase(),
|
||||
path,
|
||||
...specification,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { tags, spec: raw }
|
||||
}
|
||||
|
||||
export default useSpec
|
||||
@@ -1,27 +0,0 @@
|
||||
import React from "react"
|
||||
import { Flex } from "rebass"
|
||||
import { Helmet } from "react-helmet"
|
||||
|
||||
import rawSpec from "../../../../docs/api/admin-spec3.json"
|
||||
|
||||
import Layout from "../../components/layout"
|
||||
import SideBar from "../../components/sidebar"
|
||||
import DocsReader from "../../components/docs-reader"
|
||||
|
||||
import useSpec from "../../hooks/use-spec"
|
||||
|
||||
export default function Home() {
|
||||
const { tags, spec } = useSpec(rawSpec)
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<Helmet>
|
||||
<title>Admin API Docs | Medusa Commerce</title>
|
||||
</Helmet>
|
||||
<Flex>
|
||||
<SideBar tags={tags} />
|
||||
<DocsReader tags={tags} spec={spec} />
|
||||
</Flex>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import React from "react"
|
||||
import { Flex } from "rebass"
|
||||
import { Helmet } from "react-helmet"
|
||||
|
||||
import rawSpec from "../../../../docs/api/store-spec3.json"
|
||||
|
||||
import Layout from "../../components/layout"
|
||||
import SideBar from "../../components/sidebar"
|
||||
import DocsReader from "../../components/docs-reader"
|
||||
|
||||
import useSpec from "../../hooks/use-spec"
|
||||
|
||||
export default function Home() {
|
||||
const { tags, spec } = useSpec(rawSpec)
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<Helmet>
|
||||
<title>Storefront API Docs | Medusa Commerce</title>
|
||||
</Helmet>
|
||||
<Flex>
|
||||
<SideBar tags={tags} />
|
||||
<DocsReader tags={tags} spec={spec} />
|
||||
</Flex>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import React from "react"
|
||||
import { Flex } from "rebass"
|
||||
import { Helmet } from "react-helmet"
|
||||
|
||||
import rawSpec from "../../../docs/api/admin-spec3.json"
|
||||
|
||||
import Layout from "../components/layout"
|
||||
import SideBar from "../components/sidebar"
|
||||
import DocsReader from "../components/docs-reader"
|
||||
|
||||
import useSpec from "../hooks/use-spec"
|
||||
|
||||
export default function Home() {
|
||||
const { tags, spec } = useSpec(rawSpec)
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<Helmet>
|
||||
<title>API Docs | Medusa Commerce</title>
|
||||
</Helmet>
|
||||
<Flex>
|
||||
<SideBar tags={tags} />
|
||||
<DocsReader tags={tags} spec={spec} />
|
||||
</Flex>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export default ["40em", "52em", "64em"]
|
||||
@@ -1 +0,0 @@
|
||||
export default [0, 4, 8, 16, 32, 64]
|
||||
@@ -1,9 +0,0 @@
|
||||
export const deref = (path, spec) => {
|
||||
let cleanDets = spec
|
||||
for (const part of path) {
|
||||
if (part === "#") continue
|
||||
cleanDets = cleanDets[part]
|
||||
}
|
||||
|
||||
return cleanDets
|
||||
}
|
||||
Reference in New Issue
Block a user