Revert "feat: display api urls and keys instead of placeholders in do… (#20302)

Revert "feat: display api urls and keys instead of placeholders in docs (#19672)"

This reverts commit 0e8ee2e890.
This commit is contained in:
Charis authored and GitHub committed 2024-01-10 05:45:44 -05:00
1 parent c9a17980bd
commit 10da8f3f31
29 files changed
+110 -13671

No files matched your search

@@ -1,16 +1,58 @@
import { useTheme } from 'next-themes'
import Image from 'next/legacy/image'
import Link from 'next/link'
import { FC } from 'react'
import { useRouter } from 'next/router'
import { FC, useState } from 'react'
import { Button, IconCommand, IconGitHub, IconSearch, SearchButton } from 'ui'
import { useIsLoggedIn, useIsUserLoading } from 'common'
import { getPageType } from '~/lib/helpers'
import { REFERENCES } from './NavigationMenu.constants'
import ThemeToggle from '@ui/components/ThemeProvider/ThemeToggle'
const TopNavBar: FC = () => {
const isLoggedIn = useIsLoggedIn()
const isUserLoading = useIsUserLoading()
const { resolvedTheme } = useTheme()
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
const { asPath, push } = useRouter()
const pathSegments = asPath.split('/')
const library = pathSegments.length >= 3 ? pathSegments[2] : undefined
const libraryMeta = REFERENCES?.[library] ?? undefined
const versions = libraryMeta?.versions ?? []
const version = versions.includes(pathSegments[pathSegments.indexOf(library) + 1])
? pathSegments[pathSegments.indexOf(library) + 1]
: versions[0]
const pageType = getPageType(asPath)
const pageLinks = [
{ text: 'Guides', key: 'docs', link: '/' },
{ text: 'Reference', key: 'reference', link: '/reference' },
]
const onSelectVersion = (version: string) => {
// [Joshen] Ideally we use <Link> but this works for now
if (!library) return
if (version === versions[0]) {
push(`/reference/${library}`)
} else {
push(`/reference/${library}/${version}`)
}
}
// [Joshen] Kaizen: Use UI library's SidePanel for this
const toggleMobileMenu = () => {
setMobileMenuOpen(!mobileMenuOpen)
const sidebar = document.querySelector('.sidebar-menu-container')
const contentPane = document.querySelector('.main-content-pane')
sidebar.classList.toggle('hidden')
contentPane.classList.toggle('hidden')
}
return (
<nav className="h-[60px] border-b backdrop-blur backdrop-filter bg bg-opacity-75">
@@ -76,11 +118,6 @@ const TopNavBar: FC = () => {
</a>
</Button>
)}
{process.env.NEXT_PUBLIC_DEV_AUTH_PAGE === 'true' && (
<Button asChild>
<Link href="/__dev-secret-auth">Dev-only secret signin</Link>
</Button>
)}
<Link
href="https://github.com/supabase/supabase"
target="_blank"
@@ -1,451 +0,0 @@
import { useEffect, useMemo, useState } from 'react'
import { Check, ChevronsUpDown } from 'lucide-react'
import {
Button_Shadcn_ as Button,
Popover_Shadcn_ as Popover,
PopoverTrigger_Shadcn_ as PopoverTrigger,
PopoverContent_Shadcn_ as PopoverContent,
Command_Shadcn_ as Command,
CommandInput_Shadcn_ as CommandInput,
CommandEmpty_Shadcn_ as CommandEmpty,
CommandItem_Shadcn_ as CommandItem,
CommandGroup_Shadcn_ as CommandGroup,
Input_Shadcn_ as Input,
cn,
IconCopy,
IconCheck,
ScrollArea,
} from 'ui'
import CopyToClipboard from 'react-copy-to-clipboard'
import { proxy, useSnapshot } from 'valtio'
import { LOCAL_STORAGE_KEYS, retrieve, store } from '~/lib/storage'
import Link from 'next/link'
import { useIsLoggedIn, useIsUserLoading } from 'common'
import { ProjectsData, useProjectsQuery } from '~/lib/fetch/projects'
import { OrganizationsData, useOrganizationsQuery } from '~/lib/fetch/organizations'
import { BranchesData, useAllProjectsBranchesQuery } from '~/lib/fetch/branches'
import { useProjectApiQuery } from '~/lib/fetch/projectApi'
import { useOnLogout } from '~/lib/userAuth'
interface InstanceData {
organization: OrganizationsData[number] | null
project: ProjectsData[number]
branch?: BranchesData[number]
}
type InstanceKey = readonly [string | null, string, string | null]
function toInstanceKey(instance: InstanceData | null) {
if (!instance) return null
return [
instance.organization?.id ?? null,
// @ts-ignore -- problem with OpenAPI spec that codegen reads from
instance.project.ref as string,
instance.branch?.id ?? null,
] satisfies InstanceKey
}
function getInstanceDataFromKey(validInstances: InstanceData[], instanceKey: InstanceKey | null) {
if (!instanceKey || instanceKey[0] === null) return null
return (
validInstances.find(
(instance) =>
instance.organization?.id === instanceKey[0] &&
// @ts-ignore -- problem with OpenAPI spec that codegen reads from
instance.project.ref === instanceKey[1] &&
(instanceKey[2] === null || instance.branch?.id === instanceKey[2])
) ?? null
)
}
function toStoredString(instanceKey: InstanceKey | null) {
return JSON.stringify(instanceKey)
}
function fromStoredString(maybeInstanceKey: string) {
try {
return JSON.parse(maybeInstanceKey)
} catch {
return null
}
}
function toPrettyString(instance: InstanceData | null) {
if (!instance) return ''
const { organization, project, branch = null } = instance
return `${organization ? `${organization.name} / ` : ''}${project.name}${
branch ? ` / ${branch.name}` : ''
}`
}
/**
* The Command component uses the `value` for both search matching and selection.
* Because the two are conflated, the value must contain both:
* 1. The organization/project/branch refs, for uniquely identifying the project
* 2. The organization/project/branch name, so combobox search works as expected.
*/
function toCommandValue(instance: InstanceData) {
const instanceKey = toInstanceKey(instance)
const prettyString = toPrettyString(instance)
return JSON.stringify([instanceKey, prettyString])
}
function getKeyFromCommandValue(maybeInstance: string) {
try {
const instance = JSON.parse(maybeInstance)
// For maximum safety, should check that the structure matches rather than casting,
// but we can avoid that complexity as long as this is only ever used to decode
// values encoded by `toCommandValue`
return instance[0] as InstanceKey
} catch {
console.error('Failed to parse instance key from command value:', maybeInstance)
return null
}
}
const instanceStore = proxy({
selectedInstanceKey: null as InstanceKey | null,
setSelectedInstanceKey: (instanceKey: InstanceKey | null) => {
instanceStore.selectedInstanceKey = instanceKey
if (instanceKey !== null) {
store('local', LOCAL_STORAGE_KEYS.SAVED_ORG_PROJECT_BRANCH, toStoredString(instanceKey))
}
},
clear: () => {
instanceStore.setSelectedInstanceKey(null)
},
})
type Variable = 'url' | 'anonKey'
const prettyFormatVariable: Record<Variable, string> = {
url: 'Project URL',
anonKey: 'Anon key',
}
type ProjectConfigVariablesState =
| 'userLoading'
| 'loggedOut'
| 'loggedIn.dataPending'
| 'loggedIn.hasData'
| 'loggedIn.hasNoData'
| 'loggedIn.dataError'
type ProjectConfigVariablesPlusApiState =
| Omit<ProjectConfigVariablesState, 'loggedIn.hasData'>
| 'loggedIn.hasData.apiDataPending'
| 'loggedIn.hasData.apiDataSuccess'
| 'loggedIn.hasData.apiDataError'
function ComboBox({
parentStateSummary,
instances,
className,
}: {
parentStateSummary: ProjectConfigVariablesState
instances: InstanceData[]
className?: string
}) {
const { selectedInstanceKey, setSelectedInstanceKey } = useSnapshot(instanceStore)
const selectedInstance = getInstanceDataFromKey(instances, selectedInstanceKey)
const [open, setOpen] = useState(false)
return (
<Popover open={open} onOpenChange={setOpen}>
<div className={cn('flex items-center', className)}>
<PopoverTrigger asChild aria-label={`Select project`}>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className="overflow-hidden h-auto min-h-10 flex justify-between border-none px-1 text-left"
>
{parentStateSummary === 'userLoading' || parentStateSummary === 'loggedIn.dataPending'
? 'Loading...'
: toPrettyString(selectedInstance) || 'Select a project...'}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
</div>
<PopoverContent className="z-[999999] p-0" side="bottom">
<Command>
<CommandInput placeholder="Search project..." className="border-none ring-0" />
<CommandEmpty>No project found.</CommandEmpty>
<CommandGroup>
<ScrollArea className={instances.length > 10 ? 'h-[280px]' : ''}>
{instances.map((instance) => (
<CommandItem
key={toStoredString(toInstanceKey(instance))}
value={toCommandValue(instance)}
onSelect={(selectedValue: string) => {
const instanceKey = getKeyFromCommandValue(selectedValue)
setSelectedInstanceKey(instanceKey)
setOpen(false)
}}
>
<Check
className={cn(
'mr-2 h-4 w-4',
instance === selectedInstance ? 'opacity-100' : 'opacity-0'
)}
/>
{toPrettyString(instance)}
</CommandItem>
))}
</ScrollArea>
</CommandGroup>
</Command>
</PopoverContent>
</Popover>
)
}
function useCopy() {
const [copied, setCopied] = useState(false)
function handleCopy() {
setCopied(true)
setTimeout(() => {
setCopied(false)
}, 1000)
}
return { copied, handleCopy }
}
function ProjectConfigVariablesView({
parentStateSummary,
variable,
instances,
}: {
parentStateSummary: ProjectConfigVariablesState
variable: Variable
instances: InstanceData[]
}) {
const isLoggedIn = useIsLoggedIn()
const { selectedInstanceKey } = useSnapshot(instanceStore)
const selectedInstance = getInstanceDataFromKey(instances, selectedInstanceKey)
const projectRef = selectedInstance?.branch
? selectedInstance?.branch?.project_ref
: // @ts-ignore -- problem with OpenAPI spec that codegen reads from
selectedInstance?.project.ref
const {
data: apiData,
isPending: apiIsPending,
isSuccess: apiIsSuccess,
} = useProjectApiQuery(
{
projectRef,
},
{ enabled: isLoggedIn && !!projectRef }
)
const { copied, handleCopy } = useCopy()
const apiStateSummary: ProjectConfigVariablesPlusApiState =
parentStateSummary !== 'loggedIn.hasData'
? parentStateSummary
: apiIsPending
? 'loggedIn.hasData.apiDataPending'
: apiIsSuccess
? 'loggedIn.hasData.apiDataSuccess'
: 'loggedIn.hasData.apiDataError'
let variableValue: string = null
if (apiIsSuccess) {
switch (variable) {
case 'url':
variableValue = `${apiData.autoApiService.protocol || 'https'}://${
apiData.autoApiService.endpoint
}`
break
case 'anonKey':
variableValue = apiData.autoApiService.defaultApiKey
break
}
}
return (
<div className="max-w-[min(100%, 500px)] my-4">
<div className="flex flex-wrap items-center justify-between gap-2">
<span className="grow-[100]">{prettyFormatVariable[variable]}</span>
{(parentStateSummary === 'userLoading' ||
parentStateSummary === 'loggedIn.dataPending' ||
parentStateSummary === 'loggedIn.hasData') && (
<div className="flex items-center justify-between grow max-w-full gap-2">
<ComboBox parentStateSummary={parentStateSummary} instances={instances} />
<CopyToClipboard text={variableValue ?? ''}>
<Button
disabled={!variableValue}
variant="ghost"
className="px-0"
onClick={handleCopy}
aria-label="Copy"
>
{copied ? <IconCheck /> : <IconCopy />}
</Button>
</CopyToClipboard>
</div>
)}
</div>
<Input
disabled
type="text"
className="font-mono"
value={
apiStateSummary === 'userLoading' ||
apiStateSummary === 'loggedIn.dataPending' ||
apiStateSummary === 'loggedIn.hasData.apiDataPending'
? 'Loading...'
: apiStateSummary === 'loggedIn.hasData.apiDataSuccess'
? variableValue
: `YOUR ${prettyFormatVariable[variable].toUpperCase()}`
}
/>
{parentStateSummary === 'loggedOut' && (
<p className="text-foreground-muted text-sm mt-2 mb-0 ml-1">
There was a problem getting your {prettyFormatVariable[variable]}. Are you{' '}
<Link
className="text-foreground-muted"
href="https://supabase.com/dashboard"
rel="noreferrer noopener"
target="_blank"
>
logged in
</Link>
?
</p>
)}
{(parentStateSummary === 'loggedIn.hasNoData' ||
parentStateSummary === 'loggedIn.dataError' ||
apiStateSummary === 'loggedIn.hasData.apiDataError') && (
<>
<p className="text-foreground-muted text-sm mt-2 mb-0 ml-1">
There was a problem getting your {prettyFormatVariable[variable]}.{' '}
{parentStateSummary === 'loggedIn.hasNoData' && (
<>
Do you have{' '}
<Link
className="text-foreground-muted"
href="https://supabase.com/dashboard"
rel="noreferrer noopener"
target="_blank"
>
any projects
</Link>
?
</>
)}
</p>
{(parentStateSummary === 'loggedIn.dataError' ||
apiStateSummary === 'loggedIn.hasData.apiDataError') && (
<p className="text-foreground-muted text-sm mt-0 ml-1">
You can also try looking up the value in the{' '}
<Link
className="text-foreground-muted"
href="https://supabase.com/dashboard/project/_/settings/api"
rel="noreferrer noopener"
target="_blank"
>
dashboard
</Link>
.
</p>
)}
</>
)}
</div>
)
}
export function ProjectConfigVariables({ variable }: { variable: Variable }) {
const isUserLoading = useIsUserLoading()
const isLoggedIn = useIsLoggedIn()
const {
selectedInstanceKey,
setSelectedInstanceKey,
clear: clearSharedStoreData,
} = useSnapshot(instanceStore)
const {
data: organizations,
isPending: organizationsIsPending,
isError: organizationsIsError,
} = useOrganizationsQuery({ enabled: isLoggedIn })
const {
data: projects,
isPending: projectsIsPending,
isError: projectsIsError,
} = useProjectsQuery({ enabled: isLoggedIn })
const {
data: branches,
isPending: branchesIsPending,
isError: branchesIsError,
} = useAllProjectsBranchesQuery({ enabled: isLoggedIn })
const anyIsPending = organizationsIsPending || projectsIsPending || branchesIsPending
const anyIsError = organizationsIsError || projectsIsError || branchesIsError
const stateSummary: ProjectConfigVariablesState = isUserLoading
? 'userLoading'
: !isLoggedIn
? 'loggedOut'
: anyIsPending
? 'loggedIn.dataPending'
: anyIsError
? 'loggedIn.dataError'
: projects.length === 0
? 'loggedIn.hasNoData'
: 'loggedIn.hasData'
useOnLogout(clearSharedStoreData)
const formattedData: InstanceData[] = useMemo(
() =>
stateSummary !== 'loggedIn.hasData'
? []
: projects.flatMap((project) => {
const organization =
organizations.find((organization) => organization.id === project.organization_id) ??
null
// @ts-ignore -- problem with OpenAPI spec that codegen reads from
if (!project.is_branch_enabled) {
return { organization, project }
}
// @ts-ignore -- problem with OpenAPI spec that codegen reads from
const projectBranches = branches[project.ref]
if (!projectBranches) {
return { organization, project }
}
return projectBranches.map((branch) => ({ organization, project, branch }))
}),
[branches, organizations, projects, stateSummary]
)
useEffect(() => {
if (!selectedInstanceKey && typeof window !== undefined) {
let storedInstanceKey: InstanceKey = null
const storedMaybeInstanceKey = retrieve('local', LOCAL_STORAGE_KEYS.SAVED_ORG_PROJECT_BRANCH)
if (storedMaybeInstanceKey) {
storedInstanceKey = fromStoredString(storedMaybeInstanceKey)
}
setSelectedInstanceKey(storedInstanceKey ?? toInstanceKey(formattedData[0]) ?? null)
}
}, [selectedInstanceKey, setSelectedInstanceKey, formattedData])
return (
<ProjectConfigVariablesView
variable={variable}
instances={formattedData}
parentStateSummary={stateSummary}
/>
)
}
-2
View File
@@ -16,7 +16,6 @@ import StorageManagement from './MDX/storage_management.mdx'
import MigrationWarnings from './MDX/migration_warnings.mdx'
import { CH } from '@code-hike/mdx/components'
import RefHeaderSection from './reference/RefHeaderSection'
import { ProjectConfigVariables } from './ProjectConfigVariables'
// Ref version specific
import CliGlobalFlagsHandler from '~/components/reference/enrichments/cli/CliGlobalFlagsHandler'
@@ -64,7 +63,6 @@ const components = {
SocialProviderSetup,
SocialProviderSettingsSupabase,
StepHikeCompact,
ProjectConfigVariables,
StorageManagement,
Alert: (props: any) => (
<Alert {...props} className="not-prose">
+1 -5
View File
@@ -1,7 +1,3 @@
export const IS_PLATFORM = process.env.NEXT_PUBLIC_IS_PLATFORM === 'true'
export const LOCAL_SUPABASE = process.env.NEXT_PUBLIC_LOCAL_SUPABASE === 'true'
export const API_URL = (
process.env.NODE_ENV === 'development'
? process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8080'
: process.env.NEXT_PUBLIC_API_URL
).replace(/\/platform$/, '')
export const API_URL = process.env.NEXT_PUBLIC_API_URL
-87
View File
@@ -1,87 +0,0 @@
import { ResponseError } from '~/types/fetch'
import { get } from './fetchWrappers'
import { UseQueryOptions, useQuery } from '@tanstack/react-query'
import { getProjects } from './projects'
const branchKeys = {
list: (projectRef: string | undefined) => ['projects', projectRef, 'branches'] as const,
listAll: () => ['all-projects', 'all-branches'] as const,
}
export interface BranchVariables {
projectRef?: string
}
async function getBranches({ projectRef }: BranchVariables, signal?: AbortSignal) {
if (!projectRef) throw Error('projectRef is required')
const { data, error } = await get('/v1/projects/{ref}/branches', {
params: {
path: { ref: projectRef },
},
signal,
})
if (error) throw error
return data
}
export type BranchesData = Awaited<ReturnType<typeof getBranches>>
type BranchesError = ResponseError
export function useBranchesQuery<TData = BranchesData>(
{ projectRef }: BranchVariables,
{ enabled = true, ...options }: UseQueryOptions<BranchesData, BranchesError, TData>
) {
return useQuery<BranchesData, BranchesError, TData>({
queryKey: branchKeys.list(projectRef),
queryFn: ({ signal }) => getBranches({ projectRef }, signal),
enabled,
...options,
})
}
async function getAllBranches(signal?: AbortSignal) {
const allProjects = await getProjects(signal)
const branchesList = await Promise.all(
allProjects.map(async (project) => {
// @ts-ignore -- problem with OpenAPI spec that codegen reads from
if (!project.is_branch_enabled) {
return null
}
// @ts-ignore -- problem with OpenAPI spec that codegen reads from
const projectRef = project.ref as string
try {
const branches = await getBranches({ projectRef })
return { [projectRef]: branches }
} catch {
return null
}
})
)
const branchesMap = branchesList.reduce((record, branch) => {
if (!branch) {
return record
}
return Object.assign(record, branch)
}, {})
return branchesMap
}
export type AllBranchesData = Awaited<ReturnType<typeof getAllBranches>>
type AllBranchesError = ResponseError
export function useAllProjectsBranchesQuery<TData = AllBranchesData>({
enabled = true,
...options
}: Omit<UseQueryOptions<AllBranchesData, AllBranchesError, TData>, 'queryKey'> = {}) {
return useQuery<AllBranchesData, AllBranchesError, TData>({
queryKey: branchKeys.listAll(),
queryFn: ({ signal }) => getAllBranches(signal),
enabled,
...options,
})
}
-82
View File
@@ -1,82 +0,0 @@
import { type paths } from '~/types/api'
import createClient from 'openapi-fetch'
import { v4 as uuidv4 } from 'uuid'
import { API_URL } from '../constants'
import { getAccessToken } from '../userAuth'
const DEFAULT_HEADERS = {
'Content-Type': 'application/json',
Accept: 'application/json',
}
const { GET: _get, POST: _post } = createClient<paths>({
baseUrl: API_URL,
referrerPolicy: 'no-referrer-when-downgrade',
headers: DEFAULT_HEADERS,
})
export async function constructHeaders(
headersInit?: HeadersInit | undefined,
{ allowUnauthenticated = false }: { allowUnauthenticated?: boolean } = {}
) {
const requestId = uuidv4()
const headers = new Headers(headersInit)
headers.set('X-Request-Id', requestId)
if (!headers.has('Authorization')) {
const accessToken = await getAccessToken()
if (accessToken) {
headers.set('Authorization', `Bearer ${accessToken}`)
} else if (!allowUnauthenticated) {
throw Error("can't fetch authenticated routes without signing in")
}
}
return headers
}
export const get: typeof _get = async (url, init) => {
let headers: Headers = null
try {
headers = await constructHeaders(init?.headers)
} catch (err) {
console.error(err)
return { error: err, response: undefined }
}
return await _get(url, {
...init,
headers,
})
}
export const post: typeof _post = async (url, init) => {
let headers: Headers = null
try {
headers = await constructHeaders(init?.headers)
} catch (err) {
console.error(err)
return { error: err, response: undefined }
}
return await _post(url, {
...init,
headers,
})
}
export const unauthedAllowedPost: typeof _post = async (url, init) => {
let headers: Headers = null
try {
headers = await constructHeaders(init?.headers, { allowUnauthenticated: true })
} catch (err) {
console.error(err)
return { error: err, response: undefined }
}
return await _post(url, {
...init,
headers,
})
}
-28
View File
@@ -1,28 +0,0 @@
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
import { get } from './fetchWrappers'
import { ResponseError } from '~/types/fetch'
const organizationKeys = {
list: () => ['organizations'] as const,
}
async function getOrganizations(signal?: AbortSignal) {
const { data, error } = await get('/platform/organizations', { signal })
if (error) throw error
return data
}
export type OrganizationsData = Awaited<ReturnType<typeof getOrganizations>>
type OrganizationsError = ResponseError
export function useOrganizationsQuery<TData = OrganizationsData>({
enabled = true,
...options
}: Omit<UseQueryOptions<OrganizationsData, OrganizationsError, TData>, 'queryKey'> = {}) {
return useQuery<OrganizationsData, OrganizationsError, TData>({
queryKey: organizationKeys.list(),
queryFn: ({ signal }) => getOrganizations(signal),
enabled,
...options,
})
}
-45
View File
@@ -1,45 +0,0 @@
import { ResponseError } from '~/types/fetch'
import { get } from './fetchWrappers'
import { UseQueryOptions, useQuery } from '@tanstack/react-query'
const projectApiKeys = {
api: (projectRef: string | undefined) => ['projects', projectRef, 'api'] as const,
}
export interface ProjectApiVariables {
projectRef?: string
}
async function getProjectApi({ projectRef }: ProjectApiVariables, signal?: AbortSignal) {
if (!projectRef) {
throw Error('projectRef is required')
}
const { data, error } = await get('/platform/props/project/{ref}/api', {
params: {
path: { ref: projectRef },
},
signal,
})
if (error) throw error
return data
}
type ProjectApiData = Awaited<ReturnType<typeof getProjectApi>>
type ProjectApiError = ResponseError
export function useProjectApiQuery<TData = ProjectApiData>(
{ projectRef }: ProjectApiVariables,
{
enabled = true,
...options
}: Omit<UseQueryOptions<ProjectApiData, ProjectApiError, TData>, 'queryKey'> = {}
) {
return useQuery<ProjectApiData, ProjectApiError, TData>({
queryKey: projectApiKeys.api(projectRef),
queryFn: ({ signal }) => getProjectApi({ projectRef }, signal),
enabled,
...options,
})
}
-28
View File
@@ -1,28 +0,0 @@
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
import { ResponseError } from '~/types/fetch'
import { get } from './fetchWrappers'
const projectKeys = {
list: () => ['all-projects'] as const,
}
export async function getProjects(signal?: AbortSignal) {
const { data, error } = await get('/platform/projects', { signal })
if (error) throw error
return data
}
export type ProjectsData = Awaited<ReturnType<typeof getProjects>>
type ProjectsError = ResponseError
export function useProjectsQuery<TData = ProjectsData>({
enabled = true,
...options
}: Omit<UseQueryOptions<ProjectsData, ProjectsError, TData>, 'queryKey'> = {}) {
return useQuery<ProjectsData, ProjectsError, TData>({
queryKey: projectKeys.list(),
queryFn: ({ signal }) => getProjects(signal),
enabled,
...options,
})
}
-45
View File
@@ -1,45 +0,0 @@
import { QueryClient } from '@tanstack/react-query'
import { useState } from 'react'
let queryClient: QueryClient | undefined
function getQueryClient() {
const _queryClient =
queryClient ??
new QueryClient({
defaultOptions: {
queries: {
retry: (failureCount, error) => {
// Don't retry on 404s
if (
typeof error === 'object' &&
error !== null &&
'code' in error &&
error.code === 404
) {
return false
}
if (failureCount < 3) {
return true
}
return false
},
},
},
})
// For SSG and SSR always create a new queryClient
if (typeof window === 'undefined') return _queryClient
// Create the query client once in the client
if (!queryClient) queryClient = _queryClient
return queryClient
}
export function useRootQueryClient() {
const [_queryClient] = useState(() => getQueryClient())
return _queryClient
}
+18
View File
@@ -0,0 +1,18 @@
interface DataProps {
[prop: string]: any
}
export const post = (url: string, data: DataProps, options = {}) => {
return fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
referrerPolicy: 'no-referrer-when-downgrade',
body: JSON.stringify(data),
...options,
}).catch((error) => {
console.error('Error at fetchWrapper - post:', error)
})
}
-34
View File
@@ -1,34 +0,0 @@
export const LOCAL_STORAGE_KEYS = {
SAVED_ORG_PROJECT_BRANCH: 'docs.ui.user.selected.org_project_branch',
} as const
type LocalStorageKey = (typeof LOCAL_STORAGE_KEYS)[keyof typeof LOCAL_STORAGE_KEYS]
type StorageType = 'local' | 'session'
function getStorage(storageType: StorageType) {
return storageType === 'local' ? window.localStorage : window.sessionStorage
}
export function store(storageType: StorageType, key: LocalStorageKey, value: string) {
if (typeof window === 'undefined') return
const storage = getStorage(storageType)
try {
storage.setItem(key, value)
} catch {
console.error(`Failed to set storage item with key "${key}"`)
}
}
export function retrieve(storageType: StorageType, key: LocalStorageKey) {
if (typeof window === 'undefined') return
const storage = getStorage(storageType)
return storage.getItem(key)
}
export function remove(storageType: StorageType, key: LocalStorageKey) {
if (typeof window === 'undefined') return
const storage = getStorage(storageType)
return storage.removeItem(key)
}
-36
View File
@@ -1,36 +0,0 @@
import { gotrueClient } from 'common'
import { useEffect } from 'react'
export const auth = gotrueClient
export async function getAccessToken() {
// ignore if server-side
if (typeof window === 'undefined') {
return
}
const {
data: { session },
error,
} = await auth.getSession()
if (error) {
throw error
}
return session?.access_token
}
export function useOnLogout(callback: () => void) {
useEffect(() => {
const {
data: {
subscription: { unsubscribe },
},
} = auth.onAuthStateChange((event) => {
if (event === 'SIGNED_OUT') {
callback()
}
})
return unsubscribe
}, [callback])
}
-5
View File
@@ -7,7 +7,6 @@
"dev:secrets:pull": "AWS_PROFILE=supabase-dev node ../../scripts/getSecrets.js -n local/docs",
"build": "next build",
"build:analyze": "ANALYZE=true next build",
"codegen": "openapi-typescript http://localhost:8080/api-json -o types/api.d.ts && prettier --write types/api.d.ts",
"start": "next start",
"lint": "next lint",
"typecheck": "tsc --noEmit",
@@ -19,7 +18,6 @@
},
"dependencies": {
"@code-hike/mdx": "^0.9.0",
"@hcaptcha/react-hcaptcha": "^1.9.2",
"@mdx-js/loader": "^2.1.5",
"@mdx-js/react": "^2.3.0",
"@next/bundle-analyzer": "^13.5.6",
@@ -31,7 +29,6 @@
"@radix-ui/react-accordion": "^1.1.2",
"@supabase/auth-helpers-react": "^0.4.2",
"@supabase/supabase-js": "^2.38.2",
"@tanstack/react-query": "^5.13.4",
"common": "*",
"config": "*",
"framer-motion": "^6.5.1",
@@ -56,7 +53,6 @@
"next-seo": "^5.14.1",
"next-themes": "^0.2.1",
"openai": "^4.17.0",
"openapi-fetch": "^0.8.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-intersection-observer": "^9.5.2",
@@ -86,7 +82,6 @@
"jest-environment-jsdom": "^29.7.0",
"npm-run-all": "^4.1.5",
"openapi-types": "^12.0.2",
"openapi-typescript": "^6.7.2",
"sass": "^1.68.0",
"tsconfig": "*",
"tsx": "^4.6.2",
-62
View File
@@ -1,62 +0,0 @@
import { useRouter } from 'next/router'
import { useState } from 'react'
import { Button_Shadcn_ } from 'ui'
import { auth } from '~/lib/userAuth'
export function getServerSideProps() {
if (process.env.NEXT_PUBLIC_DEV_AUTH_PAGE === 'true') {
return {
props: {},
}
}
return {
notFound: true,
}
}
export default function DevOnlySecretAuth() {
const router = useRouter()
const [email, setEmail] = useState('')
function signInWithGitHub() {
auth.signInWithOAuth({
provider: 'github',
options: {
redirectTo: process.env.NEXT_PUBLIC_SITE_URL,
},
})
}
function signOut() {
auth.signOut().then(({ error }) => {
if (error) {
throw error
}
router.push('/')
})
}
return (
<div className="p-10 flex items-center justify-center">
<section className="space-y-4">
<h1>Sign in</h1>
<p>
This is a dev-only route to sign in and test authenticated actions within docs. In staging
and production, signin is managed via dashboard because docs and dashboard are proxied to
the same domain.
</p>
<form className="flex flex-col gap-2 max-w-sm">
<Button_Shadcn_ type="button" onClick={() => signInWithGitHub()}>
{' '}
Sign in with GitHub
</Button_Shadcn_>
<Button_Shadcn_ type="button" onClick={signOut}>
Sign out
</Button_Shadcn_>
</form>
</section>
</div>
)
}
+32 -69
View File
@@ -6,49 +6,21 @@ import '../styles/prism-okaidia.scss'
import { SessionContextProvider } from '@supabase/auth-helpers-react'
import { createClient } from '@supabase/supabase-js'
import { QueryClientProvider, useQueryClient } from '@tanstack/react-query'
import { AuthProvider, ThemeProvider, useTelemetryProps, useThemeSandbox } from 'common'
import { useRouter } from 'next/router'
import { PropsWithChildren, useCallback, useEffect, useState } from 'react'
import { useCallback, useEffect, useState } from 'react'
import { AppPropsWithLayout } from 'types'
import { CommandMenuProvider, PortalToast, useConsent } from 'ui'
import { TabsProvider } from 'ui/src/components/Tabs'
import Favicons from '~/components/Favicons'
import SiteLayout from '~/layouts/SiteLayout'
import { IS_PLATFORM } from '~/lib/constants'
import { unauthedAllowedPost } from '~/lib/fetch/fetchWrappers'
import { useRootQueryClient } from '~/lib/fetch/queryClient'
import { useOnLogout } from '~/lib/userAuth'
import { LOCAL_STORAGE_KEYS, remove } from '~/lib/storage'
/**
*
* !!! IMPORTANT !!!
* Ensure data is cleared on sign out.
*
* **/
function SignOutHandler({ children }: PropsWithChildren) {
const queryClient = useQueryClient()
const cleanUp = useCallback(() => {
queryClient.cancelQueries()
queryClient.clear()
Object.keys(LOCAL_STORAGE_KEYS).forEach((key) => {
remove('local', LOCAL_STORAGE_KEYS[key])
})
}, [queryClient])
useOnLogout(cleanUp)
return <>{children}</>
}
import { API_URL, IS_PLATFORM } from '~/lib/constants'
import { post } from '~/lib/fetchWrappers'
function MyApp({ Component, pageProps }: AppPropsWithLayout) {
const router = useRouter()
const telemetryProps = useTelemetryProps()
const { consentValue, hasAcceptedConsent } = useConsent()
const queryClient = useRootQueryClient()
useThemeSandbox()
@@ -63,22 +35,15 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
const handlePageTelemetry = useCallback(
(route: string) => {
if (IS_PLATFORM) {
unauthedAllowedPost('/platform/telemetry/page', {
body: {
referrer: document.referrer,
title: document.title,
route,
ga: {
screen_resolution: telemetryProps?.screenResolution,
language: telemetryProps?.language,
session_id: '',
},
},
}).catch((e) => {
console.error('Problem sending telemetry:', e)
})
}
return post(`${API_URL}/telemetry/page`, {
referrer: document.referrer,
title: document.title,
route,
ga: {
screen_resolution: telemetryProps?.screenResolution,
language: telemetryProps?.language,
},
})
},
[telemetryProps]
)
@@ -108,7 +73,7 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
return () => {
router.events.off('routeChangeComplete', handleRouteChange)
}
}, [router, handlePageTelemetry, consentValue, hasAcceptedConsent])
}, [router, handlePageTelemetry, consentValue])
/**
* Save/restore scroll position when reloading or navigating back/forward.
@@ -155,7 +120,7 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
if (router.isReady) {
handlePageTelemetry(router.basePath + router.asPath)
}
}, [router, handlePageTelemetry, consentValue, hasAcceptedConsent])
}, [router, handlePageTelemetry, consentValue])
/**
* Reference docs use `history.pushState()` to jump to
@@ -176,35 +141,33 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
}
}, [router])
const AuthContainer = ({ children }) => {
const SITE_TITLE = 'Supabase Documentation'
const AuthContainer = (props) => {
return IS_PLATFORM ? (
<SessionContextProvider supabaseClient={supabase}>
<AuthProvider>{children}</AuthProvider>
<AuthProvider>{props.children}</AuthProvider>
</SessionContextProvider>
) : (
<AuthProvider>{children}</AuthProvider>
<AuthProvider>{props.children}</AuthProvider>
)
}
return (
<>
<QueryClientProvider client={queryClient}>
<Favicons />
<AuthContainer>
<SignOutHandler>
<ThemeProvider defaultTheme="system" enableSystem disableTransitionOnChange>
<CommandMenuProvider site="docs">
<TabsProvider>
<SiteLayout>
<PortalToast />
<Component {...pageProps} />
</SiteLayout>
</TabsProvider>
</CommandMenuProvider>
</ThemeProvider>
</SignOutHandler>
</AuthContainer>
</QueryClientProvider>
<Favicons />
<AuthContainer>
<ThemeProvider defaultTheme="system" enableSystem disableTransitionOnChange>
<CommandMenuProvider site="docs">
<TabsProvider>
<SiteLayout>
<PortalToast />
<Component {...pageProps} />
</SiteLayout>
</TabsProvider>
</CommandMenuProvider>
</ThemeProvider>
</AuthContainer>
</>
)
}
@@ -79,10 +79,7 @@ export const meta = {
<StepHikeCompact.Details title="Initialize the Supabase client">
Open `lib/main.dart` and edit the main function to initialize Supabase using your project URL and public API (anon) key:
<ProjectConfigVariables variable="url" />
<ProjectConfigVariables variable="anonKey" />
Open `lib/main.dart` and edit the main function to initialize Supabase using your [project URL and public API (anon) key](https://supabase.com/dashboard/project/_/settings/api).
</StepHikeCompact.Details>
@@ -100,11 +100,7 @@ export const meta = {
For the sake of simplicity, we will create a client in the `MainActivity.kt` file at the top just below the imports.
Replace the `supabaseUrl` and `supabaseKey` with your own:
<ProjectConfigVariables variable="url" />
<ProjectConfigVariables variable="anonKey" />
Replace the `supabaseUrl` and `supabaseKey` with your own found in [your dashboard](https://supabase.com/dashboard/project/_/settings/api).
</StepHikeCompact.Details>
<StepHikeCompact.Code>
@@ -66,18 +66,15 @@ export const meta = {
<StepHikeCompact.Step step={3}>
<StepHikeCompact.Details title="Declare Supabase Environment Variables">
Rename `.env.local.example` to `.env.local` and populate with your Supabase connection variables:
<ProjectConfigVariables variable="url" />
<ProjectConfigVariables variable="anonKey" />
Rename `.env.local.example` to `.env.local` and populate with [your project's URL and Anon Key](https://supabase.com/dashboard/project/_/settings/api).
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```text .env.local
NEXT_PUBLIC_SUPABASE_URL=<SUBSTITUTE_SUPABASE_URL>
NEXT_PUBLIC_SUPABASE_ANON_KEY=<SUBSTITUTE_SUPABASE_ANON_KEY>
NEXT_PUBLIC_SUPABASE_URL=your-project-url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
```
</StepHikeCompact.Code>
@@ -78,10 +78,7 @@ export const meta = {
<StepHikeCompact.Step step={4}>
<StepHikeCompact.Details title="Query data from the app">
In `src/App.vue`, create a Supabase client using your project URL and public API (anon) key:
<ProjectConfigVariables variable="url" />
<ProjectConfigVariables variable="anonKey" />
In `src/App.vue`, create a Supabase client using your [Project URL and public API (anon) key](https://supabase.com/dashboard/project/_/settings/api).
Replace the existing content in your `src/App.vue` file with the following code.
@@ -77,10 +77,7 @@ export const meta = {
<StepHikeCompact.Step step={4}>
<StepHikeCompact.Details title="Query data from the app">
In `App.jsx`, create a Supabase client using your project URL and public API (anon) key:
<ProjectConfigVariables variable="url" />
<ProjectConfigVariables variable="anonKey" />
In `App.jsx`, create a Supabase client using your [Project URL and public API (anon) key](https://supabase.com/dashboard/project/_/settings/api).
Add a `getCountries` function to fetch the data and display the query result to the page.
@@ -101,9 +101,6 @@ export const meta = {
You now have to update the `supabaseClient` with the `SUPABASE_URL` and `SUPABASE_KEY` of your Supabase API. The `supabaseClient` is used in auth provider and data provider methods that allow the refine app to connect to your Supabase backend.
<ProjectConfigVariables variable="url" />
<ProjectConfigVariables variable="anonKey" />
</StepHikeCompact.Details>
<StepHikeCompact.Code>
@@ -77,10 +77,7 @@ export const meta = {
<StepHikeCompact.Step step={4}>
<StepHikeCompact.Details title="Query data from the app">
In `App.jsx`, create a Supabase client using your project URL and public API (anon) key:
<ProjectConfigVariables variable="url" />
<ProjectConfigVariables variable="anonKey" />
In `App.jsx`, create a Supabase client using your [Project URL and public API (anon) key](https://supabase.com/dashboard/project/_/settings/api).
Add a `getCountries` function to fetch the data and display the query result to the page.
@@ -77,10 +77,7 @@ export const meta = {
<StepHikeCompact.Step step={4}>
<StepHikeCompact.Details title="Create the Supabase client">
Create a `/src/lib` directory in your SvelteKit app, create a file called `supabaseClient.js` and add the following code to initialize the Supabase client with your project URL and public API (anon) key:
<ProjectConfigVariables variable="url" />
<ProjectConfigVariables variable="anonKey" />
Create a `/src/lib` directory in your SvelteKit app, create a file called `supabaseClient.js` and add the following code to initialize the Supabase client with your [project URL and public API (anon) key](https://supabase.com/dashboard/project/_/settings/api).
</StepHikeCompact.Details>
@@ -77,10 +77,7 @@ export const meta = {
<StepHikeCompact.Step step={4}>
<StepHikeCompact.Details title="Create the Supabase client">
Create a `/src/lib` directory in your Vue app, create a file called `supabaseClient.js` and add the following code to initialize the Supabase client with your project URL and public API (anon) key:
<ProjectConfigVariables variable="url" />
<ProjectConfigVariables variable="anonKey" />
Create a `/src/lib` directory in your Vue app, create a file called `supabaseClient.js` and add the following code to initialize the Supabase client with your [project URL and public API (anon) key](https://supabase.com/dashboard/project/_/settings/api).
</StepHikeCompact.Details>
-12587
View File
File diff suppressed because it is too large. Load diff
-5
View File
@@ -1,5 +0,0 @@
export interface ResponseError {
code?: number
message: string
requestId?: string
}
+6 -54
View File
@@ -486,7 +486,6 @@
"version": "0.0.0",
"dependencies": {
"@code-hike/mdx": "^0.9.0",
"@hcaptcha/react-hcaptcha": "^1.9.2",
"@mdx-js/loader": "^2.1.5",
"@mdx-js/react": "^2.3.0",
"@next/bundle-analyzer": "^13.5.6",
@@ -498,7 +497,6 @@
"@radix-ui/react-accordion": "^1.1.2",
"@supabase/auth-helpers-react": "^0.4.2",
"@supabase/supabase-js": "^2.38.2",
"@tanstack/react-query": "^5.13.4",
"common": "*",
"config": "*",
"framer-motion": "^6.5.1",
@@ -523,7 +521,6 @@
"next-seo": "^5.14.1",
"next-themes": "^0.2.1",
"openai": "^4.17.0",
"openapi-fetch": "^0.8.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-intersection-observer": "^9.5.2",
@@ -553,37 +550,12 @@
"jest-environment-jsdom": "^29.7.0",
"npm-run-all": "^4.1.5",
"openapi-types": "^12.0.2",
"openapi-typescript": "^6.7.2",
"sass": "^1.68.0",
"tsconfig": "*",
"tsx": "^4.6.2",
"typescript": "^5.2.2"
}
},
"apps/docs/node_modules/@tanstack/query-core": {
"version": "5.13.4",
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.13.4.tgz",
"integrity": "sha512-8+rJucXvC/xlr4OrxHhEIob/cTlbT4fgmz1VsvB0D12FRStKaXeLORNGcOhSAynRd2NL74SV/Qq0IIb4DedLcA==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"apps/docs/node_modules/@tanstack/react-query": {
"version": "5.13.4",
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.13.4.tgz",
"integrity": "sha512-3HjvkFFriEQwffUXtKHPiwkfFXUGbs46YATTzzyK1+Pw6Ekd3kwzS50e45qdamWuEXmXxyo5S1zp534LdFG0Rw==",
"dependencies": {
"@tanstack/query-core": "5.13.4"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
"react": "^18.0.0"
}
},
"apps/docs/node_modules/@types/mdast": {
"version": "3.0.13",
"license": "MIT",
@@ -676,14 +648,6 @@
],
"license": "MIT"
},
"apps/docs/node_modules/openapi-fetch": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/openapi-fetch/-/openapi-fetch-0.8.2.tgz",
"integrity": "sha512-4g+NLK8FmQ51RW6zLcCBOVy/lwYmFJiiT+ckYZxJWxUxH4XFhsNcX2eeqVMfVOi+mDNFja6qDXIZAz2c5J/RVw==",
"dependencies": {
"openapi-typescript-helpers": "^0.0.5"
}
},
"apps/docs/node_modules/puppeteer": {
"version": "16.2.0",
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-16.2.0.tgz",
@@ -4652,18 +4616,11 @@
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.0.0.tgz",
"integrity": "sha512-vgbBJTS4m5/KkE16t5Ly0WW9hz46swAstv0hYYwMtbG7AznRhNyfLRe8HZAiWIpcHzoO7HxhLuBQj9rJ/Ho0ZA=="
},
"node_modules/@hcaptcha/loader": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@hcaptcha/loader/-/loader-1.1.2.tgz",
"integrity": "sha512-YEBXCGNUBpPJUd5UZg64jBU6YMOhDjGdzS6dUCI9P8uIJttuQz3P76wVmK9kdKOuvLUaB7HwVBBmNv/bkAaIEw=="
},
"node_modules/@hcaptcha/react-hcaptcha": {
"version": "1.9.2",
"resolved": "https://registry.npmjs.org/@hcaptcha/react-hcaptcha/-/react-hcaptcha-1.9.2.tgz",
"integrity": "sha512-FKsfxi2PV0UTFp3TvW8L02z2MHZjhBVOvH1OznR4ccxjugCZa8ix4weP/wKRUaLHcKwmCfXSu35Agen41aP1mQ==",
"version": "1.8.1",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.17.9",
"@hcaptcha/loader": "^1.0.10"
"@babel/runtime": "^7.17.9"
},
"peerDependencies": {
"react": ">= 16.3.0",
@@ -28164,9 +28121,9 @@
"license": "MIT"
},
"node_modules/openapi-typescript": {
"version": "6.7.2",
"resolved": "https://registry.npmjs.org/openapi-typescript/-/openapi-typescript-6.7.2.tgz",
"integrity": "sha512-7rsUArlMBqmSaRd6EzPl2nGKzPFNRicsRGrxf6W+/HLEDZoOxghR3B53YlyGjcqak8YDZMBNzZQ3o93Bp3qY9Q==",
"version": "6.7.1",
"resolved": "https://registry.npmjs.org/openapi-typescript/-/openapi-typescript-6.7.1.tgz",
"integrity": "sha512-Q3Ltt0KUm2smcPrsaR8qKmSwQ1KM4yGDJVoQdpYa0yvKPeN8huDx5utMT7DvwvJastHHzUxajjivK3WN2+fobg==",
"dev": true,
"dependencies": {
"ansi-colors": "^4.1.3",
@@ -28180,11 +28137,6 @@
"openapi-typescript": "bin/cli.js"
}
},
"node_modules/openapi-typescript-helpers": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/openapi-typescript-helpers/-/openapi-typescript-helpers-0.0.5.tgz",
"integrity": "sha512-MRffg93t0hgGZbYTxg60hkRIK2sRuEOHEtCUgMuLgbCC33TMQ68AmxskzUlauzZYD47+ENeGV/ElI7qnWqrAxA=="
},
"node_modules/openapi-typescript/node_modules/argparse": {
"version": "2.0.1",
"dev": true,
-2
View File
@@ -159,8 +159,6 @@ export {
export { Input as Input_Shadcn_ } from './src/components/shadcn/ui/input'
export { Button as Button_Shadcn_ } from './src/components/shadcn/ui/button'
export { TextArea as TextArea_Shadcn_ } from './src/components/shadcn/ui/text-area'
export { Label as Label_Shadcn_ } from './src/components/shadcn/ui/label'