docs: create docs workspace (#5174)
* docs: migrate ui docs to docs universe * created yarn workspace * added eslint and tsconfig configurations * fix eslint configurations * fixed eslint configurations * shared tailwind configurations * added shared ui package * added more shared components * migrating more components * made details components shared * move InlineCode component * moved InputText * moved Loading component * Moved Modal component * moved Select components * Moved Tooltip component * moved Search components * moved ColorMode provider * Moved Notification components and providers * used icons package * use UI colors in api-reference * moved Navbar component * used Navbar and Search in UI docs * added Feedback to UI docs * general enhancements * fix color mode * added copy colors file from ui-preset * added features and enhancements to UI docs * move Sidebar component and provider * general fixes and preparations for deployment * update docusaurus version * adjusted versions * fix output directory * remove rootDirectory property * fix yarn.lock * moved code component * added vale for all docs MD and MDX * fix tests * fix vale error * fix deployment errors * change ignore commands * add output directory * fix docs test * general fixes * content fixes * fix announcement script * added changeset * fix vale checks * added nofilter option * fix vale error
@@ -0,0 +1,11 @@
|
||||
NEXT_PUBLIC_SEGMENT_API_KEY=
|
||||
NEXT_PUBLIC_BASE_PATH=
|
||||
NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME=
|
||||
NEXT_PUBLIC_API_ALGOLIA_INDEX_NAME=
|
||||
NEXT_PUBLIC_ALGOLIA_API_KEY=
|
||||
NEXT_PUBLIC_ALGOLIA_APP_ID=
|
||||
NEXT_PUBLIC_ENV=
|
||||
NEXT_PUBLIC_BASE_URL=
|
||||
NEXT_PUBLIC_DOCS_URL=
|
||||
NEXT_PUBLIC_UI_URL=
|
||||
ALGOLIA_WRITE_API_KEY=
|
||||
@@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: [
|
||||
"docs/next"
|
||||
],
|
||||
settings: {
|
||||
next: {
|
||||
rootDir: ".",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
.env.test
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
# unnecessary specs
|
||||
specs/admin.oas.json
|
||||
specs/store.oas.json
|
||||
|
||||
# analyzer
|
||||
analyze
|
||||
@@ -0,0 +1 @@
|
||||
nmMode: hardlinks-local
|
||||
@@ -0,0 +1,7 @@
|
||||
# Medusa API Reference
|
||||
|
||||
The Medusa API Reference website is built with Next.js 13. You can learn more about contributing [here](https://docs.medusajs.com/contribution-guidelines).
|
||||
|
||||
## Note About OpenAPI Specs
|
||||
|
||||
The OpenAPI Specs under the directory `specs` are automatically generated by our OAS CLI tool. So, contributions should be made in the files under `packages/medusa/src/api` instead of directly making changes to the generated spec files.
|
||||
@@ -0,0 +1,31 @@
|
||||
import { CodeTabs } from "docs-ui"
|
||||
import Space from "@/components/Space"
|
||||
|
||||
### Just Getting Started?
|
||||
|
||||
Check out the [quickstart guide](https://docs.medusajs.com/create-medusa-app).
|
||||
|
||||
<Space bottom={8} />
|
||||
|
||||
### Client Libraries
|
||||
|
||||
<CodeTabs
|
||||
tabs={[
|
||||
{
|
||||
label: 'Medusa JS Client',
|
||||
value: 'js-client',
|
||||
code: {
|
||||
source: `npm install @medusajs/medusa-js`,
|
||||
lang: `bash`,
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Medusa React',
|
||||
value: 'medusa-react',
|
||||
code: {
|
||||
source: `npm install medusa-react @tanstack/react-query @medusajs/medusa`,
|
||||
lang: `bash`,
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
@@ -0,0 +1,54 @@
|
||||
import clsx from "clsx"
|
||||
import "../../../css/globals.css"
|
||||
import Navbar from "@/components/Navbar"
|
||||
import { Inter } from "next/font/google"
|
||||
import { Roboto_Mono } from "next/font/google"
|
||||
import Providers from "../../../providers"
|
||||
import { Sidebar } from "docs-ui"
|
||||
|
||||
export const metadata = {
|
||||
title: "Medusa API Reference",
|
||||
description: "Check out Medusa's API reference",
|
||||
}
|
||||
|
||||
const inter = Inter({
|
||||
subsets: ["latin"],
|
||||
variable: "--font-inter",
|
||||
weight: ["400", "500"],
|
||||
})
|
||||
|
||||
const robotoMono = Roboto_Mono({
|
||||
subsets: ["latin"],
|
||||
variable: "--font-roboto-mono",
|
||||
})
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html lang="en" className={clsx("h-full w-full")}>
|
||||
<body
|
||||
className={clsx(
|
||||
inter.variable,
|
||||
robotoMono.variable,
|
||||
"bg-docs-bg dark:bg-docs-bg-dark font-base text-medium h-full w-full",
|
||||
"text-medusa-fg-subtle dark:text-medusa-fg-subtle-dark"
|
||||
)}
|
||||
>
|
||||
<Providers>
|
||||
<div className="w-full">
|
||||
<Navbar />
|
||||
<div className="max-w-xxl mx-auto flex w-full px-1.5">
|
||||
<Sidebar />
|
||||
<main className="lg:w-ref-main relative mt-4 w-full flex-1 lg:mt-7">
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</Providers>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
|
After Width: | Height: | Size: 253 KiB |
@@ -0,0 +1,65 @@
|
||||
import AreaProvider from "@/providers/area"
|
||||
import AdminDescription from "../../_mdx/admin.mdx"
|
||||
import StoreDescription from "../../_mdx/store.mdx"
|
||||
import ClientLibraries from "../../_mdx/client-libraries.mdx"
|
||||
import Section from "@/components/Section"
|
||||
import Tags from "@/components/Tags"
|
||||
import type { Area } from "@/types/openapi"
|
||||
import DividedLayout from "@/layouts/Divided"
|
||||
import { capitalize } from "docs-ui"
|
||||
import PageTitleProvider from "../../../providers/page-title"
|
||||
|
||||
type ReferencePageProps = {
|
||||
params: {
|
||||
area: Area
|
||||
}
|
||||
}
|
||||
|
||||
const ReferencePage = async ({ params: { area } }: ReferencePageProps) => {
|
||||
return (
|
||||
<AreaProvider area={area}>
|
||||
<PageTitleProvider>
|
||||
<h1 className="!text-h2 block lg:hidden">
|
||||
Medusa {capitalize(area)} API Reference
|
||||
</h1>
|
||||
<DividedLayout
|
||||
mainContent={
|
||||
<Section>
|
||||
<h1 className="!text-h2 hidden lg:block">
|
||||
Medusa {capitalize(area)} API Reference
|
||||
</h1>
|
||||
{area.includes("admin") && <AdminDescription />}
|
||||
{area.includes("store") && <StoreDescription />}
|
||||
</Section>
|
||||
}
|
||||
codeContent={<ClientLibraries />}
|
||||
className="flex-col-reverse"
|
||||
/>
|
||||
<Tags />
|
||||
</PageTitleProvider>
|
||||
</AreaProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default ReferencePage
|
||||
|
||||
export function generateMetadata({ params: { area } }: ReferencePageProps) {
|
||||
return {
|
||||
title: `Medusa ${capitalize(area)} API Reference`,
|
||||
description: `REST API reference for the Medusa ${area} API. This reference includes code snippets and examples for Medusa JS Client and cURL.`,
|
||||
metadataBase: process.env.NEXT_PUBLIC_BASE_URL,
|
||||
}
|
||||
}
|
||||
|
||||
export const dynamicParams = false
|
||||
|
||||
export async function generateStaticParams() {
|
||||
return [
|
||||
{
|
||||
area: "admin",
|
||||
},
|
||||
{
|
||||
area: "store",
|
||||
},
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 253 KiB |
@@ -0,0 +1,152 @@
|
||||
import OpenAPIParser from "@readme/openapi-parser"
|
||||
import algoliasearch from "algoliasearch"
|
||||
import type { ExpandedDocument, Operation } from "../../../types/openapi"
|
||||
import path from "path"
|
||||
import getPathsOfTag from "../../../utils/get-paths-of-tag"
|
||||
import getSectionId from "../../../utils/get-section-id"
|
||||
import { NextResponse } from "next/server"
|
||||
import { JSDOM } from "jsdom"
|
||||
import getUrl from "../../../utils/get-url"
|
||||
import { capitalize } from "docs-ui"
|
||||
|
||||
export async function GET() {
|
||||
const algoliaClient = algoliasearch(
|
||||
process.env.NEXT_PUBLIC_ALGOLIA_APP_ID || "",
|
||||
process.env.ALGOLIA_WRITE_API_KEY || ""
|
||||
)
|
||||
const index = algoliaClient.initIndex(
|
||||
process.env.NEXT_PUBLIC_API_ALGOLIA_INDEX_NAME || ""
|
||||
)
|
||||
|
||||
// retrieve tags and their operations to index them
|
||||
const indices: Record<string, any>[] = []
|
||||
for (const area of ["store", "admin"]) {
|
||||
const defaultIndexData = {
|
||||
version: ["current"],
|
||||
lang: "en",
|
||||
_tags: ["api", area],
|
||||
}
|
||||
// find and parse static headers from pages
|
||||
const dom = await JSDOM.fromURL(getUrl(area))
|
||||
const headers = dom.window.document.querySelectorAll("h2")
|
||||
headers.forEach((header) => {
|
||||
if (!header.textContent) {
|
||||
return
|
||||
}
|
||||
|
||||
const objectID = getSectionId([header.textContent])
|
||||
const url = getUrl(area, objectID)
|
||||
indices.push({
|
||||
objectID: getObjectId(area, `${objectID}-mdx-section`),
|
||||
hierarchy: getHierarchy(area, [header.textContent]),
|
||||
type: `content`,
|
||||
content: header.textContent,
|
||||
url,
|
||||
url_without_variables: url,
|
||||
url_without_anchor: url,
|
||||
...defaultIndexData,
|
||||
})
|
||||
})
|
||||
|
||||
// find and index tag and operations
|
||||
const baseSpecs = (await OpenAPIParser.parse(
|
||||
path.join(process.cwd(), `specs/${area}/openapi.yaml`)
|
||||
)) as ExpandedDocument
|
||||
|
||||
await Promise.all(
|
||||
baseSpecs.tags?.map(async (tag) => {
|
||||
const tagName = getSectionId([tag.name])
|
||||
const url = getUrl(area, tagName)
|
||||
indices.push({
|
||||
objectID: getObjectId(area, tagName),
|
||||
hierarchy: getHierarchy(area, [tag.name]),
|
||||
type: "lvl1",
|
||||
content: null,
|
||||
url,
|
||||
url_without_variables: url,
|
||||
url_without_anchor: url,
|
||||
...defaultIndexData,
|
||||
})
|
||||
const paths = await getPathsOfTag(tagName, area)
|
||||
|
||||
Object.values(paths.paths).forEach((path) => {
|
||||
Object.values(path).forEach((op) => {
|
||||
const operation = op as Operation
|
||||
const operationName = getSectionId([
|
||||
tag.name,
|
||||
operation.operationId,
|
||||
])
|
||||
const url = getUrl(area, operationName)
|
||||
indices.push({
|
||||
objectID: getObjectId(area, operationName),
|
||||
hierarchy: getHierarchy(area, [tag.name, operation.summary]),
|
||||
type: "content",
|
||||
content: operation.summary,
|
||||
content_camel: operation.summary,
|
||||
url,
|
||||
url_without_variables: url,
|
||||
url_without_anchor: url,
|
||||
...defaultIndexData,
|
||||
})
|
||||
|
||||
// index its description
|
||||
const operationDescriptionId = getSectionId([
|
||||
tag.name,
|
||||
operation.operationId,
|
||||
operation.description.substring(
|
||||
0,
|
||||
Math.min(20, operation.description.length)
|
||||
),
|
||||
])
|
||||
|
||||
indices.push({
|
||||
objectID: getObjectId(area, operationDescriptionId),
|
||||
hierarchy: getHierarchy(area, [
|
||||
tag.name,
|
||||
operation.summary,
|
||||
operation.description,
|
||||
]),
|
||||
type: "content",
|
||||
content: operation.description,
|
||||
content_camel: operation.description,
|
||||
url,
|
||||
url_without_variables: url,
|
||||
url_without_anchor: url,
|
||||
...defaultIndexData,
|
||||
})
|
||||
})
|
||||
})
|
||||
}) || []
|
||||
)
|
||||
}
|
||||
|
||||
if (indices.length) {
|
||||
await index.saveObjects(indices, {
|
||||
autoGenerateObjectIDIfNotExist: true,
|
||||
})
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
message: "done",
|
||||
})
|
||||
}
|
||||
|
||||
function getObjectId(area: string, objectName: string): string {
|
||||
return `${area}_${objectName}`
|
||||
}
|
||||
|
||||
function getHierarchy(area: string, levels: string[]): Record<string, string> {
|
||||
const heirarchy: Record<string, string> = {
|
||||
lvl0: `${capitalize(area)} API Reference`,
|
||||
}
|
||||
|
||||
let counter = 1
|
||||
levels.forEach((level) => {
|
||||
heirarchy[`lvl${counter}`] = level
|
||||
counter++
|
||||
})
|
||||
|
||||
return heirarchy
|
||||
}
|
||||
|
||||
export const dynamic = "force-dynamic"
|
||||
@@ -0,0 +1,37 @@
|
||||
import { NextResponse } from "next/server"
|
||||
import path from "path"
|
||||
import OpenAPIParser from "@readme/openapi-parser"
|
||||
import getPathsOfTag from "@/utils/get-paths-of-tag"
|
||||
import type { ExpandedDocument } from "@/types/openapi"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const { searchParams } = new URL(request.url)
|
||||
const area = searchParams.get("area")
|
||||
const expand = searchParams.get("expand")
|
||||
if (area !== "admin" && area !== "store") {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
message: `area ${area} is not allowed`,
|
||||
},
|
||||
{
|
||||
status: 400,
|
||||
}
|
||||
)
|
||||
}
|
||||
const baseSpecs = (await OpenAPIParser.parse(
|
||||
path.join(process.cwd(), `specs/${area}/openapi.yaml`)
|
||||
)) as ExpandedDocument
|
||||
|
||||
if (expand) {
|
||||
const paths = await getPathsOfTag(expand, area)
|
||||
if (paths) {
|
||||
baseSpecs.expandedTags = {}
|
||||
baseSpecs.expandedTags[expand] = paths.paths
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json(baseSpecs, {
|
||||
status: 200,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { NextResponse } from "next/server"
|
||||
import path from "path"
|
||||
import getPathsOfTag from "@/utils/get-paths-of-tag"
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const { searchParams } = new URL(request.url)
|
||||
const tagName = searchParams.get("tagName") || ""
|
||||
const area = searchParams.get("area")
|
||||
|
||||
if (area !== "admin" && area !== "store") {
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
message: `area ${area} is not allowed`,
|
||||
},
|
||||
{
|
||||
status: 400,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// this is just to ensure that vercel picks up these files on build
|
||||
path.join(process.cwd(), "specs/admin/code_samples")
|
||||
path.join(process.cwd(), "specs/admin/components")
|
||||
path.join(process.cwd(), "specs/admin/paths")
|
||||
path.join(process.cwd(), "specs/store/code_samples")
|
||||
path.join(process.cwd(), "specs/store/components")
|
||||
path.join(process.cwd(), "specs/store/paths")
|
||||
|
||||
// get path files
|
||||
const paths = await getPathsOfTag(tagName, area)
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
paths: paths.paths,
|
||||
},
|
||||
{
|
||||
status: 200,
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { MetadataRoute } from "next"
|
||||
import OpenAPIParser from "@readme/openapi-parser"
|
||||
import path from "path"
|
||||
import getBaseUrl from "../../utils/get-base-url"
|
||||
import type { ExpandedDocument, Operation } from "../../types/openapi"
|
||||
import getUrl from "../../utils/get-url"
|
||||
import getSectionId from "../../utils/get-section-id"
|
||||
import getPathsOfTag from "../../utils/get-paths-of-tag"
|
||||
|
||||
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
||||
const baseUrl = getBaseUrl()
|
||||
|
||||
const results = [
|
||||
{
|
||||
url: `${baseUrl}/api/admin`,
|
||||
lastModified: new Date(),
|
||||
},
|
||||
{
|
||||
url: `${baseUrl}/api/store`,
|
||||
lastModified: new Date(),
|
||||
},
|
||||
]
|
||||
|
||||
for (const area of ["store", "admin"]) {
|
||||
const baseSpecs = (await OpenAPIParser.parse(
|
||||
path.join(process.cwd(), `specs/${area}/openapi.yaml`)
|
||||
)) as ExpandedDocument
|
||||
|
||||
await Promise.all(
|
||||
baseSpecs.tags?.map(async (tag) => {
|
||||
const tagName = getSectionId([tag.name])
|
||||
const url = getUrl(area, tagName)
|
||||
results.push({
|
||||
url,
|
||||
lastModified: new Date(),
|
||||
})
|
||||
|
||||
const paths = await getPathsOfTag(tagName, area)
|
||||
|
||||
Object.values(paths.paths).forEach((path) => {
|
||||
Object.values(path).forEach((op) => {
|
||||
const operation = op as Operation
|
||||
const operationName = getSectionId([
|
||||
tag.name,
|
||||
operation.operationId,
|
||||
])
|
||||
const url = getUrl(area, operationName)
|
||||
results.push({
|
||||
url,
|
||||
lastModified: new Date(),
|
||||
})
|
||||
})
|
||||
})
|
||||
}) || []
|
||||
)
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
After Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 101 KiB |
@@ -0,0 +1,4 @@
|
||||
User-Agent: *
|
||||
Allow: /
|
||||
|
||||
Sitemap: https://docs.medusajs.com/sitemap.xml
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<sitemap>
|
||||
<loc>https://docs.medusajs.com/assets/sitemap.xml</loc>
|
||||
</sitemap>
|
||||
<sitemap>
|
||||
<loc>https://docs.medusajs.com/sitemap-docs.xml</loc>
|
||||
</sitemap>
|
||||
</sitemapindex>
|
||||
@@ -0,0 +1,24 @@
|
||||
"use server"
|
||||
|
||||
import type { OpenAPIV3 } from "openapi-types"
|
||||
import Section from "../Section"
|
||||
import MDXContentServer from "../MDXContent/Server"
|
||||
|
||||
export type DescriptionProps = {
|
||||
specs: OpenAPIV3.Document
|
||||
}
|
||||
|
||||
const Description = ({ specs }: DescriptionProps) => {
|
||||
return (
|
||||
<Section>
|
||||
<MDXContentServer
|
||||
content={specs.info.description}
|
||||
scope={{
|
||||
specs,
|
||||
}}
|
||||
/>
|
||||
</Section>
|
||||
)
|
||||
}
|
||||
|
||||
export default Description
|
||||
@@ -0,0 +1,83 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { Label, TextArea, useAnalytics, useModal, ModalFooter } from "docs-ui"
|
||||
|
||||
const DetailedFeedback = () => {
|
||||
const [improvementFeedback, setImprovementFeedback] = useState("")
|
||||
const [positiveFeedback, setPositiveFeedback] = useState("")
|
||||
const [additionalFeedback, setAdditionalFeedback] = useState("")
|
||||
const { loaded, track } = useAnalytics()
|
||||
const { closeModal } = useModal()
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col gap-1 overflow-auto py-1.5 px-2 lg:min-h-[400px]">
|
||||
<div className="flex flex-col gap-1">
|
||||
<Label>What should be improved in this API reference?</Label>
|
||||
<TextArea
|
||||
rows={4}
|
||||
value={improvementFeedback}
|
||||
onChange={(e) => setImprovementFeedback(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Label>Is there a feature you like in this API reference?</Label>
|
||||
<TextArea
|
||||
rows={4}
|
||||
value={positiveFeedback}
|
||||
onChange={(e) => setPositiveFeedback(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<Label>Do you have any additional notes or feedback?</Label>
|
||||
<TextArea
|
||||
rows={4}
|
||||
value={additionalFeedback}
|
||||
onChange={(e) => setAdditionalFeedback(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ModalFooter
|
||||
actions={[
|
||||
{
|
||||
children: "Save",
|
||||
onClick: (e) => {
|
||||
if (
|
||||
!loaded ||
|
||||
(!improvementFeedback &&
|
||||
!positiveFeedback &&
|
||||
!additionalFeedback)
|
||||
) {
|
||||
return
|
||||
}
|
||||
const buttonElm = e.target as HTMLButtonElement
|
||||
buttonElm.classList.add("cursor-not-allowed")
|
||||
buttonElm.textContent = "Please wait"
|
||||
track(
|
||||
"api-ref-general-feedback",
|
||||
{
|
||||
feedbackData: {
|
||||
improvementFeedback,
|
||||
positiveFeedback,
|
||||
additionalFeedback,
|
||||
},
|
||||
},
|
||||
function () {
|
||||
buttonElm.textContent = "Thank you!"
|
||||
setTimeout(() => {
|
||||
closeModal()
|
||||
}, 1000)
|
||||
}
|
||||
)
|
||||
},
|
||||
variant: "primary",
|
||||
},
|
||||
]}
|
||||
className="mt-1"
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default DetailedFeedback
|
||||
@@ -0,0 +1,55 @@
|
||||
import DividedLayout from "@/layouts/Divided"
|
||||
import { Loading } from "docs-ui"
|
||||
|
||||
type DividedLoadingProps = {
|
||||
className?: string
|
||||
}
|
||||
|
||||
const DividedLoading = ({ className }: DividedLoadingProps) => {
|
||||
return (
|
||||
<DividedLayout
|
||||
mainContent={
|
||||
<>
|
||||
<Loading count={1} className="mb-2 !w-1/3" />
|
||||
<Loading count={1} />
|
||||
<div className="flex gap-1">
|
||||
<Loading count={1} className="!w-1/3" />
|
||||
<Loading count={1} className="!w-2/3" />
|
||||
</div>
|
||||
<div className="flex gap-1">
|
||||
<Loading count={1} className="!w-1/3" />
|
||||
<Loading count={1} className="!w-2/3" />
|
||||
</div>
|
||||
<Loading count={1} className="mt-2 !w-1/3" />
|
||||
<Loading count={1} />
|
||||
<div className="mt-2 flex gap-1">
|
||||
<Loading count={1} className="!w-1/3" />
|
||||
<Loading count={1} className="!w-2/3" />
|
||||
</div>
|
||||
<div className="flex gap-1">
|
||||
<Loading count={1} className="!w-1/3" />
|
||||
<Loading count={1} className="!w-2/3" />
|
||||
</div>
|
||||
<div className="flex gap-1">
|
||||
<Loading count={1} className="!w-1/3" />
|
||||
<Loading count={1} className="!w-2/3" />
|
||||
</div>
|
||||
<Loading count={5} barClassName="mt-1" />
|
||||
</>
|
||||
}
|
||||
codeContent={
|
||||
<>
|
||||
<Loading count={1} />
|
||||
<Loading count={1} className="my-2" />
|
||||
<Loading count={1} barClassName="h-[200px] !rounded-sm" />
|
||||
<Loading count={1} className="my-2" />
|
||||
<Loading count={1} barClassName="h-3 !rounded-sm" />
|
||||
<Loading count={1} barClassName="h-[230px] !rounded-sm" />
|
||||
</>
|
||||
}
|
||||
className={className}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default DividedLoading
|
||||
@@ -0,0 +1,68 @@
|
||||
"use client"
|
||||
|
||||
import { InView } from "react-intersection-observer"
|
||||
import { useSidebar } from "docs-ui"
|
||||
import checkElementInViewport from "../../../utils/check-element-in-viewport"
|
||||
import { useEffect } from "react"
|
||||
import getSectionId from "../../../utils/get-section-id"
|
||||
|
||||
type H2Props = {
|
||||
addToSidebar?: boolean
|
||||
} & React.HTMLAttributes<HTMLHeadingElement>
|
||||
|
||||
const H2 = ({ addToSidebar = true, children, ...props }: H2Props) => {
|
||||
const { activePath, setActivePath, addItems } = useSidebar()
|
||||
|
||||
const handleViewChange = (
|
||||
inView: boolean,
|
||||
entry: IntersectionObserverEntry
|
||||
) => {
|
||||
if (!addToSidebar) {
|
||||
return
|
||||
}
|
||||
const heading = entry.target
|
||||
if (
|
||||
(inView ||
|
||||
checkElementInViewport(heading.parentElement || heading, 40)) &&
|
||||
window.scrollY !== 0 &&
|
||||
activePath !== heading.id
|
||||
) {
|
||||
// can't use next router as it doesn't support
|
||||
// changing url without scrolling
|
||||
history.pushState({}, "", `#${heading.id}`)
|
||||
setActivePath(heading.id)
|
||||
}
|
||||
}
|
||||
const id = getSectionId([children as string])
|
||||
|
||||
useEffect(() => {
|
||||
if (id === (activePath || location.hash.replace("#", ""))) {
|
||||
const elm = document.getElementById(id)
|
||||
elm?.scrollIntoView()
|
||||
}
|
||||
|
||||
addItems([
|
||||
{
|
||||
path: `${id}`,
|
||||
title: children as string,
|
||||
loaded: true,
|
||||
},
|
||||
])
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<InView
|
||||
as="h2"
|
||||
threshold={0.4}
|
||||
skip={!addToSidebar}
|
||||
initialInView={false}
|
||||
{...props}
|
||||
onChange={handleViewChange}
|
||||
id={id}
|
||||
>
|
||||
{children}
|
||||
</InView>
|
||||
)
|
||||
}
|
||||
|
||||
export default H2
|
||||
@@ -0,0 +1,66 @@
|
||||
import type { MDXContentClientProps } from "@/components/MDXContent/Client"
|
||||
import type { MDXContentServerProps } from "@/components/MDXContent/Server"
|
||||
import type { SecuritySchemeObject } from "@/types/openapi"
|
||||
import getSecuritySchemaTypeName from "@/utils/get-security-schema-type-name"
|
||||
import clsx from "clsx"
|
||||
import { Loading } from "docs-ui"
|
||||
import dynamic from "next/dynamic"
|
||||
|
||||
const MDXContentClient = dynamic<MDXContentClientProps>(
|
||||
async () => import("../../../MDXContent/Client"),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
) as React.FC<MDXContentClientProps>
|
||||
|
||||
const MDXContentServer = dynamic<MDXContentServerProps>(
|
||||
async () => import("../../../MDXContent/Server"),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
) as React.FC<MDXContentServerProps>
|
||||
|
||||
export type SecurityDescriptionProps = {
|
||||
securitySchema: SecuritySchemeObject
|
||||
isServer?: boolean
|
||||
}
|
||||
|
||||
const SecurityDescription = ({
|
||||
securitySchema,
|
||||
isServer = true,
|
||||
}: SecurityDescriptionProps) => {
|
||||
return (
|
||||
<>
|
||||
<h2>{securitySchema["x-displayName"] as string}</h2>
|
||||
{isServer && <MDXContentServer content={securitySchema.description} />}
|
||||
{!isServer && <MDXContentClient content={securitySchema.description} />}
|
||||
<p>
|
||||
<strong>Security Scheme Type:</strong>{" "}
|
||||
{getSecuritySchemaTypeName(securitySchema)}
|
||||
</p>
|
||||
{(securitySchema.type === "http" || securitySchema.type === "apiKey") && (
|
||||
<p
|
||||
className={clsx(
|
||||
"bg-docs-bg-surface dark:bg-docs-bg-surface-dark",
|
||||
"p-1"
|
||||
)}
|
||||
>
|
||||
<strong>
|
||||
{securitySchema.type === "http"
|
||||
? "HTTP Authorization Scheme"
|
||||
: "Cookie parameter name"}
|
||||
:
|
||||
</strong>{" "}
|
||||
<code>
|
||||
{securitySchema.type === "http"
|
||||
? securitySchema.scheme
|
||||
: securitySchema.name}
|
||||
</code>
|
||||
</p>
|
||||
)}
|
||||
<hr />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default SecurityDescription
|
||||
@@ -0,0 +1,34 @@
|
||||
import dynamic from "next/dynamic"
|
||||
import type { OpenAPIV3 } from "openapi-types"
|
||||
import type { SecurityDescriptionProps } from "./Description"
|
||||
import { Fragment } from "react"
|
||||
|
||||
const SecurityDescription = dynamic<SecurityDescriptionProps>(
|
||||
async () => import("./Description")
|
||||
) as React.FC<SecurityDescriptionProps>
|
||||
|
||||
type SecurityProps = {
|
||||
specs?: OpenAPIV3.Document
|
||||
}
|
||||
|
||||
const Security = ({ specs }: SecurityProps) => {
|
||||
return (
|
||||
<div>
|
||||
{specs && (
|
||||
<>
|
||||
{Object.values(specs.components?.securitySchemes || {}).map(
|
||||
(securitySchema, index) => (
|
||||
<Fragment key={index}>
|
||||
{!("$ref" in securitySchema) && (
|
||||
<SecurityDescription securitySchema={securitySchema} />
|
||||
)}
|
||||
</Fragment>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Security
|
||||
@@ -0,0 +1,22 @@
|
||||
import type { MDXComponents } from "mdx/types"
|
||||
import Security from "./Security"
|
||||
import type { OpenAPIV3 } from "openapi-types"
|
||||
import H2 from "./H2"
|
||||
import { CodeMdx, Kbd, NextLink } from "docs-ui"
|
||||
|
||||
export type ScopeType = {
|
||||
specs?: OpenAPIV3.Document
|
||||
addToSidebar?: boolean
|
||||
}
|
||||
|
||||
const getCustomComponents = (scope?: ScopeType): MDXComponents => {
|
||||
return {
|
||||
Security: () => <Security specs={scope?.specs} />,
|
||||
code: CodeMdx,
|
||||
a: NextLink,
|
||||
h2: (props) => <H2 addToSidebar={scope?.addToSidebar} {...props} />,
|
||||
kbd: Kbd,
|
||||
}
|
||||
}
|
||||
|
||||
export default getCustomComponents
|
||||
@@ -0,0 +1,48 @@
|
||||
"use client"
|
||||
|
||||
import { useEffect, useState } from "react"
|
||||
import getCustomComponents from "../../MDXComponents"
|
||||
import type { ScopeType } from "../../MDXComponents"
|
||||
import { MDXRemote } from "next-mdx-remote"
|
||||
import type { MDXRemoteProps, MDXRemoteSerializeResult } from "next-mdx-remote"
|
||||
import { serialize } from "next-mdx-remote/serialize"
|
||||
|
||||
export type MDXContentClientProps = {
|
||||
content: any
|
||||
className?: string
|
||||
} & Partial<MDXRemoteProps>
|
||||
|
||||
const MDXContentClient = ({
|
||||
content,
|
||||
className,
|
||||
...props
|
||||
}: MDXContentClientProps) => {
|
||||
const [parsedContent, setParsedContent] = useState<MDXRemoteSerializeResult>()
|
||||
|
||||
useEffect(() => {
|
||||
void serialize(content, {
|
||||
mdxOptions: {
|
||||
// A workaround for an error in next-mdx-remote
|
||||
// more details in this issue:
|
||||
// https://github.com/hashicorp/next-mdx-remote/issues/350
|
||||
development: process.env.NEXT_PUBLIC_ENV === "development",
|
||||
},
|
||||
scope: props.scope,
|
||||
}).then((output) => {
|
||||
setParsedContent(output)
|
||||
})
|
||||
}, [content, props.scope])
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{parsedContent !== undefined && (
|
||||
<MDXRemote
|
||||
{...parsedContent}
|
||||
components={getCustomComponents((props.scope as ScopeType) || {})}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default MDXContentClient
|
||||
@@ -0,0 +1,30 @@
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
"use server"
|
||||
|
||||
import { MDXRemote } from "next-mdx-remote/rsc"
|
||||
import getCustomComponents from "../../MDXComponents"
|
||||
import type { ScopeType } from "../../MDXComponents"
|
||||
import type { MDXRemoteProps } from "next-mdx-remote"
|
||||
|
||||
export type MDXContentServerProps = {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
content: any
|
||||
} & Partial<MDXRemoteProps>
|
||||
|
||||
const MDXContentServer = ({ content, ...props }: MDXContentServerProps) => {
|
||||
return (
|
||||
<>
|
||||
{/* @ts-ignore promise error */}
|
||||
<MDXRemote
|
||||
source={content}
|
||||
components={getCustomComponents((props.scope as ScopeType) || {})}
|
||||
options={{
|
||||
scope: props.scope,
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default MDXContentServer
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Badge, capitalize } from "docs-ui"
|
||||
|
||||
export type MethodLabelProps = {
|
||||
method: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
const MethodLabel = ({ method, className }: MethodLabelProps) => {
|
||||
return (
|
||||
<Badge
|
||||
variant={method === "get" ? "green" : method === "post" ? "blue" : "red"}
|
||||
className={className}
|
||||
>
|
||||
{method === "delete" ? "Del" : capitalize(method)}
|
||||
</Badge>
|
||||
)
|
||||
}
|
||||
|
||||
export default MethodLabel
|
||||
@@ -0,0 +1,28 @@
|
||||
"use client"
|
||||
|
||||
import { Button, useModal, usePageLoading } from "docs-ui"
|
||||
import DetailedFeedback from "../../DetailedFeedback"
|
||||
|
||||
const FeedbackModal = () => {
|
||||
const { setModalProps } = useModal()
|
||||
const { isLoading } = usePageLoading()
|
||||
|
||||
const openModal = () => {
|
||||
if (isLoading) {
|
||||
return
|
||||
}
|
||||
setModalProps({
|
||||
title: "Send your Feedback",
|
||||
children: <DetailedFeedback />,
|
||||
contentClassName: "lg:!min-h-auto !p-0",
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Button onClick={openModal} variant="secondary">
|
||||
Feedback
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export default FeedbackModal
|
||||
@@ -0,0 +1,50 @@
|
||||
"use client"
|
||||
|
||||
import { Navbar as UiNavbar, usePageLoading } from "docs-ui"
|
||||
import getLinkWithBasePath from "../../utils/get-link-with-base-path"
|
||||
import { useSidebar } from "docs-ui"
|
||||
import FeedbackModal from "./FeedbackModal"
|
||||
|
||||
const Navbar = () => {
|
||||
const { setMobileSidebarOpen, mobileSidebarOpen } = useSidebar()
|
||||
const { isLoading } = usePageLoading()
|
||||
|
||||
return (
|
||||
<UiNavbar
|
||||
logo={{
|
||||
light: "/images/logo-icon.png",
|
||||
dark: "/images/logo-icon-dark.png",
|
||||
}}
|
||||
items={[
|
||||
{
|
||||
href: `/`,
|
||||
label: "Docs",
|
||||
},
|
||||
{
|
||||
href: `/user-guide`,
|
||||
label: "User Guide",
|
||||
},
|
||||
{
|
||||
href: `${getLinkWithBasePath("/store")}`,
|
||||
label: "Store API",
|
||||
},
|
||||
{
|
||||
href: `${getLinkWithBasePath("/admin")}`,
|
||||
label: "Admin API",
|
||||
},
|
||||
{
|
||||
href: `/ui`,
|
||||
label: "UI",
|
||||
},
|
||||
]}
|
||||
mobileMenuButton={{
|
||||
setMobileSidebarOpen,
|
||||
mobileSidebarOpen,
|
||||
}}
|
||||
additionalActions={<FeedbackModal />}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default Navbar
|
||||
@@ -0,0 +1,21 @@
|
||||
import clsx from "clsx"
|
||||
import SectionDivider from "../Divider"
|
||||
import { forwardRef } from "react"
|
||||
|
||||
type SectionContainerProps = {
|
||||
children: React.ReactNode
|
||||
noTopPadding?: boolean
|
||||
}
|
||||
|
||||
const SectionContainer = forwardRef<HTMLDivElement, SectionContainerProps>(
|
||||
function SectionContainer({ children, noTopPadding = false }, ref) {
|
||||
return (
|
||||
<div className={clsx("relative pb-7", !noTopPadding && "pt-7")} ref={ref}>
|
||||
{children}
|
||||
<SectionDivider className="-left-1.5 lg:!-left-4" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
export default SectionContainer
|
||||
@@ -0,0 +1,18 @@
|
||||
import clsx from "clsx"
|
||||
|
||||
type SectionDividerProps = {
|
||||
className?: string
|
||||
}
|
||||
|
||||
const SectionDivider = ({ className }: SectionDividerProps) => {
|
||||
return (
|
||||
<hr
|
||||
className={clsx(
|
||||
"absolute bottom-0 -left-1.5 z-0 m-0 w-screen lg:left-0",
|
||||
className
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default SectionDivider
|
||||
@@ -0,0 +1,30 @@
|
||||
"use client"
|
||||
|
||||
import clsx from "clsx"
|
||||
import { useEffect, useRef } from "react"
|
||||
|
||||
export type SectionProps = {
|
||||
addToSidebar?: boolean
|
||||
} & React.AllHTMLAttributes<HTMLDivElement>
|
||||
|
||||
const Section = ({ children, className }: SectionProps) => {
|
||||
const sectionRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if ("scrollRestoration" in history) {
|
||||
// disable scroll on refresh
|
||||
history.scrollRestoration = "manual"
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={sectionRef}
|
||||
className={clsx("[&_ul]:list-disc [&_ul]:px-1", "[&_h2]:pt-7", className)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Section
|
||||
@@ -0,0 +1,23 @@
|
||||
type SpaceProps = {
|
||||
top?: number
|
||||
bottom?: number
|
||||
left?: number
|
||||
right?: number
|
||||
}
|
||||
|
||||
const Space = ({ top = 0, bottom = 0, left = 0, right = 0 }: SpaceProps) => {
|
||||
return (
|
||||
<div
|
||||
className="w-full"
|
||||
style={{
|
||||
height: `1px`,
|
||||
marginTop: `${top ? top - 1 : top}px`,
|
||||
marginBottom: `${bottom ? bottom - 1 : bottom}px`,
|
||||
marginLeft: `${left}px`,
|
||||
marginRight: `${right}px`,
|
||||
}}
|
||||
></div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Space
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { Code } from "@/types/openapi"
|
||||
import { CodeTabs } from "docs-ui"
|
||||
import slugify from "slugify"
|
||||
|
||||
export type TagOperationCodeSectionRequestSamplesProps = {
|
||||
codeSamples: Code[]
|
||||
}
|
||||
|
||||
const TagOperationCodeSectionRequestSamples = ({
|
||||
codeSamples,
|
||||
}: TagOperationCodeSectionRequestSamplesProps) => {
|
||||
return (
|
||||
<div>
|
||||
<h3>Request samples</h3>
|
||||
<CodeTabs
|
||||
tabs={codeSamples.map((codeSample) => ({
|
||||
label: codeSample.label,
|
||||
value: slugify(codeSample.label),
|
||||
code: {
|
||||
...codeSample,
|
||||
collapsed: true,
|
||||
className: "!mb-0",
|
||||
},
|
||||
}))}
|
||||
className="mt-2 !mb-0"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagOperationCodeSectionRequestSamples
|
||||
@@ -0,0 +1,122 @@
|
||||
import { CodeBlock } from "docs-ui"
|
||||
import type { ExampleObject, ResponseObject } from "@/types/openapi"
|
||||
import type { JSONSchema7 } from "json-schema"
|
||||
import stringify from "json-stringify-pretty-compact"
|
||||
import { sample } from "openapi-sampler"
|
||||
import { useCallback, useEffect, useState } from "react"
|
||||
|
||||
export type TagsOperationCodeSectionResponsesSampleProps = {
|
||||
response: ResponseObject
|
||||
} & React.AllHTMLAttributes<HTMLDivElement>
|
||||
|
||||
const TagsOperationCodeSectionResponsesSample = ({
|
||||
response,
|
||||
className,
|
||||
}: TagsOperationCodeSectionResponsesSampleProps) => {
|
||||
const [examples, setExamples] = useState<ExampleObject[]>([])
|
||||
const [selectedExample, setSelectedExample] = useState<
|
||||
ExampleObject | undefined
|
||||
>()
|
||||
|
||||
const initExamples = useCallback(() => {
|
||||
if (!response.content) {
|
||||
return []
|
||||
}
|
||||
const contentSchema = Object.values(response.content)[0]
|
||||
const tempExamples = []
|
||||
if (contentSchema.examples) {
|
||||
Object.entries(contentSchema.examples).forEach(([value, example]) => {
|
||||
if ("$ref" in example) {
|
||||
return []
|
||||
}
|
||||
|
||||
tempExamples.push({
|
||||
title: example.summary || "",
|
||||
value,
|
||||
content: stringify(example.value, {
|
||||
maxLength: 50,
|
||||
}),
|
||||
})
|
||||
})
|
||||
} else if (contentSchema.example) {
|
||||
tempExamples.push({
|
||||
title: "",
|
||||
value: "",
|
||||
content: stringify(contentSchema.example, {
|
||||
maxLength: 50,
|
||||
}),
|
||||
})
|
||||
} else {
|
||||
const contentSample = stringify(
|
||||
sample(
|
||||
{
|
||||
...contentSchema.schema,
|
||||
} as JSONSchema7,
|
||||
{
|
||||
skipNonRequired: true,
|
||||
}
|
||||
),
|
||||
{
|
||||
maxLength: 50,
|
||||
}
|
||||
)
|
||||
|
||||
tempExamples.push({
|
||||
title: "",
|
||||
value: "",
|
||||
content: contentSample,
|
||||
})
|
||||
}
|
||||
|
||||
return tempExamples
|
||||
}, [response.content])
|
||||
|
||||
useEffect(() => {
|
||||
const tempExamples = initExamples()
|
||||
setExamples(tempExamples)
|
||||
setSelectedExample(tempExamples[0])
|
||||
}, [initExamples])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={className}>
|
||||
{response.content && (
|
||||
<span>Content type: {Object.keys(response.content)[0]}</span>
|
||||
)}
|
||||
<>
|
||||
{examples.length > 1 && (
|
||||
<select
|
||||
onChange={(event) =>
|
||||
setSelectedExample(
|
||||
examples.find((ex) => ex.value === event.target.value)
|
||||
)
|
||||
}
|
||||
className="border-medusa-border-base dark:border-medusa-border-base-dark my-1 w-full rounded-sm border p-0.5"
|
||||
>
|
||||
{examples.map((example, index) => (
|
||||
<option value={example.value} key={index}>
|
||||
{example.title}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
)}
|
||||
{selectedExample && (
|
||||
<CodeBlock
|
||||
source={selectedExample.content}
|
||||
lang={getLanguageFromMedia(Object.keys(response.content)[0])}
|
||||
collapsed={true}
|
||||
className="mt-2 mb-0"
|
||||
/>
|
||||
)}
|
||||
{!selectedExample && <>Empty Response</>}
|
||||
</>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagsOperationCodeSectionResponsesSample
|
||||
|
||||
const getLanguageFromMedia = (media: string) => {
|
||||
return media.substring(media.indexOf("/"))
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import type { Operation } from "@/types/openapi"
|
||||
import dynamic from "next/dynamic"
|
||||
import type { TagsOperationCodeSectionResponsesSampleProps } from "./Sample"
|
||||
import { Badge } from "docs-ui"
|
||||
|
||||
const TagsOperationCodeSectionResponsesSample =
|
||||
dynamic<TagsOperationCodeSectionResponsesSampleProps>(
|
||||
async () => import("./Sample")
|
||||
) as React.FC<TagsOperationCodeSectionResponsesSampleProps>
|
||||
|
||||
type TagsOperationCodeSectionResponsesProps = {
|
||||
operation: Operation
|
||||
}
|
||||
|
||||
const TagsOperationCodeSectionResponses = ({
|
||||
operation,
|
||||
}: TagsOperationCodeSectionResponsesProps) => {
|
||||
const responseCodes = Object.keys(operation.responses)
|
||||
const responseCode = responseCodes.find((rc) => rc === "200" || rc === "201")
|
||||
const response = responseCode ? operation.responses[responseCode] : null
|
||||
|
||||
if (!response) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-0.5 flex items-center gap-0.5">
|
||||
<h3 className="mb-0">Response </h3>
|
||||
<Badge variant="green">{responseCode}</Badge>
|
||||
</div>
|
||||
|
||||
<TagsOperationCodeSectionResponsesSample response={response} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagsOperationCodeSectionResponses
|
||||
@@ -0,0 +1,58 @@
|
||||
"use client"
|
||||
|
||||
import MethodLabel from "@/components/MethodLabel"
|
||||
import type { Operation } from "@/types/openapi"
|
||||
import TagsOperationCodeSectionResponses from "./Responses"
|
||||
import type { TagOperationCodeSectionRequestSamplesProps } from "./RequestSamples"
|
||||
import dynamic from "next/dynamic"
|
||||
import clsx from "clsx"
|
||||
import { CopyButton } from "docs-ui"
|
||||
import { SquareTwoStack } from "@medusajs/icons"
|
||||
|
||||
const TagOperationCodeSectionRequestSamples =
|
||||
dynamic<TagOperationCodeSectionRequestSamplesProps>(
|
||||
async () => import("./RequestSamples")
|
||||
) as React.FC<TagOperationCodeSectionRequestSamplesProps>
|
||||
|
||||
export type TagOperationCodeSectionProps = {
|
||||
operation: Operation
|
||||
method: string
|
||||
endpointPath: string
|
||||
} & React.HTMLAttributes<HTMLDivElement>
|
||||
|
||||
const TagOperationCodeSection = ({
|
||||
operation,
|
||||
method,
|
||||
endpointPath,
|
||||
className,
|
||||
}: TagOperationCodeSectionProps) => {
|
||||
return (
|
||||
<div className={clsx("mt-2 flex flex-col gap-2", className)}>
|
||||
<div
|
||||
className={clsx(
|
||||
"bg-medusa-bg-subtle border-medusa-border-base px-0.75 rounded border py-0.5",
|
||||
"text-code-body flex w-full justify-between gap-1",
|
||||
"dark:bg-medusa-bg-subtle-dark dark:border-medusa-border-base-dark"
|
||||
)}
|
||||
>
|
||||
<div className={clsx("flex w-[calc(100%-36px)] gap-1")}>
|
||||
<MethodLabel method={method} className="h-fit" />
|
||||
<code className="text-medusa-fg-subtle dark:text-medusa-fg-subtle-dark break-words break-all">
|
||||
{endpointPath}
|
||||
</code>
|
||||
</div>
|
||||
<CopyButton text={endpointPath} tooltipClassName="font-base">
|
||||
<SquareTwoStack className="text-medusa-fg-muted dark:text-medusa-fg-muted-dark" />
|
||||
</CopyButton>
|
||||
</div>
|
||||
{operation["x-codeSamples"] && (
|
||||
<TagOperationCodeSectionRequestSamples
|
||||
codeSamples={operation["x-codeSamples"]}
|
||||
/>
|
||||
)}
|
||||
<TagsOperationCodeSectionResponses operation={operation} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagOperationCodeSection
|
||||
@@ -0,0 +1,71 @@
|
||||
import type { Parameter, SchemaObject } from "@/types/openapi"
|
||||
import TagOperationParameters from "../../Parameters"
|
||||
|
||||
export type TagsOperationDescriptionSectionParametersProps = {
|
||||
parameters: Parameter[]
|
||||
}
|
||||
|
||||
const TagsOperationDescriptionSectionParameters = ({
|
||||
parameters,
|
||||
}: TagsOperationDescriptionSectionParametersProps) => {
|
||||
const pathParameters: SchemaObject = {
|
||||
type: "object",
|
||||
required: [],
|
||||
properties: {},
|
||||
}
|
||||
const queryParameters: SchemaObject = {
|
||||
type: "object",
|
||||
required: [],
|
||||
properties: {},
|
||||
}
|
||||
|
||||
parameters.forEach((parameter) => {
|
||||
const parameterObject = {
|
||||
...parameter.schema,
|
||||
parameterName: parameter.name,
|
||||
description: parameter.description,
|
||||
example: parameter.example,
|
||||
examples: parameter.examples,
|
||||
}
|
||||
if (parameter.in === "path") {
|
||||
if (parameter.required) {
|
||||
pathParameters.required?.push(parameter.name)
|
||||
}
|
||||
pathParameters.properties[parameter.name] = parameterObject
|
||||
} else if (parameter.in === "query") {
|
||||
if (parameter.required) {
|
||||
queryParameters.required?.push(parameter.name)
|
||||
}
|
||||
queryParameters.properties[parameter.name] = parameterObject
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
{Object.values(pathParameters.properties).length > 0 && (
|
||||
<>
|
||||
<h3 className="border-medusa-border-base dark:border-medusa-border-base-dark border-b py-1.5">
|
||||
Path Parameters
|
||||
</h3>
|
||||
<TagOperationParameters
|
||||
schemaObject={pathParameters}
|
||||
topLevel={true}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{Object.values(queryParameters.properties).length > 0 && (
|
||||
<>
|
||||
<h3 className="border-medusa-border-base dark:border-medusa-border-base-dark border-b py-1.5">
|
||||
Query Parameters
|
||||
</h3>
|
||||
<TagOperationParameters
|
||||
schemaObject={queryParameters}
|
||||
topLevel={true}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagsOperationDescriptionSectionParameters
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { RequestObject } from "@/types/openapi"
|
||||
import TagOperationParameters from "../../Parameters"
|
||||
import { DetailsSummary } from "docs-ui"
|
||||
|
||||
export type TagsOperationDescriptionSectionRequestProps = {
|
||||
requestBody: RequestObject
|
||||
}
|
||||
|
||||
const TagsOperationDescriptionSectionRequest = ({
|
||||
requestBody,
|
||||
}: TagsOperationDescriptionSectionRequestProps) => {
|
||||
return (
|
||||
<>
|
||||
<DetailsSummary
|
||||
title="Request Body"
|
||||
subtitle={Object.keys(requestBody.content)[0]}
|
||||
expandable={false}
|
||||
className="border-t-0"
|
||||
titleClassName="text-h3"
|
||||
/>
|
||||
<TagOperationParameters
|
||||
schemaObject={
|
||||
requestBody.content[Object.keys(requestBody.content)[0]].schema
|
||||
}
|
||||
topLevel={true}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagsOperationDescriptionSectionRequest
|
||||
@@ -0,0 +1,100 @@
|
||||
import type { ResponsesObject } from "@/types/openapi"
|
||||
import clsx from "clsx"
|
||||
import TagOperationParameters from "../../Parameters"
|
||||
import { Fragment } from "react"
|
||||
import { Badge, Details, DetailsSummary } from "docs-ui"
|
||||
|
||||
export type TagsOperationDescriptionSectionResponsesProps = {
|
||||
responses: ResponsesObject
|
||||
}
|
||||
|
||||
const TagsOperationDescriptionSectionResponses = ({
|
||||
responses,
|
||||
}: TagsOperationDescriptionSectionResponsesProps) => {
|
||||
return (
|
||||
<>
|
||||
<h3 className="my-1.5">Responses</h3>
|
||||
<div
|
||||
className={clsx("[&>details:not(:first-of-type)>summary]:border-t-0")}
|
||||
>
|
||||
{Object.entries(responses).map(([code, response], index) => {
|
||||
return (
|
||||
<Fragment key={index}>
|
||||
{response.content && (
|
||||
<>
|
||||
{(code === "200" || code === "201") && (
|
||||
<>
|
||||
<DetailsSummary
|
||||
title={`${code} ${response.description}`}
|
||||
subtitle={Object.keys(response.content)[0]}
|
||||
badge={<Badge variant="green">Success</Badge>}
|
||||
expandable={false}
|
||||
className={clsx(
|
||||
index !== 0 && "border-t-0",
|
||||
index === 0 && "border-b-0"
|
||||
)}
|
||||
/>
|
||||
<TagOperationParameters
|
||||
schemaObject={
|
||||
response.content[Object.keys(response.content)[0]]
|
||||
.schema
|
||||
}
|
||||
topLevel={true}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{code !== "200" && code !== "201" && (
|
||||
<Details
|
||||
summaryElm={
|
||||
<DetailsSummary
|
||||
title={`${code} ${response.description}`}
|
||||
subtitle={Object.keys(response.content)[0]}
|
||||
badge={<Badge variant="red">Error</Badge>}
|
||||
open={index === 0}
|
||||
/>
|
||||
}
|
||||
openInitial={index === 0}
|
||||
className={clsx(index > 1 && "border-t-0")}
|
||||
>
|
||||
<TagOperationParameters
|
||||
schemaObject={
|
||||
response.content[Object.keys(response.content)[0]]
|
||||
.schema
|
||||
}
|
||||
topLevel={true}
|
||||
/>
|
||||
</Details>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{!response.content && (
|
||||
<DetailsSummary
|
||||
title={`${code} ${response.description}`}
|
||||
subtitle={"Empty response"}
|
||||
badge={
|
||||
<Badge
|
||||
variant={
|
||||
code === "200" || code === "201" ? "green" : "red"
|
||||
}
|
||||
>
|
||||
{code === "200" || code === "201" ? "Success" : "Error"}
|
||||
</Badge>
|
||||
}
|
||||
expandable={false}
|
||||
className={clsx(
|
||||
index !== 0 && "border-t-0",
|
||||
index === 0 &&
|
||||
Object.entries(responses).length > 1 &&
|
||||
"border-b-0"
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagsOperationDescriptionSectionResponses
|
||||
@@ -0,0 +1,36 @@
|
||||
import { useBaseSpecs } from "@/providers/base-specs"
|
||||
import type { OpenAPIV3 } from "openapi-types"
|
||||
import { Card } from "docs-ui"
|
||||
|
||||
export type TagsOperationDescriptionSectionSecurityProps = {
|
||||
security: OpenAPIV3.SecurityRequirementObject[]
|
||||
}
|
||||
|
||||
const TagsOperationDescriptionSectionSecurity = ({
|
||||
security,
|
||||
}: TagsOperationDescriptionSectionSecurityProps) => {
|
||||
const { getSecuritySchema } = useBaseSpecs()
|
||||
|
||||
const getDescription = () => {
|
||||
let str = ""
|
||||
security.forEach((item) => {
|
||||
if (str.length) {
|
||||
str += " or "
|
||||
}
|
||||
str += getSecuritySchema(Object.keys(item)[0])?.["x-displayName"]
|
||||
})
|
||||
return str
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="my-2">
|
||||
<Card
|
||||
title="Authorization"
|
||||
text={getDescription()}
|
||||
href="#authentication"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagsOperationDescriptionSectionSecurity
|
||||
@@ -0,0 +1,107 @@
|
||||
"use client"
|
||||
|
||||
import type { Operation } from "@/types/openapi"
|
||||
import type { TagsOperationDescriptionSectionSecurityProps } from "./Security"
|
||||
import type { TagsOperationDescriptionSectionRequestProps } from "./RequestBody"
|
||||
import type { TagsOperationDescriptionSectionResponsesProps } from "./Responses"
|
||||
import dynamic from "next/dynamic"
|
||||
import TagsOperationDescriptionSectionParameters from "./Parameters"
|
||||
import MDXContentClient from "@/components/MDXContent/Client"
|
||||
import type { TagsOperationFeatureFlagNoticeProps } from "../FeatureFlagNotice"
|
||||
import { useArea } from "../../../../providers/area"
|
||||
import { Feedback, Badge, NextLink } from "docs-ui"
|
||||
import { usePathname } from "next/navigation"
|
||||
import formatReportLink from "../../../../utils/format-report-link"
|
||||
|
||||
const TagsOperationDescriptionSectionSecurity =
|
||||
dynamic<TagsOperationDescriptionSectionSecurityProps>(
|
||||
async () => import("./Security")
|
||||
) as React.FC<TagsOperationDescriptionSectionSecurityProps>
|
||||
|
||||
const TagsOperationDescriptionSectionRequest =
|
||||
dynamic<TagsOperationDescriptionSectionRequestProps>(
|
||||
async () => import("./RequestBody")
|
||||
) as React.FC<TagsOperationDescriptionSectionRequestProps>
|
||||
|
||||
const TagsOperationDescriptionSectionResponses =
|
||||
dynamic<TagsOperationDescriptionSectionResponsesProps>(
|
||||
async () => import("./Responses")
|
||||
) as React.FC<TagsOperationDescriptionSectionResponsesProps>
|
||||
|
||||
const TagsOperationFeatureFlagNotice =
|
||||
dynamic<TagsOperationFeatureFlagNoticeProps>(
|
||||
async () => import("../FeatureFlagNotice")
|
||||
) as React.FC<TagsOperationFeatureFlagNoticeProps>
|
||||
|
||||
type TagsOperationDescriptionSectionProps = {
|
||||
operation: Operation
|
||||
}
|
||||
const TagsOperationDescriptionSection = ({
|
||||
operation,
|
||||
}: TagsOperationDescriptionSectionProps) => {
|
||||
const { area } = useArea()
|
||||
const pathname = usePathname()
|
||||
|
||||
return (
|
||||
<>
|
||||
<h2>
|
||||
{operation.summary}
|
||||
{operation.deprecated && (
|
||||
<Badge variant="orange" className="ml-0.5">
|
||||
deprecated
|
||||
</Badge>
|
||||
)}
|
||||
{operation["x-featureFlag"] && (
|
||||
<TagsOperationFeatureFlagNotice
|
||||
featureFlag={operation["x-featureFlag"]}
|
||||
tooltipTextClassName="font-normal text-medusa-fg-subtle dark:text-medusa-fg-subtle-dark"
|
||||
badgeClassName="ml-0.5"
|
||||
/>
|
||||
)}
|
||||
</h2>
|
||||
<div className="my-1">
|
||||
<MDXContentClient content={operation.description} />
|
||||
</div>
|
||||
<Feedback
|
||||
event="survey_api-ref"
|
||||
extraData={{
|
||||
area,
|
||||
section: operation.summary,
|
||||
}}
|
||||
pathName={pathname}
|
||||
reportLink={formatReportLink(area, operation.summary)}
|
||||
className="!my-2"
|
||||
vertical={true}
|
||||
question="Did this endpoint run successfully?"
|
||||
/>
|
||||
{operation.externalDocs && (
|
||||
<>
|
||||
Related guide:{" "}
|
||||
<NextLink href={operation.externalDocs.url} target="_blank">
|
||||
{operation.externalDocs.description || "Read More"}
|
||||
</NextLink>
|
||||
</>
|
||||
)}
|
||||
{operation.security && (
|
||||
<TagsOperationDescriptionSectionSecurity
|
||||
security={operation.security}
|
||||
/>
|
||||
)}
|
||||
{operation.parameters && (
|
||||
<TagsOperationDescriptionSectionParameters
|
||||
parameters={operation.parameters}
|
||||
/>
|
||||
)}
|
||||
{operation.requestBody && (
|
||||
<TagsOperationDescriptionSectionRequest
|
||||
requestBody={operation.requestBody}
|
||||
/>
|
||||
)}
|
||||
<TagsOperationDescriptionSectionResponses
|
||||
responses={operation.responses}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagsOperationDescriptionSection
|
||||
@@ -0,0 +1,39 @@
|
||||
import { Badge, NextLink, Tooltip } from "docs-ui"
|
||||
|
||||
export type TagsOperationFeatureFlagNoticeProps = {
|
||||
featureFlag: string
|
||||
type?: "endpoint" | "parameter"
|
||||
tooltipTextClassName?: string
|
||||
badgeClassName?: string
|
||||
}
|
||||
|
||||
const TagsOperationFeatureFlagNotice = ({
|
||||
featureFlag,
|
||||
type = "endpoint",
|
||||
tooltipTextClassName,
|
||||
badgeClassName,
|
||||
}: TagsOperationFeatureFlagNoticeProps) => {
|
||||
return (
|
||||
<Tooltip
|
||||
tooltipChildren={
|
||||
<span className={tooltipTextClassName}>
|
||||
To use this {type}, make sure to
|
||||
<br />
|
||||
<NextLink
|
||||
href="https://docs.medusajs.com/development/feature-flags/toggle"
|
||||
target="__blank"
|
||||
>
|
||||
enable its feature flag: <code>{featureFlag}</code>
|
||||
</NextLink>
|
||||
</span>
|
||||
}
|
||||
clickable
|
||||
>
|
||||
<Badge variant="green" className={badgeClassName}>
|
||||
feature flag
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagsOperationFeatureFlagNotice
|
||||
@@ -0,0 +1,152 @@
|
||||
import MDXContentClient from "@/components/MDXContent/Client"
|
||||
import type { SchemaObject } from "@/types/openapi"
|
||||
import clsx from "clsx"
|
||||
import dynamic from "next/dynamic"
|
||||
import { Fragment } from "react"
|
||||
import { NextLink, type InlineCodeProps, capitalize } from "docs-ui"
|
||||
|
||||
const InlineCode = dynamic<InlineCodeProps>(
|
||||
async () => (await import("docs-ui")).InlineCode
|
||||
) as React.FC<InlineCodeProps>
|
||||
|
||||
type TagOperationParametersDescriptionProps = {
|
||||
schema: SchemaObject
|
||||
}
|
||||
|
||||
const TagOperationParametersDescription = ({
|
||||
schema,
|
||||
}: TagOperationParametersDescriptionProps) => {
|
||||
let typeDescription: React.ReactNode = <></>
|
||||
switch (true) {
|
||||
case schema.type === "object":
|
||||
typeDescription = (
|
||||
<>
|
||||
{schema.type} {schema.title ? `(${schema.title})` : ""}
|
||||
{schema.nullable ? ` or null` : ""}
|
||||
</>
|
||||
)
|
||||
break
|
||||
case schema.type === "array":
|
||||
typeDescription = (
|
||||
<>
|
||||
{schema.type === "array" && formatArrayDescription(schema.items)}
|
||||
{schema.nullable ? ` or null` : ""}
|
||||
</>
|
||||
)
|
||||
break
|
||||
case schema.anyOf !== undefined:
|
||||
case schema.allOf !== undefined:
|
||||
typeDescription = (
|
||||
<>
|
||||
{formatUnionDescription(schema.allOf)}
|
||||
{schema.nullable ? ` or null` : ""}
|
||||
</>
|
||||
)
|
||||
break
|
||||
case schema.oneOf !== undefined:
|
||||
typeDescription = (
|
||||
<>
|
||||
{schema.oneOf?.map((item, index) => (
|
||||
<Fragment key={index}>
|
||||
{index !== 0 && <> or </>}
|
||||
{item.type !== "array" && <>{item.title || item.type}</>}
|
||||
{item.type === "array" && (
|
||||
<>array{item.items.type ? ` of ${item.items.type}s` : ""}</>
|
||||
)}
|
||||
</Fragment>
|
||||
))}
|
||||
{schema.nullable ? ` or null` : ""}
|
||||
</>
|
||||
)
|
||||
break
|
||||
default:
|
||||
typeDescription = (
|
||||
<>
|
||||
{schema.type}
|
||||
{schema.nullable ? ` or null` : ""}
|
||||
{schema.format ? ` <${schema.format}>` : ""}
|
||||
</>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className={clsx("w-2/3 break-words pb-0.5")}>
|
||||
{typeDescription}
|
||||
{schema.default !== undefined && (
|
||||
<>
|
||||
<br />
|
||||
<span>
|
||||
Default:{" "}
|
||||
<InlineCode className="break-words">
|
||||
{JSON.stringify(schema.default)}
|
||||
</InlineCode>
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
{schema.enum && (
|
||||
<>
|
||||
<br />
|
||||
<span>
|
||||
Enum:{" "}
|
||||
{schema.enum.map((value, index) => (
|
||||
<Fragment key={index}>
|
||||
{index !== 0 && <>, </>}
|
||||
<InlineCode key={index}>{JSON.stringify(value)}</InlineCode>
|
||||
</Fragment>
|
||||
))}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
{schema.example !== undefined && (
|
||||
<>
|
||||
<br />
|
||||
<span>
|
||||
Example:{" "}
|
||||
<InlineCode className="break-words">
|
||||
{JSON.stringify(schema.example)}
|
||||
</InlineCode>
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
{schema.description && (
|
||||
<>
|
||||
<br />
|
||||
<MDXContentClient
|
||||
content={capitalize(schema.description)}
|
||||
className={clsx("!mb-0 [&>*]:!mb-0")}
|
||||
scope={{
|
||||
addToSidebar: false,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{schema.externalDocs && (
|
||||
<>
|
||||
Related guide:{" "}
|
||||
<NextLink href={schema.externalDocs.url} target="_blank">
|
||||
{schema.externalDocs.description || "Read More"}
|
||||
</NextLink>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagOperationParametersDescription
|
||||
|
||||
function formatArrayDescription(schema?: SchemaObject) {
|
||||
if (!schema) {
|
||||
return "Array"
|
||||
}
|
||||
|
||||
const type =
|
||||
schema.type === "object"
|
||||
? `objects ${schema.title ? `(${schema.title})` : ""}`
|
||||
: `${schema.type || "object"}s`
|
||||
|
||||
return `Array of ${type}`
|
||||
}
|
||||
|
||||
function formatUnionDescription(arr?: SchemaObject[]) {
|
||||
const types = [...new Set(arr?.map((type) => type.type || "object"))]
|
||||
return <>{types.join(" or ")}</>
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import type { SchemaObject } from "@/types/openapi"
|
||||
import dynamic from "next/dynamic"
|
||||
import type { TooltipProps } from "docs-ui"
|
||||
import type { TagsOperationFeatureFlagNoticeProps } from "../../FeatureFlagNotice"
|
||||
import { Badge, NextLink } from "docs-ui"
|
||||
|
||||
const Tooltip = dynamic<TooltipProps>(
|
||||
async () => (await import("docs-ui")).Tooltip
|
||||
) as React.FC<TooltipProps>
|
||||
|
||||
const TagsOperationFeatureFlagNotice =
|
||||
dynamic<TagsOperationFeatureFlagNoticeProps>(
|
||||
async () => import("../../FeatureFlagNotice")
|
||||
) as React.FC<TagsOperationFeatureFlagNoticeProps>
|
||||
|
||||
export type TagOperationParametersNameProps = {
|
||||
name: string
|
||||
isRequired?: boolean
|
||||
schema: SchemaObject
|
||||
}
|
||||
|
||||
const TagOperationParametersName = ({
|
||||
name,
|
||||
isRequired,
|
||||
schema,
|
||||
}: TagOperationParametersNameProps) => {
|
||||
return (
|
||||
<span className="w-1/3 break-words pr-0.5">
|
||||
<span className="font-monospace">{name}</span>
|
||||
{schema.deprecated && (
|
||||
<Badge variant="orange" className="ml-1">
|
||||
deprecated
|
||||
</Badge>
|
||||
)}
|
||||
{schema["x-expandable"] && (
|
||||
<>
|
||||
<br />
|
||||
<Tooltip
|
||||
tooltipChildren={
|
||||
<>
|
||||
If this request accepts an <code>expand</code> parameter,
|
||||
<br /> this field can be{" "}
|
||||
<NextLink href="#expanding-fields">expanded</NextLink> into an
|
||||
object.
|
||||
</>
|
||||
}
|
||||
clickable
|
||||
>
|
||||
<Badge variant="blue">expandable</Badge>
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
{schema["x-featureFlag"] && (
|
||||
<>
|
||||
<br />
|
||||
<TagsOperationFeatureFlagNotice
|
||||
featureFlag={schema["x-featureFlag"]}
|
||||
type="parameter"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{isRequired && (
|
||||
<>
|
||||
<br />
|
||||
<span className="text-medusa-tag-red-text dark:text-medusa-tag-red-text-dark text-compact-x-small">
|
||||
required
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagOperationParametersName
|
||||
@@ -0,0 +1,24 @@
|
||||
import clsx from "clsx"
|
||||
|
||||
export type TagsOperationParametersNestedProps =
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
|
||||
const TagsOperationParametersNested = ({
|
||||
children,
|
||||
...props
|
||||
}: TagsOperationParametersNestedProps) => {
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
className={clsx(
|
||||
props.className,
|
||||
"bg-docs-bg-surface dark:bg-docs-bg-surface-dark px-1 pt-1",
|
||||
"border-medusa-border-base dark:border-medusa-border-base-dark my-1 rounded-sm border"
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagsOperationParametersNested
|
||||
@@ -0,0 +1,44 @@
|
||||
import type { SchemaObject } from "@/types/openapi"
|
||||
import clsx from "clsx"
|
||||
import type { TagOperationParametersProps } from ".."
|
||||
import dynamic from "next/dynamic"
|
||||
import { Loading } from "docs-ui"
|
||||
|
||||
const TagOperationParameters = dynamic<TagOperationParametersProps>(
|
||||
async () => import(".."),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
) as React.FC<TagOperationParametersProps>
|
||||
|
||||
type TagsOperationParametersSectionProps = {
|
||||
header?: string
|
||||
contentType?: string
|
||||
schema: SchemaObject
|
||||
}
|
||||
|
||||
const TagsOperationParametersSection = ({
|
||||
header,
|
||||
contentType,
|
||||
schema,
|
||||
}: TagsOperationParametersSectionProps) => {
|
||||
return (
|
||||
<>
|
||||
{header && (
|
||||
<h3
|
||||
className={clsx(!contentType && "my-2", contentType && "mt-2 mb-0")}
|
||||
>
|
||||
{header}
|
||||
</h3>
|
||||
)}
|
||||
{contentType && (
|
||||
<span className={clsx("mb-2 inline-block")}>
|
||||
Content type: {contentType}
|
||||
</span>
|
||||
)}
|
||||
<TagOperationParameters schemaObject={schema} topLevel={true} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagsOperationParametersSection
|
||||
@@ -0,0 +1,79 @@
|
||||
import type { SchemaObject } from "@/types/openapi"
|
||||
import dynamic from "next/dynamic"
|
||||
import type { TagOperationParametersDefaultProps } from "../Default"
|
||||
import type { TagOperationParametersProps } from "../.."
|
||||
import TagsOperationParametersNested from "../../Nested"
|
||||
import { Details, Loading } from "docs-ui"
|
||||
|
||||
const TagOperationParametersDefault =
|
||||
dynamic<TagOperationParametersDefaultProps>(
|
||||
async () => import("../Default"),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
) as React.FC<TagOperationParametersDefaultProps>
|
||||
|
||||
const TagOperationParameters = dynamic<TagOperationParametersProps>(
|
||||
async () => import("../.."),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
) as React.FC<TagOperationParametersProps>
|
||||
|
||||
export type TagOperationParametersArrayProps = {
|
||||
name: string
|
||||
schema: SchemaObject
|
||||
isRequired?: boolean
|
||||
}
|
||||
|
||||
const TagOperationParametersArray = ({
|
||||
name,
|
||||
schema,
|
||||
isRequired,
|
||||
}: TagOperationParametersArrayProps) => {
|
||||
if (schema.type !== "array") {
|
||||
return <></>
|
||||
}
|
||||
|
||||
if (
|
||||
!schema.items ||
|
||||
(schema.items.type !== "object" &&
|
||||
schema.items.type !== "array" &&
|
||||
schema.items.type !== undefined) ||
|
||||
(schema.items.type === "object" &&
|
||||
!schema.items.properties &&
|
||||
!schema.items.allOf &&
|
||||
!schema.items.anyOf &&
|
||||
!schema.items.oneOf)
|
||||
) {
|
||||
return (
|
||||
<TagOperationParametersDefault
|
||||
name={name}
|
||||
schema={schema}
|
||||
isRequired={isRequired}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Details
|
||||
summaryElm={
|
||||
<summary className="cursor-pointer">
|
||||
<TagOperationParametersDefault
|
||||
name={name}
|
||||
schema={schema}
|
||||
isRequired={isRequired}
|
||||
expandable={true}
|
||||
/>
|
||||
</summary>
|
||||
}
|
||||
className="!border-y-0"
|
||||
>
|
||||
<TagsOperationParametersNested>
|
||||
<TagOperationParameters schemaObject={schema.items} topLevel={true} />
|
||||
</TagsOperationParametersNested>
|
||||
</Details>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagOperationParametersArray
|
||||
@@ -0,0 +1,42 @@
|
||||
import type { SchemaObject } from "@/types/openapi"
|
||||
import TagOperationParametersDescription from "../../Description"
|
||||
import clsx from "clsx"
|
||||
import TagOperationParametersName from "../../Name"
|
||||
|
||||
export type TagOperationParametersDefaultProps = {
|
||||
name?: string
|
||||
schema: SchemaObject
|
||||
isRequired?: boolean
|
||||
className?: string
|
||||
expandable?: boolean
|
||||
}
|
||||
|
||||
const TagOperationParametersDefault = ({
|
||||
name,
|
||||
schema,
|
||||
isRequired,
|
||||
className,
|
||||
expandable = false,
|
||||
}: TagOperationParametersDefaultProps) => {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"my-0.5 inline-flex justify-between",
|
||||
expandable && "w-[calc(100%-16px)]",
|
||||
!expandable && "w-full pl-1",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{name && (
|
||||
<TagOperationParametersName
|
||||
name={name}
|
||||
isRequired={isRequired}
|
||||
schema={schema}
|
||||
/>
|
||||
)}
|
||||
<TagOperationParametersDescription schema={schema} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagOperationParametersDefault
|
||||
@@ -0,0 +1,135 @@
|
||||
import type { SchemaObject } from "@/types/openapi"
|
||||
import TagOperationParametersDefault from "../Default"
|
||||
import dynamic from "next/dynamic"
|
||||
import type { TagOperationParametersProps } from "../.."
|
||||
import type { TagsOperationParametersNestedProps } from "../../Nested"
|
||||
import checkRequired from "@/utils/check-required"
|
||||
import { Loading, type DetailsProps } from "docs-ui"
|
||||
|
||||
const TagOperationParameters = dynamic<TagOperationParametersProps>(
|
||||
async () => import("../.."),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
) as React.FC<TagOperationParametersProps>
|
||||
|
||||
const TagsOperationParametersNested =
|
||||
dynamic<TagsOperationParametersNestedProps>(
|
||||
async () => import("../../Nested"),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
) as React.FC<TagsOperationParametersNestedProps>
|
||||
|
||||
const Details = dynamic<DetailsProps>(
|
||||
async () => (await import("docs-ui")).Details,
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
) as React.FC<DetailsProps>
|
||||
|
||||
export type TagOperationParametersObjectProps = {
|
||||
name?: string
|
||||
schema: SchemaObject
|
||||
isRequired?: boolean
|
||||
topLevel?: boolean
|
||||
}
|
||||
|
||||
const TagOperationParametersObject = ({
|
||||
name,
|
||||
schema,
|
||||
isRequired,
|
||||
topLevel = false,
|
||||
}: TagOperationParametersObjectProps) => {
|
||||
if (
|
||||
(schema.type !== "object" && schema.type !== undefined) ||
|
||||
(!schema.properties && !name)
|
||||
) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
const getPropertyDescriptionElm = (expandable = false) => {
|
||||
const content = (
|
||||
<TagOperationParametersDefault
|
||||
name={name}
|
||||
schema={schema}
|
||||
isRequired={isRequired}
|
||||
expandable={expandable}
|
||||
/>
|
||||
)
|
||||
return expandable ? (
|
||||
<summary className="cursor-pointer">{content}</summary>
|
||||
) : (
|
||||
<>{content}</>
|
||||
)
|
||||
}
|
||||
|
||||
const getPropertyParameterElms = (isNested = false) => {
|
||||
// sort properties to show required fields first
|
||||
const sortedProperties = Object.keys(schema.properties).sort(
|
||||
(property1, property2) => {
|
||||
schema.properties[property1].isRequired = checkRequired(
|
||||
schema,
|
||||
property1
|
||||
)
|
||||
schema.properties[property2].isRequired = checkRequired(
|
||||
schema,
|
||||
property2
|
||||
)
|
||||
|
||||
return schema.properties[property1].isRequired &&
|
||||
schema.properties[property2].isRequired
|
||||
? 0
|
||||
: schema.properties[property1].isRequired
|
||||
? -1
|
||||
: 1
|
||||
}
|
||||
)
|
||||
const content = (
|
||||
<>
|
||||
{sortedProperties.map((property, index) => (
|
||||
<TagOperationParameters
|
||||
schemaObject={{
|
||||
...schema.properties[property],
|
||||
parameterName: property,
|
||||
}}
|
||||
key={index}
|
||||
isRequired={
|
||||
schema.properties[property].isRequired ||
|
||||
checkRequired(schema, property)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
return (
|
||||
<>
|
||||
{isNested && (
|
||||
<TagsOperationParametersNested>
|
||||
{content}
|
||||
</TagsOperationParametersNested>
|
||||
)}
|
||||
{!isNested && <div>{content}</div>}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
if (!schema.properties) {
|
||||
return getPropertyDescriptionElm()
|
||||
}
|
||||
|
||||
if (topLevel) {
|
||||
return getPropertyParameterElms()
|
||||
}
|
||||
|
||||
return (
|
||||
<Details
|
||||
summaryElm={getPropertyDescriptionElm(true)}
|
||||
className="!border-y-0"
|
||||
>
|
||||
{getPropertyParameterElms(true)}
|
||||
</Details>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagOperationParametersObject
|
||||
@@ -0,0 +1,132 @@
|
||||
import type { SchemaObject } from "@/types/openapi"
|
||||
import clsx from "clsx"
|
||||
import dynamic from "next/dynamic"
|
||||
import { useState } from "react"
|
||||
import type { TagOperationParametersDefaultProps } from "../Default"
|
||||
import type { TagsOperationParametersNestedProps } from "../../Nested"
|
||||
import type { TagOperationParametersProps } from "../.."
|
||||
import { Details, Loading } from "docs-ui"
|
||||
|
||||
const TagOperationParameters = dynamic<TagOperationParametersProps>(
|
||||
async () => import("../.."),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
) as React.FC<TagOperationParametersProps>
|
||||
|
||||
const TagOperationParametersDefault =
|
||||
dynamic<TagOperationParametersDefaultProps>(
|
||||
async () => import("../Default"),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
) as React.FC<TagOperationParametersDefaultProps>
|
||||
|
||||
const TagsOperationParametersNested =
|
||||
dynamic<TagsOperationParametersNestedProps>(
|
||||
async () => import("../../Nested"),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
) as React.FC<TagsOperationParametersNestedProps>
|
||||
|
||||
export type TagOperationParamatersOneOfProps = {
|
||||
schema: SchemaObject
|
||||
isRequired?: boolean
|
||||
isNested?: boolean
|
||||
}
|
||||
|
||||
const TagOperationParamatersOneOf = ({
|
||||
schema,
|
||||
isRequired = false,
|
||||
isNested = false,
|
||||
}: TagOperationParamatersOneOfProps) => {
|
||||
const [activeTab, setActiveTab] = useState<number>(0)
|
||||
|
||||
const getName = (item: SchemaObject): string => {
|
||||
if (item.title) {
|
||||
return item.title
|
||||
}
|
||||
|
||||
if (item.anyOf || item.allOf) {
|
||||
// return the name of any of the items
|
||||
const name = item.anyOf
|
||||
? item.anyOf.find((i) => i.title !== undefined)?.title
|
||||
: item.allOf?.find((i) => i.title !== undefined)?.title
|
||||
|
||||
if (name) {
|
||||
return name
|
||||
}
|
||||
}
|
||||
|
||||
return item.type || ""
|
||||
}
|
||||
|
||||
const getContent = () => {
|
||||
return (
|
||||
<>
|
||||
<div className={clsx("flex items-center gap-1 pl-1")}>
|
||||
<span className="inline-block">One of</span>
|
||||
<ul className="mb-0 flex list-none gap-1">
|
||||
{schema.oneOf?.map((item, index) => (
|
||||
<li
|
||||
key={index}
|
||||
className={clsx(
|
||||
"rounded-xs cursor-pointer p-0.5",
|
||||
"border border-solid",
|
||||
activeTab === index && [
|
||||
"bg-medusa-bg-subtle border-medusa-border-strong",
|
||||
"dark:bg-medusa-bg-subtle-dark dark:border-medusa-border-strong-dark",
|
||||
],
|
||||
activeTab !== index && [
|
||||
"bg-medusa-bg-base border-medusa-border-base",
|
||||
"dark:bg-medusa-bg-base-dark dark:border-medusa-border-base-dark",
|
||||
]
|
||||
)}
|
||||
onClick={() => setActiveTab(index)}
|
||||
>
|
||||
{getName(item)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{schema.oneOf && (
|
||||
<>
|
||||
<TagOperationParameters
|
||||
schemaObject={schema.oneOf[activeTab]}
|
||||
topLevel={true}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{isNested && (
|
||||
<Details
|
||||
summaryElm={
|
||||
<summary className="cursor-pointer">
|
||||
<TagOperationParametersDefault
|
||||
schema={schema}
|
||||
name={schema.parameterName || schema.title || ""}
|
||||
isRequired={isRequired}
|
||||
expandable={true}
|
||||
/>
|
||||
</summary>
|
||||
}
|
||||
className="!border-y-0"
|
||||
>
|
||||
<TagsOperationParametersNested>
|
||||
{getContent()}
|
||||
</TagsOperationParametersNested>
|
||||
</Details>
|
||||
)}
|
||||
{!isNested && getContent()}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagOperationParamatersOneOf
|
||||
@@ -0,0 +1,65 @@
|
||||
import type { SchemaObject } from "@/types/openapi"
|
||||
import dynamic from "next/dynamic"
|
||||
import type { TagOperationParametersDefaultProps } from "../Default"
|
||||
import { TagOperationParametersObjectProps } from "../Object"
|
||||
import { Loading } from "docs-ui"
|
||||
|
||||
const TagOperationParametersObject = dynamic<TagOperationParametersObjectProps>(
|
||||
async () => import("../Object"),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
) as React.FC<TagOperationParametersObjectProps>
|
||||
|
||||
const TagOperationParametersDefault =
|
||||
dynamic<TagOperationParametersDefaultProps>(
|
||||
async () => import("../Default"),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
) as React.FC<TagOperationParametersDefaultProps>
|
||||
|
||||
export type TagOperationParametersUnionProps = {
|
||||
name: string
|
||||
schema: SchemaObject
|
||||
isRequired?: boolean
|
||||
topLevel?: boolean
|
||||
}
|
||||
|
||||
const TagOperationParametersUnion = ({
|
||||
name,
|
||||
schema,
|
||||
isRequired,
|
||||
topLevel,
|
||||
}: TagOperationParametersUnionProps) => {
|
||||
const objectSchema = schema.anyOf
|
||||
? schema.anyOf.find((item) => item.type === "object" && item.properties)
|
||||
: schema.allOf?.find((item) => item.type === "object" && item.properties)
|
||||
|
||||
if (!objectSchema) {
|
||||
return (
|
||||
<TagOperationParametersDefault
|
||||
schema={schema}
|
||||
name={name}
|
||||
isRequired={isRequired}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (!objectSchema.description) {
|
||||
objectSchema.description = schema.anyOf
|
||||
? schema.anyOf.find((item) => item.description !== undefined)?.description
|
||||
: schema.allOf?.find((item) => item.description !== undefined)
|
||||
?.description
|
||||
}
|
||||
|
||||
return (
|
||||
<TagOperationParametersObject
|
||||
schema={objectSchema}
|
||||
name={name}
|
||||
topLevel={topLevel}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagOperationParametersUnion
|
||||
@@ -0,0 +1,116 @@
|
||||
import type { SchemaObject } from "@/types/openapi"
|
||||
import dynamic from "next/dynamic"
|
||||
import type { TagOperationParametersObjectProps } from "./Types/Object"
|
||||
import type { TagOperationParametersDefaultProps } from "./Types/Default"
|
||||
import type { TagOperationParametersArrayProps } from "./Types/Array"
|
||||
import type { TagOperationParametersUnionProps } from "./Types/Union"
|
||||
import type { TagOperationParamatersOneOfProps } from "./Types/OneOf"
|
||||
import checkRequired from "@/utils/check-required"
|
||||
import { Loading } from "docs-ui"
|
||||
|
||||
const TagOperationParametersObject = dynamic<TagOperationParametersObjectProps>(
|
||||
async () => import("./Types/Object"),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
) as React.FC<TagOperationParametersObjectProps>
|
||||
const TagOperationParametersDefault =
|
||||
dynamic<TagOperationParametersDefaultProps>(
|
||||
async () => import("./Types/Default"),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
) as React.FC<TagOperationParametersDefaultProps>
|
||||
const TagOperationParametersArray = dynamic<TagOperationParametersArrayProps>(
|
||||
async () => import("./Types/Array"),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
) as React.FC<TagOperationParametersArrayProps>
|
||||
const TagOperationParametersUnion = dynamic<TagOperationParametersUnionProps>(
|
||||
async () => import("./Types/Union"),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
) as React.FC<TagOperationParametersUnionProps>
|
||||
const TagOperationParamatersOneOf = dynamic<TagOperationParamatersOneOfProps>(
|
||||
async () => import("./Types/OneOf"),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
) as React.FC<TagOperationParamatersOneOfProps>
|
||||
|
||||
export type TagOperationParametersProps = {
|
||||
schemaObject: SchemaObject
|
||||
topLevel?: boolean
|
||||
className?: string
|
||||
isRequired?: boolean
|
||||
}
|
||||
|
||||
const TagOperationParameters = ({
|
||||
schemaObject,
|
||||
className,
|
||||
topLevel = false,
|
||||
isRequired: originalIsRequired = false,
|
||||
}: TagOperationParametersProps) => {
|
||||
const isRequired =
|
||||
originalIsRequired || checkRequired(schemaObject, schemaObject.title)
|
||||
const propertyName = schemaObject.parameterName || schemaObject.title || ""
|
||||
|
||||
const getElement = () => {
|
||||
if (schemaObject.anyOf || schemaObject.allOf) {
|
||||
return (
|
||||
<TagOperationParametersUnion
|
||||
schema={schemaObject}
|
||||
name={propertyName}
|
||||
isRequired={isRequired}
|
||||
topLevel={topLevel}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (schemaObject.oneOf) {
|
||||
return (
|
||||
<TagOperationParamatersOneOf
|
||||
schema={schemaObject}
|
||||
isNested={!topLevel}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (schemaObject.type === "array") {
|
||||
return (
|
||||
<TagOperationParametersArray
|
||||
name={propertyName}
|
||||
schema={schemaObject}
|
||||
isRequired={isRequired}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (schemaObject.type === "object" || !schemaObject.type) {
|
||||
return (
|
||||
<TagOperationParametersObject
|
||||
name={propertyName}
|
||||
schema={schemaObject}
|
||||
topLevel={topLevel}
|
||||
isRequired={isRequired}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<TagOperationParametersDefault
|
||||
schema={schemaObject}
|
||||
name={propertyName}
|
||||
isRequired={isRequired}
|
||||
/>
|
||||
)
|
||||
|
||||
return <></>
|
||||
}
|
||||
|
||||
return <div className={className}>{getElement()}</div>
|
||||
}
|
||||
|
||||
export default TagOperationParameters
|
||||
@@ -0,0 +1,126 @@
|
||||
"use client"
|
||||
|
||||
import type { Operation } from "@/types/openapi"
|
||||
import clsx from "clsx"
|
||||
import type { OpenAPIV3 } from "openapi-types"
|
||||
import getSectionId from "@/utils/get-section-id"
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
||||
import dynamic from "next/dynamic"
|
||||
import { useInView } from "react-intersection-observer"
|
||||
import { useSidebar } from "docs-ui"
|
||||
import type { TagOperationCodeSectionProps } from "./CodeSection"
|
||||
import TagsOperationDescriptionSection from "./DescriptionSection"
|
||||
import DividedLayout from "@/layouts/Divided"
|
||||
import { useLoading } from "@/providers/loading"
|
||||
import SectionDivider from "../../Section/Divider"
|
||||
|
||||
const TagOperationCodeSection = dynamic<TagOperationCodeSectionProps>(
|
||||
async () => import("./CodeSection")
|
||||
) as React.FC<TagOperationCodeSectionProps>
|
||||
|
||||
export type TagOperationProps = {
|
||||
operation: Operation
|
||||
method?: string
|
||||
tag: OpenAPIV3.TagObject
|
||||
endpointPath: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
const TagOperation = ({
|
||||
operation,
|
||||
method,
|
||||
endpointPath,
|
||||
className,
|
||||
}: TagOperationProps) => {
|
||||
const { setActivePath } = useSidebar()
|
||||
const [show, setShow] = useState(false)
|
||||
const path = useMemo(
|
||||
() => getSectionId([...(operation.tags || []), operation.operationId]),
|
||||
[operation]
|
||||
)
|
||||
const nodeRef = useRef<Element | null>(null)
|
||||
const { loading, removeLoading } = useLoading()
|
||||
const { ref } = useInView({
|
||||
threshold: 0.3,
|
||||
rootMargin: `112px 0px 112px 0px`,
|
||||
onChange: (changedInView) => {
|
||||
if (changedInView) {
|
||||
if (!show) {
|
||||
if (loading) {
|
||||
removeLoading()
|
||||
}
|
||||
setShow(true)
|
||||
}
|
||||
// can't use next router as it doesn't support
|
||||
// changing url without scrolling
|
||||
history.replaceState({}, "", `#${path}`)
|
||||
setActivePath(path)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
// Use `useCallback` so we don't recreate the function on each render
|
||||
const setRefs = useCallback(
|
||||
(node: Element | null) => {
|
||||
// Ref's from useRef needs to have the node assigned to `current`
|
||||
nodeRef.current = node
|
||||
// Callback refs, like the one from `useInView`, is a function that takes the node as an argument
|
||||
ref(node)
|
||||
},
|
||||
[ref]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const enableShow = () => {
|
||||
setShow(true)
|
||||
}
|
||||
|
||||
if (nodeRef && nodeRef.current) {
|
||||
removeLoading()
|
||||
const currentHash = location.hash.replace("#", "")
|
||||
if (currentHash === path) {
|
||||
setTimeout(() => {
|
||||
nodeRef.current?.scrollIntoView()
|
||||
enableShow()
|
||||
}, 100)
|
||||
} else if (currentHash.split("_")[0] === path.split("_")[0]) {
|
||||
enableShow()
|
||||
}
|
||||
}
|
||||
}, [nodeRef, path])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx("relative min-h-screen w-full pb-7", className)}
|
||||
id={path}
|
||||
ref={setRefs}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
"flex w-full justify-between gap-1 opacity-0",
|
||||
!show && "invisible",
|
||||
show && "animate-fadeIn"
|
||||
)}
|
||||
style={{
|
||||
animationFillMode: "forwards",
|
||||
}}
|
||||
>
|
||||
<DividedLayout
|
||||
mainContent={
|
||||
<TagsOperationDescriptionSection operation={operation} />
|
||||
}
|
||||
codeContent={
|
||||
<TagOperationCodeSection
|
||||
method={method || ""}
|
||||
operation={operation}
|
||||
endpointPath={endpointPath}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<SectionDivider />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagOperation
|
||||
@@ -0,0 +1,101 @@
|
||||
"use client"
|
||||
|
||||
import getSectionId from "@/utils/get-section-id"
|
||||
import fetcher from "@/utils/swr-fetcher"
|
||||
import type { OpenAPIV3 } from "openapi-types"
|
||||
import useSWR from "swr"
|
||||
import type { Operation, PathsObject } from "@/types/openapi"
|
||||
import { SidebarItemSections, useSidebar, type SidebarItemType } from "docs-ui"
|
||||
import { Fragment, useEffect, useMemo } from "react"
|
||||
import dynamic from "next/dynamic"
|
||||
import type { TagOperationProps } from "../Operation"
|
||||
import { useArea } from "@/providers/area"
|
||||
import getLinkWithBasePath from "@/utils/get-link-with-base-path"
|
||||
import clsx from "clsx"
|
||||
import { useBaseSpecs } from "@/providers/base-specs"
|
||||
import getTagChildSidebarItems from "@/utils/get-tag-child-sidebar-items"
|
||||
import { useLoading } from "@/providers/loading"
|
||||
import DividedLoading from "@/components/DividedLoading"
|
||||
|
||||
const TagOperation = dynamic<TagOperationProps>(
|
||||
async () => import("../Operation")
|
||||
) as React.FC<TagOperationProps>
|
||||
|
||||
export type TagPathsProps = {
|
||||
tag: OpenAPIV3.TagObject
|
||||
} & React.HTMLAttributes<HTMLDivElement>
|
||||
|
||||
const TagPaths = ({ tag, className }: TagPathsProps) => {
|
||||
const tagSlugName = useMemo(() => getSectionId([tag.name]), [tag])
|
||||
const { area } = useArea()
|
||||
const { items, addItems, findItemInSection } = useSidebar()
|
||||
const { baseSpecs } = useBaseSpecs()
|
||||
const { loading } = useLoading()
|
||||
// if paths are already loaded since through
|
||||
// the expanded field, they're loaded directly
|
||||
// otherwise, they're loaded using the API endpoint
|
||||
let paths: PathsObject =
|
||||
baseSpecs?.expandedTags &&
|
||||
Object.hasOwn(baseSpecs.expandedTags, tagSlugName)
|
||||
? baseSpecs.expandedTags[tagSlugName]
|
||||
: {}
|
||||
const { data } = useSWR<{
|
||||
paths: PathsObject
|
||||
}>(
|
||||
!Object.keys(paths).length
|
||||
? getLinkWithBasePath(`/tag?tagName=${tagSlugName}&area=${area}`)
|
||||
: null,
|
||||
fetcher,
|
||||
{
|
||||
errorRetryInterval: 2000,
|
||||
}
|
||||
)
|
||||
|
||||
paths = data?.paths || paths
|
||||
|
||||
useEffect(() => {
|
||||
if (paths) {
|
||||
const parentItem = findItemInSection(
|
||||
items[SidebarItemSections.BOTTOM],
|
||||
{ path: tagSlugName },
|
||||
false
|
||||
)
|
||||
if (!parentItem?.children?.length) {
|
||||
const items: SidebarItemType[] = getTagChildSidebarItems(paths)
|
||||
|
||||
addItems(items, {
|
||||
section: SidebarItemSections.BOTTOM,
|
||||
parent: {
|
||||
path: tagSlugName,
|
||||
changeLoaded: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [paths])
|
||||
|
||||
return (
|
||||
<div className={clsx("relative", className)}>
|
||||
{loading && <DividedLoading className="mt-7" />}
|
||||
{Object.entries(paths).map(([endpointPath, operations], pathIndex) => (
|
||||
<Fragment key={pathIndex}>
|
||||
{Object.entries(operations).map(
|
||||
([method, operation], operationIndex) => (
|
||||
<TagOperation
|
||||
method={method}
|
||||
operation={operation as Operation}
|
||||
tag={tag}
|
||||
key={`${pathIndex}-${operationIndex}`}
|
||||
endpointPath={endpointPath}
|
||||
className={clsx("pt-7")}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagPaths
|
||||
@@ -0,0 +1,126 @@
|
||||
"use client"
|
||||
|
||||
import getSectionId from "@/utils/get-section-id"
|
||||
import type { OpenAPIV3 } from "openapi-types"
|
||||
import { useInView } from "react-intersection-observer"
|
||||
import { useEffect, useMemo, useState } from "react"
|
||||
import { useSidebar } from "docs-ui"
|
||||
import dynamic from "next/dynamic"
|
||||
import type { SectionProps } from "../../Section"
|
||||
import type { MDXContentClientProps } from "../../MDXContent/Client"
|
||||
import TagPaths from "../Paths"
|
||||
import DividedLayout from "@/layouts/Divided"
|
||||
import LoadingProvider from "@/providers/loading"
|
||||
import SectionContainer from "../../Section/Container"
|
||||
import { useArea } from "../../../providers/area"
|
||||
import SectionDivider from "../../Section/Divider"
|
||||
import clsx from "clsx"
|
||||
import { Feedback, Loading, NextLink } from "docs-ui"
|
||||
import { usePathname } from "next/navigation"
|
||||
import formatReportLink from "../../../utils/format-report-link"
|
||||
|
||||
export type TagSectionProps = {
|
||||
tag: OpenAPIV3.TagObject
|
||||
} & React.HTMLAttributes<HTMLDivElement>
|
||||
|
||||
const Section = dynamic<SectionProps>(
|
||||
async () => import("../../Section")
|
||||
) as React.FC<SectionProps>
|
||||
|
||||
const MDXContentClient = dynamic<MDXContentClientProps>(
|
||||
async () => import("../../MDXContent/Client"),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
}
|
||||
) as React.FC<MDXContentClientProps>
|
||||
|
||||
const TagSection = ({ tag }: TagSectionProps) => {
|
||||
const { activePath, setActivePath } = useSidebar()
|
||||
const [loadPaths, setLoadPaths] = useState(false)
|
||||
const slugTagName = useMemo(() => getSectionId([tag.name]), [tag])
|
||||
const { area } = useArea()
|
||||
const pathname = usePathname()
|
||||
const { ref } = useInView({
|
||||
threshold: 0.5,
|
||||
rootMargin: `112px 0px 112px 0px`,
|
||||
onChange: (inView) => {
|
||||
if (inView && !loadPaths) {
|
||||
setLoadPaths(true)
|
||||
}
|
||||
if (inView) {
|
||||
// ensure that the hash link doesn't change if it links to an inner path
|
||||
const currentHashArr = location.hash.replace("#", "").split("_")
|
||||
if (currentHashArr.length < 2 || currentHashArr[0] !== slugTagName) {
|
||||
// can't use next router as it doesn't support
|
||||
// changing url without scrolling
|
||||
history.replaceState({}, "", `#${slugTagName}`)
|
||||
setActivePath(slugTagName)
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (activePath && activePath.includes(slugTagName)) {
|
||||
const tagName = activePath.split("_")
|
||||
if (tagName.length === 1 && tagName[0] === slugTagName) {
|
||||
const elm = document.getElementById(tagName[0]) as Element
|
||||
elm?.scrollIntoView()
|
||||
} else if (tagName.length > 1 && tagName[0] === slugTagName) {
|
||||
setLoadPaths(true)
|
||||
}
|
||||
}
|
||||
}, [slugTagName, activePath])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx("min-h-screen", !loadPaths && "relative")}
|
||||
id={slugTagName}
|
||||
>
|
||||
<DividedLayout
|
||||
ref={ref}
|
||||
mainContent={
|
||||
<SectionContainer>
|
||||
<h2>{tag.name}</h2>
|
||||
{tag.description && (
|
||||
<Section>
|
||||
<MDXContentClient
|
||||
content={tag.description}
|
||||
scope={{
|
||||
addToSidebar: false,
|
||||
}}
|
||||
/>
|
||||
</Section>
|
||||
)}
|
||||
{tag.externalDocs && (
|
||||
<>
|
||||
Related guide:{" "}
|
||||
<NextLink href={tag.externalDocs.url} target="_blank">
|
||||
{tag.externalDocs.description || "Read More"}
|
||||
</NextLink>
|
||||
</>
|
||||
)}
|
||||
<Feedback
|
||||
event="survey_api-ref"
|
||||
extraData={{
|
||||
area,
|
||||
section: tag.name,
|
||||
}}
|
||||
pathName={pathname}
|
||||
reportLink={formatReportLink(area, tag.name)}
|
||||
/>
|
||||
</SectionContainer>
|
||||
}
|
||||
codeContent={<></>}
|
||||
/>
|
||||
{loadPaths && (
|
||||
<LoadingProvider initialLoading={true}>
|
||||
<TagPaths tag={tag} />
|
||||
</LoadingProvider>
|
||||
)}
|
||||
{!loadPaths && <SectionDivider />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TagSection
|
||||
@@ -0,0 +1,97 @@
|
||||
"use client"
|
||||
|
||||
import type { OpenAPIV3 } from "openapi-types"
|
||||
import { useEffect, useState } from "react"
|
||||
import useSWR from "swr"
|
||||
import fetcher from "@/utils/swr-fetcher"
|
||||
import { useBaseSpecs } from "@/providers/base-specs"
|
||||
import dynamic from "next/dynamic"
|
||||
import type { TagSectionProps } from "./Section"
|
||||
import { useArea } from "@/providers/area"
|
||||
import getLinkWithBasePath from "@/utils/get-link-with-base-path"
|
||||
import { SidebarItemSections, useSidebar } from "docs-ui"
|
||||
import getSectionId from "@/utils/get-section-id"
|
||||
import { ExpandedDocument } from "@/types/openapi"
|
||||
import getTagChildSidebarItems from "@/utils/get-tag-child-sidebar-items"
|
||||
|
||||
const TagSection = dynamic<TagSectionProps>(
|
||||
async () => import("./Section")
|
||||
) as React.FC<TagSectionProps>
|
||||
|
||||
export type TagsProps = React.HTMLAttributes<HTMLDivElement>
|
||||
|
||||
function getCurrentTag() {
|
||||
return typeof location !== "undefined"
|
||||
? location.hash.replace("#", "").split("_")[0]
|
||||
: ""
|
||||
}
|
||||
|
||||
const Tags = () => {
|
||||
const [tags, setTags] = useState<OpenAPIV3.TagObject[]>([])
|
||||
const [loadData, setLoadData] = useState<boolean>(false)
|
||||
const [expand, setExpand] = useState<string>("")
|
||||
const { baseSpecs, setBaseSpecs } = useBaseSpecs()
|
||||
const { addItems } = useSidebar()
|
||||
const { area } = useArea()
|
||||
|
||||
const { data } = useSWR<ExpandedDocument>(
|
||||
loadData && !baseSpecs
|
||||
? getLinkWithBasePath(`/base-specs?area=${area}&expand=${expand}`)
|
||||
: null,
|
||||
fetcher,
|
||||
{
|
||||
errorRetryInterval: 2000,
|
||||
}
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
setExpand(getCurrentTag())
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
setLoadData(true)
|
||||
}, [expand])
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
setBaseSpecs(data)
|
||||
}
|
||||
if (data?.tags) {
|
||||
setTags(data.tags)
|
||||
}
|
||||
}, [data, setBaseSpecs])
|
||||
|
||||
useEffect(() => {
|
||||
if (baseSpecs) {
|
||||
addItems(
|
||||
baseSpecs.tags?.map((tag) => {
|
||||
const tagPathName = getSectionId([tag.name.toLowerCase()])
|
||||
const childItems =
|
||||
baseSpecs.expandedTags &&
|
||||
Object.hasOwn(baseSpecs.expandedTags, tagPathName)
|
||||
? getTagChildSidebarItems(baseSpecs.expandedTags[tagPathName])
|
||||
: []
|
||||
return {
|
||||
path: tagPathName,
|
||||
title: tag.name,
|
||||
children: childItems,
|
||||
loaded: childItems.length > 0,
|
||||
}
|
||||
}) || [],
|
||||
{
|
||||
section: SidebarItemSections.BOTTOM,
|
||||
}
|
||||
)
|
||||
}
|
||||
}, [baseSpecs, addItems])
|
||||
|
||||
return (
|
||||
<>
|
||||
{tags.map((tag, index) => (
|
||||
<TagSection tag={tag} key={index} />
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Tags
|
||||
@@ -0,0 +1,72 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
h1 {
|
||||
@apply text-h1 mb-1;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@apply text-h2 mb-1;
|
||||
}
|
||||
|
||||
h3 {
|
||||
@apply text-h3 mb-0.5;
|
||||
}
|
||||
|
||||
h4 {
|
||||
@apply text-h4 mb-0.5;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4 {
|
||||
@apply text-medusa-fg-base dark:text-medusa-fg-base-dark;
|
||||
}
|
||||
|
||||
hr {
|
||||
@apply h-[1px] w-full border-0 bg-medusa-border-base dark:bg-medusa-border-base-dark my-2;
|
||||
}
|
||||
|
||||
p, ul, ol {
|
||||
@apply mb-1.5;
|
||||
}
|
||||
|
||||
html {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
body {
|
||||
@apply overflow-x-hidden;
|
||||
}
|
||||
|
||||
*::selection {
|
||||
@apply bg-medusa-bg-highlight dark:bg-medusa-bg-highlight-dark;
|
||||
}
|
||||
|
||||
pre *::selection {
|
||||
@apply !bg-medusa-code-text-highlight;
|
||||
}
|
||||
|
||||
body[data-modal="opened"] {
|
||||
@apply !overflow-hidden;
|
||||
}
|
||||
|
||||
mark {
|
||||
@apply bg-medusa-bg-highlight dark:bg-medusa-bg-highlight-dark;
|
||||
@apply text-medusa-fg-interactive dark:text-medusa-fg-interactive-dark;
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.clip {
|
||||
clip-path: inset(0);
|
||||
}
|
||||
|
||||
.no-marker {
|
||||
@apply marker:content-none;
|
||||
}
|
||||
|
||||
.no-marker::-webkit-details-marker {
|
||||
@apply hidden;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import clsx from "clsx"
|
||||
import { forwardRef } from "react"
|
||||
|
||||
type DividedLayoutProps = {
|
||||
mainContent: React.ReactNode
|
||||
codeContent: React.ReactNode
|
||||
className?: string
|
||||
mainContentClassName?: string
|
||||
codeContentClassName?: string
|
||||
}
|
||||
|
||||
const DividedLayout = forwardRef<HTMLDivElement, DividedLayoutProps>(
|
||||
function DividedLayout(
|
||||
{
|
||||
mainContent,
|
||||
codeContent,
|
||||
className,
|
||||
mainContentClassName,
|
||||
codeContentClassName,
|
||||
},
|
||||
ref
|
||||
) {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"flex w-full flex-col justify-between lg:flex-row lg:gap-4",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
"w-full flex-shrink-0 flex-grow-0 lg:w-[calc(50%-32px)] lg:basis-[calc(50%-32px)] lg:pl-4",
|
||||
mainContentClassName
|
||||
)}
|
||||
>
|
||||
{mainContent}
|
||||
</div>
|
||||
<div
|
||||
className={clsx(
|
||||
"w-full flex-shrink-0 flex-grow-0 lg:w-[calc(50%-32px)] lg:basis-[calc(50%-32px)] lg:pr-1.5",
|
||||
codeContentClassName
|
||||
)}
|
||||
>
|
||||
{codeContent}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
export default DividedLayout
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { MDXComponents } from "mdx/types"
|
||||
import getCustomComponents from "./components/MDXComponents"
|
||||
|
||||
// This file allows you to provide custom React components
|
||||
// to be used in MDX files. You can import and use any
|
||||
// React component you want, including components from
|
||||
// other libraries.
|
||||
|
||||
// This file is required to use MDX in `app` directory.
|
||||
export function useMDXComponents(components: MDXComponents): MDXComponents {
|
||||
return {
|
||||
// Allows customizing built-in components, e.g. to add styling.
|
||||
// h1: ({ children }) => <h1 style={{ fontSize: "100px" }}>{children}</h1>,
|
||||
...components,
|
||||
...getCustomComponents(),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import mdx from "@next/mdx"
|
||||
import bundleAnalyzer from "@next/bundle-analyzer"
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
experimental: {
|
||||
serverActions: true,
|
||||
},
|
||||
async rewrites() {
|
||||
return {
|
||||
fallback: [
|
||||
{
|
||||
source: "/ui",
|
||||
destination: `${process.env.NEXT_PUBLIC_UI_URL}/ui`,
|
||||
},
|
||||
{
|
||||
source: "/ui/:path*",
|
||||
destination: `${process.env.NEXT_PUBLIC_UI_URL}/ui/:path*`,
|
||||
},
|
||||
{
|
||||
source: "/:path*",
|
||||
destination: `${process.env.NEXT_PUBLIC_DOCS_URL}/:path*`,
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
webpack: (config) => {
|
||||
config.ignoreWarnings = [{ module: /node_modules\/keyv\/src\/index\.js/ }]
|
||||
|
||||
return config
|
||||
},
|
||||
transpilePackages: ["docs-ui"],
|
||||
}
|
||||
|
||||
const withMDX = mdx({
|
||||
extension: /\.mdx?$/,
|
||||
options: {
|
||||
rehypePlugins: [],
|
||||
},
|
||||
})
|
||||
|
||||
const withBundleAnalyzer = bundleAnalyzer({
|
||||
enabled: process.env.ANALYZE_BUNDLE === "true",
|
||||
})
|
||||
|
||||
export default withBundleAnalyzer(withMDX(nextConfig))
|
||||
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"name": "api-reference",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"dev:monorepo": "yarn dev -p 3000",
|
||||
"build": "next build",
|
||||
"build:dev": "NODE_ENV=test next build",
|
||||
"start": "next start",
|
||||
"start:monorepo": "yarn start -p 3000",
|
||||
"lint": "next lint --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mdx-js/loader": "^2.3.0",
|
||||
"@mdx-js/react": "^2.3.0",
|
||||
"@medusajs/icons": "^1.0.0",
|
||||
"@next/mdx": "13.4.19",
|
||||
"@readme/openapi-parser": "^2.5.0",
|
||||
"@types/mapbox__rehype-prism": "^0.8.0",
|
||||
"@types/mdx": "^2.0.5",
|
||||
"@types/node": "20.4.5",
|
||||
"@types/react": "18.2.17",
|
||||
"@types/react-dom": "18.2.7",
|
||||
"@types/react-transition-group": "^4.4.6",
|
||||
"autoprefixer": "10.4.14",
|
||||
"clsx": "^2.0.0",
|
||||
"docs-ui": "*",
|
||||
"eslint-config-docs": "*",
|
||||
"jsdom": "^22.1.0",
|
||||
"json-schema": "^0.4.0",
|
||||
"json-stringify-pretty-compact": "^4.0.0",
|
||||
"next": "13.4.19",
|
||||
"next-mdx-remote": "^4.4.1",
|
||||
"openapi-sampler": "^1.3.1",
|
||||
"openapi-types": "^12.1.3",
|
||||
"postcss": "8.4.27",
|
||||
"prism-react-renderer": "^2.0.6",
|
||||
"react": "latest",
|
||||
"react-dom": "latest",
|
||||
"react-instantsearch": "^7.0.1",
|
||||
"react-intersection-observer": "^9.5.2",
|
||||
"react-tooltip": "^5.19.0",
|
||||
"react-transition-group": "^4.4.5",
|
||||
"slugify": "^1.6.6",
|
||||
"swr": "^2.2.0",
|
||||
"tailwind": "*",
|
||||
"tailwindcss": "3.3.3",
|
||||
"typescript": "5.1.6",
|
||||
"yaml": "^2.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next/bundle-analyzer": "^13.4.19",
|
||||
"@types/jsdom": "^21.1.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require("tailwind/postcss.config")
|
||||
@@ -0,0 +1,51 @@
|
||||
"use client"
|
||||
|
||||
import type { Area } from "@/types/openapi"
|
||||
import { useSearch } from "docs-ui"
|
||||
import { createContext, useContext, useEffect, useState } from "react"
|
||||
|
||||
type AreaContextType = {
|
||||
area: Area
|
||||
setArea: (value: Area) => void
|
||||
}
|
||||
|
||||
const AreaContext = createContext<AreaContextType | null>(null)
|
||||
|
||||
type AreaProviderProps = {
|
||||
area: Area
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
const AreaProvider = ({ area: passedArea, children }: AreaProviderProps) => {
|
||||
const [area, setArea] = useState<Area>(passedArea)
|
||||
const { defaultFilters, setDefaultFilters } = useSearch()
|
||||
|
||||
useEffect(() => {
|
||||
if (!defaultFilters.includes(area)) {
|
||||
setDefaultFilters([area])
|
||||
}
|
||||
}, [area, defaultFilters, setDefaultFilters])
|
||||
|
||||
return (
|
||||
<AreaContext.Provider
|
||||
value={{
|
||||
area,
|
||||
setArea,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</AreaContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export default AreaProvider
|
||||
|
||||
export const useArea = (): AreaContextType => {
|
||||
const context = useContext(AreaContext)
|
||||
|
||||
if (!context) {
|
||||
throw new Error("useAreaProvider must be used inside an AreaProvider")
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
"use client"
|
||||
|
||||
import { ExpandedDocument, SecuritySchemeObject } from "@/types/openapi"
|
||||
import { ReactNode, createContext, useContext, useState } from "react"
|
||||
|
||||
type BaseSpecsContextType = {
|
||||
baseSpecs: ExpandedDocument | null
|
||||
setBaseSpecs: (value: ExpandedDocument) => void
|
||||
getSecuritySchema: (securityName: string) => SecuritySchemeObject | null
|
||||
}
|
||||
|
||||
const BaseSpecsContext = createContext<BaseSpecsContextType | null>(null)
|
||||
|
||||
type BaseSpecsProviderProps = {
|
||||
initialSpecs?: ExpandedDocument | null
|
||||
children?: ReactNode
|
||||
}
|
||||
|
||||
const BaseSpecsProvider = ({
|
||||
children,
|
||||
initialSpecs = null,
|
||||
}: BaseSpecsProviderProps) => {
|
||||
const [baseSpecs, setBaseSpecs] = useState<ExpandedDocument | null>(
|
||||
initialSpecs
|
||||
)
|
||||
|
||||
const getSecuritySchema = (
|
||||
securityName: string
|
||||
): SecuritySchemeObject | null => {
|
||||
if (
|
||||
baseSpecs?.components?.securitySchemes &&
|
||||
Object.prototype.hasOwnProperty.call(
|
||||
baseSpecs?.components?.securitySchemes,
|
||||
securityName
|
||||
)
|
||||
) {
|
||||
const schema = baseSpecs?.components?.securitySchemes[securityName]
|
||||
if (!("$ref" in schema)) {
|
||||
return schema
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseSpecsContext.Provider
|
||||
value={{
|
||||
baseSpecs,
|
||||
setBaseSpecs,
|
||||
getSecuritySchema,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</BaseSpecsContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export default BaseSpecsProvider
|
||||
|
||||
export const useBaseSpecs = (): BaseSpecsContextType => {
|
||||
const context = useContext(BaseSpecsContext)
|
||||
|
||||
if (!context) {
|
||||
throw new Error("useBaseSpecs must be used inside a BaseSpecsProvider")
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
"use client"
|
||||
|
||||
import {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState,
|
||||
} from "react"
|
||||
|
||||
type ColorMode = "light" | "dark"
|
||||
|
||||
type ColorModeContextType = {
|
||||
colorMode: ColorMode
|
||||
setColorMode: (value: ColorMode) => void
|
||||
toggleColorMode: () => void
|
||||
}
|
||||
|
||||
const ColorModeContext = createContext<ColorModeContextType | null>(null)
|
||||
|
||||
type ColorModeProviderProps = {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
const ColorModeProvider = ({ children }: ColorModeProviderProps) => {
|
||||
const [colorMode, setColorMode] = useState<ColorMode>("light")
|
||||
|
||||
const toggleColorMode = () =>
|
||||
setColorMode(colorMode === "light" ? "dark" : "light")
|
||||
|
||||
const init = () => {
|
||||
const theme = localStorage.getItem("theme")
|
||||
if (theme && (theme === "light" || theme === "dark")) {
|
||||
setColorMode(theme)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
init()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const theme = localStorage.getItem("theme")
|
||||
if (theme !== colorMode) {
|
||||
localStorage.setItem("theme", colorMode)
|
||||
}
|
||||
|
||||
document.querySelector("html")?.setAttribute("data-theme", colorMode)
|
||||
}, [colorMode])
|
||||
|
||||
return (
|
||||
<ColorModeContext.Provider
|
||||
value={{
|
||||
colorMode,
|
||||
setColorMode,
|
||||
toggleColorMode,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ColorModeContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export default ColorModeProvider
|
||||
|
||||
export const useColorMode = (): ColorModeContextType => {
|
||||
const context = useContext(ColorModeContext)
|
||||
|
||||
if (!context) {
|
||||
throw new Error("useColorMode must be used inside a ColorModeProvider")
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
"use client"
|
||||
|
||||
import {
|
||||
AnalyticsProvider,
|
||||
ColorModeProvider,
|
||||
MobileProvider,
|
||||
ModalProvider,
|
||||
NavbarProvider,
|
||||
PageLoadingProvider,
|
||||
ScrollControllerProvider,
|
||||
} from "docs-ui"
|
||||
import BaseSpecsProvider from "./base-specs"
|
||||
import SidebarProvider from "./sidebar"
|
||||
import SearchProvider from "./search"
|
||||
|
||||
type ProvidersProps = {
|
||||
children?: React.ReactNode
|
||||
}
|
||||
|
||||
const Providers = ({ children }: ProvidersProps) => {
|
||||
return (
|
||||
<AnalyticsProvider writeKey={process.env.NEXT_PUBLIC_SEGMENT_API_KEY}>
|
||||
<PageLoadingProvider>
|
||||
<ModalProvider>
|
||||
<ColorModeProvider>
|
||||
<BaseSpecsProvider>
|
||||
<SidebarProvider>
|
||||
<NavbarProvider>
|
||||
<ScrollControllerProvider>
|
||||
<SearchProvider>
|
||||
<MobileProvider>{children}</MobileProvider>
|
||||
</SearchProvider>
|
||||
</ScrollControllerProvider>
|
||||
</NavbarProvider>
|
||||
</SidebarProvider>
|
||||
</BaseSpecsProvider>
|
||||
</ColorModeProvider>
|
||||
</ModalProvider>
|
||||
</PageLoadingProvider>
|
||||
</AnalyticsProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default Providers
|
||||
@@ -0,0 +1,45 @@
|
||||
import { createContext, useContext, useState } from "react"
|
||||
|
||||
type LoadingContextType = {
|
||||
loading: boolean
|
||||
removeLoading: () => void
|
||||
}
|
||||
|
||||
const LoadingContext = createContext<LoadingContextType | null>(null)
|
||||
|
||||
type LoadingProviderProps = {
|
||||
children: React.ReactNode
|
||||
initialLoading?: boolean
|
||||
}
|
||||
|
||||
const LoadingProvider = ({
|
||||
children,
|
||||
initialLoading = false,
|
||||
}: LoadingProviderProps) => {
|
||||
const [loading, setLoading] = useState<boolean>(initialLoading)
|
||||
|
||||
const removeLoading = () => setLoading(false)
|
||||
|
||||
return (
|
||||
<LoadingContext.Provider
|
||||
value={{
|
||||
loading,
|
||||
removeLoading,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</LoadingContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export default LoadingProvider
|
||||
|
||||
export const useLoading = (): LoadingContextType => {
|
||||
const context = useContext(LoadingContext)
|
||||
|
||||
if (!context) {
|
||||
throw new Error("useLoading must be used inside a LoadingProvider")
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
"use client"
|
||||
|
||||
import { createContext, useEffect } from "react"
|
||||
import { capitalize, useSidebar } from "docs-ui"
|
||||
import { useArea } from "./area"
|
||||
|
||||
const PageTitleContext = createContext(null)
|
||||
|
||||
type PageTitleProviderProps = {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
const PageTitleProvider = ({ children }: PageTitleProviderProps) => {
|
||||
const { activePath, getActiveItem } = useSidebar()
|
||||
const { area } = useArea()
|
||||
|
||||
useEffect(() => {
|
||||
const titleSuffix = `Medusa ${capitalize(area)} API Reference`
|
||||
|
||||
if (!activePath?.length) {
|
||||
document.title = titleSuffix
|
||||
} else {
|
||||
const activeItem = getActiveItem()
|
||||
if (activeItem?.path === activePath) {
|
||||
document.title = `${activeItem?.title} - ${titleSuffix}`
|
||||
} else {
|
||||
// find the child that matches the active path
|
||||
const item = activeItem?.children?.find((i) => i.path === activePath)
|
||||
if (item) {
|
||||
document.title = `${item.title} - ${titleSuffix}`
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [activePath, area, getActiveItem])
|
||||
|
||||
return (
|
||||
<PageTitleContext.Provider value={null}>
|
||||
{children}
|
||||
</PageTitleContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export default PageTitleProvider
|
||||
@@ -0,0 +1,75 @@
|
||||
"use client"
|
||||
|
||||
import { usePageLoading, SearchProvider as UiSearchProvider } from "docs-ui"
|
||||
import getBaseUrl from "../utils/get-base-url"
|
||||
|
||||
type SearchProviderProps = {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
const SearchProvider = ({ children }: SearchProviderProps) => {
|
||||
const { isLoading } = usePageLoading()
|
||||
return (
|
||||
<UiSearchProvider
|
||||
algolia={{
|
||||
appId: process.env.NEXT_PUBLIC_ALGOLIA_APP_ID || "temp",
|
||||
apiKey: process.env.NEXT_PUBLIC_ALGOLIA_API_KEY || "temp",
|
||||
mainIndexName: process.env.NEXT_PUBLIC_API_ALGOLIA_INDEX_NAME || "temp",
|
||||
indices: [
|
||||
process.env.NEXT_PUBLIC_API_ALGOLIA_INDEX_NAME || "temp",
|
||||
process.env.NEXT_PUBLIC_DOCS_ALGOLIA_INDEX_NAME || "temp",
|
||||
],
|
||||
}}
|
||||
searchProps={{
|
||||
isLoading,
|
||||
suggestions: [
|
||||
{
|
||||
title: "Search Suggestions",
|
||||
items: [
|
||||
"Authentication",
|
||||
"Expanding fields",
|
||||
"Selecting fields",
|
||||
"Pagination",
|
||||
"Query parameter types",
|
||||
],
|
||||
},
|
||||
],
|
||||
checkInternalPattern: new RegExp(`^${getBaseUrl()}/api/(admin|store)`),
|
||||
filterOptions: [
|
||||
{
|
||||
value: "admin",
|
||||
label: "Admin API",
|
||||
},
|
||||
{
|
||||
value: "store",
|
||||
label: "Store API",
|
||||
},
|
||||
{
|
||||
value: "docs",
|
||||
label: "Docs",
|
||||
},
|
||||
{
|
||||
value: "user-guide",
|
||||
label: "User Guide",
|
||||
},
|
||||
{
|
||||
value: "plugins",
|
||||
label: "Plugins",
|
||||
},
|
||||
{
|
||||
value: "reference",
|
||||
label: "References",
|
||||
},
|
||||
{
|
||||
value: "ui",
|
||||
label: "UI",
|
||||
},
|
||||
],
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</UiSearchProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default SearchProvider
|
||||
@@ -0,0 +1,58 @@
|
||||
"use client"
|
||||
import { SidebarProvider as UiSidebarProvider, usePageLoading } from "docs-ui"
|
||||
|
||||
type SidebarProviderProps = {
|
||||
children?: React.ReactNode
|
||||
}
|
||||
|
||||
const SidebarProvider = ({ children }: SidebarProviderProps) => {
|
||||
const { isLoading, setIsLoading } = usePageLoading()
|
||||
|
||||
return (
|
||||
<UiSidebarProvider
|
||||
isLoading={isLoading}
|
||||
setIsLoading={setIsLoading}
|
||||
shouldHandleHashChange={true}
|
||||
initialItems={{
|
||||
top: [
|
||||
{
|
||||
title: "Introduction",
|
||||
path: "",
|
||||
loaded: true,
|
||||
},
|
||||
],
|
||||
bottom: [],
|
||||
mobile: [
|
||||
{
|
||||
title: "Docs",
|
||||
path: "https://docs.medusajs.com/",
|
||||
loaded: true,
|
||||
isPathHref: true,
|
||||
},
|
||||
{
|
||||
title: "User Guide",
|
||||
path: "https://docs.medusajs.com/user-guide",
|
||||
loaded: true,
|
||||
isPathHref: true,
|
||||
},
|
||||
{
|
||||
title: "Store API",
|
||||
path: "/api/store",
|
||||
loaded: true,
|
||||
isPathHref: true,
|
||||
},
|
||||
{
|
||||
title: "Admin API",
|
||||
path: "/api/admin",
|
||||
loaded: true,
|
||||
isPathHref: true,
|
||||
},
|
||||
],
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</UiSidebarProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default SidebarProvider
|
||||
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 570 B |
|
After Width: | Height: | Size: 921 B |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 8.0 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="magnifying-glass">
|
||||
<path id="Vector" d="M17.4997 17.5001L13.1688 13.1693M13.1688 13.1693C14.341 11.9972 14.9995 10.4074 14.9995 8.74972C14.9995 7.09205 14.341 5.50228 13.1688 4.33014C11.9967 3.15799 10.4069 2.49949 8.74926 2.49949C7.09159 2.49949 5.50182 3.15799 4.32967 4.33014C3.15753 5.50228 2.49902 7.09205 2.49902 8.74972C2.49902 10.4074 3.15753 11.9972 4.32967 13.1693C5.50182 14.3414 7.09159 15 8.74926 15C10.4069 15 11.9967 14.3414 13.1688 13.1693V13.1693Z" stroke="#6E6E6E" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 666 B |
@@ -0,0 +1,5 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g id="magnifying-glass">
|
||||
<path id="Vector" d="M17.4997 17.5001L13.1688 13.1693M13.1688 13.1693C14.341 11.9972 14.9995 10.4074 14.9995 8.74972C14.9995 7.09205 14.341 5.50228 13.1688 4.33014C11.9967 3.15799 10.4069 2.49949 8.74926 2.49949C7.09159 2.49949 5.50182 3.15799 4.32967 4.33014C3.15753 5.50228 2.49902 7.09205 2.49902 8.74972C2.49902 10.4074 3.15753 11.9972 4.32967 13.1693C5.50182 14.3414 7.09159 15 8.74926 15C10.4069 15 11.9967 14.3414 13.1688 13.1693V13.1693Z" stroke="#9CA3AF" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 666 B |
@@ -0,0 +1,4 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.99984 8.66669L4.6665 12L7.99984 15.3334" stroke="#889096" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M15.3332 4.66669V9.33335C15.3332 10.0406 15.0522 10.7189 14.5521 11.219C14.052 11.7191 13.3737 12 12.6665 12H4.6665" stroke="#889096" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 452 B |
@@ -0,0 +1,4 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.99984 8.66669L4.6665 12L7.99984 15.3334" stroke="#706F78" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M15.3332 4.66669V9.33335C15.3332 10.0406 15.0522 10.7189 14.5521 11.219C14.052 11.7191 13.3737 12 12.6665 12H4.6665" stroke="#706F78" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 452 B |
@@ -0,0 +1,5 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="32" height="32" rx="4" fill="#F1F3F5"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.915 7.625C10.0886 7.625 9.41943 8.295 9.41943 9.12054V22.8795C9.41943 23.705 10.0894 24.375 10.915 24.375H21.0846C21.9101 24.375 22.5801 23.705 22.5801 22.8795V16.5982C22.5801 15.8049 22.265 15.0441 21.7041 14.4832C21.1431 13.9223 20.3824 13.6071 19.5891 13.6071H18.0935C17.6969 13.6071 17.3165 13.4496 17.036 13.1691C16.7556 12.8886 16.598 12.5082 16.598 12.1116V10.6161C16.598 9.82279 16.2829 9.062 15.7219 8.50106C15.161 7.94013 14.4002 7.625 13.6069 7.625H10.915ZM12.4105 18.3929C12.4105 18.2342 12.4735 18.082 12.5857 17.9699C12.6979 17.8577 12.8501 17.7946 13.0087 17.7946H18.9909C19.1495 17.7946 19.3017 17.8577 19.4139 17.9699C19.5261 18.082 19.5891 18.2342 19.5891 18.3929C19.5891 18.5515 19.5261 18.7037 19.4139 18.8159C19.3017 18.928 19.1495 18.9911 18.9909 18.9911H13.0087C12.8501 18.9911 12.6979 18.928 12.5857 18.8159C12.4735 18.7037 12.4105 18.5515 12.4105 18.3929ZM13.0087 20.1875C12.8501 20.1875 12.6979 20.2505 12.5857 20.3627C12.4735 20.4749 12.4105 20.6271 12.4105 20.7857C12.4105 20.9444 12.4735 21.0965 12.5857 21.2087C12.6979 21.3209 12.8501 21.3839 13.0087 21.3839H15.9998C16.1584 21.3839 16.3106 21.3209 16.4228 21.2087C16.535 21.0965 16.598 20.9444 16.598 20.7857C16.598 20.6271 16.535 20.4749 16.4228 20.3627C16.3106 20.2505 16.1584 20.1875 15.9998 20.1875H13.0087Z" fill="#687076"/>
|
||||
<path d="M16.7744 7.8772C17.4337 8.6373 17.796 9.61006 17.7946 10.6162V12.1118C17.7946 12.2769 17.9286 12.4109 18.0937 12.4109H19.5892C20.5954 12.4095 21.5681 12.7718 22.3282 13.431C21.9773 12.0964 21.2782 10.8789 20.3024 9.90306C19.3266 8.92724 18.1091 8.22812 16.7744 7.8772Z" fill="#687076"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="32" height="32" rx="4" fill="#2E2E32"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.915 7.625C10.0886 7.625 9.41943 8.295 9.41943 9.12054V22.8795C9.41943 23.705 10.0894 24.375 10.915 24.375H21.0846C21.9101 24.375 22.5801 23.705 22.5801 22.8795V16.5982C22.5801 15.8049 22.265 15.0441 21.7041 14.4832C21.1431 13.9223 20.3824 13.6071 19.5891 13.6071H18.0935C17.6969 13.6071 17.3165 13.4496 17.036 13.1691C16.7556 12.8886 16.598 12.5082 16.598 12.1116V10.6161C16.598 9.82279 16.2829 9.062 15.7219 8.50106C15.161 7.94013 14.4002 7.625 13.6069 7.625H10.915ZM12.4105 18.3929C12.4105 18.2342 12.4735 18.082 12.5857 17.9699C12.6979 17.8577 12.8501 17.7946 13.0087 17.7946H18.9909C19.1495 17.7946 19.3017 17.8577 19.4139 17.9699C19.5261 18.082 19.5891 18.2342 19.5891 18.3929C19.5891 18.5515 19.5261 18.7037 19.4139 18.8159C19.3017 18.928 19.1495 18.9911 18.9909 18.9911H13.0087C12.8501 18.9911 12.6979 18.928 12.5857 18.8159C12.4735 18.7037 12.4105 18.5515 12.4105 18.3929ZM13.0087 20.1875C12.8501 20.1875 12.6979 20.2505 12.5857 20.3627C12.4735 20.4749 12.4105 20.6271 12.4105 20.7857C12.4105 20.9444 12.4735 21.0965 12.5857 21.2087C12.6979 21.3209 12.8501 21.3839 13.0087 21.3839H15.9998C16.1584 21.3839 16.3106 21.3209 16.4228 21.2087C16.535 21.0965 16.598 20.9444 16.598 20.7857C16.598 20.6271 16.535 20.4749 16.4228 20.3627C16.3106 20.2505 16.1584 20.1875 15.9998 20.1875H13.0087Z" fill="#7E7D86"/>
|
||||
<path d="M16.7744 7.8772C17.4337 8.6373 17.796 9.61006 17.7946 10.6162V12.1118C17.7946 12.2769 17.9286 12.4109 18.0937 12.4109H19.5892C20.5954 12.4095 21.5681 12.7718 22.3282 13.431C21.9773 12.0964 21.2782 10.8789 20.3024 9.90306C19.3266 8.92724 18.1091 8.22812 16.7744 7.8772Z" fill="#7E7D86"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg width="21" height="20" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.8169 6.06351C10.8167 5.44855 10.9606 4.84211 11.237 4.29277C11.5134 3.74343 11.9147 3.26648 12.4086 2.90013C12.9025 2.53379 13.4754 2.28824 14.0813 2.18315C14.6872 2.07807 15.3093 2.11638 15.8978 2.29501C15.9908 2.32318 16.0749 2.37498 16.1419 2.44534C16.209 2.5157 16.2566 2.60222 16.2803 2.69649C16.3039 2.79075 16.3027 2.88952 16.2768 2.98319C16.2509 3.07686 16.2012 3.16222 16.1325 3.23095L13.6427 5.72003C13.6899 6.07626 13.8497 6.42049 14.1234 6.69422C14.3971 6.96795 14.7414 7.12769 15.0983 7.17419L17.5867 4.6851C17.6554 4.6164 17.7408 4.56668 17.8344 4.54078C17.9281 4.51489 18.0269 4.5137 18.1211 4.53734C18.2154 4.56099 18.3019 4.60864 18.3723 4.67568C18.4426 4.74271 18.4944 4.82683 18.5226 4.91984C18.7095 5.53544 18.7427 6.18746 18.6193 6.81886C18.4959 7.45026 18.2197 8.0418 17.8147 8.54174C17.4098 9.04167 16.8886 9.43477 16.2966 9.68663C15.7046 9.93849 15.0599 10.0415 14.4189 9.9865C13.6554 9.92201 13.0165 10.0615 12.6872 10.462L7.32509 16.9753C7.10554 17.2406 6.833 17.4571 6.52493 17.611C6.21686 17.7649 5.88004 17.8528 5.53606 17.869C5.19207 17.8852 4.84848 17.8294 4.52731 17.7052C4.20613 17.5809 3.91443 17.391 3.6709 17.1475C3.42737 16.904 3.23736 16.6124 3.11305 16.2912C2.98874 15.9701 2.93287 15.6265 2.94901 15.2825C2.96514 14.9385 3.05294 14.6017 3.20676 14.2936C3.36059 13.9855 3.57707 13.7129 3.84232 13.4933L10.3549 8.13037C10.7546 7.8004 10.8949 7.16219 10.8304 6.39874C10.8212 6.28723 10.8167 6.17539 10.8169 6.06351ZM4.905 15.3442C4.905 15.195 4.96426 15.0519 5.06974 14.9464C5.17523 14.8409 5.31829 14.7817 5.46746 14.7817H5.47346C5.62264 14.7817 5.7657 14.8409 5.87119 14.9464C5.97667 15.0519 6.03593 15.195 6.03593 15.3442V15.3501C6.03593 15.4993 5.97667 15.6424 5.87119 15.7479C5.7657 15.8534 5.62264 15.9126 5.47346 15.9126H5.46746C5.31829 15.9126 5.17523 15.8534 5.06974 15.7479C4.96426 15.6424 4.905 15.4993 4.905 15.3501V15.3442Z" fill="#889096"/>
|
||||
<path d="M9.37377 7.48082L7.72313 5.83093V4.65651C7.72313 4.5594 7.69798 4.46395 7.65014 4.37945C7.60231 4.29495 7.53341 4.22427 7.45015 4.17429L4.63783 2.4869C4.53027 2.42242 4.40424 2.39574 4.27978 2.41109C4.15531 2.42645 4.03955 2.48296 3.95088 2.57164L3.38842 3.13411C3.29973 3.22278 3.24322 3.33854 3.22787 3.46301C3.21251 3.58747 3.23919 3.7135 3.30367 3.82106L4.99106 6.63338C5.04104 6.71663 5.11172 6.78553 5.19622 6.83337C5.28073 6.88121 5.37617 6.90635 5.47328 6.90636H6.6462L8.1926 8.45276L9.37377 7.48007V7.48082Z" fill="#889096"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.2339 13.9971L14.3709 17.1334C14.606 17.3685 14.885 17.5549 15.1921 17.6821C15.4992 17.8093 15.8283 17.8748 16.1607 17.8748C16.4931 17.8748 16.8222 17.8093 17.1293 17.6821C17.4364 17.5549 17.7154 17.3685 17.9504 17.1334C18.1855 16.8984 18.3719 16.6194 18.4991 16.3123C18.6263 16.0052 18.6918 15.6761 18.6918 15.3437C18.6918 15.0113 18.6263 14.6821 18.4991 14.3751C18.3719 14.068 18.1855 13.7889 17.9504 13.5539L15.4711 11.0753C15.0911 11.1292 14.7062 11.14 14.3237 11.1076C14.0282 11.0821 13.8122 11.1031 13.6735 11.1391C13.6296 11.1484 13.5873 11.1643 13.5482 11.1863L11.2339 13.9971ZM13.7942 12.9772C13.8997 12.8719 14.0426 12.8127 14.1917 12.8127C14.3407 12.8127 14.4837 12.8719 14.5892 12.9772L15.9953 14.3841C16.0506 14.4356 16.0949 14.4977 16.1257 14.5667C16.1564 14.6357 16.1729 14.7102 16.1743 14.7857C16.1756 14.8612 16.1617 14.9362 16.1334 15.0063C16.1051 15.0763 16.063 15.1399 16.0096 15.1933C15.9562 15.2467 15.8926 15.2888 15.8225 15.3171C15.7525 15.3454 15.6775 15.3593 15.602 15.358C15.5264 15.3566 15.452 15.3401 15.383 15.3094C15.314 15.2786 15.2519 15.2343 15.2004 15.179L13.7942 13.7729C13.6889 13.6674 13.6297 13.5245 13.6297 13.3754C13.6297 13.2264 13.6889 13.0834 13.7942 12.9779V12.9772Z" fill="#889096"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg width="21" height="20" viewBox="0 0 21 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.8169 6.06351C10.8167 5.44855 10.9606 4.84211 11.237 4.29277C11.5134 3.74343 11.9147 3.26648 12.4086 2.90013C12.9025 2.53379 13.4754 2.28824 14.0813 2.18315C14.6872 2.07807 15.3093 2.11638 15.8978 2.29501C15.9908 2.32318 16.0749 2.37498 16.1419 2.44534C16.209 2.5157 16.2566 2.60222 16.2803 2.69649C16.3039 2.79075 16.3027 2.88952 16.2768 2.98319C16.2509 3.07686 16.2012 3.16222 16.1325 3.23095L13.6427 5.72003C13.6899 6.07626 13.8497 6.42049 14.1234 6.69422C14.3971 6.96795 14.7414 7.12769 15.0983 7.17419L17.5867 4.6851C17.6554 4.6164 17.7408 4.56668 17.8344 4.54078C17.9281 4.51489 18.0269 4.5137 18.1211 4.53734C18.2154 4.56099 18.3019 4.60864 18.3723 4.67568C18.4426 4.74271 18.4944 4.82683 18.5226 4.91984C18.7095 5.53544 18.7427 6.18746 18.6193 6.81886C18.4959 7.45026 18.2197 8.0418 17.8147 8.54174C17.4098 9.04167 16.8886 9.43477 16.2966 9.68663C15.7046 9.93849 15.0599 10.0415 14.4189 9.9865C13.6554 9.92201 13.0165 10.0615 12.6872 10.462L7.32509 16.9753C7.10554 17.2406 6.833 17.4571 6.52493 17.611C6.21686 17.7649 5.88004 17.8528 5.53606 17.869C5.19207 17.8852 4.84848 17.8294 4.52731 17.7052C4.20613 17.5809 3.91443 17.391 3.6709 17.1475C3.42737 16.904 3.23736 16.6124 3.11305 16.2912C2.98874 15.9701 2.93287 15.6265 2.94901 15.2825C2.96514 14.9385 3.05294 14.6017 3.20676 14.2936C3.36059 13.9855 3.57707 13.7129 3.84232 13.4933L10.3549 8.13037C10.7546 7.8004 10.8949 7.16219 10.8304 6.39874C10.8212 6.28723 10.8167 6.17539 10.8169 6.06351ZM4.905 15.3442C4.905 15.195 4.96426 15.0519 5.06974 14.9464C5.17523 14.8409 5.31829 14.7817 5.46746 14.7817H5.47346C5.62264 14.7817 5.7657 14.8409 5.87119 14.9464C5.97667 15.0519 6.03593 15.195 6.03593 15.3442V15.3501C6.03593 15.4993 5.97667 15.6424 5.87119 15.7479C5.7657 15.8534 5.62264 15.9126 5.47346 15.9126H5.46746C5.31829 15.9126 5.17523 15.8534 5.06974 15.7479C4.96426 15.6424 4.905 15.4993 4.905 15.3501V15.3442Z" fill="#706F78"/>
|
||||
<path d="M9.37377 7.48082L7.72313 5.83093V4.65651C7.72313 4.5594 7.69798 4.46395 7.65014 4.37945C7.60231 4.29495 7.53341 4.22427 7.45015 4.17429L4.63783 2.4869C4.53027 2.42242 4.40424 2.39574 4.27978 2.41109C4.15531 2.42645 4.03955 2.48296 3.95088 2.57164L3.38842 3.13411C3.29973 3.22278 3.24322 3.33854 3.22787 3.46301C3.21251 3.58747 3.23919 3.7135 3.30367 3.82106L4.99106 6.63338C5.04104 6.71663 5.11172 6.78553 5.19622 6.83337C5.28073 6.88121 5.37617 6.90635 5.47328 6.90636H6.6462L8.1926 8.45276L9.37377 7.48007V7.48082Z" fill="#706F78"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.2339 13.9971L14.3709 17.1334C14.606 17.3685 14.885 17.5549 15.1921 17.6821C15.4992 17.8093 15.8283 17.8748 16.1607 17.8748C16.4931 17.8748 16.8222 17.8093 17.1293 17.6821C17.4364 17.5549 17.7154 17.3685 17.9504 17.1334C18.1855 16.8984 18.3719 16.6194 18.4991 16.3123C18.6263 16.0052 18.6918 15.6761 18.6918 15.3437C18.6918 15.0113 18.6263 14.6821 18.4991 14.3751C18.3719 14.068 18.1855 13.7889 17.9504 13.5539L15.4711 11.0753C15.0911 11.1292 14.7062 11.14 14.3237 11.1076C14.0282 11.0821 13.8122 11.1031 13.6735 11.1391C13.6296 11.1484 13.5873 11.1643 13.5482 11.1863L11.2339 13.9971ZM13.7942 12.9772C13.8997 12.8719 14.0426 12.8127 14.1917 12.8127C14.3407 12.8127 14.4837 12.8719 14.5892 12.9772L15.9953 14.3841C16.0506 14.4356 16.0949 14.4977 16.1257 14.5667C16.1564 14.6357 16.1729 14.7102 16.1743 14.7857C16.1756 14.8612 16.1617 14.9362 16.1334 15.0063C16.1051 15.0763 16.063 15.1399 16.0096 15.1933C15.9562 15.2467 15.8926 15.2888 15.8225 15.3171C15.7525 15.3454 15.6775 15.3593 15.602 15.358C15.5264 15.3566 15.452 15.3401 15.383 15.3094C15.314 15.2786 15.2519 15.2343 15.2004 15.179L13.7942 13.7729C13.6889 13.6674 13.6297 13.5245 13.6297 13.3754C13.6297 13.2264 13.6889 13.0834 13.7942 12.9779V12.9772Z" fill="#706F78"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>
|
||||
|
After Width: | Height: | Size: 629 B |
@@ -0,0 +1,4 @@
|
||||
import Medusa from "@medusajs/medusa-js"
|
||||
const medusa = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3 })
|
||||
// must be previously logged in
|
||||
medusa.admin.auth.deleteSession()
|
||||
@@ -0,0 +1,7 @@
|
||||
import Medusa from "@medusajs/medusa-js"
|
||||
const medusa = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3 })
|
||||
// must be previously logged in or use api token
|
||||
medusa.admin.auth.getSession()
|
||||
.then(({ user }) => {
|
||||
console.log(user.id);
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
import Medusa from "@medusajs/medusa-js"
|
||||
const medusa = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3 })
|
||||
medusa.admin.auth.createSession({
|
||||
email: "user@example.com",
|
||||
password: "supersecret"
|
||||
})
|
||||
.then(({ user }) => {
|
||||
console.log(user.id);
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
import Medusa from "@medusajs/medusa-js"
|
||||
const medusa = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3 })
|
||||
// must be previously logged in or use api token
|
||||
medusa.admin.batchJobs.list()
|
||||
.then(({ batch_jobs, limit, offset, count }) => {
|
||||
console.log(batch_jobs.length)
|
||||
})
|
||||
@@ -0,0 +1,10 @@
|
||||
import Medusa from "@medusajs/medusa-js"
|
||||
const medusa = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3 })
|
||||
// must be previously logged in or use api token
|
||||
medusa.admin.batchJobs.create({
|
||||
type: 'product-export',
|
||||
context: {},
|
||||
dry_run: false
|
||||
}).then((({ batch_job }) => {
|
||||
console.log(batch_job.id);
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
import Medusa from "@medusajs/medusa-js"
|
||||
const medusa = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3 })
|
||||
// must be previously logged in or use api token
|
||||
medusa.admin.batchJobs.retrieve(batchJobId)
|
||||
.then(({ batch_job }) => {
|
||||
console.log(batch_job.id);
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
import Medusa from "@medusajs/medusa-js"
|
||||
const medusa = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3 })
|
||||
// must be previously logged in or use api token
|
||||
medusa.admin.batchJobs.cancel(batchJobId)
|
||||
.then(({ batch_job }) => {
|
||||
console.log(batch_job.id);
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
import Medusa from "@medusajs/medusa-js"
|
||||
const medusa = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3 })
|
||||
// must be previously logged in or use api token
|
||||
medusa.admin.batchJobs.confirm(batchJobId)
|
||||
.then(({ batch_job }) => {
|
||||
console.log(batch_job.id);
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
import Medusa from "@medusajs/medusa-js"
|
||||
const medusa = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3 })
|
||||
// must be previously logged in or use api token
|
||||
medusa.admin.collections.list()
|
||||
.then(({ collections, limit, offset, count }) => {
|
||||
console.log(collections.length);
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
import Medusa from "@medusajs/medusa-js"
|
||||
const medusa = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3 })
|
||||
// must be previously logged in or use api token
|
||||
medusa.admin.collections.create({
|
||||
title: "New Collection"
|
||||
})
|
||||
.then(({ collection }) => {
|
||||
console.log(collection.id);
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
import Medusa from "@medusajs/medusa-js"
|
||||
const medusa = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3 })
|
||||
// must be previously logged in or use api token
|
||||
medusa.admin.collections.delete(collectionId)
|
||||
.then(({ id, object, deleted }) => {
|
||||
console.log(id);
|
||||
});
|
||||