mirror of
https://github.com/supabase/supabase.git
synced 2026-10-17 23:29:08 +03:00
checkpoint 1
This commit is contained in:
1 parent
3fa447d57d
commit
3a4e4ad1ef
26 files changed
+2802
-3
No files matched your search
@@ -0,0 +1,110 @@
|
||||
import { useParams } from 'common'
|
||||
import { Badge } from 'ui'
|
||||
|
||||
import { useBucketsQuery } from 'data/storage/buckets-query'
|
||||
import { formatBytes } from 'lib/helpers'
|
||||
import { useAppStateSnapshot } from 'state/app-state'
|
||||
import { DOCS_RESOURCE_CONTENT } from '../ProjectAPIDocs.constants'
|
||||
import ResourceContent from '../ResourceContent'
|
||||
import type { ContentProps } from './Content.types'
|
||||
|
||||
const Bucket = ({ language, apikey, endpoint }: ContentProps) => {
|
||||
const { ref } = useParams()
|
||||
const snap = useAppStateSnapshot()
|
||||
const { data } = useBucketsQuery({ projectRef: ref })
|
||||
|
||||
const resource = snap.activeDocsSection[1]
|
||||
const buckets = data ?? []
|
||||
const bucket = buckets.find((b) => b.name === resource)
|
||||
const allowedMimeTypes = bucket?.allowed_mime_types
|
||||
const maxFileSizeLimit = bucket?.file_size_limit
|
||||
|
||||
if (bucket === undefined) return null
|
||||
|
||||
return (
|
||||
<div className="divide-y">
|
||||
<div className="space-y-1 px-4 py-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<h2 className="text-xl">{bucket.name}</h2>
|
||||
<Badge variant={bucket.public ? 'warning' : 'default'}>
|
||||
{bucket.public ? 'Public' : 'Private'}
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-sm text-foreground-light">
|
||||
Allowed MIME types:{' '}
|
||||
{allowedMimeTypes === null
|
||||
? 'All types are allowed'
|
||||
: (allowedMimeTypes ?? []).length === 0
|
||||
? 'No types are allowed'
|
||||
: (allowedMimeTypes ?? []).length > 1
|
||||
? (allowedMimeTypes ?? []).join(', ')
|
||||
: 'Unknown'}
|
||||
</p>
|
||||
<p className="text-sm text-foreground-light">
|
||||
Max file size limit:{' '}
|
||||
{maxFileSizeLimit === null ? 'No limit' : `${formatBytes(maxFileSizeLimit)}`}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ResourceContent
|
||||
selectedLanguage={language}
|
||||
snippet={DOCS_RESOURCE_CONTENT.uploadFile}
|
||||
codeSnippets={DOCS_RESOURCE_CONTENT.uploadFile.code({
|
||||
name: resource,
|
||||
apikey,
|
||||
endpoint,
|
||||
})}
|
||||
/>
|
||||
<ResourceContent
|
||||
selectedLanguage={language}
|
||||
snippet={DOCS_RESOURCE_CONTENT.deleteFiles}
|
||||
codeSnippets={DOCS_RESOURCE_CONTENT.deleteFiles.code({
|
||||
name: resource,
|
||||
apikey,
|
||||
endpoint,
|
||||
})}
|
||||
/>
|
||||
<ResourceContent
|
||||
selectedLanguage={language}
|
||||
snippet={DOCS_RESOURCE_CONTENT.listFiles}
|
||||
codeSnippets={DOCS_RESOURCE_CONTENT.listFiles.code({
|
||||
name: resource,
|
||||
apikey,
|
||||
endpoint,
|
||||
})}
|
||||
/>
|
||||
<ResourceContent
|
||||
selectedLanguage={language}
|
||||
snippet={DOCS_RESOURCE_CONTENT.downloadFile}
|
||||
codeSnippets={DOCS_RESOURCE_CONTENT.downloadFile.code({
|
||||
name: resource,
|
||||
apikey,
|
||||
endpoint,
|
||||
})}
|
||||
/>
|
||||
{bucket.public ? (
|
||||
<ResourceContent
|
||||
selectedLanguage={language}
|
||||
snippet={DOCS_RESOURCE_CONTENT.retrievePublicURL}
|
||||
codeSnippets={DOCS_RESOURCE_CONTENT.retrievePublicURL.code({
|
||||
name: resource,
|
||||
apikey,
|
||||
endpoint,
|
||||
})}
|
||||
/>
|
||||
) : (
|
||||
<ResourceContent
|
||||
selectedLanguage={language}
|
||||
snippet={DOCS_RESOURCE_CONTENT.createSignedURL}
|
||||
codeSnippets={DOCS_RESOURCE_CONTENT.createSignedURL.code({
|
||||
name: resource,
|
||||
apikey,
|
||||
endpoint,
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Bucket
|
||||
@@ -0,0 +1,6 @@
|
||||
export interface ContentProps {
|
||||
showKeys?: boolean
|
||||
language: 'js' | 'bash'
|
||||
endpoint?: string
|
||||
apikey?: string
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
export const navigateToSection = (key: string) => {
|
||||
if (typeof window !== 'undefined') {
|
||||
const el = document.getElementById(key)
|
||||
if (el) el.scrollIntoView({ behavior: 'smooth' })
|
||||
}
|
||||
}
|
||||
|
||||
// Removes some auto-generated Postgrest text
|
||||
// Ideally PostgREST wouldn't add this if there is already a comment
|
||||
export const tempRemovePostgrestText = (content: string) => {
|
||||
const postgrestTextPk = `Note:\nThis is a Primary Key.<pk/>`
|
||||
const postgrestTextFk = `Note:\nThis is a Foreign Key to`
|
||||
const pkTextPos = content.lastIndexOf(postgrestTextPk)
|
||||
const fkTextPos = content.lastIndexOf(postgrestTextFk)
|
||||
|
||||
let cleansed = content
|
||||
if (pkTextPos >= 0) cleansed = cleansed.substring(0, pkTextPos)
|
||||
if (fkTextPos >= 0) cleansed = cleansed.substring(0, fkTextPos)
|
||||
return cleansed
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { useParams } from 'common'
|
||||
|
||||
import { useEdgeFunctionsQuery } from 'data/edge-functions/edge-functions-query'
|
||||
import { useAppStateSnapshot } from 'state/app-state'
|
||||
import { DOCS_RESOURCE_CONTENT } from '../ProjectAPIDocs.constants'
|
||||
import ResourceContent from '../ResourceContent'
|
||||
import type { ContentProps } from './Content.types'
|
||||
|
||||
const Bucket = ({ language, apikey = 'API_KEY', endpoint }: ContentProps) => {
|
||||
const { ref } = useParams()
|
||||
const snap = useAppStateSnapshot()
|
||||
const { data } = useEdgeFunctionsQuery({ projectRef: ref })
|
||||
|
||||
const resource = snap.activeDocsSection[1]
|
||||
const edgeFunctions = data ?? []
|
||||
const edgeFunction = edgeFunctions.find((fn) => fn.name === resource)
|
||||
|
||||
if (edgeFunction === undefined) return null
|
||||
|
||||
return (
|
||||
<div className="divide-y">
|
||||
<div className="space-y-1 px-4 py-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<h2 className="text-xl">{edgeFunction.name}</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ResourceContent
|
||||
selectedLanguage={language}
|
||||
snippet={DOCS_RESOURCE_CONTENT.invokeEdgeFunction}
|
||||
codeSnippets={DOCS_RESOURCE_CONTENT.invokeEdgeFunction.code({
|
||||
name: resource,
|
||||
endpoint: `${endpoint}`,
|
||||
apikey: apikey,
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Bucket
|
||||
@@ -0,0 +1,16 @@
|
||||
import ContentSnippet from '../ContentSnippet'
|
||||
import { DOCS_CONTENT } from '../ProjectAPIDocs.constants'
|
||||
import type { ContentProps } from './Content.types'
|
||||
|
||||
const EdgeFunctions = ({ language }: ContentProps) => {
|
||||
return (
|
||||
<>
|
||||
<ContentSnippet selectedLanguage={language} snippet={DOCS_CONTENT.edgeFunctions} />
|
||||
<ContentSnippet selectedLanguage={language} snippet={DOCS_CONTENT.edgeFunctionsPreReq} />
|
||||
<ContentSnippet selectedLanguage={language} snippet={DOCS_CONTENT.createEdgeFunction} />
|
||||
<ContentSnippet selectedLanguage={language} snippet={DOCS_CONTENT.deployEdgeFunction} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default EdgeFunctions
|
||||
@@ -0,0 +1,69 @@
|
||||
import Link from 'next/link'
|
||||
import { useState } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { useParams } from 'common'
|
||||
import { generateTypes } from 'data/projects/project-type-generation-query'
|
||||
import { Button, IconDownload, IconExternalLink } from 'ui'
|
||||
import ContentSnippet from '../ContentSnippet'
|
||||
import { DOCS_CONTENT } from '../ProjectAPIDocs.constants'
|
||||
import type { ContentProps } from './Content.types'
|
||||
|
||||
const Entities = ({ language }: ContentProps) => {
|
||||
const { ref } = useParams()
|
||||
const [isGeneratingTypes, setIsGeneratingTypes] = useState(false)
|
||||
|
||||
const onClickGenerateTypes = async () => {
|
||||
try {
|
||||
setIsGeneratingTypes(true)
|
||||
const res = await generateTypes({ ref })
|
||||
let element = document.createElement('a')
|
||||
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(res.types))
|
||||
element.setAttribute('download', 'supabase.ts')
|
||||
element.style.display = 'none'
|
||||
document.body.appendChild(element)
|
||||
element.click()
|
||||
document.body.removeChild(element)
|
||||
toast.success(`Successfully generated types! File is being downloaded`)
|
||||
} catch (error: any) {
|
||||
toast.error(`Failed to generate types: ${error.message}`)
|
||||
} finally {
|
||||
setIsGeneratingTypes(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<ContentSnippet selectedLanguage={language} snippet={DOCS_CONTENT.entitiesIntroduction} />
|
||||
<div>
|
||||
<ContentSnippet selectedLanguage={language} snippet={DOCS_CONTENT.generatingTypes} />
|
||||
<div className="flex items-center space-x-2 px-4 mt-3">
|
||||
<Button asChild type="default" icon={<IconExternalLink />}>
|
||||
<Link
|
||||
href="https://supabase.com/docs/guides/database/api/generating-types"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Documentation
|
||||
</Link>
|
||||
</Button>
|
||||
<Button
|
||||
type="default"
|
||||
disabled={isGeneratingTypes}
|
||||
loading={isGeneratingTypes}
|
||||
icon={<IconDownload strokeWidth={1.5} />}
|
||||
onClick={onClickGenerateTypes}
|
||||
>
|
||||
Generate and download types
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-xs text-foreground-light px-4 mt-2">
|
||||
Remember to re-generate and download this file as you make changes to your tables.
|
||||
</p>
|
||||
</div>
|
||||
<ContentSnippet selectedLanguage={language} snippet={DOCS_CONTENT.graphql} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Entities
|
||||
@@ -0,0 +1,148 @@
|
||||
import { useParams } from 'common'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
import Table from 'components/to-be-cleaned/Table'
|
||||
import { useProjectJsonSchemaQuery } from 'data/docs/project-json-schema-query'
|
||||
import { useAppStateSnapshot } from 'state/app-state'
|
||||
import { DOCS_RESOURCE_CONTENT } from '../ProjectAPIDocs.constants'
|
||||
import ResourceContent from '../ResourceContent'
|
||||
import type { ContentProps } from './Content.types'
|
||||
import { tempRemovePostgrestText } from './Content.utils'
|
||||
import LanguageSelector from '../LanguageSelector'
|
||||
|
||||
function getColumnType(type: string, format: string) {
|
||||
// json and jsonb both have type=undefined, so check format instead
|
||||
if (type === undefined && (format === 'jsonb' || format === 'json')) return 'json'
|
||||
|
||||
switch (type) {
|
||||
case 'string':
|
||||
return 'string'
|
||||
case 'integer':
|
||||
return 'number'
|
||||
case 'json':
|
||||
return 'json'
|
||||
case 'number':
|
||||
return 'number'
|
||||
case 'boolean':
|
||||
return 'boolean'
|
||||
default:
|
||||
return type
|
||||
}
|
||||
}
|
||||
|
||||
const Entity = ({ language, apikey = '', endpoint = '' }: ContentProps) => {
|
||||
const { ref } = useParams()
|
||||
const snap = useAppStateSnapshot()
|
||||
const resource = snap.activeDocsSection[1]
|
||||
|
||||
const { data: jsonSchema, refetch } = useProjectJsonSchemaQuery({ projectRef: ref })
|
||||
|
||||
const definition = jsonSchema?.definitions?.[resource]
|
||||
const columns =
|
||||
definition !== undefined
|
||||
? Object.entries(definition.properties).map(([id, val]: any) => ({
|
||||
...val,
|
||||
id,
|
||||
required: (definition?.required ?? []).includes(id),
|
||||
}))
|
||||
: []
|
||||
|
||||
useEffect(() => {
|
||||
if (resource !== undefined) refetch()
|
||||
}, [resource])
|
||||
|
||||
if (resource === undefined) return null
|
||||
|
||||
return (
|
||||
<div className="divide-y relative">
|
||||
<div className="flex items-center justify-between px-4 py-4 sticky top-0 bg-surface-100 z-10 border-b shadow-md">
|
||||
<div className="flex flex-col gap-y-1">
|
||||
<h2 className="text-xl">{resource}</h2>
|
||||
<p className="text-sm text-foreground-light">
|
||||
{definition?.description ?? 'No description available'}
|
||||
</p>
|
||||
</div>
|
||||
<LanguageSelector />
|
||||
</div>
|
||||
<div className="space-y-2 px-4 py-4 !border-t-0">
|
||||
<p className="text-sm text-foreground-light">Columns</p>
|
||||
<Table
|
||||
head={[
|
||||
<Table.th key="name">Name</Table.th>,
|
||||
<Table.th key="format">Format</Table.th>,
|
||||
<Table.th key="type">Type</Table.th>,
|
||||
<Table.th key="description">Description</Table.th>,
|
||||
]}
|
||||
body={columns.map((column) => {
|
||||
const formattedColumnType = getColumnType(column.type, column.format)
|
||||
return (
|
||||
<Table.tr key={column.id}>
|
||||
<Table.td title={column.id}>{column.id}</Table.td>
|
||||
<Table.td title={column.format}>
|
||||
<p className="truncate">{column.format}</p>
|
||||
</Table.td>
|
||||
<Table.td title={formattedColumnType}>{formattedColumnType}</Table.td>
|
||||
<Table.td title={column.description}>
|
||||
{tempRemovePostgrestText(column.description ?? '').trim()}
|
||||
</Table.td>
|
||||
</Table.tr>
|
||||
)
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ResourceContent
|
||||
selectedLanguage={language}
|
||||
snippet={DOCS_RESOURCE_CONTENT.readRows}
|
||||
codeSnippets={DOCS_RESOURCE_CONTENT.readRows.code({
|
||||
resourceId: resource,
|
||||
endpoint,
|
||||
apikey,
|
||||
})}
|
||||
/>
|
||||
<ResourceContent
|
||||
selectedLanguage={language}
|
||||
snippet={DOCS_RESOURCE_CONTENT.filtering}
|
||||
codeSnippets={DOCS_RESOURCE_CONTENT.filtering.code({
|
||||
resourceId: resource,
|
||||
endpoint,
|
||||
apikey,
|
||||
})}
|
||||
/>
|
||||
<ResourceContent
|
||||
selectedLanguage={language}
|
||||
snippet={DOCS_RESOURCE_CONTENT.insertRows}
|
||||
codeSnippets={DOCS_RESOURCE_CONTENT.insertRows.code({
|
||||
resourceId: resource,
|
||||
endpoint,
|
||||
apikey,
|
||||
})}
|
||||
/>
|
||||
<ResourceContent
|
||||
selectedLanguage={language}
|
||||
snippet={DOCS_RESOURCE_CONTENT.updateRows}
|
||||
codeSnippets={DOCS_RESOURCE_CONTENT.updateRows.code({
|
||||
resourceId: resource,
|
||||
endpoint,
|
||||
apikey,
|
||||
})}
|
||||
/>
|
||||
<ResourceContent
|
||||
selectedLanguage={language}
|
||||
snippet={DOCS_RESOURCE_CONTENT.deleteRows}
|
||||
codeSnippets={DOCS_RESOURCE_CONTENT.deleteRows.code({
|
||||
resourceId: resource,
|
||||
endpoint,
|
||||
apikey,
|
||||
})}
|
||||
/>
|
||||
<ResourceContent
|
||||
selectedLanguage={language}
|
||||
snippet={DOCS_RESOURCE_CONTENT.subscribeChanges}
|
||||
codeSnippets={DOCS_RESOURCE_CONTENT.subscribeChanges.code({ resourceId: resource })}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Entity
|
||||
@@ -0,0 +1,115 @@
|
||||
import { useParams } from 'common'
|
||||
import { Button, IconCopy, Input } from 'ui'
|
||||
|
||||
import { useProjectApiQuery } from 'data/config/project-api-query'
|
||||
import ContentSnippet from '../ContentSnippet'
|
||||
import { DOCS_CONTENT } from '../ProjectAPIDocs.constants'
|
||||
import type { ContentProps } from './Content.types'
|
||||
import { copyToClipboard } from 'lib/helpers'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
const Introduction = ({ showKeys, language, apikey, endpoint }: ContentProps) => {
|
||||
const { ref } = useParams()
|
||||
const { data } = useProjectApiQuery({ projectRef: ref })
|
||||
|
||||
const [copied, setCopied] = useState<'anon' | 'service'>()
|
||||
|
||||
useEffect(() => {
|
||||
if (copied !== undefined) setTimeout(() => setCopied(undefined), 2000)
|
||||
}, [copied])
|
||||
|
||||
const serviceKey = showKeys
|
||||
? data?.autoApiService.serviceApiKey ?? 'SUPABASE_CLIENT_SERVICE_KEY'
|
||||
: 'SUPABASE_CLIENT_SERVICE_KEY'
|
||||
|
||||
return (
|
||||
<>
|
||||
<ContentSnippet
|
||||
selectedLanguage={language}
|
||||
apikey={apikey}
|
||||
endpoint={endpoint}
|
||||
snippet={DOCS_CONTENT.init}
|
||||
>
|
||||
<div className="px-4 space-y-6">
|
||||
<div className="flex space-x-4 mt-8">
|
||||
<p className="text-sm w-40">Project URL</p>
|
||||
<Input disabled readOnly copy size="small" value={endpoint} className="w-full" />
|
||||
</div>
|
||||
<div className="flex space-x-4">
|
||||
<p className="text-sm w-40">Client API key</p>
|
||||
<Input
|
||||
disabled
|
||||
readOnly
|
||||
size="small"
|
||||
value={showKeys ? apikey : 'Reveal API keys via dropdown in the header'}
|
||||
className="w-full"
|
||||
descriptionText="This key is safe to use in a browser if you have enabled Row Level Security (RLS) for your tables and configured policies."
|
||||
actions={[
|
||||
<Button
|
||||
key="copy"
|
||||
type="default"
|
||||
icon={<IconCopy />}
|
||||
onClick={() => {
|
||||
setCopied('anon')
|
||||
copyToClipboard(
|
||||
data?.autoApiService.defaultApiKey ?? 'SUPABASE_CLIENT_ANON_KEY'
|
||||
)
|
||||
}}
|
||||
>
|
||||
{copied === 'anon' ? 'Copied' : 'Copy'}
|
||||
</Button>,
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex space-x-4">
|
||||
<p className="text-sm w-40 mb-16">Service key</p>
|
||||
<Input
|
||||
disabled
|
||||
readOnly
|
||||
size="small"
|
||||
value={showKeys ? serviceKey : 'Reveal API keys via dropdown in the header'}
|
||||
className="w-full"
|
||||
descriptionText={
|
||||
<p>
|
||||
This key has the ability to bypass Row Level Security.{' '}
|
||||
<span className="text-amber-900">Never share it publicly.</span>
|
||||
</p>
|
||||
}
|
||||
actions={[
|
||||
<Button
|
||||
key="copy"
|
||||
type="default"
|
||||
icon={<IconCopy />}
|
||||
onClick={() => {
|
||||
setCopied('service')
|
||||
copyToClipboard(
|
||||
data?.autoApiService.serviceApiKey ?? 'SUPABASE_CLIENT_SERVICE_KEY'
|
||||
)
|
||||
}}
|
||||
>
|
||||
{copied === 'service' ? 'Copied' : 'Copy'}
|
||||
</Button>,
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ContentSnippet>
|
||||
|
||||
<ContentSnippet
|
||||
selectedLanguage={language}
|
||||
apikey={apikey}
|
||||
endpoint={endpoint}
|
||||
snippet={DOCS_CONTENT.clientApiKeys}
|
||||
/>
|
||||
|
||||
<ContentSnippet
|
||||
selectedLanguage={language}
|
||||
apikey={serviceKey}
|
||||
endpoint={endpoint}
|
||||
snippet={DOCS_CONTENT.serviceApiKeys}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Introduction
|
||||
@@ -0,0 +1,103 @@
|
||||
import { useParams } from 'common'
|
||||
import { useEffect } from 'react'
|
||||
import { Badge } from 'ui'
|
||||
|
||||
import Table from 'components/to-be-cleaned/Table'
|
||||
import { useProjectJsonSchemaQuery } from 'data/docs/project-json-schema-query'
|
||||
import { useOpenAPISpecQuery } from 'data/open-api/api-spec-query'
|
||||
import { useAppStateSnapshot } from 'state/app-state'
|
||||
import { DOCS_RESOURCE_CONTENT } from '../ProjectAPIDocs.constants'
|
||||
import ResourceContent from '../ResourceContent'
|
||||
import type { ContentProps } from './Content.types'
|
||||
|
||||
export const RPC = ({ language }: ContentProps) => {
|
||||
const { ref } = useParams()
|
||||
const snap = useAppStateSnapshot()
|
||||
|
||||
const { data: jsonSchema, refetch: refetchJsonSchema } = useProjectJsonSchemaQuery({
|
||||
projectRef: ref,
|
||||
})
|
||||
const { data, refetch: refetchOpenAPISpec } = useOpenAPISpecQuery({ projectRef: ref })
|
||||
const functions = data?.functions ?? []
|
||||
|
||||
const rpcName = snap.activeDocsSection[1]
|
||||
const rpc = functions.find((fn) => fn.name === rpcName)
|
||||
|
||||
const {
|
||||
post: { parameters: postParameters },
|
||||
} = rpc ?? {}
|
||||
const rpcJsonSchema = jsonSchema?.paths[rpc?.path]
|
||||
const summary = rpcJsonSchema?.post?.summary
|
||||
|
||||
const rpcParamsObject =
|
||||
postParameters &&
|
||||
postParameters[0] &&
|
||||
postParameters[0].schema &&
|
||||
postParameters[0].schema.properties
|
||||
? postParameters[0].schema.properties
|
||||
: {}
|
||||
const parameters: { name: string; format: string; type: string; required: boolean }[] =
|
||||
Object.entries(rpcParamsObject)
|
||||
.map(([k, v]: any) => ({
|
||||
name: k,
|
||||
...v,
|
||||
required: postParameters[0].schema.required.includes(k),
|
||||
}))
|
||||
.filter((x) => !!x.name)
|
||||
|
||||
useEffect(() => {
|
||||
if (rpcName !== undefined) {
|
||||
refetchJsonSchema()
|
||||
refetchOpenAPISpec()
|
||||
}
|
||||
}, [rpcName])
|
||||
|
||||
if (rpc === undefined) return null
|
||||
|
||||
return (
|
||||
<div className="divide-y">
|
||||
<div className="space-y-1 px-4 py-4">
|
||||
<h2 className="text-xl">{rpc.name}</h2>
|
||||
<p className="text-sm text-foreground-light">{summary ?? 'No description available'}</p>
|
||||
</div>
|
||||
<div className="space-y-2 px-4 py-4">
|
||||
<p className="text-sm text-foreground-light">Function arguments</p>
|
||||
<Table
|
||||
head={[
|
||||
<Table.th key="name">Name</Table.th>,
|
||||
<Table.th key="format">Format</Table.th>,
|
||||
<Table.th key="type">Type</Table.th>,
|
||||
<Table.th key="required"></Table.th>,
|
||||
]}
|
||||
body={parameters.map((parameter) => (
|
||||
<Table.tr key={parameter.name}>
|
||||
<Table.td title={parameter.name}>
|
||||
<p className="font-mono text-xs text-foreground truncate">{parameter.name}</p>
|
||||
</Table.td>
|
||||
<Table.td title={parameter.format}>{parameter.format}</Table.td>
|
||||
<Table.td title={parameter.type}>{parameter.type}</Table.td>
|
||||
<Table.td>
|
||||
{parameter.required ? (
|
||||
<Badge variant="warning">Required</Badge>
|
||||
) : (
|
||||
<Badge variant="default">Optional</Badge>
|
||||
)}
|
||||
</Table.td>
|
||||
</Table.tr>
|
||||
))}
|
||||
/>
|
||||
</div>
|
||||
<ResourceContent
|
||||
selectedLanguage={language}
|
||||
snippet={DOCS_RESOURCE_CONTENT.rpcSingle}
|
||||
codeSnippets={DOCS_RESOURCE_CONTENT.rpcSingle.code({
|
||||
rpcName,
|
||||
rpcParams: parameters,
|
||||
endpoint: 'endpoint',
|
||||
apiKey: 'apiKey',
|
||||
showBearer: true,
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import ContentSnippet from '../ContentSnippet'
|
||||
import { DOCS_CONTENT } from '../ProjectAPIDocs.constants'
|
||||
import type { ContentProps } from './Content.types'
|
||||
|
||||
const Realtime = ({ language }: ContentProps) => {
|
||||
return (
|
||||
<>
|
||||
<ContentSnippet selectedLanguage={language} snippet={DOCS_CONTENT.realtime} />
|
||||
<ContentSnippet selectedLanguage={language} snippet={DOCS_CONTENT.subscribeChannel} />
|
||||
<ContentSnippet selectedLanguage={language} snippet={DOCS_CONTENT.unsubscribeChannel} />
|
||||
<ContentSnippet selectedLanguage={language} snippet={DOCS_CONTENT.unsubscribeChannels} />
|
||||
<ContentSnippet selectedLanguage={language} snippet={DOCS_CONTENT.retrieveAllChannels} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Realtime
|
||||
@@ -0,0 +1,13 @@
|
||||
import ContentSnippet from '../ContentSnippet'
|
||||
import { DOCS_CONTENT } from '../ProjectAPIDocs.constants'
|
||||
import type { ContentProps } from './Content.types'
|
||||
|
||||
const Storage = ({ language }: ContentProps) => {
|
||||
return (
|
||||
<>
|
||||
<ContentSnippet selectedLanguage={language} snippet={DOCS_CONTENT.storage} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Storage
|
||||
@@ -0,0 +1,16 @@
|
||||
import ContentSnippet from '../ContentSnippet'
|
||||
import { DOCS_CONTENT } from '../ProjectAPIDocs.constants'
|
||||
import type { ContentProps } from './Content.types'
|
||||
|
||||
const StoredProcedures = ({ language }: ContentProps) => {
|
||||
return (
|
||||
<>
|
||||
<ContentSnippet
|
||||
selectedLanguage={language}
|
||||
snippet={DOCS_CONTENT.storedProceduresIntroduction}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default StoredProcedures
|
||||
@@ -0,0 +1,85 @@
|
||||
import ContentSnippet from '../ContentSnippet'
|
||||
import { DOCS_CONTENT } from '../ProjectAPIDocs.constants'
|
||||
import type { ContentProps } from './Content.types'
|
||||
|
||||
const UserManagement = ({ language, apikey, endpoint }: ContentProps) => {
|
||||
return (
|
||||
<>
|
||||
<ContentSnippet selectedLanguage={language} snippet={DOCS_CONTENT.userManagement} />
|
||||
<ContentSnippet
|
||||
selectedLanguage={language}
|
||||
apikey={apikey}
|
||||
endpoint={endpoint}
|
||||
snippet={DOCS_CONTENT.signUp}
|
||||
/>
|
||||
<ContentSnippet
|
||||
selectedLanguage={language}
|
||||
apikey={apikey}
|
||||
endpoint={endpoint}
|
||||
snippet={DOCS_CONTENT.emailLogin}
|
||||
/>
|
||||
<ContentSnippet
|
||||
selectedLanguage={language}
|
||||
apikey={apikey}
|
||||
endpoint={endpoint}
|
||||
snippet={DOCS_CONTENT.magicLinkLogin}
|
||||
/>
|
||||
<ContentSnippet
|
||||
selectedLanguage={language}
|
||||
apikey={apikey}
|
||||
endpoint={endpoint}
|
||||
snippet={DOCS_CONTENT.phoneLogin}
|
||||
/>
|
||||
<ContentSnippet
|
||||
selectedLanguage={language}
|
||||
apikey={apikey}
|
||||
endpoint={endpoint}
|
||||
snippet={DOCS_CONTENT.smsLogin}
|
||||
/>
|
||||
<ContentSnippet
|
||||
selectedLanguage={language}
|
||||
apikey={apikey}
|
||||
endpoint={endpoint}
|
||||
snippet={DOCS_CONTENT.smsVerify}
|
||||
/>
|
||||
<ContentSnippet
|
||||
selectedLanguage={language}
|
||||
apikey={apikey}
|
||||
endpoint={endpoint}
|
||||
snippet={DOCS_CONTENT.oauthLogin}
|
||||
/>
|
||||
<ContentSnippet
|
||||
selectedLanguage={language}
|
||||
apikey={apikey}
|
||||
endpoint={endpoint}
|
||||
snippet={DOCS_CONTENT.user}
|
||||
/>
|
||||
<ContentSnippet
|
||||
selectedLanguage={language}
|
||||
apikey={apikey}
|
||||
endpoint={endpoint}
|
||||
snippet={DOCS_CONTENT.forgotPassWordEmail}
|
||||
/>
|
||||
<ContentSnippet
|
||||
selectedLanguage={language}
|
||||
apikey={apikey}
|
||||
endpoint={endpoint}
|
||||
snippet={DOCS_CONTENT.updateUser}
|
||||
/>
|
||||
<ContentSnippet
|
||||
selectedLanguage={language}
|
||||
apikey={apikey}
|
||||
endpoint={endpoint}
|
||||
snippet={DOCS_CONTENT.logout}
|
||||
/>
|
||||
<ContentSnippet
|
||||
selectedLanguage={language}
|
||||
apikey={apikey}
|
||||
endpoint={endpoint}
|
||||
snippet={DOCS_CONTENT.emailInvite}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default UserManagement
|
||||
@@ -0,0 +1,12 @@
|
||||
export { default as EdgeFunctions } from './EdgeFunctions'
|
||||
export { default as Entities } from './Entities'
|
||||
export { default as Introduction } from './Introduction'
|
||||
export { default as Realtime } from './Realtime'
|
||||
export { default as Storage } from './Storage'
|
||||
export { default as StoredProcedures } from './StoredProcedures'
|
||||
export { default as UserManagement } from './UserManagement'
|
||||
|
||||
export { default as Bucket } from './Bucket'
|
||||
export { default as EdgeFunction } from './EdgeFunction'
|
||||
export { default as Entity } from './Entity'
|
||||
export { RPC } from './RPC'
|
||||
@@ -0,0 +1,60 @@
|
||||
import { useParams } from 'common'
|
||||
|
||||
import SimpleCodeBlock from 'components/to-be-cleaned/SimpleCodeBlock'
|
||||
import { Markdown } from '../Markdown'
|
||||
import { PropsWithChildren } from 'react'
|
||||
|
||||
interface ContentSnippetProps {
|
||||
apikey?: string
|
||||
endpoint?: string
|
||||
selectedLanguage: 'js' | 'bash'
|
||||
snippet: {
|
||||
key: string
|
||||
category: string
|
||||
title: string
|
||||
description?: string
|
||||
js?: (apikey?: string, endpoint?: string) => string
|
||||
bash?: (apikey?: string, endpoint?: string) => string
|
||||
}
|
||||
}
|
||||
|
||||
const ContentSnippet = ({
|
||||
apikey,
|
||||
endpoint,
|
||||
selectedLanguage,
|
||||
snippet,
|
||||
children,
|
||||
}: PropsWithChildren<ContentSnippetProps>) => {
|
||||
const { ref: projectRef } = useParams()
|
||||
const codeSnippet = snippet[selectedLanguage]?.(apikey, endpoint).replaceAll(
|
||||
'[ref]',
|
||||
projectRef ?? ''
|
||||
)
|
||||
|
||||
return (
|
||||
<div id={snippet.key} className="space-y-4 py-6 pb-2 last:pb-6">
|
||||
<div className="px-4 space-y-4">
|
||||
<h2 className="doc-heading">{snippet.title}</h2>
|
||||
{snippet.description !== undefined && (
|
||||
<div className="doc-section">
|
||||
<article className="text text-sm text-foreground-light">
|
||||
<Markdown
|
||||
className="max-w-none"
|
||||
content={snippet.description.replaceAll('[ref]', projectRef ?? '_')}
|
||||
/>
|
||||
</article>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{children}
|
||||
{codeSnippet !== undefined && (
|
||||
<div className="px-4 codeblock-container">
|
||||
<div className="bg rounded p-2">
|
||||
<SimpleCodeBlock className={selectedLanguage}>{codeSnippet}</SimpleCodeBlock>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default ContentSnippet
|
||||
@@ -0,0 +1,209 @@
|
||||
import { useParams } from 'common'
|
||||
import Link from 'next/link'
|
||||
import { Fragment } from 'react'
|
||||
import SVG from 'react-inlinesvg'
|
||||
|
||||
import { useEdgeFunctionsQuery } from 'data/edge-functions/edge-functions-query'
|
||||
import { useOpenAPISpecQuery } from 'data/open-api/api-spec-query'
|
||||
import { useBucketsQuery } from 'data/storage/buckets-query'
|
||||
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
|
||||
import { BASE_PATH } from 'lib/constants'
|
||||
import { useAppStateSnapshot } from 'state/app-state'
|
||||
import { Button, IconBook, IconBookOpen } from 'ui'
|
||||
import { navigateToSection } from './Content/Content.utils'
|
||||
import { DOCS_CONTENT, DOCS_MENU } from './ProjectAPIDocs.constants'
|
||||
|
||||
const Separator = () => <div className="border-t !mt-3 pb-1 mx-3" />
|
||||
|
||||
const FirstLevelNav = () => {
|
||||
const { ref } = useParams()
|
||||
const snap = useAppStateSnapshot()
|
||||
|
||||
const {
|
||||
projectAuthAll: authEnabled,
|
||||
projectStorageAll: storageEnabled,
|
||||
projectEdgeFunctionAll: edgeFunctionsEnabled,
|
||||
realtimeAll: realtimeEnabled,
|
||||
} = useIsFeatureEnabled([
|
||||
'project_auth:all',
|
||||
'project_storage:all',
|
||||
'project_edge_function:all',
|
||||
'realtime:all',
|
||||
])
|
||||
|
||||
const docsMenu = DOCS_MENU.filter((item) => {
|
||||
if (item.key === 'user-management') return authEnabled
|
||||
if (item.key === 'storage') return storageEnabled
|
||||
if (item.key === 'edge-functions') return edgeFunctionsEnabled
|
||||
if (item.key === 'realtime') return realtimeEnabled
|
||||
return true
|
||||
})
|
||||
|
||||
const { data } = useOpenAPISpecQuery({ projectRef: ref })
|
||||
const tables = data?.tables ?? []
|
||||
const functions = data?.functions ?? []
|
||||
|
||||
const { data: buckets } = useBucketsQuery({ projectRef: ref })
|
||||
const { data: edgeFunctions } = useEdgeFunctionsQuery({ projectRef: ref })
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="px-2 py-4 border-b">
|
||||
{docsMenu.map((item) => {
|
||||
const isActive = snap.activeDocsSection[0] === item.key
|
||||
const sections = Object.values(DOCS_CONTENT).filter(
|
||||
(snippet) => snippet.category === item.key
|
||||
)
|
||||
|
||||
// [Joshen] Need to find the right UI component for accessbility
|
||||
return (
|
||||
<Fragment key={item.key}>
|
||||
<div
|
||||
className={`cursor-pointer text-sm py-2 px-3 rounded-md transition ${
|
||||
isActive ? 'bg-surface-300' : ''
|
||||
}`}
|
||||
onClick={() => snap.setActiveDocsSection([item.key])}
|
||||
>
|
||||
{item.name}
|
||||
</div>
|
||||
{isActive && sections.length > 0 && (
|
||||
<div className="space-y-2 py-2">
|
||||
{sections.map((section) => (
|
||||
<p
|
||||
key={section.key}
|
||||
title={section.title}
|
||||
className="text-sm text-foreground-light px-4 hover:text-foreground transition cursor-pointer"
|
||||
onClick={() => {
|
||||
snap.setActiveDocsSection([item.key])
|
||||
navigateToSection(section.key)
|
||||
}}
|
||||
>
|
||||
{section.title}
|
||||
</p>
|
||||
))}
|
||||
{item.key === 'entities' && (
|
||||
<>
|
||||
{tables.length > 0 && <Separator />}
|
||||
{tables.map((table) => (
|
||||
<p
|
||||
key={table.name}
|
||||
title={table.name}
|
||||
className="text-sm text-foreground-light px-4 hover:text-foreground transition cursor-pointer"
|
||||
onClick={() => snap.setActiveDocsSection([item.key, table.name])}
|
||||
>
|
||||
{table.name}
|
||||
</p>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
{item.key === 'stored-procedures' && (
|
||||
<>
|
||||
{functions.length > 0 && <Separator />}
|
||||
{functions.map((fn) => (
|
||||
<p
|
||||
key={fn.name}
|
||||
title={fn.name}
|
||||
className="text-sm text-foreground-light px-4 hover:text-foreground transition cursor-pointer"
|
||||
onClick={() => snap.setActiveDocsSection([item.key, fn.name])}
|
||||
>
|
||||
{fn.name}
|
||||
</p>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
{item.key === 'storage' && (
|
||||
<>
|
||||
{(buckets ?? []).length > 0 && <Separator />}
|
||||
{(buckets ?? []).map((bucket) => (
|
||||
<p
|
||||
key={bucket.name}
|
||||
title={bucket.name}
|
||||
className="text-sm text-foreground-light px-4 hover:text-foreground transition cursor-pointer"
|
||||
onClick={() => snap.setActiveDocsSection([item.key, bucket.name])}
|
||||
>
|
||||
{bucket.name}
|
||||
</p>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
{item.key === 'edge-functions' && (
|
||||
<>
|
||||
{(edgeFunctions ?? []).length > 0 && <Separator />}
|
||||
{(edgeFunctions ?? []).map((fn) => (
|
||||
<p
|
||||
key={fn.name}
|
||||
title={fn.name}
|
||||
className="text-sm text-foreground-light px-4 hover:text-foreground transition cursor-pointer"
|
||||
onClick={() => snap.setActiveDocsSection([item.key, fn.name])}
|
||||
>
|
||||
{fn.name}
|
||||
</p>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Fragment>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="px-2 py-4 border-b">
|
||||
<Button
|
||||
block
|
||||
asChild
|
||||
type="text"
|
||||
size="small"
|
||||
icon={
|
||||
<SVG
|
||||
src={`${BASE_PATH}/img/graphql.svg`}
|
||||
style={{ width: `${16}px`, height: `${16}px` }}
|
||||
className="text-foreground"
|
||||
preProcessor={(code) => code.replace(/svg/, 'svg class="m-auto text-color-inherit"')}
|
||||
/>
|
||||
}
|
||||
onClick={() => snap.setShowProjectApiDocs(false)}
|
||||
>
|
||||
<Link className="!justify-start" href={`/project/${ref}/api/graphiql`}>
|
||||
GraphiQL
|
||||
</Link>
|
||||
</Button>
|
||||
<Button block asChild type="text" size="small" icon={<IconBookOpen />}>
|
||||
<Link
|
||||
href="https://supabase.com/docs/guides/graphql"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="!justify-start"
|
||||
>
|
||||
GraphQL guide
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="px-2 py-4">
|
||||
<Button block asChild type="text" size="small" icon={<IconBook />}>
|
||||
<Link
|
||||
href="https://supabase.com/docs"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="!justify-start"
|
||||
>
|
||||
Documentation
|
||||
</Link>
|
||||
</Button>
|
||||
<Button block asChild type="text" size="small" icon={<IconBookOpen />}>
|
||||
<Link
|
||||
href="https://supabase.com/docs/guides/api"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="!justify-start"
|
||||
>
|
||||
REST guide
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default FirstLevelNav
|
||||
@@ -0,0 +1,87 @@
|
||||
import { BASE_PATH } from 'lib/constants'
|
||||
import { useState } from 'react'
|
||||
import { useAppStateSnapshot } from 'state/app-state'
|
||||
import {
|
||||
Button,
|
||||
CommandGroup_Shadcn_,
|
||||
CommandItem_Shadcn_,
|
||||
CommandList_Shadcn_,
|
||||
Command_Shadcn_,
|
||||
IconChevronDown,
|
||||
IconTerminal,
|
||||
PopoverContent_Shadcn_,
|
||||
PopoverTrigger_Shadcn_,
|
||||
Popover_Shadcn_,
|
||||
} from 'ui'
|
||||
|
||||
interface LanguageSelectorProps {
|
||||
/**
|
||||
* Only show icons to represent the language
|
||||
*/
|
||||
simplifiedVersion?: boolean
|
||||
}
|
||||
|
||||
const LanguageSelector = ({ simplifiedVersion = false }: LanguageSelectorProps) => {
|
||||
const snap = useAppStateSnapshot()
|
||||
const [showLanguage, setShowLanguage] = useState(false)
|
||||
|
||||
const updateLanguage = (value: 'js' | 'bash') => {
|
||||
snap.setDocsLanguage(value)
|
||||
setShowLanguage(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Popover_Shadcn_ modal={false} open={showLanguage} onOpenChange={setShowLanguage}>
|
||||
<PopoverTrigger_Shadcn_ asChild>
|
||||
<Button
|
||||
type="default"
|
||||
className={simplifiedVersion ? 'px-1' : ''}
|
||||
icon={
|
||||
simplifiedVersion ? (
|
||||
snap.docsLanguage === 'js' ? (
|
||||
<img
|
||||
src={`${BASE_PATH}/img/libraries/javascript-icon.svg`}
|
||||
alt={`javascript logo`}
|
||||
width="14"
|
||||
/>
|
||||
) : (
|
||||
<IconTerminal size={14} strokeWidth={2.5} />
|
||||
)
|
||||
) : undefined
|
||||
}
|
||||
iconRight={!simplifiedVersion && <IconChevronDown size={14} strokeWidth={2} />}
|
||||
>
|
||||
{!simplifiedVersion
|
||||
? `Language: ${snap.docsLanguage === 'js' ? 'Javascript' : 'Bash'}`
|
||||
: undefined}
|
||||
</Button>
|
||||
</PopoverTrigger_Shadcn_>
|
||||
<PopoverContent_Shadcn_ className="p-0 w-24" side="bottom" align="end">
|
||||
<Command_Shadcn_>
|
||||
<CommandList_Shadcn_>
|
||||
<CommandGroup_Shadcn_>
|
||||
<CommandItem_Shadcn_
|
||||
className="cursor-pointer"
|
||||
onSelect={() => updateLanguage('js')}
|
||||
onClick={() => updateLanguage('js')}
|
||||
>
|
||||
<p>Javascript</p>
|
||||
</CommandItem_Shadcn_>
|
||||
<CommandItem_Shadcn_
|
||||
className="cursor-pointer"
|
||||
onSelect={() => updateLanguage('bash')}
|
||||
onClick={() => updateLanguage('bash')}
|
||||
>
|
||||
<p>Bash</p>
|
||||
</CommandItem_Shadcn_>
|
||||
</CommandGroup_Shadcn_>
|
||||
</CommandList_Shadcn_>
|
||||
</Command_Shadcn_>
|
||||
</PopoverContent_Shadcn_>
|
||||
</Popover_Shadcn_>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LanguageSelector
|
||||
@@ -0,0 +1,40 @@
|
||||
import { useParams } from 'common'
|
||||
import type { CommandOptions } from 'ui-patterns/CommandMenu'
|
||||
import { useRegisterCommands } from 'ui-patterns/CommandMenu'
|
||||
import { COMMAND_MENU_SECTIONS } from '../App/CommandMenu/CommandMenu.utils'
|
||||
|
||||
export function useApiDocsGotoCommands(options?: CommandOptions) {
|
||||
let { ref } = useParams()
|
||||
ref ||= '_'
|
||||
|
||||
useRegisterCommands(
|
||||
COMMAND_MENU_SECTIONS.NAVIGATE,
|
||||
[
|
||||
{
|
||||
id: 'nav-api',
|
||||
name: 'Project API Docs',
|
||||
route: `/project/${ref}/api`,
|
||||
defaultHidden: true,
|
||||
},
|
||||
{
|
||||
id: 'nav-api-auth',
|
||||
name: 'Auth Docs',
|
||||
route: `/project/${ref}/api?page=auth`,
|
||||
defaultHidden: true,
|
||||
},
|
||||
{
|
||||
id: 'nav-api-user-management',
|
||||
name: 'User Management Docs',
|
||||
route: `/project/${ref}/api?page=users`,
|
||||
defaultHidden: true,
|
||||
},
|
||||
{
|
||||
id: 'nav-api-graphql',
|
||||
name: 'GraphQL Docs',
|
||||
route: `/project/${ref}/api/graphiql`,
|
||||
defaultHidden: true,
|
||||
},
|
||||
],
|
||||
{ ...options, deps: [ref] }
|
||||
)
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,140 @@
|
||||
import { useState } from 'react'
|
||||
import { Button, SidePanel } from 'ui'
|
||||
|
||||
import { useParams } from 'common'
|
||||
import { useProjectApiQuery } from 'data/config/project-api-query'
|
||||
import { useCustomDomainsQuery } from 'data/custom-domains/custom-domains-query'
|
||||
import { useAppStateSnapshot } from 'state/app-state'
|
||||
import {
|
||||
Bucket,
|
||||
EdgeFunction,
|
||||
EdgeFunctions,
|
||||
Entities,
|
||||
Entity,
|
||||
Introduction,
|
||||
RPC,
|
||||
Realtime,
|
||||
Storage,
|
||||
StoredProcedures,
|
||||
UserManagement,
|
||||
} from './Content'
|
||||
import FirstLevelNav from './FirstLevelNav'
|
||||
import LanguageSelector from './LanguageSelector'
|
||||
import SecondLevelNav from './SecondLevelNav'
|
||||
|
||||
/**
|
||||
* [Joshen] Reminder: when we choose to release this as a main feature
|
||||
* Ensure that UX is better than the existing, and make sure we do the
|
||||
* necessary communications around releasing this.
|
||||
*
|
||||
* Problems:
|
||||
* - Needs URL support
|
||||
* - Language selector is not clear, users are missing the bash language option
|
||||
* - GraphiQL needs a better home, cannot be placed under Database as its "API"
|
||||
*/
|
||||
|
||||
const ProjectAPIDocs = () => {
|
||||
const { ref } = useParams()
|
||||
const snap = useAppStateSnapshot()
|
||||
const isEntityDocs =
|
||||
snap.activeDocsSection.length === 2 && snap.activeDocsSection[0] === 'entities'
|
||||
|
||||
const [showKeys, setShowKeys] = useState(false)
|
||||
const language = snap.docsLanguage
|
||||
|
||||
const { data } = useProjectApiQuery({ projectRef: ref })
|
||||
const { data: customDomainData } = useCustomDomainsQuery({ projectRef: ref })
|
||||
|
||||
const apikey = showKeys
|
||||
? data?.autoApiService.defaultApiKey ?? 'SUPABASE_CLIENT_ANON_KEY'
|
||||
: 'SUPABASE_CLIENT_ANON_KEY'
|
||||
const endpoint =
|
||||
customDomainData?.customDomain?.status === 'active'
|
||||
? `https://${customDomainData.customDomain?.hostname}`
|
||||
: `https://${data?.autoApiService.endpoint ?? ''}`
|
||||
|
||||
return (
|
||||
<SidePanel
|
||||
hideFooter
|
||||
size="xxlarge"
|
||||
className="max-w-5xl"
|
||||
visible={snap.showProjectApiDocs}
|
||||
onCancel={() => snap.setShowProjectApiDocs(false)}
|
||||
>
|
||||
<div className="flex items-start h-full">
|
||||
<div className="w-72 border-r h-full">
|
||||
<div className="border-b px-4 py-2 flex items-center justify-between">
|
||||
<h4>API Docs</h4>
|
||||
<div className="flex items-center space-x-1">
|
||||
{!isEntityDocs && <LanguageSelector simplifiedVersion />}
|
||||
<Button type="default" onClick={() => setShowKeys(!showKeys)}>
|
||||
{showKeys ? 'Hide keys' : 'Show keys'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{snap.activeDocsSection.length === 1 ? <FirstLevelNav /> : <SecondLevelNav />}
|
||||
</div>
|
||||
|
||||
<div className="flex-1 divide-y space-y-4 max-h-screen overflow-auto">
|
||||
{snap.activeDocsSection[0] === 'introduction' && (
|
||||
<Introduction
|
||||
showKeys={showKeys}
|
||||
language={language}
|
||||
apikey={apikey}
|
||||
endpoint={endpoint}
|
||||
/>
|
||||
)}
|
||||
|
||||
{snap.activeDocsSection[0] === 'user-management' && (
|
||||
<UserManagement language={language} apikey={apikey} endpoint={endpoint} />
|
||||
)}
|
||||
|
||||
{snap.activeDocsSection[0] === 'realtime' && <Realtime language={language} />}
|
||||
|
||||
{snap.activeDocsSection[0] === 'storage' && (
|
||||
<>
|
||||
{snap.activeDocsSection[1] !== undefined ? (
|
||||
<Bucket language={language} apikey={apikey} endpoint={endpoint} />
|
||||
) : (
|
||||
<Storage language={language} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{snap.activeDocsSection[0] === 'edge-functions' && (
|
||||
<>
|
||||
{snap.activeDocsSection[1] !== undefined ? (
|
||||
<EdgeFunction language={language} apikey={apikey} endpoint={endpoint} />
|
||||
) : (
|
||||
<EdgeFunctions language={language} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{snap.activeDocsSection[0] === 'entities' && (
|
||||
<>
|
||||
{snap.activeDocsSection[1] !== undefined ? (
|
||||
<Entity language={language} apikey={apikey} endpoint={endpoint} />
|
||||
) : (
|
||||
<Entities language={language} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{snap.activeDocsSection[0] === 'stored-procedures' && (
|
||||
<>
|
||||
{snap.activeDocsSection[1] !== undefined ? (
|
||||
<RPC language={language} />
|
||||
) : (
|
||||
<StoredProcedures language={language} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</SidePanel>
|
||||
)
|
||||
}
|
||||
|
||||
export default ProjectAPIDocs
|
||||
@@ -0,0 +1,61 @@
|
||||
import { useParams } from 'common'
|
||||
import Link from 'next/link'
|
||||
import { Button, IconExternalLink } from 'ui'
|
||||
|
||||
import SimpleCodeBlock from 'components/to-be-cleaned/SimpleCodeBlock'
|
||||
import { Markdown } from '../Markdown'
|
||||
|
||||
interface ResourceContentProps {
|
||||
selectedLanguage: 'js' | 'bash'
|
||||
snippet: {
|
||||
key: string
|
||||
title: string
|
||||
description?: string
|
||||
docsUrl?: string
|
||||
}
|
||||
codeSnippets: any[]
|
||||
}
|
||||
|
||||
const ResourceContent = ({ selectedLanguage, snippet, codeSnippets }: ResourceContentProps) => {
|
||||
const { ref: projectRef } = useParams()
|
||||
|
||||
return (
|
||||
<div id={snippet.key} className="space-y-4 py-6">
|
||||
<div className="px-4 space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="doc-heading">{snippet.title}</h2>
|
||||
{snippet.docsUrl !== undefined && (
|
||||
<Button asChild type="default" icon={<IconExternalLink />}>
|
||||
<Link href={snippet.docsUrl} target="_blank" rel="noreferrer">
|
||||
Documentation
|
||||
</Link>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{snippet.description !== undefined && (
|
||||
<div className="doc-section">
|
||||
<article className="text text-sm text-foreground-light">
|
||||
<Markdown
|
||||
className="max-w-none"
|
||||
content={snippet.description.replaceAll('[ref]', projectRef ?? '_')}
|
||||
/>
|
||||
</article>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{codeSnippets.map((codeSnippet) => (
|
||||
<div key={codeSnippet.key} className="px-4 space-y-2">
|
||||
<p className="text-sm text-foreground-light">{codeSnippet.title}</p>
|
||||
<div className="codeblock-container">
|
||||
<div className="bg rounded p-2">
|
||||
<SimpleCodeBlock className={selectedLanguage}>
|
||||
{codeSnippet[selectedLanguage]}
|
||||
</SimpleCodeBlock>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default ResourceContent
|
||||
@@ -0,0 +1,178 @@
|
||||
import { useParams } from 'common'
|
||||
import Link from 'next/link'
|
||||
import { useState } from 'react'
|
||||
import {
|
||||
AlertDescription_Shadcn_,
|
||||
AlertTitle_Shadcn_,
|
||||
Alert_Shadcn_,
|
||||
Button,
|
||||
CommandGroup_Shadcn_,
|
||||
CommandItem_Shadcn_,
|
||||
CommandList_Shadcn_,
|
||||
Command_Shadcn_,
|
||||
IconChevronLeft,
|
||||
IconCode,
|
||||
IconExternalLink,
|
||||
PopoverContent_Shadcn_,
|
||||
PopoverTrigger_Shadcn_,
|
||||
Popover_Shadcn_,
|
||||
} from 'ui'
|
||||
|
||||
import { useEdgeFunctionsQuery } from 'data/edge-functions/edge-functions-query'
|
||||
import { useOpenAPISpecQuery } from 'data/open-api/api-spec-query'
|
||||
import { useBucketsQuery } from 'data/storage/buckets-query'
|
||||
import { useAppStateSnapshot } from 'state/app-state'
|
||||
import { useIsAPIDocsSidePanelEnabled } from '../App/FeaturePreview/FeaturePreviewContext'
|
||||
import { navigateToSection } from './Content/Content.utils'
|
||||
import { DOCS_RESOURCE_CONTENT } from './ProjectAPIDocs.constants'
|
||||
|
||||
const SecondLevelNav = () => {
|
||||
const { ref } = useParams()
|
||||
const snap = useAppStateSnapshot()
|
||||
const [open, setOpen] = useState(false)
|
||||
const isNewAPIDocsEnabled = useIsAPIDocsSidePanelEnabled()
|
||||
|
||||
const { data } = useOpenAPISpecQuery({ projectRef: ref })
|
||||
const tables = data?.tables ?? []
|
||||
const functions = data?.functions ?? []
|
||||
const [section, resource] = snap.activeDocsSection
|
||||
|
||||
const { data: buckets } = useBucketsQuery({ projectRef: ref })
|
||||
const { data: edgeFunctions } = useEdgeFunctionsQuery({ projectRef: ref })
|
||||
const bucket = (buckets ?? []).find((b) => b.name === resource)
|
||||
|
||||
const content: { [key: string]: { title: string; options: any[]; docsUrl: string } } = {
|
||||
entities: {
|
||||
title: 'Tables & Views',
|
||||
options: tables,
|
||||
docsUrl: 'https://supabase.com/docs/reference/javascript/select',
|
||||
},
|
||||
'stored-procedures': {
|
||||
title: 'Stored Procedures',
|
||||
options: functions,
|
||||
docsUrl: 'https://supabase.com/docs/reference/javascript/rpc',
|
||||
},
|
||||
storage: {
|
||||
title: 'Storage',
|
||||
options: buckets ?? [],
|
||||
docsUrl: 'https://supabase.com/docs/reference/javascript/storage-createbucket',
|
||||
},
|
||||
'edge-functions': {
|
||||
title: 'Edge Functions',
|
||||
options: edgeFunctions ?? [],
|
||||
docsUrl: 'https://supabase.com/docs/reference/javascript/functions-invoke',
|
||||
},
|
||||
}
|
||||
|
||||
const updateSection = (value: string) => {
|
||||
snap.setActiveDocsSection([snap.activeDocsSection[0], value])
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
const menuItems = Object.values(DOCS_RESOURCE_CONTENT).filter(
|
||||
(content) => content.category === snap.activeDocsSection[0]
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="py-4">
|
||||
<div className="px-4 flex items-center space-x-2 mb-2">
|
||||
{isNewAPIDocsEnabled && (
|
||||
<Button
|
||||
type="text"
|
||||
icon={<IconChevronLeft />}
|
||||
className="px-1"
|
||||
onClick={() => snap.setActiveDocsSection([snap.activeDocsSection[0]])}
|
||||
/>
|
||||
)}
|
||||
<p className="text-sm text-foreground-light capitalize">{content[section].title}</p>
|
||||
</div>
|
||||
|
||||
<Popover_Shadcn_ open={open} onOpenChange={setOpen} modal={false}>
|
||||
<PopoverTrigger_Shadcn_ asChild>
|
||||
<div className="px-4">
|
||||
<Button block type="default" size="small" className="[&>span]:w-full">
|
||||
<div>
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<p>{snap.activeDocsSection[1]}</p>
|
||||
<div>
|
||||
<IconCode className="rotate-90" strokeWidth={1.5} size={12} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</PopoverTrigger_Shadcn_>
|
||||
<PopoverContent_Shadcn_ className="p-0 w-60" side="bottom" align="center">
|
||||
<Command_Shadcn_>
|
||||
<CommandList_Shadcn_>
|
||||
<CommandGroup_Shadcn_>
|
||||
{content[section].options.map((option) => (
|
||||
<CommandItem_Shadcn_
|
||||
key={option.name}
|
||||
className="cursor-pointer"
|
||||
onSelect={() => updateSection(option.name)}
|
||||
onClick={() => updateSection(option.name)}
|
||||
>
|
||||
<p>{option.name}</p>
|
||||
</CommandItem_Shadcn_>
|
||||
))}
|
||||
</CommandGroup_Shadcn_>
|
||||
</CommandList_Shadcn_>
|
||||
</Command_Shadcn_>
|
||||
</PopoverContent_Shadcn_>
|
||||
</Popover_Shadcn_>
|
||||
|
||||
<div className="px-2 py-4 space-y-2">
|
||||
{menuItems.map((item) => {
|
||||
if (section === 'storage' && bucket !== undefined) {
|
||||
if (
|
||||
(!bucket.public && item.key === 'retrieve-public-url') ||
|
||||
(bucket.public && item.key === 'create-signed-url')
|
||||
)
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<p
|
||||
key={item.key}
|
||||
title={item.title}
|
||||
className="text-sm text-foreground-light px-4 hover:text-foreground transition cursor-pointer"
|
||||
onClick={() => navigateToSection(item.key)}
|
||||
>
|
||||
{item.title}
|
||||
</p>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="px-4 py-4 border-t space-y-2">
|
||||
<Alert_Shadcn_ className="p-3">
|
||||
<AlertTitle_Shadcn_>
|
||||
<p className="text-xs">Unable to find what you're looking for?</p>
|
||||
</AlertTitle_Shadcn_>
|
||||
<AlertDescription_Shadcn_ className="space-y-1">
|
||||
<p className="text-xs !leading-normal">
|
||||
The API methods shown here are only the commonly used ones to get you started building
|
||||
quickly.
|
||||
</p>
|
||||
<p className="text-xs !leading-normal">
|
||||
Head over to our docs site for the full API documentation.
|
||||
</p>
|
||||
<Button
|
||||
asChild
|
||||
className="!mt-2"
|
||||
size="tiny"
|
||||
type="default"
|
||||
icon={<IconExternalLink strokeWidth={1.5} />}
|
||||
>
|
||||
<Link href={content[section].docsUrl} target="_blank" rel="noreferrer">
|
||||
Documentation
|
||||
</Link>
|
||||
</Button>
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default SecondLevelNav
|
||||
@@ -90,7 +90,7 @@ const GlobalNavigationMenu: FC = () => {
|
||||
<div className="p-3 md:p-1">
|
||||
{section[0].menuItems?.map((menuItem, menuItemIndex) => (
|
||||
<Fragment key={`desktop-docs-menu-section-${menuItemIndex}-${menuItemIndex}`}>
|
||||
{menuItemIndex !== 0 && <MenubarSeparator className="bg-border-muted" />}
|
||||
{menuItemIndex !== 0 && <MenubarSeparator className="bg-border" />}
|
||||
{menuItem.map((item, itemIdx) =>
|
||||
!item.href ? (
|
||||
<div
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
IconMenuDevCli,
|
||||
IconSupport,
|
||||
IconTroubleshooting,
|
||||
IconAPIDocs,
|
||||
} from './MenuIcons'
|
||||
|
||||
function getMenuIcon(menuKey: string, width: number = 16, height: number = 16, className?: string) {
|
||||
@@ -87,6 +88,8 @@ function getMenuIcon(menuKey: string, width: number = 16, height: number = 16, c
|
||||
return <IconSupport width={width} height={height} className={className} />
|
||||
case 'contributing':
|
||||
return <IconTroubleshooting width={width} height={height} className={className} />
|
||||
case 'api-docs':
|
||||
return <IconAPIDocs width={width} height={height} className={className} />
|
||||
default:
|
||||
return <IconMenuPlatform width={width} height={height} className={className} />
|
||||
}
|
||||
|
||||
@@ -635,3 +635,26 @@ export function IconTroubleshooting({ width = 16, height = 16, className }: Home
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function IconAPIDocs({ width = 16, height = 16, className }: HomeMenuIcon) {
|
||||
return (
|
||||
<svg
|
||||
className={className}
|
||||
width={width}
|
||||
height={height}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"></path>
|
||||
<path d="M14 2v4a2 2 0 0 0 2 2h4"></path>
|
||||
<path d="M10 9H8"></path>
|
||||
<path d="M16 13H8"></path>
|
||||
<path d="M16 17H8"></path>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -140,13 +140,19 @@ export const GLOBAL_MENU_ITEMS: GlobalMenuItems = [
|
||||
label: 'CLI Commands',
|
||||
icon: 'reference-cli',
|
||||
href: '/reference/cli/introduction',
|
||||
level: 'reference_javascript',
|
||||
level: 'api',
|
||||
},
|
||||
{
|
||||
label: 'Management API',
|
||||
icon: 'reference-api',
|
||||
href: '/reference/api/introduction',
|
||||
level: 'reference_javascript',
|
||||
level: 'api',
|
||||
},
|
||||
{
|
||||
label: 'API Docs',
|
||||
icon: 'api-docs',
|
||||
href: '/reference/api/introduction',
|
||||
level: 'api',
|
||||
},
|
||||
],
|
||||
[
|
||||
|
||||
Reference in new issue
Block a user