docs: oas (#197)

Adds OpenAPI specification of Storefront and Admin APIs.
Updates docs project.
This commit is contained in:
Sebastian Rindom authored and GitHub committed 2021-03-10 11:51:54 +01:00
1 parent 975de99ee7
commit 8edb32c742
240 files changed
+23592 -419

No files matched your search

+6
View File
@@ -21,5 +21,11 @@ module.exports = {
path: `${__dirname}/../docs/api/store`,
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
path: `${__dirname}/../docs/api`,
},
},
],
}
+1
View File
@@ -16,6 +16,7 @@
"dependencies": {
"@emotion/react": "^11.1.2",
"@emotion/styled": "^11.0.0",
"@rebass/forms": "^4.0.6",
"emotion-theming": "^10.0.27",
"gatsby": "^2.24.66",
"gatsby-plugin-anchor-links": "^1.1.1",
+88
View File
@@ -0,0 +1,88 @@
import React from "react"
import { Flex, Box, 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 endpoint={endpoint} />
</Flex>
<Flex py={5} width="45%" flex="1">
<JsonBox name={tagName} endpoint={endpoint} />
</Flex>
</Flex>
))}
</Flex>
</Flex>
</Flex>
</Flex>
</EndpointContainer>
))}
</Flex>
)
}
export default DocsReader
+65
View File
@@ -0,0 +1,65 @@
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} />
))}
</Box>
)}
</Flex>
<Flex width={"45%"} flexDirection="column">
{routes && <RoutesOverview content={routes} />}
{resourceId && <JsonBox text={"OBJECT"} resourceId={resourceId} />}
</Flex>
</Flex>
)
}
export default EndpointOverview
+88
View File
@@ -0,0 +1,88 @@
import React, { useState, useEffect } from "react"
import { graphql } from "gatsby"
import { Flex, Box, Text, Image } from "rebass"
import styled from "@emotion/styled"
import { AnchorLink } from "gatsby-plugin-anchor-links"
import Markdown from "react-markdown"
import Highlight from "react-highlight.js"
import "highlight.js/styles/a11y-light.css"
import fixtures from "../../../docs/api/fixtures.json"
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 }) => {
const [json, setJson] = useState({})
useEffect(() => {
const toSet = {}
if (endpoint) {
const props =
endpoint?.responses?.["200"]?.content?.["application/json"]?.schema
?.properties
if (props) {
for (const [name, details] of Object.entries(props)) {
if (
details["x-resourceId"] &&
details["x-resourceId"] in fixtures.resources
) {
toSet[name] = fixtures.resources[details["x-resourceId"]]
} else {
toSet[name] = details
}
}
}
}
if (resourceId) {
setJson(fixtures.resources[resourceId])
} else {
setJson(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
+145
View File
@@ -0,0 +1,145 @@
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"
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 }) => {
const properties = schema.properties
let aggregated = []
for (const [name, details] of Object.entries(properties)) {
if (!aggregated.find(a => a.name === name)) {
aggregated.push({
name,
...details,
})
}
}
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 }) => {
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} />)}
{param.oneOf && param.oneOf.map(schema => <Expand schema={schema} />)}
{param.type === "array" && param.items?.properties && (
<Expand schema={param.items} />
)}
</Box>
)
}
export default ParamSection
+41
View File
@@ -0,0 +1,41 @@
import React from "react"
import { Flex, Box, Text } from "rebass"
import ParamSection from "./param-section"
const Parameters = ({ endpoint }) => {
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)) {
if (!aggregated.find(a => a.name === name)) {
aggregated.push({
name,
...details,
})
}
}
}
if (!aggregated.length) {
return null
}
return (
<Box
sx={{
borderBottom: "hairline",
}}
my="2"
>
<Text my={2}>Parameters</Text>
{aggregated.map(p => (
<ParamSection param={p} />
))}
</Box>
)
}
export default Parameters
+19
View File
@@ -0,0 +1,19 @@
import React from "react"
import { Flex, Box, Text } from "rebass"
const RouteSection = ({ basePath, path, method }) => {
path = path.replaceAll(/{(.*?)}/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
+64
View File
@@ -0,0 +1,64 @@
import React, { useState, useEffect } from "react"
import styled from "@emotion/styled"
import { Flex, Box, Text, Image } 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.replaceAll(/{(.*?)}/g, ":$1")}
</Text>
</Flex>
))}
</Flex>
</Box>
</StyledRoutesOverview>
)
}
export default RoutesOverview
+107
View File
@@ -0,0 +1,107 @@
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
+136
View File
@@ -0,0 +1,136 @@
import React, { useState, useEffect } from "react"
import { Link, 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 StyledLink = styled(Link)`
color: #212121;
font-decoration: none;
`
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}
justifyContent="center"
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
+54
View File
@@ -0,0 +1,54 @@
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,
}
Binary file not shown.
+2 -2
View File
@@ -1,4 +1,4 @@
@font-face {
font-family: "Medusa";
src: url("./Velocista-Demo-FFP.ttf");
}
src: url("./Montserrat-Medium.ttf");
}
+47
View File
@@ -0,0 +1,47 @@
import React, { useState, useEffect } from "react"
const useSpec = raw => {
const [tags, setTags] = useState({})
const [spec, setSpec] = useState({})
useEffect(() => {
setSpec(raw)
for (const [path, methods] of Object.entries(raw.paths)) {
for (const [method, specification] of Object.entries(methods)) {
for (const t of specification.tags) {
setTags(ts => {
if (t in ts) {
return {
...ts,
[t]: [
...ts[t],
{
method: method.toUpperCase(),
path,
...specification,
},
],
}
} else {
return {
...ts,
[t]: [
{
method: method.toUpperCase(),
path,
...specification,
},
],
}
}
})
}
}
}
}, [])
return { tags, spec }
}
export default useSpec
+27
View File
@@ -0,0 +1,27 @@
import React, { useState, useEffect } 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>
)
}
+12 -388
View File
@@ -1,403 +1,27 @@
import React, { useState, useEffect } from "react"
import { graphql } from "gatsby"
import { Flex, Box, Text, Image } from "rebass"
import styled from "@emotion/styled"
import { AnchorLink } from "gatsby-plugin-anchor-links"
import Markdown from "react-markdown"
import Highlight from "react-highlight.js"
import React from "react"
import { Flex } from "rebass"
import { Helmet } from "react-helmet"
import Collapsible from "react-collapsible"
import rawSpec from "../../../../docs/api/store-spec3.json"
import Layout from "../../components/layout"
import "highlight.js/styles/a11y-light.css"
import SideBar from "../../components/sidebar"
import DocsReader from "../../components/docs-reader"
const EndpointContainer = styled(Flex)`
min-height: 90vh;
position: relative;
border-top: hairline;
import useSpec from "../../hooks/use-spec"
code {
background-color: #e3e8ee;
border-radius: 5px;
padding: 4px;
}
`
const StyledRoutesOverview = styled(Flex)`
border: 1px solid #e3e8ee;
border-radius: 5px;
margin-left: auto;
margin-right: auto;
width: 45%;
max-height: calc(90vh - 20px);
overflow-y: scroll;
align-self: flex-start;
font-size: 1;
top: 20px;
bottom: 20px;
`
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;
bottom: 20px;
code {
background: #f7fafc !important;
}
`
const StyledNavItem = styled(Flex)`
padding-left: 10px;
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;
padding-left: 10px;
padding-right: 10px;
margin-left: 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 convertToKebabCase = string => {
return string.replace(/\s+/g, "-").toLowerCase()
}
const RoutesOverview = ({ content }) => {
if (!content) return null
return (
<StyledRoutesOverview 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}</Text>
</Flex>
))}
</Flex>
</Box>
</StyledRoutesOverview>
)
}
const JsonBox = ({ content }) => {
const json = JSON.parse(content)
return (
<ResponseContainer flexDirection="column" as="pre">
<Text
fontSize={0}
fontFamily="body"
py={2}
px={3}
color="#4f566b"
backgroundColor="#e3e8ee"
>
RESPONSE
</Text>
<Box
w={1}
flex="1"
sx={{ overflowY: "scroll" }}
backgroundColor="#f7fafc"
>
<Highlight language="json">
{JSON.stringify(json, undefined, 2)}
</Highlight>
</Box>
</ResponseContainer>
)
}
const ParamSection = ({ routeParam, param }) => {
return (
<Box
py={2}
sx={{
borderTop: "hairline",
}}
>
<Flex fontSize="1" alignItems="center" pb={2} fontFamily="monospace">
<Box mr={2}>{param.name}</Box>
{(param.required || routeParam) && (
<Text variant="labels.required">required</Text>
)}
</Flex>
<Text fontSize="1">{param.description}</Text>
</Box>
)
}
const RouteSection = ({ basePath, path, method }) => {
return (
<Box py={2}>
<Flex fontFamily="monospace">
<Text mr={2} variant={`labels.${method}`}>
{method}
</Text>
<Text>{`${basePath}${path === "/" ? "" : path}`}</Text>
</Flex>
</Box>
)
}
const SideBar = ({ endpoints }) => {
return (
<SideBarContainer>
<Flex
width="100"
alignContent="center"
sx={{ borderBottom: "hairline" }}
py={3}
mb={3}
justifyContent="center"
>
<Text
fontFamily="Medusa"
fontSize="26px"
color="#454b54"
fontWeight={300}
>
MEDUSA
</Text>
</Flex>
{endpoints.edges.map(({ node }) => {
return (
<Box pt={3} px={3}>
<Collapsible
transitionTime={50}
trigger={<StyledNavItem fontSize={1}>{node.title}</StyledNavItem>}
>
{node.endpoints.map(e => (
<StyledAnchorLink to={`#${convertToKebabCase(e.title)}`}>
<Text fontSize={0}>{e.title}</Text>
</StyledAnchorLink>
))}
</Collapsible>
</Box>
)
})}
</SideBarContainer>
)
}
const EndpointOverview = ({ title, description, routes }) => {
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}</Text>
</Flex>
{routes && <RoutesOverview content={routes} />}
</Flex>
)
}
const StoreApi = ({ data }) => {
let { endpoints } = data
export default function Home() {
const { tags, spec } = useSpec(rawSpec)
return (
<Layout>
<Helmet>
<title>Store API | Medusa Commerce</title>
<title>Storefront API Docs | Medusa Commerce</title>
</Helmet>
<Flex>
<SideBar endpoints={endpoints} />
<Flex flexDirection="column" width="100%">
{endpoints.edges.map(({ node }) => (
<EndpointContainer id={convertToKebabCase(node.title)} p={4}>
<Flex flexDirection="row" width="100%">
<Flex py={5} flexDirection="column" p={4} width="100%">
<EndpointOverview
title={node.title}
description={node.description}
route={node.route}
routes={node.routes}
/>
<Flex
flexDirection="row"
sx={{
position: "relative",
borderTop: "hairline",
}}
>
<Flex width="55%" flexDirection="column" pr={5}>
{node.endpoints.map((endpoint, i) => (
<Flex
id={convertToKebabCase(endpoint.title)}
py={4}
flexDirection="row"
width="100%"
>
<Flex
flexDirection="column"
width="100%"
sx={{ lineHeight: "26px" }}
>
<Text mb={3} fontSize={3}>
{endpoint.title}
</Text>
<RouteSection
basePath={node.route}
method={endpoint.method}
path={endpoint.path}
/>
<Markdown>{endpoint.description}</Markdown>
{((endpoint.params && endpoint.params.length) ||
(endpoint.body && endpoint.body.length)) && (
<Box
sx={{
borderBottom: "hairline",
}}
my="2"
>
<Text my={2}>Parameters</Text>
{(endpoint.params || []).map(p => (
<ParamSection
routeParam={p.optional ? false : true}
param={p}
/>
))}
{(endpoint.body || []).map(p => (
<ParamSection param={p} />
))}
</Box>
)}
</Flex>
</Flex>
))}
</Flex>
<Flex py={5} width="45%" flex="1">
<JsonBox name={node.title} content={node.response} />
</Flex>
</Flex>
</Flex>
</Flex>
</EndpointContainer>
))}
</Flex>
<SideBar tags={tags} />
<DocsReader tags={tags} spec={spec} />
</Flex>
</Layout>
)
}
export default StoreApi
export const pageQuery = graphql`
{
endpoints: allEndpointsYaml(filter: { domain: { eq: "store" } }) {
edges {
node {
id
title
routes {
method
path
}
route
response
description
endpoints {
title
path
params {
description
name
optional
type
}
method
description
body {
type
required
name
description
}
}
}
}
}
}
`
+45 -2
View File
@@ -1,5 +1,48 @@
import React from "react"
import React, { useState, useEffect } from "react"
import { Flex } from "rebass"
import { Helmet } from "react-helmet"
import adminSpec from "../../../docs/api/admin-spec3.json"
import spec from "../../../docs/api/admin-spec3.json"
import Layout from "../components/layout"
import SideBar from "../components/sidebar"
import DocsReader from "../components/docs-reader"
export default function Home() {
return <div>Hello world!</div>
const tags = {}
for (const [path, methods] of Object.entries(spec.paths)) {
for (const [method, specification] of Object.entries(methods)) {
for (const t of specification.tags) {
if (t in tags) {
tags[t].push({
method: method.toUpperCase(),
path,
...specification,
})
} else {
tags[t] = [
{
method: method.toUpperCase(),
path,
...specification,
},
]
}
}
}
}
return (
<Layout>
<Helmet>
<title>API Docs | Medusa Commerce</title>
</Helmet>
<Flex>
<SideBar tags={tags} />
<DocsReader tags={tags} />
</Flex>
</Layout>
)
}
+7
View File
@@ -1560,6 +1560,13 @@
dependencies:
tslib "^2.0.0"
"@rebass/forms@^4.0.6":
version "4.0.6"
resolved "https://registry.yarnpkg.com/@rebass/forms/-/forms-4.0.6.tgz#19a3af64f855baa65dec65aecadda5cd95a09dd7"
integrity sha512-20dHAlb5V0Na2HxCr73Jjoc4hD1ElimmlBpQtclXMoXUsjJ7ORdCNL1S9R54MX5f7iU2VrLGHU42eL7+nONNPQ==
dependencies:
reflexbox "^4.0.6"
"@sindresorhus/is@^0.14.0":
version "0.14.0"
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"