diff --git a/www/apps/api-reference/app/admin/page.tsx b/www/apps/api-reference/app/admin/page.tsx
index 7c27b2652d..619896df30 100644
--- a/www/apps/api-reference/app/admin/page.tsx
+++ b/www/apps/api-reference/app/admin/page.tsx
@@ -3,22 +3,35 @@ import AdminContent from "../_mdx/admin.mdx"
import Tags from "@/components/Tags"
import PageTitleProvider from "@/providers/page-title"
import { H1 } from "docs-ui"
+import { getBaseSpecs } from "../../lib"
+import BaseSpecsProvider from "../../providers/base-specs"
+import clsx from "clsx"
+
+const AdminPage = async () => {
+ const data = await getBaseSpecs("admin")
-const ReferencePage = async () => {
return (
-
-
-
- Medusa V2 Admin API Reference
-
-
-
-
-
+
+
+
+
+ Medusa V2 Admin API Reference
+
+
+
+
+
+
)
}
-export default ReferencePage
+export default AdminPage
export function generateMetadata() {
return {
diff --git a/www/apps/api-reference/app/api/schema/route.ts b/www/apps/api-reference/app/api/schema/route.ts
index fb0ec3448a..11fc281bbe 100644
--- a/www/apps/api-reference/app/api/schema/route.ts
+++ b/www/apps/api-reference/app/api/schema/route.ts
@@ -1,9 +1,7 @@
import { NextResponse } from "next/server"
-import { SchemaObject } from "../../../types/openapi"
import path from "path"
-import { existsSync, promises as fs } from "fs"
-import { parseDocument } from "yaml"
-import dereference from "../../../utils/dereference"
+import { existsSync } from "fs"
+import getSchemaContent from "../../../utils/get-schema-content"
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
@@ -59,14 +57,8 @@ export async function GET(request: Request) {
)
}
- const schemaContent = await fs.readFile(schemaPath, "utf-8")
- const schema = parseDocument(schemaContent).toJS() as SchemaObject
-
- // resolve references in schema
- const dereferencedDocument = await dereference({
- basePath: baseSchemasPath,
- schemas: [schema],
- })
+ const { dereferencedDocument, originalSchema: schema } =
+ await getSchemaContent(schemaPath, baseSchemasPath)
return NextResponse.json(
{
diff --git a/www/apps/api-reference/app/store/page.tsx b/www/apps/api-reference/app/store/page.tsx
index be5006eb33..96bc547536 100644
--- a/www/apps/api-reference/app/store/page.tsx
+++ b/www/apps/api-reference/app/store/page.tsx
@@ -3,22 +3,35 @@ import StoreContent from "../_mdx/store.mdx"
import Tags from "@/components/Tags"
import PageTitleProvider from "@/providers/page-title"
import { H1 } from "docs-ui"
+import { getBaseSpecs } from "../../lib"
+import BaseSpecsProvider from "../../providers/base-specs"
+import clsx from "clsx"
+
+const StorePage = async () => {
+ const data = await getBaseSpecs("store")
-const ReferencePage = async () => {
return (
-
-
-
- Medusa V2 Store API Reference
-
-
-
-
-
+
+
+
+
+ Medusa V2 Store API Reference
+
+
+
+
+
+
)
}
-export default ReferencePage
+export default StorePage
export function generateMetadata() {
return {
diff --git a/www/apps/api-reference/components/MDXComponents/H2/index.tsx b/www/apps/api-reference/components/MDXComponents/H2/index.tsx
index ec148b3a22..15ce67062f 100644
--- a/www/apps/api-reference/components/MDXComponents/H2/index.tsx
+++ b/www/apps/api-reference/components/MDXComponents/H2/index.tsx
@@ -3,12 +3,13 @@
import { useScrollController, useSidebar, H2 as UiH2 } from "docs-ui"
import { useEffect, useMemo, useRef, useState } from "react"
import getSectionId from "../../../utils/get-section-id"
+import { SidebarItem } from "types"
type H2Props = React.HTMLAttributes
const H2 = ({ children, ...props }: H2Props) => {
const headingRef = useRef(null)
- const { activePath, addItems } = useSidebar()
+ const { activePath, addItems, removeItems } = useSidebar()
const { scrollableElement, scrollToElement } = useScrollController()
const [scrolledFirstTime, setScrolledFirstTime] = useState(false)
@@ -28,14 +29,19 @@ const H2 = ({ children, ...props }: H2Props) => {
}, [scrollableElement, headingRef, id])
useEffect(() => {
- addItems([
+ const item: SidebarItem[] = [
{
type: "link",
path: `${id}`,
title: children as string,
loaded: true,
},
- ])
+ ]
+ addItems(item)
+
+ return () => {
+ removeItems(item)
+ }
}, [id])
return (
diff --git a/www/apps/api-reference/components/Tags/Operation/Parameters/Types/Object/index.tsx b/www/apps/api-reference/components/Tags/Operation/Parameters/Types/Object/index.tsx
index 497a21da50..d86f589989 100644
--- a/www/apps/api-reference/components/Tags/Operation/Parameters/Types/Object/index.tsx
+++ b/www/apps/api-reference/components/Tags/Operation/Parameters/Types/Object/index.tsx
@@ -7,7 +7,7 @@ import type { TagOperationParametersProps } from "../.."
import type { TagsOperationParametersNestedProps } from "../../Nested"
import checkRequired from "@/utils/check-required"
import { Loading, type DetailsProps } from "docs-ui"
-import { useMemo } from "react"
+import { Fragment, useMemo } from "react"
const TagOperationParameters = dynamic(
async () => import("../.."),
@@ -101,20 +101,19 @@ const TagOperationParametersObject = ({
const content = (
<>
{sortedProperties.map((property, index) => (
- <>
+
{index !== 0 &&
}
- >
+
))}
>
)
diff --git a/www/apps/api-reference/components/Tags/Operation/index.tsx b/www/apps/api-reference/components/Tags/Operation/index.tsx
index 2b4a4ed05f..df44a3c331 100644
--- a/www/apps/api-reference/components/Tags/Operation/index.tsx
+++ b/www/apps/api-reference/components/Tags/Operation/index.tsx
@@ -7,7 +7,12 @@ 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 { isElmWindow, useScrollController, useSidebar } from "docs-ui"
+import {
+ isElmWindow,
+ useIsBrowser,
+ useScrollController,
+ useSidebar,
+} from "docs-ui"
import type { TagOperationCodeSectionProps } from "./CodeSection"
import TagsOperationDescriptionSection from "./DescriptionSection"
import DividedLayout from "@/layouts/Divided"
@@ -44,9 +49,14 @@ const TagOperation = ({
const nodeRef = useRef(null)
const { loading, removeLoading } = useLoading()
const { scrollableElement, scrollToTop } = useScrollController()
+ const { isBrowser } = useIsBrowser()
const root = useMemo(() => {
+ if (!isBrowser) {
+ return
+ }
+
return isElmWindow(scrollableElement) ? document.body : scrollableElement
- }, [scrollableElement])
+ }, [isBrowser, scrollableElement])
const { ref } = useInView({
threshold: 0.3,
rootMargin: `112px 0px 112px 0px`,
@@ -83,6 +93,10 @@ const TagOperation = ({
)
const scrollIntoView = useCallback(() => {
+ if (!isBrowser) {
+ return
+ }
+
if (nodeRef.current && !checkElementInViewport(nodeRef.current, 0)) {
const elm = nodeRef.current as HTMLElement
scrollToTop(
@@ -91,7 +105,7 @@ const TagOperation = ({
)
}
setShow(true)
- }, [scrollToTop, nodeRef])
+ }, [scrollToTop, nodeRef, isBrowser])
useEffect(() => {
if (nodeRef && nodeRef.current) {
diff --git a/www/apps/api-reference/components/Tags/Paths/index.tsx b/www/apps/api-reference/components/Tags/Paths/index.tsx
index 1c19089857..431d2e92e3 100644
--- a/www/apps/api-reference/components/Tags/Paths/index.tsx
+++ b/www/apps/api-reference/components/Tags/Paths/index.tsx
@@ -1,21 +1,16 @@
"use client"
-import getSectionId from "@/utils/get-section-id"
import type { OpenAPIV3 } from "openapi-types"
-import useSWR from "swr"
import type { Operation, PathsObject } from "@/types/openapi"
-import { useSidebar, swrFetcher } from "docs-ui"
-import { Fragment, useEffect, useMemo } from "react"
+import { useSidebar } from "docs-ui"
+import { Fragment, useEffect } from "react"
import dynamic from "next/dynamic"
import type { TagOperationProps } from "../Operation"
-import { useArea } from "@/providers/area"
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"
import { SidebarItemSections, SidebarItem, SidebarItemCategory } from "types"
-import basePathUrl from "../../../utils/base-path-url"
const TagOperation = dynamic(
async () => import("../Operation")
@@ -23,35 +18,12 @@ const TagOperation = dynamic(
export type TagPathsProps = {
tag: OpenAPIV3.TagObject
+ paths: PathsObject
} & React.HTMLAttributes
-const TagPaths = ({ tag, className }: TagPathsProps) => {
- const tagSlugName = useMemo(() => getSectionId([tag.name]), [tag])
- const { area } = useArea()
+const TagPaths = ({ tag, className, paths }: TagPathsProps) => {
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 route
- let paths: PathsObject =
- baseSpecs?.expandedTags &&
- Object.hasOwn(baseSpecs.expandedTags, tagSlugName)
- ? baseSpecs.expandedTags[tagSlugName]
- : {}
- const { data } = useSWR<{
- paths: PathsObject
- }>(
- !Object.keys(paths).length
- ? basePathUrl(`/api/tag?tagName=${tagSlugName}&area=${area}`)
- : null,
- swrFetcher,
- {
- errorRetryInterval: 2000,
- }
- )
-
- paths = data?.paths || paths
useEffect(() => {
if (paths) {
diff --git a/www/apps/api-reference/components/Tags/Section/Schema/index.tsx b/www/apps/api-reference/components/Tags/Section/Schema/index.tsx
index c542fe2db9..29a12d258c 100644
--- a/www/apps/api-reference/components/Tags/Section/Schema/index.tsx
+++ b/www/apps/api-reference/components/Tags/Section/Schema/index.tsx
@@ -1,10 +1,13 @@
-import { useEffect, useMemo, useRef, useState } from "react"
+"use client"
+
+import { useEffect, useMemo, useRef } from "react"
import { SchemaObject } from "../../../../types/openapi"
import TagOperationParameters from "../../Operation/Parameters"
import {
Badge,
CodeBlock,
isElmWindow,
+ useIsBrowser,
useScrollController,
useSidebar,
} from "docs-ui"
@@ -16,7 +19,6 @@ import useSchemaExample from "../../../../hooks/use-schema-example"
import { InView } from "react-intersection-observer"
import checkElementInViewport from "../../../../utils/check-element-in-viewport"
import { singular } from "pluralize"
-import useResizeObserver from "@react-hook/resize-observer"
import clsx from "clsx"
export type TagSectionSchemaProps = {
@@ -26,7 +28,6 @@ export type TagSectionSchemaProps = {
const TagSectionSchema = ({ schema, tagName }: TagSectionSchemaProps) => {
const paramsRef = useRef(null)
- const [maxCodeHeight, setMaxCodeHeight] = useState(0)
const { addItems, setActivePath, activePath } = useSidebar()
const tagSlugName = useMemo(() => getSectionId([tagName]), [tagName])
const formattedName = useMemo(
@@ -43,14 +44,16 @@ const TagSectionSchema = ({ schema, tagName }: TagSectionSchemaProps) => {
skipNonRequired: false,
},
})
- useResizeObserver(paramsRef, () => {
- setMaxCodeHeight(paramsRef.current?.clientHeight || 0)
- })
+ const { isBrowser } = useIsBrowser()
const { scrollableElement, scrollToElement } = useScrollController()
const root = useMemo(() => {
+ if (!isBrowser) {
+ return
+ }
+
return isElmWindow(scrollableElement) ? document.body : scrollableElement
- }, [scrollableElement])
+ }, [isBrowser, scrollableElement])
useEffect(() => {
addItems(
@@ -77,18 +80,26 @@ const TagSectionSchema = ({ schema, tagName }: TagSectionSchemaProps) => {
}, [formattedName])
useEffect(() => {
+ if (!isBrowser) {
+ return
+ }
+
if (schemaSlug === (activePath || location.hash.replace("#", ""))) {
const elm = document.getElementById(schemaSlug) as HTMLElement
if (!checkElementInViewport(elm, 0)) {
scrollToElement(elm)
}
}
- }, [activePath, schemaSlug])
+ }, [activePath, schemaSlug, isBrowser])
const handleViewChange = (
inView: boolean,
entry: IntersectionObserverEntry
) => {
+ if (!isBrowser) {
+ return
+ }
+
const section = entry.target
if (
@@ -106,7 +117,7 @@ const TagSectionSchema = ({ schema, tagName }: TagSectionSchemaProps) => {
{
source={examples[0].content}
lang="json"
title={`The ${formattedName} Object`}
- className={clsx(maxCodeHeight && "overflow-auto")}
+ className={clsx("overflow-auto")}
style={{
- // remove padding + extra space
- maxHeight: maxCodeHeight ? maxCodeHeight - 212 : "unset",
+ maxHeight: "100vh",
}}
/>
)}
diff --git a/www/apps/api-reference/components/Tags/Section/index.tsx b/www/apps/api-reference/components/Tags/Section/index.tsx
index 291c4b5dd0..774dc4d34f 100644
--- a/www/apps/api-reference/components/Tags/Section/index.tsx
+++ b/www/apps/api-reference/components/Tags/Section/index.tsx
@@ -6,13 +6,13 @@ import { useEffect, useMemo, useState } from "react"
import {
isElmWindow,
swrFetcher,
+ useIsBrowser,
useScrollController,
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"
@@ -22,11 +22,12 @@ import clsx from "clsx"
import { Feedback, Loading, Link } from "docs-ui"
import { usePathname, useRouter } from "next/navigation"
import formatReportLink from "@/utils/format-report-link"
-import { SchemaObject, TagObject } from "@/types/openapi"
-import useSWR from "swr"
+import { PathsObject, SchemaObject, TagObject } from "@/types/openapi"
import { TagSectionSchemaProps } from "./Schema"
-import basePathUrl from "../../../utils/base-path-url"
import checkElementInViewport from "../../../utils/check-element-in-viewport"
+import TagPaths from "../Paths"
+import useSWR from "swr"
+import basePathUrl from "../../../utils/base-path-url"
export type TagSectionProps = {
tag: TagObject
@@ -47,39 +48,30 @@ const MDXContentClient = dynamic(
}
) as React.FC
-const TagSection = ({ tag }: TagSectionProps) => {
+const TagSectionComponent = ({ tag }: TagSectionProps) => {
const { activePath, setActivePath } = useSidebar()
const router = useRouter()
- const [loadPaths, setLoadPaths] = useState(false)
+ const [loadData, setLoadData] = useState(false)
const slugTagName = useMemo(() => getSectionId([tag.name]), [tag])
const { area } = useArea()
const pathname = usePathname()
const { scrollableElement, scrollToTop } = useScrollController()
- const { data } = useSWR<{
- schema: SchemaObject
- }>(
- tag["x-associatedSchema"]
- ? basePathUrl(
- `/api/schema?name=${tag["x-associatedSchema"].$ref}&area=${area}`
- )
- : null,
- swrFetcher,
- {
- errorRetryInterval: 2000,
- }
- )
- const associatedSchema = data?.schema
+ const { isBrowser } = useIsBrowser()
const root = useMemo(() => {
+ if (!isBrowser) {
+ return
+ }
+
return isElmWindow(scrollableElement) ? document.body : scrollableElement
- }, [scrollableElement])
- const { ref } = useInView({
+ }, [scrollableElement, isBrowser])
+ const { ref, inView } = useInView({
threshold: 0.8,
rootMargin: `112px 0px 112px 0px`,
root,
onChange: (inView) => {
- if (inView && !loadPaths) {
- setLoadPaths(true)
+ if (inView && !loadData) {
+ setLoadData(true)
}
if (inView) {
// ensure that the hash link doesn't change if it links to an inner path
@@ -97,8 +89,36 @@ const TagSection = ({ tag }: TagSectionProps) => {
}
},
})
+ const { data: schemaData } = useSWR<{
+ schema: SchemaObject
+ }>(
+ loadData && tag["x-associatedSchema"]
+ ? basePathUrl(
+ `/api/schema?name=${tag["x-associatedSchema"].$ref}&area=${area}`
+ )
+ : null,
+ swrFetcher,
+ {
+ errorRetryInterval: 2000,
+ }
+ )
+ const { data: pathsData } = useSWR<{
+ paths: PathsObject
+ }>(
+ loadData
+ ? basePathUrl(`/api/tag?tagName=${slugTagName}&area=${area}`)
+ : null,
+ swrFetcher,
+ {
+ errorRetryInterval: 2000,
+ }
+ )
useEffect(() => {
+ if (!isBrowser) {
+ return
+ }
+
if (activePath && activePath.includes(slugTagName)) {
const tagName = activePath.split("_")
if (tagName.length === 1 && tagName[0] === slugTagName) {
@@ -110,18 +130,18 @@ const TagSection = ({ tag }: TagSectionProps) => {
)
}
} else if (tagName.length > 1 && tagName[0] === slugTagName) {
- setLoadPaths(true)
+ setLoadData(true)
}
}
- }, [slugTagName, activePath])
+ }, [slugTagName, activePath, isBrowser])
return (
{tag.name}
@@ -159,17 +179,17 @@ const TagSection = ({ tag }: TagSectionProps) => {
}
codeContent={<>>}
/>
- {associatedSchema && (
-
+ {schemaData && (
+
)}
- {loadPaths && (
+ {loadData && pathsData && (
-
+
)}
- {!loadPaths && }
+ {!loadData && }
)
}
-export default TagSection
+export default TagSectionComponent
diff --git a/www/apps/api-reference/components/Tags/index.tsx b/www/apps/api-reference/components/Tags/index.tsx
index f38f3e3a60..baab29971f 100644
--- a/www/apps/api-reference/components/Tags/index.tsx
+++ b/www/apps/api-reference/components/Tags/index.tsx
@@ -1,120 +1,20 @@
-"use client"
-
-import type { OpenAPIV3 } from "openapi-types"
-import { useEffect, useState } from "react"
-import useSWR from "swr"
-import { useBaseSpecs } from "@/providers/base-specs"
+import { OpenAPIV3 } from "openapi-types"
+import { TagSectionProps } from "./Section"
import dynamic from "next/dynamic"
-import type { TagSectionProps } from "./Section"
-import { useArea } from "@/providers/area"
-import { swrFetcher, 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"
-import { SidebarItem, SidebarItemSections } from "types"
-import basePathUrl from "../../utils/base-path-url"
-import { useRouter } from "next/navigation"
const TagSection = dynamic(
async () => import("./Section")
) as React.FC
-export type TagsProps = React.HTMLAttributes
-
-function getCurrentTag() {
- return typeof location !== "undefined"
- ? location.hash.replace("#", "").split("_")[0]
- : ""
+type TagsProps = {
+ tags?: OpenAPIV3.TagObject[]
}
-const Tags = () => {
- const [tags, setTags] = useState([])
- const [loadData, setLoadData] = useState(false)
- const [expand, setExpand] = useState("")
- const { baseSpecs, setBaseSpecs } = useBaseSpecs()
- const { activePath, addItems, setActivePath } = useSidebar()
- const { area, prevArea } = useArea()
- const router = useRouter()
-
- const { data } = useSWR(
- loadData && !baseSpecs
- ? basePathUrl(`/api/base-specs?area=${area}&expand=${expand}`)
- : null,
- swrFetcher,
- {
- 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) {
- if (prevArea !== area) {
- setBaseSpecs(null)
- setLoadData(true)
- return
- }
-
- const itemsToAdd: SidebarItem[] = [
- {
- type: "separator",
- },
- ]
-
- if (baseSpecs.tags) {
- baseSpecs.tags.forEach((tag) => {
- const tagPathName = getSectionId([tag.name.toLowerCase()])
- const childItems =
- baseSpecs.expandedTags &&
- Object.hasOwn(baseSpecs.expandedTags, tagPathName)
- ? getTagChildSidebarItems(baseSpecs.expandedTags[tagPathName])
- : []
- itemsToAdd.push({
- type: "category",
- title: tag.name,
- children: childItems,
- loaded: childItems.length > 0,
- showLoadingIfEmpty: true,
- onOpen: () => {
- if (location.hash !== tagPathName) {
- router.push(`#${tagPathName}`, {
- scroll: false,
- })
- }
- if (activePath !== tagPathName) {
- setActivePath(tagPathName)
- }
- },
- })
- })
- }
-
- addItems(itemsToAdd, {
- section: SidebarItemSections.DEFAULT,
- })
- }
- }, [baseSpecs, prevArea, area])
-
+const Tags = ({ tags }: TagsProps) => {
return (
<>
- {tags.map((tag, index) => (
-
+ {tags?.map((tag) => (
+
))}
>
)
diff --git a/www/apps/api-reference/lib/index.ts b/www/apps/api-reference/lib/index.ts
new file mode 100644
index 0000000000..2584c817c5
--- /dev/null
+++ b/www/apps/api-reference/lib/index.ts
@@ -0,0 +1,20 @@
+"use server"
+
+import { Area, ExpandedDocument } from "../types/openapi"
+
+const URL = `${process.env.NEXT_PUBLIC_BASE_URL}${process.env.NEXT_PUBLIC_BASE_PATH}`
+
+export async function getBaseSpecs(area: Area) {
+ try {
+ const res = await fetch(`${URL}/api/base-specs?area=${area}`, {
+ next: {
+ revalidate: 3000,
+ tags: [area],
+ },
+ }).then(async (res) => res.json())
+
+ return res as ExpandedDocument
+ } catch (e) {
+ console.error(e)
+ }
+}
diff --git a/www/apps/api-reference/providers/base-specs.tsx b/www/apps/api-reference/providers/base-specs.tsx
index fbe43b37bd..b7c0ffca8d 100644
--- a/www/apps/api-reference/providers/base-specs.tsx
+++ b/www/apps/api-reference/providers/base-specs.tsx
@@ -1,28 +1,34 @@
"use client"
import { ExpandedDocument, SecuritySchemeObject } from "@/types/openapi"
-import { ReactNode, createContext, useContext, useState } from "react"
+import {
+ ReactNode,
+ createContext,
+ useCallback,
+ useContext,
+ useEffect,
+} from "react"
+import { SidebarItem, SidebarItemSections } from "types"
+import getSectionId from "../utils/get-section-id"
+import getTagChildSidebarItems from "../utils/get-tag-child-sidebar-items"
+import { useRouter } from "next/navigation"
+import { useSidebar } from "docs-ui"
type BaseSpecsContextType = {
- baseSpecs: ExpandedDocument | null
- setBaseSpecs: React.Dispatch>
+ baseSpecs: ExpandedDocument | undefined
getSecuritySchema: (securityName: string) => SecuritySchemeObject | null
}
const BaseSpecsContext = createContext(null)
type BaseSpecsProviderProps = {
- initialSpecs?: ExpandedDocument | null
+ baseSpecs: ExpandedDocument | undefined
children?: ReactNode
}
-const BaseSpecsProvider = ({
- children,
- initialSpecs = null,
-}: BaseSpecsProviderProps) => {
- const [baseSpecs, setBaseSpecs] = useState(
- initialSpecs
- )
+const BaseSpecsProvider = ({ children, baseSpecs }: BaseSpecsProviderProps) => {
+ const router = useRouter()
+ const { activePath, addItems, setActivePath, resetItems } = useSidebar()
const getSecuritySchema = (
securityName: string
@@ -43,11 +49,63 @@ const BaseSpecsProvider = ({
return null
}
+ const handleOpen = useCallback(
+ (tagPathName: string) => {
+ if (location.hash !== tagPathName) {
+ router.push(`#${tagPathName}`, {
+ scroll: false,
+ })
+ }
+ if (activePath !== tagPathName) {
+ setActivePath(tagPathName)
+ }
+ },
+ [activePath, router, setActivePath]
+ )
+
+ useEffect(() => {
+ if (!baseSpecs) {
+ return
+ }
+
+ const itemsToAdd: SidebarItem[] = [
+ {
+ type: "separator",
+ },
+ ]
+
+ baseSpecs.tags?.forEach((tag) => {
+ const tagPathName = getSectionId([tag.name.toLowerCase()])
+ const childItems =
+ baseSpecs.expandedTags &&
+ Object.hasOwn(baseSpecs.expandedTags, tagPathName)
+ ? getTagChildSidebarItems(baseSpecs.expandedTags[tagPathName])
+ : []
+ itemsToAdd.push({
+ type: "category",
+ title: tag.name,
+ children: childItems,
+ loaded: childItems.length > 0,
+ showLoadingIfEmpty: true,
+ onOpen: () => {
+ handleOpen(tagPathName)
+ },
+ })
+ })
+
+ addItems(itemsToAdd, {
+ section: SidebarItemSections.DEFAULT,
+ })
+
+ return () => {
+ resetItems()
+ }
+ }, [baseSpecs])
+
return (
diff --git a/www/apps/api-reference/providers/index.tsx b/www/apps/api-reference/providers/index.tsx
index a160fe596a..a95d278694 100644
--- a/www/apps/api-reference/providers/index.tsx
+++ b/www/apps/api-reference/providers/index.tsx
@@ -1,12 +1,9 @@
-"use client"
-
import {
AnalyticsProvider,
PageLoadingProvider,
ScrollControllerProvider,
SiteConfigProvider,
} from "docs-ui"
-import BaseSpecsProvider from "./base-specs"
import SidebarProvider from "./sidebar"
import SearchProvider from "./search"
import { config } from "../config"
@@ -21,15 +18,13 @@ const Providers = ({ children }: ProvidersProps) => {
-
-
-
-
- {children}
-
-
-
-
+
+
+
+ {children}
+
+
+
diff --git a/www/apps/api-reference/providers/sidebar.tsx b/www/apps/api-reference/providers/sidebar.tsx
index 9df31784dc..59c6a662c5 100644
--- a/www/apps/api-reference/providers/sidebar.tsx
+++ b/www/apps/api-reference/providers/sidebar.tsx
@@ -3,12 +3,9 @@
import {
SidebarProvider as UiSidebarProvider,
usePageLoading,
- usePrevious,
useScrollController,
} from "docs-ui"
import { config } from "../config"
-import { useCallback } from "react"
-import { usePathname } from "next/navigation"
type SidebarProviderProps = {
children?: React.ReactNode
@@ -17,13 +14,6 @@ type SidebarProviderProps = {
const SidebarProvider = ({ children }: SidebarProviderProps) => {
const { isLoading, setIsLoading } = usePageLoading()
const { scrollableElement } = useScrollController()
- const pathname = usePathname()
- const prevPathName = usePrevious(pathname)
-
- const resetOnCondition = useCallback(
- () => prevPathName !== undefined && pathname !== prevPathName,
- [pathname, prevPathName]
- )
return (
{
shouldHandleHashChange={true}
scrollableElement={scrollableElement}
initialItems={config.sidebar}
- resetOnCondition={resetOnCondition}
persistState={false}
projectName="api"
>
diff --git a/www/apps/api-reference/utils/dereference.ts b/www/apps/api-reference/utils/dereference.ts
index 9a1f10bac7..a96b0c9204 100644
--- a/www/apps/api-reference/utils/dereference.ts
+++ b/www/apps/api-reference/utils/dereference.ts
@@ -53,6 +53,9 @@ export default async function dereference({
canParse: /.*/,
},
},
+ dereference: {
+ circular: "ignore",
+ },
})) as unknown as Document
return document
diff --git a/www/apps/api-reference/utils/get-paths-of-tag.ts b/www/apps/api-reference/utils/get-paths-of-tag.ts
index 5d91cd51b5..012e4767c1 100644
--- a/www/apps/api-reference/utils/get-paths-of-tag.ts
+++ b/www/apps/api-reference/utils/get-paths-of-tag.ts
@@ -5,8 +5,9 @@ import type { Operation, Document, ParsedPathItemObject } from "@/types/openapi"
import readSpecDocument from "./read-spec-document"
import getSectionId from "./get-section-id"
import dereference from "./dereference"
+import { unstable_cache } from "next/cache"
-export default async function getPathsOfTag(
+async function getPathsOfTag_(
tagName: string,
area: string
): Promise {
@@ -47,3 +48,13 @@ export default async function getPathsOfTag(
paths: documents,
})
}
+
+const getPathsOfTag = unstable_cache(
+ async (tagName: string, area: string) => getPathsOfTag_(tagName, area),
+ ["tag-paths"],
+ {
+ revalidate: 3600,
+ }
+)
+
+export default getPathsOfTag
diff --git a/www/apps/api-reference/utils/get-schema-content.ts b/www/apps/api-reference/utils/get-schema-content.ts
new file mode 100644
index 0000000000..b261762a6c
--- /dev/null
+++ b/www/apps/api-reference/utils/get-schema-content.ts
@@ -0,0 +1,29 @@
+import { promises as fs } from "fs"
+import { parseDocument } from "yaml"
+import { SchemaObject } from "../types/openapi"
+import dereference from "./dereference"
+import { unstable_cache } from "next/cache"
+
+async function getSchemaContent_(schemaPath: string, baseSchemasPath: string) {
+ const schemaContent = await fs.readFile(schemaPath, "utf-8")
+ const schema = parseDocument(schemaContent).toJS() as SchemaObject
+
+ // resolve references in schema
+ const dereferencedDocument = await dereference({
+ basePath: baseSchemasPath,
+ schemas: [schema],
+ })
+
+ return {
+ dereferencedDocument,
+ originalSchema: schema,
+ }
+}
+
+const getSchemaContent = unstable_cache(
+ async (schemaPath: string, baseSchemasPath: string) =>
+ getSchemaContent_(schemaPath, baseSchemasPath),
+ ["tag-schema"]
+)
+
+export default getSchemaContent
diff --git a/www/packages/docs-ui/src/components/RootProviders/index.tsx b/www/packages/docs-ui/src/components/RootProviders/index.tsx
index f11830c7f3..f472756909 100644
--- a/www/packages/docs-ui/src/components/RootProviders/index.tsx
+++ b/www/packages/docs-ui/src/components/RootProviders/index.tsx
@@ -1,5 +1,3 @@
-"use client"
-
import React from "react"
import {
BrowserProvider,
diff --git a/www/packages/docs-ui/src/components/Sidebar/index.tsx b/www/packages/docs-ui/src/components/Sidebar/index.tsx
index 306e6c0e88..e90978df0a 100644
--- a/www/packages/docs-ui/src/components/Sidebar/index.tsx
+++ b/www/packages/docs-ui/src/components/Sidebar/index.tsx
@@ -134,14 +134,22 @@ export const Sidebar = ({
{!sidebarItems.default.length && !staticSidebarItems && (
)}
- {sidebarItems.default.map((item, index) => (
-
- ))}
+ {sidebarItems.default.map((item, index) => {
+ const itemKey =
+ item.type === "separator"
+ ? index
+ : item.type === "link"
+ ? `${item.path}-${index}`
+ : `${item.title}-${index}`
+ return (
+
+ )
+ })}
diff --git a/www/packages/docs-ui/src/providers/Sidebar/index.tsx b/www/packages/docs-ui/src/providers/Sidebar/index.tsx
index 4d35acb38d..b62602e58c 100644
--- a/www/packages/docs-ui/src/providers/Sidebar/index.tsx
+++ b/www/packages/docs-ui/src/providers/Sidebar/index.tsx
@@ -40,7 +40,8 @@ export type SidebarContextType = {
setActivePath: (path: string | null) => void
isLinkActive: (item: SidebarItem, checkChildren?: boolean) => boolean
isChildrenActive: (item: SidebarItemCategory) => boolean
- addItems: (item: SidebarItem[], options?: ActionOptionsType) => void
+ addItems: (items: SidebarItem[], options?: ActionOptionsType) => void
+ removeItems: (items: SidebarItem[]) => void
findItemInSection: (
section: SidebarItem[],
item: Partial,
@@ -87,6 +88,10 @@ export type ActionType =
type: "replace"
replacementItems: SidebarSectionItems
}
+ | {
+ type: "remove"
+ items: SidebarItem[]
+ }
type LinksMap = Map
@@ -152,10 +157,46 @@ const getLinksMap = (
return map
}
-export const reducer = (state: SidebarSectionItems, actionData: ActionType) => {
+export const reducer = (
+ state: SidebarSectionItems,
+ actionData: ActionType
+): SidebarSectionItems => {
if (actionData.type === "replace") {
return actionData.replacementItems
}
+ if (actionData.type === "remove") {
+ return {
+ ...state,
+ default: state.default.filter((item) => {
+ if (item.type === "separator") {
+ return true
+ }
+
+ const found = actionData.items.some((itemToRemove) => {
+ if (
+ itemToRemove.type === "separator" ||
+ itemToRemove.type !== item.type
+ ) {
+ return false
+ }
+
+ if (
+ itemToRemove.type === "category" ||
+ itemToRemove.type === "sub-category"
+ ) {
+ return itemToRemove.title === item.title
+ }
+
+ return (
+ itemToRemove.path === (item as SidebarItemLink).path &&
+ itemToRemove.title === (item as SidebarItemLink).title
+ )
+ })
+
+ return !found
+ }),
+ }
+ }
const { type, options } = actionData
let { items } = actionData
@@ -306,6 +347,13 @@ export const SidebarProvider = ({
})
}
+ const removeItems = (itemsToRemove: SidebarItem[]) => {
+ dispatch({
+ type: "remove",
+ items: itemsToRemove,
+ })
+ }
+
const isLinkActive = useCallback(
(item: SidebarItem, checkChildren = false): boolean => {
if (item.type !== "link") {
@@ -516,6 +564,7 @@ export const SidebarProvider = ({
useEffect(() => {
if (resetOnCondition?.()) {
resetItems()
+ setActivePath(null)
}
}, [resetOnCondition, resetItems])
@@ -575,6 +624,7 @@ export const SidebarProvider = ({
items,
currentItems,
addItems,
+ removeItems,
activePath,
setActivePath,
isLinkActive: isLinkActive,