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
This commit is contained in:
Shahed Nasser
2023-09-21 20:57:15 +03:00
committed by GitHub
parent 19c5d5ba36
commit fa7c94b4cc
3209 changed files with 32188 additions and 31018 deletions
+11
View File
@@ -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=
+11
View File
@@ -0,0 +1,11 @@
module.exports = {
root: true,
extends: [
"docs/next"
],
settings: {
next: {
rootDir: ".",
},
},
}
+43
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
nmMode: hardlinks-local
+7
View File
@@ -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.
File diff suppressed because it is too large Load Diff
@@ -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`,
}
}
]}
/>
File diff suppressed because it is too large Load Diff
@@ -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>
)
}
Binary file not shown.

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",
},
]
}
Binary file not shown.

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
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

+4
View File
@@ -0,0 +1,4 @@
User-Agent: *
Allow: /
Sitemap: https://docs.medusajs.com/sitemap.xml
+9
View File
@@ -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
+72
View File
@@ -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
+17
View File
@@ -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(),
}
}
+46
View File
@@ -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))
+56
View File
@@ -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"
}
}
+1
View File
@@ -0,0 +1 @@
module.exports = require("tailwind/postcss.config")
+51
View File
@@ -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
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 570 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 921 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

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

+1
View File
@@ -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

+1
View File
@@ -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);
});

Some files were not shown because too many files have changed in this diff Show More