Merge pull request #13291 from supabase/master

Prod deploy dashboard
This commit is contained in:
Joshen Lim authored and GitHub committed 2023-03-29 18:31:17 +08:00
commit d697289cf1
151 files changed
+5830 -4401

No files matched your search

@@ -1,169 +0,0 @@
import { render } from 'react-dom'
import { IconCommand } from 'ui'
import { createElement, FC, useEffect, useRef, Fragment } from 'react'
import algoliasearch from 'algoliasearch/lite'
import { autocomplete, getAlgoliaResults } from '@algolia/autocomplete-js'
import { createLocalStorageRecentSearchesPlugin } from '@algolia/autocomplete-plugin-recent-searches'
import { useRouter } from 'next/router'
// [Joshen] We're currently using DocSearch from Algolia as it provides a nice
// UI out of the box + some good preconfigured search settings (e.g hierarchy).
// However, we're using our own Algolia account to store the records in the indexes
// (rather than going through the DocSearch program from Algolia where they'll crawl
// our site for us). Refer to scripts/build-search on how we're saving the records.
// Using Algolia's autocomplete library gives us full flexbility in terms of customizing
// our search experience, but that will take time to figure out. Hence why for now we're just
// using DocSearch with our own records.
// Potentially for search, we could
// - Go ahead with the DocSearch program and let them crawl our site to generate the records
// - But we need to ensure that our site is semantically correct first
// - Use Algolia itself to flesh out our own search logic
// - The basics are already set up to be honest, but will take time to make it great
// - Go back to Typesense if we deem that Algolia is not helpful in the long run
const searchClient = algoliasearch(
process.env.NEXT_PUBLIC_ALGOLIA_APP_ID,
process.env.NEXT_PUBLIC_ALGOLIA_SEARCH_API_KEY
)
// [Joshen] Not working properly, but lets not get stuck on this
// Priority just to get a search working
const recentSearchesPlugin = createLocalStorageRecentSearchesPlugin({
key: 'docs-search',
limit: 3,
//@ts-ignore
transformSource({ source }) {
return {
...source,
templates: {
...source.templates,
header({ state }) {
if (state.query) return null
return (
<Fragment>
<span className="aa-SourceHeaderTitle">Your searches</span>
<div className="aa-SourceHeaderLine" />
</Fragment>
)
},
},
}
},
})
interface Props {}
const AlgoliaSearch: FC<Props> = ({}) => {
const searchRef = useRef(null)
const router = useRouter()
useEffect(() => {
if (!searchRef.current) {
return undefined
}
const search = autocomplete({
openOnFocus: true,
container: searchRef.current,
defaultActiveItemId: 0,
detachedMediaQuery: '',
// @ts-ignore
renderer: { createElement, Fragment, render },
placeholder: 'Search docs',
plugins: [recentSearchesPlugin],
renderNoResults({ state, render }, root) {
render(
<div className="text-scale-1100 py-2 text-sm px-4">
No results found for {state.query}.
</div>,
root
)
},
navigator: {
navigate({ itemUrl }) {
router.push(itemUrl)
},
},
onSelect({ item, setQuery, setIsOpen, refresh }) {
//console.log('onSelect', item.url)
},
// @ts-ignore
getSources({ query }) {
return [
{
sourceId: 'pages',
templates: {
item({ item, components }) {
return (
<a
href={item.url as string}
className="aa-ItemLink truncate flex justify-between space-x-4"
>
<div className="aa-ItemContent w-full">
<div className="aa-ItemTitle flex items-center space-x-1">
{item.category && (
<p
className={`${
['cli', 'api'].includes(item.category as string)
? 'uppercase'
: 'capitalize'
}`}
>
<>
{item.category}
{item.version ? ` (${item.version})` : ''}:
</>
</p>
)}
<p>
<components.Highlight hit={item} attribute="title" />
</p>
</div>
<p className="aa-ItemContentSubtitle">{item.description as string}</p>
</div>
</a>
)
},
},
getItemUrl({ item }) {
return item.url
},
getItems() {
// if (!query) return []
return getAlgoliaResults({
searchClient,
queries: [
{
indexName: 'dev_docs',
query,
},
],
})
},
},
]
},
})
return () => {
search.destroy()
}
}, [])
return (
<div className="w-[200px] relative">
<div ref={searchRef} />
<div className="flex items-center space-x-1 absolute top-[7px] right-2">
<div className="text-scale-1200 flex items-center justify-center h-6 w-6 rounded bg-scale-500">
<IconCommand size={12} strokeWidth={1.5} />
</div>
<div className="text-xs text-scale-1200 flex items-center justify-center h-6 w-6 rounded bg-scale-500">
K
</div>
</div>
</div>
)
}
export default AlgoliaSearch
+5 -12
View File
@@ -1,12 +1,13 @@
import { useTheme } from 'common/Providers'
import Image from 'next/image'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useState, useEffect, FC } from 'react'
import { IconMenu, IconMoon, IconSearch, IconSun, IconCommand, Listbox } from 'ui'
import { useTheme } from 'common/Providers'
import { FC, useEffect, useState } from 'react'
import { IconCommand, IconMenu, IconMoon, IconSearch, IconSun, Listbox } from 'ui'
import { REFERENCES } from './Navigation.constants'
import { getPageType } from '~/lib/helpers'
import SearchButton from '../Search/SearchButton'
const NavBar: FC = () => {
@@ -36,14 +37,6 @@ const NavBar: FC = () => {
{ text: 'Reference', key: 'reference', link: '/reference' },
]
const toggleDarkMode = () => {
localStorage.setItem('supabaseDarkMode', (!isDarkMode).toString())
toggleTheme()
const key = localStorage.getItem('supabaseDarkMode')
document.documentElement.className = key === 'true' ? 'dark' : ''
}
const onSelectVersion = (version: string) => {
// [Joshen] Ideally we use <Link> but this works for now
if (!library) return
@@ -183,7 +176,7 @@ const NavBar: FC = () => {
</a>
</li>
<li className="px-4">
<div className="cursor-pointer" onClick={toggleDarkMode}>
<div className="cursor-pointer" onClick={() => toggleTheme()}>
{isDarkMode ? (
<IconMoon size={18} strokeWidth={2} className="text-scale-1200" />
) : (
@@ -1,13 +1,14 @@
import { useTheme } from 'common/Providers'
import Image from 'next/image'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { FC, useEffect, useState } from 'react'
import { Button, IconCommand, IconMenu, IconMoon, IconSearch, IconSun, Input, Listbox } from 'ui'
import { Button, IconCommand, IconMenu, IconMoon, IconSearch, IconSun, Listbox } from 'ui'
import { REFERENCES } from '~/components/Navigation/Navigation.constants'
import { useTheme } from 'common/Providers'
import { getPageType } from '~/lib/helpers'
import SearchButton from '~/components/Search/SearchButton'
import SearchButton from './../../Search/SearchButton'
const TopNavBar: FC = () => {
const { isDarkMode, toggleTheme } = useTheme()
@@ -36,14 +37,6 @@ const TopNavBar: FC = () => {
{ text: 'Reference', key: 'reference', link: '/reference' },
]
const toggleDarkMode = () => {
localStorage.setItem('supabaseDarkMode', (!isDarkMode).toString())
toggleTheme()
const key = localStorage.getItem('supabaseDarkMode')
document.documentElement.className = key === 'true' ? 'dark' : ''
}
const onSelectVersion = (version: string) => {
// [Joshen] Ideally we use <Link> but this works for now
if (!library) return
@@ -147,7 +140,7 @@ const TopNavBar: FC = () => {
</Button>
<ul className="flex items-center">
<li className="px-4">
<div className="cursor-pointer" onClick={toggleDarkMode}>
<div className="cursor-pointer" onClick={() => toggleTheme()}>
{isDarkMode ? (
<IconMoon
size={16}
@@ -6,9 +6,10 @@ import { FC, useEffect, useState } from 'react'
import { Button, IconCommand, IconGitHub, IconMoon, IconSearch, IconSun } from 'ui'
import { REFERENCES } from '~/components/Navigation/Navigation.constants'
import SearchButton from '~/components/Search/SearchButton'
import { getPageType } from '~/lib/helpers'
import SearchButton from '~/components/Search/SearchButton'
const TopNavBarRef: FC = () => {
const { isDarkMode, toggleTheme } = useTheme()
const [mounted, setMounted] = useState(false)
@@ -36,15 +37,6 @@ const TopNavBarRef: FC = () => {
{ text: 'Reference', key: 'reference', link: '/reference' },
]
const toggleDarkMode = () => {
localStorage.setItem('supabaseDarkMode', (!isDarkMode).toString())
toggleTheme()
const key = localStorage.getItem('supabaseDarkMode')
document.documentElement.className = key === 'true' ? 'dark' : ''
document.documentElement.style.colorScheme = key === 'true' ? 'dark' : ''
}
const onSelectVersion = (version: string) => {
// [Joshen] Ideally we use <Link> but this works for now
if (!library) return
@@ -149,7 +141,7 @@ const TopNavBarRef: FC = () => {
</ul>
<ul className="flex items-center">
<li className="px-4">
<div className="cursor-pointer" onClick={toggleDarkMode}>
<div className="cursor-pointer" onClick={() => toggleTheme()}>
{isDarkMode ? (
<IconMoon
size={16}
+5 -2
View File
@@ -1,11 +1,10 @@
import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
import { SessionContextProvider } from '@supabase/auth-helpers-react'
import { ThemeProvider } from 'common/Providers'
import { DefaultSeo } from 'next-seo'
import Head from 'next/head'
import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'
import { AppPropsWithLayout } from 'types'
// import { CommandMenuProvider } from 'ui'
import Favicons from '~/components/Favicons'
import SearchProvider from '~/components/Search/SearchProvider'
import SiteLayout from '~/layouts/SiteLayout'
@@ -69,18 +68,22 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
<SessionContextProvider supabaseClient={supabase}>
<ThemeProvider>
<SearchProvider>
{/* <CommandMenuProvider site="docs"> */}
<SiteLayout>
<Component {...pageProps} />
</SiteLayout>
{/* </CommandMenuProvider> */}
</SearchProvider>
</ThemeProvider>
</SessionContextProvider>
) : (
<ThemeProvider>
<SearchProvider>
{/* <CommandMenuProvider site="docs"> */}
<SiteLayout>
<Component {...pageProps} />
</SiteLayout>
{/* </CommandMenuProvider> */}
</SearchProvider>
</ThemeProvider>
)}
@@ -16,18 +16,22 @@ const BuiltExamples = () => {
<p className="p">There are many example apps and starter projects to get going</p>
<div className="flex justify-center gap-2 py-4">
<Link href="/docs/guides/examples" as="/docs/guides/examples" passHref>
<Button as="a" type="default" size="small">
View all examples
</Button>
<a>
<Button type="default" size="small">
View all examples
</Button>
</a>
</Link>
<Link
href="https://github.com/supabase/supabase/tree/master/examples"
as="https://github.com/supabase/supabase/tree/master/examples"
passHref
>
<Button as="a" type="default" icon={<IconGitHub />} size="small">
Official github library
</Button>
<a>
<Button type="default" icon={<IconGitHub />} size="small">
Official github library
</Button>
</a>
</Link>
</div>
</div>
@@ -126,9 +126,7 @@ function SplitCodeBlockCarousel(props: SplitCodeBlockCarousel) {
{extension.url && (
<Link href={extension.url} as={extension.url}>
<a className="ml-px">
<Button as="a" type="default">
View documentation
</Button>
<Button type="default">View documentation</Button>
</a>
</Link>
)}
+1 -9
View File
@@ -4,14 +4,6 @@ import { IconSun, IconMoon } from 'ui'
function DarkModeToggle() {
const { isDarkMode, toggleTheme } = useTheme()
const toggleDarkMode = () => {
localStorage.setItem('supabaseDarkMode', (!isDarkMode).toString())
toggleTheme()
const key = localStorage.getItem('supabaseDarkMode')
document.documentElement.className = key === 'true' ? 'dark' : ''
}
return (
<div className="flex items-center">
<IconSun className="text-scale-900" strokeWidth={2} />
@@ -24,7 +16,7 @@ function DarkModeToggle() {
isDarkMode ? 'bg-scale-500' : 'bg-scale-900'
} mx-5
`}
onClick={() => toggleDarkMode()}
onClick={() => toggleTheme()}
>
<span className="sr-only">Toggle Themes</span>
<span
+10 -6
View File
@@ -40,14 +40,18 @@ const Hero = () => {
</div>
<div className="flex items-center gap-2">
<Link href="https://app.supabase.com" as="https://app.supabase.com" passHref>
<Button as="a" size="small" className="text-white">
Start your project
</Button>
<a>
<Button size="small" className="text-white">
Start your project
</Button>
</a>
</Link>
<Link href="/docs" as="/docs" passHref>
<Button as="a" size="small" type="default" icon={<IconBookOpen />}>
Documentation
</Button>
<a>
<Button size="small" type="default" icon={<IconBookOpen />}>
Documentation
</Button>
</a>
</Link>
</div>
<div className="flex flex-col gap-4">
@@ -32,18 +32,22 @@ function GithubExamples() {
</p>
<div className="flex items-center justify-center gap-2 py-4">
<Link href="/docs/guides/auth/overview" passHref>
<Button size="small" as="a" type="default" icon={<IconBookOpen size={12} />}>
View guides
</Button>
<a>
<Button size="small" type="default" icon={<IconBookOpen size={12} />}>
View guides
</Button>
</a>
</Link>
<Link
href="https://github.com/supabase/supabase/tree/master/examples"
as="https://github.com/supabase/supabase/tree/master/examples"
passHref
>
<Button size="small" as="a" type="default" icon={<IconGitHub size={12} />}>
Official GitHub library
</Button>
<a>
<Button size="small" type="default" icon={<IconGitHub size={12} />}>
Official GitHub library
</Button>
</a>
</Link>
</div>
</div>
@@ -37,28 +37,18 @@ function TwitterSocialProof() {
</p>
<div className="my-8 flex justify-center gap-2">
<Link href={'https://github.com/supabase/supabase/discussions'} passHref>
<Button
as="a"
// @ts-ignore
target="_blank"
size="small"
iconRight={<IconMessageCircle size={14} />}
type="default"
>
GitHub discussions
</Button>
<a target="_blank">
<Button size="small" iconRight={<IconMessageCircle size={14} />} type="default">
GitHub discussions
</Button>
</a>
</Link>
<Link href={'https://discord.supabase.com/'} passHref>
<Button
as="a"
type="default"
// @ts-ignore
target="_blank"
size="small"
iconRight={<IconMessageCircle size={14} />}
>
Discord
</Button>
<a target="_blank">
<Button type="default" size="small" iconRight={<IconMessageCircle size={14} />}>
Discord
</Button>
</a>
</Link>
</div>
</div>
+5 -3
View File
@@ -61,9 +61,11 @@ const Error404 = () => {
</div>
<div className="flex items-center space-x-4">
<Link href="/">
<Button as="a" size="small" className="text-white">
Head back
</Button>
<a>
<Button size="small" className="text-white">
Head back
</Button>
</a>
</Link>
</div>
</div>
+5 -3
View File
@@ -298,9 +298,11 @@ function Database() {
text="We only show a few of the extensions supported by supabase here, but we preinstall many more that you can use right away."
/>
<Link href="/docs/guides/database" passHref>
<Button as="a" size="small" type="default" icon={<IconArrowUpRight />}>
Explore documentation
</Button>
<a>
<Button size="small" type="default" icon={<IconArrowUpRight />}>
Explore documentation
</Button>
</a>
</Link>
</div>
<div className="col-span-12 mt-8 lg:col-span-6 lg:col-start-7 lg:mt-0">
+5 -3
View File
@@ -178,9 +178,11 @@ export default function IndexPage() {
</div>
<p className="p">{description}</p>
<Link href="https://app.supabase.com" passHref>
<Button as="a" size="medium" block>
Get started
</Button>
<a>
<Button size="medium" block>
Get started
</Button>
</a>
</Link>
</div>
)
+9 -9
View File
@@ -145,9 +145,9 @@ function RealtimePage() {
</p>
<div className="not-prose mt-3">
<Link passHref href="/docs/guides/realtime/postgres-changes">
<Button as="a" type="default">
View docs
</Button>
<a>
<Button type="default">View docs</Button>
</a>
</Link>
</div>
</div>
@@ -163,9 +163,9 @@ function RealtimePage() {
<p>Store and synchronize online user state consistently across clients.</p>
<div className="not-prose mt-3">
<Link passHref href="/docs/guides/realtime/presence">
<Button as="a" type="default">
View docs
</Button>
<a>
<Button type="default">View docs</Button>
</a>
</Link>
</div>
</div>
@@ -181,9 +181,9 @@ function RealtimePage() {
<p>Send any data to any client subscribed to the same Channel.</p>
<div className="not-prose mt-3">
<Link passHref href="/docs/guides/realtime/broadcast">
<Button as="a" type="default">
View docs
</Button>
<a>
<Button type="default">View docs</Button>
</a>
</Link>
</div>
</div>
+5 -3
View File
@@ -130,9 +130,11 @@ As a database company, being SOC2 compliant is important when handling sensitive
<div className="not-prose">
<Link passHref href="https://forms.supabase.com/soc2">
<Button size="medium" type="default" as="a" target="_blank">
Request SOC2 documents
</Button>
<a target="_blank">
<Button size="medium" type="default">
Request SOC2 documents
</Button>
</a>
</Link>
</div>
+30 -27
View File
@@ -188,15 +188,16 @@ function StoragePage() {
text="Serve from the edge to reduce latency."
/>
<Link href="/docs/guides/storage/cdn" passHref>
<Button
as="a"
size="small"
type="default"
className="mt-4"
icon={<IconArrowUpRight />}
>
Explore docs
</Button>
<a>
<Button
size="small"
type="default"
className="mt-4"
icon={<IconArrowUpRight />}
>
Explore docs
</Button>
</a>
</Link>
</div>
<div className="col-span-6 lg:col-span-12 xl:col-span-4">
@@ -206,15 +207,16 @@ function StoragePage() {
text="Resize and compress your media before you serve it."
/>
<Link href="/docs/guides/storage/image-transformations" passHref>
<Button
as="a"
size="small"
type="default"
className="mt-4"
icon={<IconArrowUpRight />}
>
Explore docs
</Button>
<a>
<Button
size="small"
type="default"
className="mt-4"
icon={<IconArrowUpRight />}
>
Explore docs
</Button>
</a>
</Link>
</div>
</div>,
@@ -252,15 +254,16 @@ function StoragePage() {
</p>
<Link href="/docs/reference/javascript/storage-createbucket" passHref>
<Button
as="a"
size="small"
type="default"
className="mt-4"
icon={<IconArrowUpRight />}
>
Explore documentation
</Button>
<a>
<Button
size="small"
type="default"
className="mt-4"
icon={<IconArrowUpRight />}
>
Explore documentation
</Button>
</a>
</Link>
</div>
<div className="col-span-12 lg:col-span-6 lg:col-start-7">
+26 -2
View File
@@ -79,7 +79,7 @@
</url>
<url>
<loc>https://supabase.com/edge-functions</loc>
<loc>https://supabase.com/legal/dpa</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
@@ -91,7 +91,7 @@
</url>
<url>
<loc>https://supabase.com/legal/dpa</loc>
<loc>https://supabase.com/edge-functions</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
@@ -1058,6 +1058,30 @@
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/blog/case-study-happyteams</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/blog/type-constraints-in-65-lines-of-sql</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/blog/geo-queries-with-postgis-in-ionic-angular</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/blog/supabase-beta-update-february-2023</loc>
<changefreq>weekly</changefreq>
<changefreq>0.5</changefreq>
</url>
<url>
<loc>https://supabase.com/alternatives/supabase-vs-firebase</loc>
<changefreq>weekly</changefreq>
+12
View File
@@ -0,0 +1,12 @@
declare module 'sse.js' {
export type SSEOptions = EventSourceInit & {
headers?: Record<string, string>
payload?: string
method?: string
}
export class SSE extends EventSource {
constructor(url: string | URL, sseOptions?: SSEOptions)
stream(): void
}
}
+1666 -2431
View File
File diff suppressed because it is too large. Load diff
+1
View File
@@ -40,6 +40,7 @@
"json-stringify-safe": "^5.0.1",
"prettier": "^2.8.4",
"prettier-plugin-sql-cst": "^0.5.0",
"supabase": "^1.45.2",
"ts-jest": "^27.0.7",
"turbo": "^1.4.7"
},
+21 -4
View File
@@ -1,8 +1,18 @@
import { createContext, useContext, useEffect, useState } from 'react'
interface UseThemeProps {
export interface UseThemeProps {
isDarkMode?: boolean
toggleTheme: () => void
/**
* Toggle between dark mode and light mode.
*
* ---
*
* If `darkMode` left `undefined`, toggles between modes.
*
* If `darkMode` set, forces
* dark mode if `true` and light mode if `false`.
*/
toggleTheme: (darkMode?: boolean) => void
}
interface ThemeProviderProps {
@@ -25,8 +35,15 @@ export const ThemeProvider = ({ children }: ThemeProviderProps) => {
setIsDarkMode(!key || key === 'true')
}, [])
const toggleTheme = () => {
setIsDarkMode(!isDarkMode)
const toggleTheme: UseThemeProps['toggleTheme'] = (darkMode) => {
const newMode = typeof darkMode === 'boolean' ? darkMode : !isDarkMode
localStorage.setItem('supabaseDarkMode', newMode.toString())
const key = localStorage.getItem('supabaseDarkMode')
document.documentElement.className = key === 'true' ? 'dark' : ''
setIsDarkMode(newMode)
}
return (
+27 -1
View File
@@ -225,6 +225,26 @@ const uiConfig = {
'0%': { transform: 'translate-x-0', opacity: 1 },
'100%': { transform: 'translateX(100%)', opacity: 0 },
},
lineLoading: {
'0%': {
marginLeft: '-10%',
width: '80px',
},
'25%': {
width: ' 240px',
},
'50%': {
marginLeft: '100%',
width: '80px',
},
'75%': {
width: '240px',
},
'100%': {
marginLeft: '-10%',
width: '80px',
},
},
},
animation: {
'fade-in': 'fadeIn 300ms',
@@ -250,6 +270,8 @@ const uiConfig = {
'panel-slide-right-out': 'panelSlideRightOut 200ms cubic-bezier(0.87, 0, 0.13, 1)',
'panel-slide-right-in': 'panelSlideRightIn 250ms cubic-bezier(0.87, 0, 0.13, 1)',
'line-loading': 'lineLoading 1.8s infinite',
// tailwind class for this is `animate-dropdownFadeIn`
dropdownFadeIn: 'dropdownFadeIn 0.1s ease-out',
// tailwind class for this is `animate-dropdownFadeOut`
@@ -267,13 +289,17 @@ const uiConfig = {
function ({ addUtilities, addVariant }) {
// addVariant('data-open', '&:[data-state=open]')
addUtilities({
'.line-loading-bg': {
background: 'rgb(0, 0, 0)',
background:
'linear-gradient(90deg,rgba(0, 0, 0, 0) 0%,rgba(255, 255, 255, 0.65) 50%,rgba(0, 0, 0, 0) 100%)',
},
".dropdown-content[data-state='open']": {
animation: 'fadeIn 50ms ease-out',
},
".dropdown-content[data-state='closed']": {
animation: 'fadeOut 50ms ease-in',
},
"[data-state='open'] .accordion-content-animation": {
animation: 'slideDown 200ms ease-out',
},
+3
View File
@@ -55,6 +55,9 @@ export * from './src/components/Toggle'
export * from './src/components/Upload'
export * from './src/components/Form'
// CMD+K
// export * from './src/components/Command'
// config
// export { default as Config } from './../ui.config'
+16 -8
View File
@@ -10,24 +10,32 @@
"dependencies": {
"@headlessui/react": "^1.0.0",
"@mertasan/tailwindcss-variables": "^2.0.1",
"@radix-ui/colors": "^0.1.8",
"@radix-ui/react-accordion": "^0.1.5",
"@radix-ui/react-collapsible": "^0.1.5",
"@radix-ui/react-context-menu": "^0.1.0",
"@radix-ui/react-dialog": "^0.1.5",
"@radix-ui/react-dropdown-menu": "^0.1.4",
"@radix-ui/react-popover": "^0.1.0",
"@radix-ui/react-accordion": "^1.1.0",
"@radix-ui/react-collapsible": "^1.0.1",
"@radix-ui/react-context-menu": "^2.1.2",
"@radix-ui/react-dialog": "^1.0.2",
"@radix-ui/react-dropdown-menu": "^2.0.3",
"@radix-ui/react-popover": "^1.0.4",
"@radix-ui/react-portal": "^1.0.1",
"@radix-ui/react-tabs": "^0.1.0",
"@radix-ui/react-tabs": "^1.0.2",
"@supabase/auth-helpers-react": "^0.3.1",
"@tailwindcss/forms": "^0.4.0",
"@tailwindcss/typography": "^0.5.0",
"autoprefixer": "^10.4.2",
"clsx": "^1.2.1",
"cmdk-supabase": "^0.2.2",
"deepmerge": "^4.2.2",
"formik": "^2.2.9",
"lodash": "^4.17.20",
"lucide-react": "^0.124.0",
"openai": "^3.2.1",
"postcss": "^8.4.5",
"prop-types": "^15.7.2",
"react-feather": "^2.0.10",
"react-markdown": "^8.0.3",
"remark-gfm": "^3.0.1",
"sse.js": "^0.6.1",
"tailwind-merge": "^1.10.0",
"tailwindcss": "^3.0.15",
"tailwindcss-radix": "^1.6.0"
},
@@ -0,0 +1,342 @@
import type {
ChatCompletionResponseMessage,
CreateChatCompletionResponse,
CreateChatCompletionResponseChoicesInner,
} from 'openai'
import { useCallback, useEffect, useReducer, useRef, useState } from 'react'
import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
import { SSE } from 'sse.js'
import { Button, IconAlertCircle, IconAlertTriangle, IconLoader, IconUser, Input } from 'ui'
import { AiIcon, AiIconChat } from './Command.icons'
import { CommandGroup, CommandItem } from './Command.utils'
import { useCommandMenu } from './CommandMenuProvider'
const questions = [
'How do I get started with Supabase?',
'How do I run Supabase locally?',
'How do I connect to my database?',
'How do I run migrations? ',
'How do I listen to changes in a table?',
'How do I set up authentication?',
]
type CreateChatCompletionResponseChoicesInnerDelta = Omit<
CreateChatCompletionResponseChoicesInner,
'message'
> & {
delta: Partial<ChatCompletionResponseMessage>
}
function getEdgeFunctionUrl() {
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL?.replace(/\/$/, '')
if (!supabaseUrl) {
throw new Error('Missing environment variable NEXT_PUBLIC_SUPABASE_URL')
}
// https://github.com/supabase/supabase-js/blob/10d3423506cbd56345f7f6ab2ec2093c8db629d4/src/SupabaseClient.ts#L96
const isPlatform = supabaseUrl.match(/(supabase\.co)|(supabase\.in)/)
if (isPlatform) {
const [schemeAndProjectId, domain, tld] = supabaseUrl.split('.')
return `${schemeAndProjectId}.functions.${domain}.${tld}`
} else {
return `${supabaseUrl}/functions/v1`
}
}
const edgeFunctionUrl = getEdgeFunctionUrl()
function promptDataReducer(
state: any[],
action: {
index?: number
answer?: string | undefined
status?: string
query?: string | undefined
type?: 'remove-last-item' | string
}
) {
// set a standard state to use later
let current = [...state]
if (action.type) {
switch (action.type) {
case 'remove-last-item':
current.pop()
return [...current]
default:
break
}
}
// check that an index is present
if (action.index === undefined) return [...state]
if (!current[action.index]) {
current[action.index] = { query: '', answer: '', status: '' }
}
current[action.index].answer = action.answer
if (action.query) {
current[action.index].query = action.query
}
if (action.status) {
current[action.index].status = action.status
}
return [...current]
}
const AiCommand = () => {
const [answer, setAnswer] = useState<string | undefined>('')
const [isResponding, setIsResponding] = useState(false)
const [hasClippyError, setHasClippyError] = useState(false)
const eventSourceRef = useRef<SSE>()
const { isLoading, setIsLoading, currentPage, search, setSearch } = useCommandMenu()
const [promptIndex, setPromptIndex] = useState(0)
const [promptData, dispatchPromptData] = useReducer(promptDataReducer, [])
const cantHelp = answer?.trim() === "Sorry, I don't know how to help with that."
const status = isLoading
? 'Clippy is searching...'
: isResponding
? 'Clippy is responding...'
: cantHelp || hasClippyError
? 'Clippy has failed you'
: undefined
const handleConfirm = useCallback(
async (query: string) => {
setAnswer(undefined)
setSearch('')
dispatchPromptData({ index: promptIndex, answer: undefined, query })
setIsResponding(false)
setHasClippyError(false)
setIsLoading(true)
const eventSource = new SSE(`${edgeFunctionUrl}/clippy-search`, {
headers: {
apikey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY ?? '',
Authorization: `Bearer ${process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY}`,
'Content-Type': 'application/json',
},
payload: JSON.stringify({ query }),
})
function handleError<T>(err: T) {
setIsLoading(false)
setIsResponding(false)
setHasClippyError(true)
console.error(err)
}
eventSource.addEventListener('error', handleError)
eventSource.addEventListener('message', (e: any) => {
try {
setIsLoading(false)
if (e.data === '[DONE]') {
setIsResponding(false)
setAnswer(undefined)
setPromptIndex((x) => {
return x + 1
})
return
}
setIsResponding(true)
const completionResponse: CreateChatCompletionResponse = JSON.parse(e.data)
const [
{
delta: { content },
},
] = completionResponse.choices as CreateChatCompletionResponseChoicesInnerDelta[]
const text = content ?? ''
setAnswer((answer) => {
const currentAnswer = answer ?? ''
dispatchPromptData({
index: promptIndex,
answer: currentAnswer + text,
})
return (answer ?? '') + text
})
} catch (err) {
handleError(err)
}
})
eventSource.stream()
eventSourceRef.current = eventSource
setIsLoading(true)
},
[promptIndex, promptData]
)
function handleResetPrompt() {
eventSourceRef.current?.close()
eventSourceRef.current = undefined
setSearch('')
setAnswer(undefined)
setIsResponding(false)
setHasClippyError(false)
}
useEffect(() => {
if (search) {
handleConfirm(search)
}
}, [])
return (
<div onClick={(e) => e.stopPropagation()}>
<div className="relative mb-[70px] py-4 overflow-y-auto overflow-hidden max-h-[720px]">
<div className="flex flex-col gap-6">
{promptData.map((prompt, i) => {
if (!prompt.query) return <></>
return (
<>
{prompt.query && (
<div className="flex gap-6 mx-4">
<div className="w-7 h-7 bg-brand-900 rounded-full border border-brand-800 flex items-center justify-center text-brand-1200">
<IconUser strokeWidth={2} size={16} />
</div>
<div className="prose text-scale-1000">{prompt.query}</div>
</div>
)}
<div className="px-4">
{cantHelp ? (
<p className="flex flex-col gap-4 items-center p-4">
<div className="grid md:flex items-center gap-2 mt-4 text-center justify-items-center">
<IconAlertCircle />
<p>Sorry, I don&apos;t know how to help with that.</p>
</div>
<Button size="tiny" type="secondary" onClick={handleResetPrompt}>
Try again?
</Button>
</p>
) : (
<div className="flex gap-6">
<AiIconChat />
<div className="w-full">
{isLoading && promptIndex === i ? (
<div className="bg-scale-700 h-[21px] w-[13px] mt-1 animate-pulse animate-bounce"></div>
) : (
// TODO: pull in markdown components from docs for better styling (code blocks, etc)
<ReactMarkdown
linkTarget="_blank"
className="prose dark:prose-dark"
remarkPlugins={[remarkGfm]}
transformLinkUri={(href) => {
const supabaseUrl = new URL('https://supabase.com')
const linkUrl = new URL(href, 'https://supabase.com')
if (linkUrl.origin === supabaseUrl.origin) {
return linkUrl.toString()
}
return href
}}
>
{prompt.answer}
</ReactMarkdown>
)}
</div>
</div>
)}
</div>
</>
)
})}
</div>
{promptData.length === 0 && !hasClippyError && (
<CommandGroup heading="Examples" forceMount>
{questions.map((question) => {
const key = question.replace(/\s+/g, '_')
return (
<CommandItem
type="command"
onSelect={() => {
if (!search) {
handleConfirm(question)
}
}}
forceMount
key={key}
>
<AiIcon />
{question}
</CommandItem>
)
})}
</CommandGroup>
)}
{hasClippyError && (
<div className="p-6 flex flex-col items-center gap-6 mt-4">
<IconAlertTriangle className="text-amber-900" strokeWidth={1.5} size={21} />
<p className="text-lg text-scale-1200 text-center">
Sorry, looks like Clippy is having a hard time!
</p>
<p className="text-sm text-scale-900 text-center">Please try again in a bit.</p>
<Button size="tiny" type="secondary" onClick={handleResetPrompt}>
Try again?
</Button>
</div>
)}
</div>
<div className="absolute bottom-0 w-full bg-scale-200">
<Input
className="bg-scale-100 rounded mx-3"
autoFocus
type="textarea"
placeholder="Ask a question"
value={search}
onChange={(e) => setSearch(e.target.value)}
onKeyDown={(e) => {
switch (e.key) {
case 'Enter':
if (!search) {
return
}
handleConfirm(search)
return
default:
return
}
}}
/>
<div className="text-scale-1100 px-3">
<div className="flex justify-between items-center text-xs">
<div className="flex items-center gap-1 py-2 text-scale-800">
<span>Powered by OpenAI.</span>
</div>
<div className="flex items-center gap-6 py-1">
{status && (
<span className="bg-scale-400 rounded-lg py-1 px-2 items-center gap-2 hidden md:flex">
{(isLoading || isResponding) && <IconLoader size={14} className="animate-spin" />}
{status}
</span>
)}
</div>
</div>
</div>
</div>
</div>
)
}
export default AiCommand
@@ -0,0 +1,46 @@
import React from 'react'
export const AiIcon = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth="1.5"
stroke="currentColor"
className="w-6 h-6 text-purple-900"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09zM18.259 8.715L18 9.75l-.259-1.035a3.375 3.375 0 00-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 002.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 002.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 00-2.456 2.456zM16.894 20.567L16.5 21.75l-.394-1.183a2.25 2.25 0 00-1.423-1.423L13.5 18.75l1.183-.394a2.25 2.25 0 001.423-1.423l.394-1.183.394 1.183a2.25 2.25 0 001.423 1.423l1.183.394-1.183.394a2.25 2.25 0 00-1.423 1.423z"
/>
</svg>
)
export const AiIconChat = () => (
<div
className="w-7 h-7
bg-gradient-to-r from-purple-900 to-purple-800
ring-purple-600
ring-1
rounded-md border border-purple-400 flex items-center justify-center
shadow-sm
"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth="1.5"
stroke="currentColor"
className="w-4 h-4 text-white"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09zM18.259 8.715L18 9.75l-.259-1.035a3.375 3.375 0 00-2.455-2.456L14.25 6l1.036-.259a3.375 3.375 0 002.455-2.456L18 2.25l.259 1.035a3.375 3.375 0 002.456 2.456L21.75 6l-1.035.259a3.375 3.375 0 00-2.456 2.456zM16.894 20.567L16.5 21.75l-.394-1.183a2.25 2.25 0 00-1.423-1.423L13.5 18.75l1.183-.394a2.25 2.25 0 001.423-1.423l.394-1.183.394 1.183a2.25 2.25 0 001.423 1.423l1.183.394-1.183.394a2.25 2.25 0 00-1.423 1.423z"
/>
</svg>
</div>
)
@@ -0,0 +1,14 @@
export interface NavItem {
label: string;
icon: string;
url: string;
sites: Site[];
subItems?: NavItem[];
}
export type Site = "docs" | "dashboard";
export interface NavItems {
navItems: NavItem[];
}
@@ -0,0 +1,307 @@
import { Command as CommandPrimitive } from 'cmdk-supabase'
import * as React from 'react'
import { cn } from './../../utils/cn'
import { DetailedHTMLProps, HTMLAttributes, KeyboardEventHandler } from 'react'
import { Modal } from '../Modal'
import { ModalProps } from '../Modal/Modal'
import { useCommandMenu } from './CommandMenuProvider'
import { LoadingLine } from './LoadingLine'
type CommandPrimitiveElement = React.ElementRef<typeof CommandPrimitive>
type CommandPrimitiveProps = React.ComponentPropsWithoutRef<typeof CommandPrimitive>
export const Command = React.forwardRef<CommandPrimitiveElement, CommandPrimitiveProps>(
({ className, ...props }, ref) => (
<CommandPrimitive
ref={ref}
className={cn('flex h-full w-full flex-col overflow-hidden', className)}
{...props}
/>
)
)
Command.displayName = CommandPrimitive.displayName
export interface CommandDialogProps extends ModalProps {
onKeyDown?: KeyboardEventHandler<HTMLDivElement>
page?: number | string
}
export const CommandDialog = ({ children, onKeyDown, page, ...props }: CommandDialogProps) => {
const [animateBounce, setAnimateBounce] = React.useState(false)
React.useEffect(() => {
setAnimateBounce(true)
setTimeout(() => setAnimateBounce(false), 126)
}, [page])
return (
<Modal
{...props}
hideFooter
className={cn(
'!bg-[#f8f9fa]/80 dark:!bg-[#1c1c1c]/80 backdrop-filter backdrop-blur-sm',
'!border-[#e6e8eb]/90 dark:!border-[#282828]/90',
'transition ease-out',
animateBounce ? 'scale-[101.5%]' : 'scale-100'
)}
>
<Command
className={[
'[&_[cmdk-group]]:px-2 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-scale-800 [&_[cmdk-input]]:h-12',
'[&_[cmdk-item]_svg]:mr-3',
'[&_[cmdk-item]_svg]:h-5',
'[&_[cmdk-item]_svg]:w-5',
'[&_[cmdk-input-wrapper]_svg]:h-5',
'[&_[cmdk-input-wrapper]_svg]:w-5',
'[&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0',
].join(' ')}
>
{children}
</Command>
</Modal>
)
}
CommandDialog.displayName = 'CommandDialog'
type CommandPrimitiveInputElement = React.ElementRef<typeof CommandPrimitive.Input>
type CommandPrimitiveInputProps = React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
export const CommandInput = React.forwardRef<
CommandPrimitiveInputElement,
CommandPrimitiveInputProps
>(({ className, value, onValueChange, ...props }, ref) => {
const { isLoading } = useCommandMenu()
return (
<div className="flex flex-col items-center" cmdk-input-wrapper="">
<CommandPrimitive.Input
value={value}
autoFocus
onValueChange={onValueChange}
ref={ref}
className={cn(
'flex h-11 w-full rounded-md bg-transparent px-4 py-7 text-sm outline-none',
'focus:shadow-none focus:ring-transparent',
'placeholder:text-scale-800 disabled:cursor-not-allowed disabled:opacity-50 dark:text-scale-1200 border-0',
className
)}
{...props}
/>
<LoadingLine loading={isLoading} />
</div>
)
})
CommandInput.displayName = CommandPrimitive.Input.displayName
type CommandPrimitiveListElement = React.ElementRef<typeof CommandPrimitive.List>
type CommandPrimitiveListProps = React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
export const CommandList = React.forwardRef<CommandPrimitiveListElement, CommandPrimitiveListProps>(
({ className, ...props }, ref) => (
<CommandPrimitive.List
ref={ref}
className={cn('overflow-y-auto overflow-x-hidden', className)}
{...props}
/>
)
)
CommandList.displayName = CommandPrimitive.List.displayName
type CommandPrimitiveEmptyElement = React.ElementRef<typeof CommandPrimitive.Empty>
type CommandPrimitiveEmptyProps = React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
export const CommandEmpty = React.forwardRef<
CommandPrimitiveEmptyElement,
CommandPrimitiveEmptyProps
>((props, ref) => (
<CommandPrimitive.Empty
ref={ref}
className="py-6 text-center text-sm text-scale-900"
{...props}
/>
))
CommandEmpty.displayName = CommandPrimitive.Empty.displayName
type CommandPrimitiveGroupElement = React.ElementRef<typeof CommandPrimitive.Group>
type CommandPrimitiveGroupProps = React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
export const CommandGroup = React.forwardRef<
CommandPrimitiveGroupElement,
CommandPrimitiveGroupProps
>(({ className, ...props }, ref) => (
<CommandPrimitive.Group
ref={ref}
className={cn(
'overflow-hidden py-3 px-2 text-scale-700 dark:text-scale-800 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:pb-1.5 [&_[cmdk-group-heading]]:text-sm [&_[cmdk-group-heading]]:font-normal [&_[cmdk-group-heading]]:text-scale-900 [&_[cmdk-group-heading]]:dark:text-sca-300',
className
)}
{...props}
/>
))
CommandGroup.displayName = CommandPrimitive.Group.displayName
type CommandPrimitiveSeparatorElement = React.ElementRef<typeof CommandPrimitive.Separator>
type CommandPrimitiveSeparatorProps = React.ComponentPropsWithoutRef<
typeof CommandPrimitive.Separator
>
export const CommandSeparator = React.forwardRef<
CommandPrimitiveSeparatorElement,
CommandPrimitiveSeparatorProps
>(({ className, ...props }, ref) => (
<CommandPrimitive.Separator
ref={ref}
className={cn(
`h-px
w-full
bg-scale-50
`,
className
)}
{...props}
/>
))
CommandSeparator.displayName = CommandPrimitive.Separator.displayName
type CommandPrimitiveItemElement = React.ElementRef<typeof CommandPrimitive.Item>
type CommandPrimitiveItemProps = React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
export interface CommandItemProps extends CommandPrimitiveItemProps {
type: 'link' | 'block-link' | 'command'
}
export const CommandItem = React.forwardRef<CommandPrimitiveItemElement, CommandItemProps>(
({ className, type, ...props }, ref) => (
<CommandPrimitive.Item
ref={ref}
className={cn(
'cursor-default',
'select-none',
'items-center',
'rounded-md',
'text-sm',
'group',
'py-3',
'text-scale-1100',
'relative',
'flex',
type === 'block-link'
? `
bg-[#fbfcfd]/90
dark:bg-[#232323]/90
border
border-[#ddd]/90
dark:border-[#282828]/90
backdrop-filter
backdrop-blur-md
px-5
transition-all
outline-none
aria-selected:border-[#ccc]
dark:aria-selected:border-[#323232]
aria-selected:bg-[#f1f3f5]/90
dark:aria-selected:bg-[#323232]
aria-selected:shadow-sm
aria-selected:scale-[100.3%]
data-[disabled]:pointer-events-none data-[disabled]:opacity-50`
: type === 'link'
? `
px-2
backdrop-filter
backdrop-blur-md
transition-all
outline-none
aria-selected:bg-[#f1f3f5]/90
dark:aria-selected:bg-[#323232]
data-[disabled]:pointer-events-none data-[disabled]:opacity-50`
: `
px-2
aria-selected:bg-scale-300
dark:aria-selected:bg-[#323232]/80
aria-selected:backdrop-filter
aria-selected:backdrop-blur-md
data-[disabled]:pointer-events-none
data-[disabled]:opacity-50
`,
className
)}
{...props}
/>
)
)
CommandItem.displayName = CommandPrimitive.Item.displayName
export const CommandItemStale = React.forwardRef<CommandPrimitiveItemElement, CommandItemProps>(
({ className, ...props }, ref) => (
<CommandPrimitive.Item
ref={ref}
className={cn(
'text-scale-1100 relative flex cursor-default select-none items-center rounded-md py-1.5 px-2 text-sm outline-none aria-selected:bg-scale-500 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:aria-selected:bg-scale-500',
className
)}
{...props}
/>
)
)
CommandItemStale.displayName = 'CommandItemStale'
export const CommandShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => {
return (
<div
className={cn(
'[&:not(:last-child)]:hover:bg-scale-600 [&:not(:last-child)]:hover:cursor-pointer',
'cursor-default bg-scale-500 px-1.5 py-0.5 rounded text-xs text-scale-900',
'last:bg-scale-600 last:text-scale-900',
'justify-end',
className
)}
{...props}
/>
)
}
CommandShortcut.displayName = 'CommandShortcut'
export const CommandLabel = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
return <span {...props} className={cn('grow', className)} />
}
CommandShortcut.displayName = 'CommandLabel'
export interface TextHighlighterProps
extends DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement> {
text: string | undefined
query: string | undefined
}
export const TextHighlighter = ({ text, query, ...props }: TextHighlighterProps) => {
const highlightMatches = (text?: string) => {
if (!text) {
return ''
}
if (!query) {
return text
}
const regex = new RegExp(query, 'gi')
return text.replace(regex, (match) => `<span class="font-bold text-scale-1200">${match}</span>`)
}
return <span dangerouslySetInnerHTML={{ __html: highlightMatches(text) }} {...props} />
}
TextHighlighter.displayName = 'TextHighlighter'
@@ -0,0 +1,237 @@
import { useCommandState } from 'cmdk-supabase'
import { useRouter } from 'next/router'
import * as React from 'react'
import { ElementRef, useRef } from 'react'
import { IconHome } from '../Icon/icons/IconHome'
import { IconArrowRight } from './../Icon/icons/IconArrowRight'
import { IconBook } from './../Icon/icons/IconBook'
import { IconColumns } from './../Icon/icons/IconColumns'
import { IconInbox } from './../Icon/icons/IconInbox'
import { IconLifeBuoy } from './../Icon/icons/IconLifeBuoy'
import { IconMonitor } from './../Icon/icons/IconMonitor'
import { IconPhone } from './../Icon/icons/IconPhone'
import { IconUser } from './../Icon/icons/IconUser'
import AiCommand from './AiCommand'
import navItems from './utils/docs-nav-items.json'
import sharedItems from './utils/shared-nav-items.json'
import { AiIcon } from './Command.icons'
import {
CommandDialog,
CommandGroup,
CommandInput,
CommandItem,
CommandLabel,
CommandList,
CommandShortcut,
} from './Command.utils'
import { useCommandMenu } from './CommandMenuProvider'
import DocsSearch from './DocsSearch'
import DashboardTableEditor from './sections/DashboardTableEditor'
import CommandMenuShortcuts from './CommandMenuShortcuts'
import SearchOnlyItem from './SearchOnlyItem'
import SearchableStudioItems from './SearchableStudioItems'
export const COMMAND_ROUTES = {
AI: 'Supabase AI',
DOCS_SEARCH: 'Docs Search',
THEME: 'Theme',
AI_ASK_ANYTHING: 'Ask anything',
AI_RLS_POLICY: 'Help me create a RLS policy',
}
export const CHAT_ROUTES = [
COMMAND_ROUTES.AI, // this one is temporary
COMMAND_ROUTES.AI_ASK_ANYTHING,
COMMAND_ROUTES.AI_RLS_POLICY,
]
const iconPicker: { [key: string]: React.ReactNode } = {
arrowRight: <IconArrowRight />,
book: <IconBook />,
inbox: <IconInbox />,
mobile: <IconPhone />,
person: <IconUser />,
services: <IconColumns />,
contact: <IconMonitor />,
icon: <IconHome />,
products: <IconColumns />,
}
const projectRef = ''
const CommandMenu = () => {
const router = useRouter()
const commandInputRef = useRef<ElementRef<typeof CommandInput>>(null)
const { isOpen, setIsOpen, actions, search, setSearch, pages, setPages, currentPage, site } =
useCommandMenu()
console.log('currentPage page', currentPage)
console.log('pages page', pages)
const ThemeOptions = ({ isSubItem = false }) => {
return (
<CommandGroup>
<SearchOnlyItem
isSubItem={isSubItem}
onSelect={() => {
actions.toggleTheme(true)
setIsOpen(false)
}}
>
Change Theme to dark
</SearchOnlyItem>
<SearchOnlyItem
isSubItem={isSubItem}
onSelect={() => {
actions.toggleTheme(false)
setIsOpen(false)
}}
>
Change Theme to light
</SearchOnlyItem>
</CommandGroup>
)
}
const showCommandInput = !currentPage || !CHAT_ROUTES.includes(currentPage)
return (
<>
<CommandDialog
page={currentPage}
visible={isOpen}
onInteractOutside={() => {
setIsOpen(!open)
}}
size={'xlarge'}
className={'max-h-[70vh] lg:max-h-[50vh] overflow-hidden overflow-y-auto'}
>
{pages.length > 0 && <CommandMenuShortcuts />}
{showCommandInput && (
<CommandInput
ref={commandInputRef}
placeholder="Type a command or search..."
value={search}
onValueChange={setSearch}
/>
)}
<CommandList className={['my-2', showCommandInput && 'max-h-[300px]'].join(' ')}>
{!currentPage && (
<>
<CommandGroup heading="Documentation" forceMount>
<CommandItem
type="command"
onSelect={() => {
setPages([...pages, COMMAND_ROUTES.AI])
}}
forceMount
>
<AiIcon />
<span className="text-transparent bg-clip-text bg-gradient-to-r from-purple-900 to-purple-1100">
Ask Supabase AI
{search ? (
<>
{': '}
<span className="text-scale-1200 font-semibold">{search}</span>
</>
) : (
'...'
)}
</span>
</CommandItem>
<CommandItem
type="command"
onSelect={() => setPages([...pages, COMMAND_ROUTES.DOCS_SEARCH])}
forceMount
>
<IconBook className="" />
<span>
Search the docs
{search ? (
<>
{': '}
<span className="text-scale-1200 font-semibold">{search}</span>
</>
) : (
'...'
)}
</span>
</CommandItem>
</CommandGroup>
{site === 'docs' && (
<CommandGroup heading="Studio tools">
{sharedItems.tools.map((item) => (
<CommandItem key={item.url} type="link" onSelect={() => router.push(item.url)}>
<IconArrowRight className="text-scale-900" />
<CommandLabel>
Go to <span className="font-bold"> {item.label}</span>
</CommandLabel>
</CommandItem>
))}
</CommandGroup>
)}
{site === 'studio' && (
<CommandGroup heading="Navigate">
{sharedItems.tools.map((item) => {
const itemUrl = projectRef ? item.url.replace('_', projectRef) : item.url
return (
<CommandItem key={item.url} type="link" onSelect={() => router.push(itemUrl)}>
<IconArrowRight className="text-scale-900" />
<CommandLabel>
Go to <span className="font-bold"> {item.label}</span>
</CommandLabel>
</CommandItem>
)
})}
</CommandGroup>
)}
{/* <DashboardTableEditor /> */}
<CommandGroup heading="Support">
{navItems.docsSupport.map((item) => (
<CommandItem key={item.url} type="link" onSelect={() => router.push(item.url)}>
<IconLifeBuoy className="text-scale-900" />
<CommandLabel>
Go to <span className="font-bold"> {item.label}</span>
</CommandLabel>
</CommandItem>
))}
</CommandGroup>
<CommandGroup heading="General">
{navItems.docsGeneral.map((item) => (
<CommandItem key={item.url} type="link" onSelect={() => router.push(item.url)}>
{item?.icon && iconPicker[item.icon]}
<CommandLabel>{item.label}</CommandLabel>
</CommandItem>
))}
</CommandGroup>
<CommandGroup heading="Settings">
<CommandItem type="link" onSelect={() => setPages([...pages, 'Theme'])}>
<IconMonitor className="mr-2" />
Change theme
</CommandItem>
</CommandGroup>
<ThemeOptions isSubItem />
{site === 'studio' && <SearchableStudioItems />}
</>
)}
{currentPage === COMMAND_ROUTES.AI && <AiCommand />}
{currentPage === COMMAND_ROUTES.DOCS_SEARCH && <DocsSearch />}
{currentPage === COMMAND_ROUTES.THEME && <ThemeOptions />}
</CommandList>
</CommandDialog>
</>
)
}
export default CommandMenu
@@ -0,0 +1,111 @@
import { useTheme, UseThemeProps } from 'common'
import * as React from 'react'
import { createContext, PropsWithChildren, useContext, useEffect, useState } from 'react'
import CommandMenu from './CommandMenu'
export interface CommandMenuContextValue {
isOpen: boolean
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>
isLoading: boolean
setIsLoading: React.Dispatch<React.SetStateAction<boolean>>
actions: CommandMenuActions
search: string
setSearch: React.Dispatch<React.SetStateAction<string>>
pages: string[]
setPages: React.Dispatch<React.SetStateAction<string[]>>
currentPage?: string
site: 'studio' | 'docs'
}
export const CommandMenuContext = createContext<CommandMenuContextValue | undefined>(undefined)
export const useCommandMenu = () => {
const context = useContext(CommandMenuContext)
if (context === undefined) {
throw new Error('useCommandMenu was used outside of CommandMenuProvider')
}
return context
}
export interface CommandMenuActions {
toggleTheme: UseThemeProps['toggleTheme']
}
const CommandMenuProvider = ({
children,
site,
}: PropsWithChildren<{ site: 'studio' | 'docs' }>) => {
const [isOpen, setIsOpen] = useState(false)
const [isLoading, setIsLoading] = useState(false)
const [search, setSearch] = React.useState('')
const [pages, setPages] = React.useState<string[]>([])
const { toggleTheme } = useTheme()
const currentPage = pages[pages.length - 1]
const actions: CommandMenuActions = {
toggleTheme,
}
useKeyboardEvents({ setIsOpen, currentPage, setSearch, setPages })
return (
<CommandMenuContext.Provider
value={{
isOpen,
setIsOpen,
isLoading,
setIsLoading,
actions,
setSearch,
search,
pages,
setPages,
currentPage,
site,
}}
>
{children}
<CommandMenu />
</CommandMenuContext.Provider>
)
}
function useKeyboardEvents({
setIsOpen,
currentPage,
setSearch,
setPages,
}: {
setIsOpen: (isOpen: boolean) => void
setSearch: React.Dispatch<React.SetStateAction<string>>
setPages: React.Dispatch<React.SetStateAction<string[]>>
currentPage?: string
}) {
useEffect(() => {
function onKeyDown(event: KeyboardEvent) {
switch (event.key) {
case 'Escape':
event.preventDefault()
// if on homepage, close the command palette
if (!currentPage) setIsOpen(false)
setSearch('')
// if NOT on homepage, return to last page
setPages((pages) => pages.slice(0, -1))
return
case 'k':
case '/':
if (event.metaKey || event.ctrlKey) {
setIsOpen(true)
}
return
}
}
window.addEventListener('keydown', onKeyDown)
return () => {
window.removeEventListener('keydown', onKeyDown)
}
}, [setIsOpen, currentPage])
}
export default CommandMenuProvider
@@ -0,0 +1,25 @@
import React from 'react'
import { CommandShortcut } from './Command.utils'
import { useCommandMenu } from './CommandMenuProvider'
export default function CommandMenuShortcuts() {
const { setPages, pages } = useCommandMenu()
return (
<div className="flex w-full gap-2 px-4 pt-4 justify-items-start flex-row">
<CommandShortcut onClick={() => setPages([])}>{'Home'}</CommandShortcut>
{pages.map((page, index) => (
<CommandShortcut
key={page}
onClick={() => {
if (index === pages.length - 1) {
return
}
setPages(pages.slice(0, index - 1))
}}
>
{page}
</CommandShortcut>
))}
</div>
)
}
@@ -0,0 +1,280 @@
import * as React from 'react'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useSupabaseClient } from '@supabase/auth-helpers-react'
import {
Button,
IconAlertTriangle,
IconBook,
IconChevronRight,
IconHash,
IconSearch,
useCommandMenu,
} from 'ui'
import { CommandGroup, CommandItem, CommandLabel, TextHighlighter } from './Command.utils'
import { debounce } from 'lodash'
const questions = [
'How do I get started with Supabase?',
'How do I run Supabase locally?',
'How do I connect to my database?',
'How do I run migrations? ',
'How do I listen to changes in a table?',
'How do I set up authentication?',
]
export enum PageType {
Markdown = 'markdown',
Reference = 'reference',
}
export interface PageSection {
slug: string
heading: string
}
export interface PageMetadata {
title: string
description: string
}
export interface PageResult {
type: PageType
path: string
meta: PageMetadata
sections: PageSection[]
}
const DocsSearch = () => {
const [results, setResults] = useState<PageResult[]>()
const [hasSearchError, setHasSearchError] = useState(false)
const supabaseClient = useSupabaseClient()
const { isLoading, setIsLoading, search, setSearch } = useCommandMenu()
const handleSearch = useCallback(
async (query: string) => {
setHasSearchError(false)
setIsLoading(true)
const { error, data: pageSections } = await supabaseClient.functions.invoke('search', {
body: { query },
})
setIsLoading(false)
if (error) {
setIsLoading(false)
setHasSearchError(true)
console.error(error)
return
}
if (!Array.isArray(pageSections)) {
setIsLoading(false)
setHasSearchError(true)
console.error('Malformed response')
return
}
setResults(pageSections)
},
[supabaseClient]
)
function handleResetPrompt() {
setSearch('')
setResults(undefined)
setHasSearchError(false)
}
const debouncedSearch = useMemo(() => debounce(handleSearch, 1000), [handleSearch])
// Search initial query immediately (note - empty useEffect deps)
useEffect(() => {
if (search) {
handleSearch(search)
}
}, [])
// TODO: can we do this w/o useEffect if query comes from context?
useEffect(() => {
if (search) {
debouncedSearch(search)
}
}, [search])
const ChevronArrow = () => (
<IconChevronRight
strokeWidth={1.5}
className="
text-scale-900
opacity-0
-left-4
group-aria-selected:scale-[101%]
group-aria-selected:opacity-100
group-aria-selected:left-0
"
/>
)
const IconContainer = (
props: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
) => (
<div
className="
transition
w-6 h-6
bg-scale-100
group-aria-selected:scale-[105%]
group-aria-selected:bg-scale-1200
text-scale-1200
group-aria-selected:text-scale-100
rounded flex
items-center
justify-center
group-aria-selected:[&_svg]:scale-[103%]
"
{...props}
/>
)
return (
<>
{results &&
results.length > 0 &&
results.map((page, i) => {
const pageSections = page.sections.filter((section) => !!section.heading)
return (
<CommandGroup
heading=""
forceMount
key={`${page.meta.title}-group-index-${i}`}
value={`${page.meta.title}-group-index-${i}`}
>
<CommandItem
forceMount
key={`${page.meta.title}-item-index-${i}`}
value={`${page.meta.title}-item-index-${i}`}
type="block-link"
onSelect={() => {
// TODO: replace with Next.js router/Link when cross-project link logic solved
window.location.assign(`/docs/${page.path}`)
}}
>
<div className="grow flex gap-3 items-center">
<IconContainer>
<IconBook strokeWidth={1.5} className="!mr-0 !w-4 !h-4" />
</IconContainer>
<div className="flex flex-col gap-0">
<CommandLabel>
<TextHighlighter text={page.meta.title} query={search} />
</CommandLabel>
<div className="text-xs text-scale-900">
<TextHighlighter text={page.meta.description} query={search} />
</div>
</div>
</div>
<ChevronArrow />
</CommandItem>
{pageSections.length > 0 && (
<div className="border-l border-scale-500 ml-3 pt-3">
{pageSections.map((section, i) => (
<CommandItem
forceMount
className="ml-3 mb-3"
onSelect={() => {
// TODO: replace with Next.js router/Link when cross-project link logic solved
window.location.assign(
`/docs/${page.path}${page.type === PageType.Reference ? '/' : '#'}${
section.slug
}`
)
}}
key={`${page.meta.title}__${section.heading}-item-index-${i}`}
value={`${page.meta.title}__${section.heading}-item-index-${i}`}
type="block-link"
>
<div className="grow flex gap-3 items-center">
<IconContainer>
<IconHash strokeWidth={1.5} className="!mr-0 !w-4 !h-4" />
</IconContainer>
<div className="flex flex-col gap-2">
<cite>
<TextHighlighter
className="not-italic text-xs rounded-full px-2 py-1 bg-scale-500 text-scale-1200"
text={page.meta.title}
query={search}
/>
</cite>
<CommandLabel>
<TextHighlighter text={section.heading} query={search} />
</CommandLabel>
</div>
</div>
<ChevronArrow />
</CommandItem>
))}
</div>
)}
</CommandGroup>
)
})}
{!results && !hasSearchError && (
<CommandGroup forceMount>
{questions.map((question) => {
const key = question.replace(/\s+/g, '_')
return (
<CommandItem
disabled={isLoading}
onSelect={() => {
if (!search) {
handleSearch(question)
setSearch(question)
}
}}
type="command"
forceMount
key={key}
>
<IconSearch />
{question}
</CommandItem>
)
})}
</CommandGroup>
)}
{isLoading && !results && (
<div className="p-6 grid gap-6 my-4">
<p className="text-lg text-scale-900 text-center">Searching for results</p>
</div>
)}
{results && results.length === 0 && (
<div className="p-6 flex flex-col items-center gap-6 mt-4">
<IconAlertTriangle strokeWidth={1.5} size={40} />
<p className="text-lg text-center">No results found.</p>
<Button size="tiny" type="secondary" onClick={handleResetPrompt}>
Try again?
</Button>
</div>
)}
{hasSearchError && (
<div className="p-6 flex flex-col items-center gap-6 mt-4">
<IconAlertTriangle strokeWidth={1.5} size={40} />
<p className="text-lg text-center">
Sorry, looks like we&apos;re having some issues with search!
</p>
<p className="text-sm text-center">Please try again in a bit.</p>
<Button size="tiny" type="secondary" onClick={handleResetPrompt}>
Try again?
</Button>
</div>
)}
</>
)
}
export default DocsSearch
@@ -0,0 +1,18 @@
import React from 'react'
import { cn } from '../../utils/cn'
export const LoadingLine = ({ loading }: { loading: boolean }) => {
return (
<div className="relative overflow-hidden w-full h-px bg-[#eceef0] dark:bg-[#2e2e2e] m-auto">
<span
className={cn(
'absolute w-[80px] h-px ml-auto mr-auto left-0 right-0 text-center block top-0',
'transition-all',
'line-loading-bg',
loading && 'animate-line-loading opacity-100',
loading ? 'opacity-100' : 'opacity-0'
)}
></span>
</div>
)
}
@@ -0,0 +1,21 @@
import * as React from 'react'
import { ButtonHTMLAttributes, DetailedHTMLProps, PropsWithChildren, useRef } from 'react'
import { useCommandMenu } from './CommandMenuProvider'
const SearchButton = ({
children,
...props
}: PropsWithChildren<
DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
>) => {
const searchButtonRef = useRef<HTMLButtonElement>(null)
const { setIsOpen } = useCommandMenu()
return (
<button type="button" ref={searchButtonRef} onClick={() => setIsOpen(true)} {...props}>
{children}
</button>
)
}
export default SearchButton
@@ -0,0 +1,11 @@
import { useCommandState } from 'cmdk-supabase'
import React from 'react'
import { CommandItem } from './Command.utils'
export default function SearchOnlyItem({ children, isSubItem, ...props }: any) {
const search = useCommandState((state) => state.search)
// if search is empty & items is marked as a subItem, don't show it
// ie: only show these items in search results, not top level
if (!search && isSubItem) return null
return <CommandItem {...props}>{children}</CommandItem>
}
@@ -0,0 +1,27 @@
import React from 'react'
import { CommandGroup } from './Command.utils'
import SearchOnlyItem from './SearchOnlyItem'
import { useRouter } from 'next/router'
import sharedItems from './utils/shared-nav-items.json'
export default function SearchableStudioItems() {
const router = useRouter()
return (
<CommandGroup heading="Find">
{sharedItems.tools
.filter((item) => item.subItems) // filter items with subItems
.flatMap((item) =>
item.subItems?.map((subItem) => (
<SearchOnlyItem
key={subItem.url}
isSubItem={true}
onSelect={() => router.push(subItem.url)}
>
{subItem.label}
</SearchOnlyItem>
))
)}
</CommandGroup>
)
}
@@ -0,0 +1,96 @@
{
"items": [
{
"label": "Home",
"icon": "home",
"url": "http://www.example.com/home",
"sites": ["docs", "dashboard"],
"subItems": [
{
"label": "About",
"icon": "book",
"url": "http://www.example.com/about",
"sites": ["docs", "dashboard"]
},
{
"label": "Services",
"icon": "services",
"url": "http://www.example.com/services",
"sites": ["docs", "dashboard"]
},
{
"label": "Contact",
"icon": "contact",
"url": "http://www.example.com/contact",
"sites": ["docs", "dashboard"]
}
]
},
{
"label": "Products",
"icon": "products",
"url": "http://www.example.com/products",
"sites": ["dashboard"],
"subItems": [
{
"label": "Mobile",
"icon": "mobile",
"url": "http://www.example.com/products/mobile"
},
{
"label": "In person",
"icon": "person",
"url": "http://www.example.com/products/in-person"
}
]
}
],
"docsTools": [
{
"label": "Database",
"url": "https://supabase.com/docs/guides/database"
},
{
"label": "Auth",
"url": "https://supabase.com/docs/guides/auth/overview"
},
{
"label": "Edge Functions",
"url": "https://supabase.com/docs/guides/functions"
},
{
"label": "Realtime",
"url": "https://supabase.com/docs/guides/realtime"
},
{
"label": "Storage",
"url": "https://supabase.com/docs/guides/storage"
}
],
"docsSupport": [
{
"label": "Support",
"url": "https://supabase.com/support"
},
{
"label": "System Status",
"url": "https://status.supabase.com/"
},
{
"label": "Github Discussions",
"url": "https://github.com/supabase/supabase/discussions"
}
],
"docsGeneral": [
{
"icon": "arrowRight",
"label": "Go to the Dashboard",
"url": "https://app.supabase.io"
},
{
"icon": "inbox",
"label": "Read the Changelog",
"url": "https://supabase.com/changelog"
}
]
}
@@ -0,0 +1,16 @@
export { default as CommandMenuProvider } from './CommandMenuProvider'
export * from './CommandMenuProvider'
export { default as CommandMenu } from './CommandMenu'
export * from './CommandMenu'
export { default as AiCommand } from './AiCommand'
export * from './AiCommand'
export { default as DocsSearch } from './DocsSearch'
export * from './DocsSearch'
export { default as SearchButton } from './SearchButton'
export * from './SearchButton'
export * from './Command.icons'
@@ -0,0 +1,49 @@
import { useRouter } from 'next/router'
import * as React from 'react'
import { IconArrowRight } from '../../Icon/icons/IconArrowRight'
import dashboardItems from '../utils/dashboard-nav-items.json'
import { CommandGroup, CommandItem, CommandLabel, CommandList } from '../Command.utils'
import { useCommandMenu } from '../CommandMenuProvider'
export default function DashboardTableEditor() {
const router = useRouter()
const { setPages, currentPage, pages } = useCommandMenu()
return (
<CommandGroup heading={dashboardItems.tools.label}>
<CommandList>
{!currentPage && (
<>
<CommandItem type="command" onSelect={() => setPages([...pages, 'projects'])}>
Search projects…
</CommandItem>
<CommandItem type="command" onSelect={() => setPages([...pages, 'teams'])}>
Join a team…
</CommandItem>
</>
)}
{currentPage === 'projects' && (
<>
<CommandItem type="link">Project B</CommandItem>
</>
)}
{currentPage === 'teams' && (
<>
<CommandItem type="link">Team 1</CommandItem>
<CommandItem type="link">Team 2</CommandItem>
</>
)}
</CommandList>
{/* {dashboardItems.tools.items.map((item) => (
<CommandItem key={item.url} type="link" onSelect={() => router.push(item.url)}>
<IconArrowRight className="text-scale-900" />
<CommandLabel>
Go to <span className="font-bold"> {item.label}</span>
</CommandLabel>
</CommandItem>
))} */}
</CommandGroup>
)
}
@@ -0,0 +1,75 @@
{
"tools": {
"label": "Dashboard Tools",
"items": [
{
"label": "dashboard - Database",
"url": "https://supabase.com/docs/guides/database",
"subItems": [
{
"label": "Triggers",
"url": "http://www.example.com/triggers"
},
{
"label": "Functions",
"icon": "functions",
"url": "http://www.example.com/functions"
}
]
},
{
"label": "dashboard - Auth",
"url": "https://supabase.com/docs/guides/auth/overview"
},
{
"label": "dashboard - Edge Functions",
"url": "https://supabase.com/docs/guides/functions"
},
{
"label": "dashboard - Realtime",
"url": "https://supabase.com/docs/guides/realtime"
},
{
"label": "dashboard - Storage",
"url": "https://supabase.com/docs/guides/storage"
}
]
},
"settings": {
"label": "Dashboard Settings",
"items": [
{
"label": "settings - Database general",
"url": "https://supabase.com/docs/guides/database"
},
{
"label": "settings - Database settings",
"url": "https://supabase.com/docs/guides/auth/overview"
},
{
"label": "settings - API settings",
"url": "https://supabase.com/docs/guides/functions"
},
{
"label": "settings - Auth settings",
"url": "https://supabase.com/docs/guides/realtime"
},
{
"label": "settings - Storage settings",
"url": "https://supabase.com/docs/guides/storage"
},
{
"label": "settings - subscription settings",
"url": "https://supabase.com/docs/guides/storage"
},
{
"label": "settings - usage settings",
"url": "https://supabase.com/docs/guides/storage"
},
{
"label": "settings - invoices settings",
"url": "https://supabase.com/docs/guides/storage"
}
]
}
}
@@ -0,0 +1,96 @@
{
"items": [
{
"label": "Home",
"icon": "home",
"url": "http://www.example.com/home",
"sites": ["docs", "dashboard"],
"subItems": [
{
"label": "About",
"icon": "book",
"url": "http://www.example.com/about",
"sites": ["docs", "dashboard"]
},
{
"label": "Services",
"icon": "services",
"url": "http://www.example.com/services",
"sites": ["docs", "dashboard"]
},
{
"label": "Contact",
"icon": "contact",
"url": "http://www.example.com/contact",
"sites": ["docs", "dashboard"]
}
]
},
{
"label": "Products",
"icon": "products",
"url": "http://www.example.com/products",
"sites": ["dashboard"],
"subItems": [
{
"label": "Mobile",
"icon": "mobile",
"url": "http://www.example.com/products/mobile"
},
{
"label": "In person",
"icon": "person",
"url": "http://www.example.com/products/in-person"
}
]
}
],
"docsTools": [
{
"label": "Database",
"url": "https://supabase.com/docs/guides/database"
},
{
"label": "Auth",
"url": "https://supabase.com/docs/guides/auth/overview"
},
{
"label": "Edge Functions",
"url": "https://supabase.com/docs/guides/functions"
},
{
"label": "Realtime",
"url": "https://supabase.com/docs/guides/realtime"
},
{
"label": "Storage",
"url": "https://supabase.com/docs/guides/storage"
}
],
"docsSupport": [
{
"label": "Support",
"url": "https://supabase.com/support"
},
{
"label": "System Status",
"url": "https://status.supabase.com/"
},
{
"label": "Github Discussions",
"url": "https://github.com/supabase/supabase/discussions"
}
],
"docsGeneral": [
{
"icon": "arrowRight",
"label": "Go to the Dashboard",
"url": "https://app.supabase.io"
},
{
"icon": "inbox",
"label": "Read the Changelog",
"url": "https://supabase.com/changelog"
}
]
}
@@ -0,0 +1,174 @@
{
"tools": [
{
"label": "Table Editor",
"url": "https://app.supabase.com/project/_/editor"
},
{
"label": "SQL Editor",
"url": "https://app.supabase.com/project/_/sql"
},
{
"label": "Database",
"url": "https://app.supabase.com/project/_/database/tables",
"subItems": [
{
"label": "Tables",
"url": "https://app.supabase.com/project/_/database/tables"
},
{
"label": "Triggers",
"url": "https://app.supabase.com/project/_/database/triggers"
},
{
"label": "Functions",
"url": "https://app.supabase.com/project/_/database/functions"
},
{
"label": "Extensions",
"url": "https://app.supabase.com/project/_/database/extensions"
},
{
"label": "Roles",
"url": "https://app.supabase.com/project/_/database/roles"
},
{
"label": "Replication",
"url": "https://app.supabase.com/project/_/database/replication"
},
{
"label": "Webhooks",
"url": "https://app.supabase.com/project/_/database/webhooks"
},
{
"label": "Backups",
"url": "https://app.supabase.com/project/_/database/backups/scheduled"
}
]
},
{
"label": "Auth",
"url": "https://app.supabase.com/project/_/auth/users",
"subItems": [
{
"label": "Users",
"url": "https://app.supabase.com/project/_/auth/users"
},
{
"label": "Policies",
"url": "https://app.supabase.com/project/_/auth/policies"
},
{
"label": "Providers",
"url": "https://app.supabase.com/project/_/auth/providers"
},
{
"label": "Email Templates",
"url": "https://app.supabase.com/project/_/auth/templates"
},
{
"label": "URL Configuration",
"url": "https://app.supabase.com/project/_/auth/url-configuration"
}
]
},
{
"label": "Storage",
"url": "https://app.supabase.com/project/_/storage"
},
{
"label": "Edge Functions",
"url": "https://app.supabase.com/project/_/functions"
},
{
"label": "Logs",
"url": "https://app.supabase.com/project/_/logs/explorer",
"subItems": [
{
"label": "Postgres logs",
"url": "https://app.supabase.com/project/_/logs/postgres-logs"
},
{
"label": "PostgREST logs",
"url": "https://app.supabase.com/project/_/logs/postgrest-logs"
},
{
"label": "Postgres logs",
"url": "https://app.supabase.com/project/_/logs/pgbouncer-logs"
},
{
"label": "Auth logs logs",
"url": "https://app.supabase.com/project/_/logs/auth-logs"
},
{
"label": "Storage logs",
"url": "https://app.supabase.com/project/_/logs/storage-logs"
},
{
"label": "realtime logs",
"url": "https://app.supabase.com/project/_/logs/realtime-logs"
}
]
},
{
"label": "API Docs",
"url": "https://app.supabase.com/project/_/api",
"subItems": [
{
"label": "Auth docs",
"url": "https://app.supabase.com/project/_/api?page=auth"
},
{
"label": "User management docs",
"url": "https://app.supabase.com/project/_/api?page=users"
},
{
"label": "Graphql",
"url": "https://app.supabase.com/project/_/api/graphiql"
}
]
},
{
"label": "Project Settings",
"url": "https://app.supabase.com/project/_/settings/general",
"subItems": [
{
"label": "General settings",
"url": "https://app.supabase.com/project/_/settings/general"
},
{
"label": "Database settings",
"url": "https://app.supabase.com/project/_/settings/database"
},
{
"label": "API settings",
"url": "https://app.supabase.com/project/_/settings/api"
},
{
"label": "Auth settings",
"url": "https://app.supabase.com/project/_/settings/auth"
},
{
"label": "Storage settings",
"url": "https://app.supabase.com/project/_/settings/storage"
},
{
"label": "Subscription settings",
"url": "https://app.supabase.com/project/_/settings/billing/subscription"
},
{
"label": "Project Usage",
"url": "https://app.supabase.com/project/_/settings/billing/usage"
},
{
"label": "Project Invoices",
"url": "https://app.supabase.com/project/_/settings/billing/invoices"
},
{
"label": "Project Invoices",
"url": "https://app.supabase.com/project/_/settings/billing/invoices"
}
]
}
]
}
@@ -43,7 +43,7 @@ function ContextMenu({
</RadixContextMenu.Trigger>
<RadixContextMenu.Content
sideOffset={alignOffset}
alignOffset={alignOffset}
// className={classes.join(' ')}
style={style}
>
@@ -152,11 +152,7 @@ interface RadioGroupProps {
onChange?(x: string): void
}
export function RadioGroup({
children,
value: propsValue,
onChange,
}: RadioGroupProps) {
export function RadioGroup({ children, value: propsValue, onChange }: RadioGroupProps) {
const [value, setValue] = useState(propsValue ? propsValue : '')
const handleChange = (e: string) => {
@@ -3,23 +3,20 @@ import React, { useState } from 'react'
import * as RadixDropdown from '@radix-ui/react-dropdown-menu'
import { IconCheck } from '../Icon/icons/IconCheck'
// @ts-ignore
// import DropdownStyles from './Dropdown.module.css'
import type * as RadixDropdownTypes from '@radix-ui/react-dropdown-menu/'
import styleHandler from '../../lib/theme/styleHandler'
import { IconTarget } from '../Icon/icons/IconTarget'
import { cn } from '../../utils/cn'
interface RootProps {
interface RootProps extends RadixDropdownTypes.DropdownMenuProps {
open?: boolean
arrow?: boolean
onOpenChange?: RadixDropdownTypes.DropdownMenuProps['onOpenChange'] // DropdownMenu['onOpenChange']
side?: RadixDropdownTypes.DropdownMenuContentProps['side']
align?: RadixDropdownTypes.DropdownMenuContentProps['align']
sideOffset?: RadixDropdownTypes.DropdownMenuContentProps['sideOffset']
overlay?: React.ReactNode
children?: React.ReactNode
overlay?: RadixDropdownTypes.DropdownMenuContentProps['children']
children?: RadixDropdownTypes.DropdownMenuTriggerProps['children']
size?: 'tiny' | 'small' | 'medium' | 'large' | 'xlarge' | 'content'
className?: string
style?: React.CSSProperties
@@ -27,8 +24,6 @@ interface RootProps {
}
function Dropdown({
open,
onOpenChange,
align = 'center', //Default value
side = 'bottom', //Default value
sideOffset = 6,
@@ -39,6 +34,7 @@ function Dropdown({
style,
arrow,
isNested,
...props
}: RootProps) {
let __styles = styleHandler('dropdown')
@@ -48,57 +44,63 @@ function Dropdown({
}
return (
<RadixDropdown.Root onOpenChange={onOpenChange} open={open}>
<RadixDropdown.Root {...props}>
{isNested ? (
<RadixDropdown.TriggerItem className={[__styles.item_nested].join(' ')}>
{children}
</RadixDropdown.TriggerItem>
<RadixDropdown.Sub>
<RadixDropdown.SubTrigger className={[__styles.item_nested].join(' ')}>
{children}
</RadixDropdown.SubTrigger>
<RadixDropdown.SubContent
sideOffset={sideOffset}
className={classes.join(' ')}
style={style}
>
{arrow && (
<RadixDropdown.Arrow className={__styles.arrow} offset={10}></RadixDropdown.Arrow>
)}
{overlay}
</RadixDropdown.SubContent>
</RadixDropdown.Sub>
) : (
<RadixDropdown.Trigger className={__styles.trigger}>{children}</RadixDropdown.Trigger>
<>
<RadixDropdown.Trigger className={__styles.trigger}>{children}</RadixDropdown.Trigger>
<RadixDropdown.Portal>
<RadixDropdown.Content
sideOffset={sideOffset}
side={side}
align={align}
className={classes.join(' ')}
style={style}
>
{arrow && (
<RadixDropdown.Arrow className={__styles.arrow} offset={10}></RadixDropdown.Arrow>
)}
{overlay}
</RadixDropdown.Content>
</RadixDropdown.Portal>
</>
)}
<RadixDropdown.Content
portalled={true}
sideOffset={sideOffset}
side={side}
align={align}
className={classes.join(' ')}
style={style}
>
{arrow && (
<RadixDropdown.Arrow className={__styles.arrow} offset={10}></RadixDropdown.Arrow>
)}
{overlay}
</RadixDropdown.Content>
</RadixDropdown.Root>
)
}
export function RightSlot({ children }: any) {
export function RightSlot({ children }: { children: React.ReactNode }) {
let __styles = styleHandler('dropdown')
return <div className={__styles.right_slot}>{children}</div>
}
interface ItemProps {
className?: string
interface ItemProps extends RadixDropdownTypes.DropdownMenuItemProps {
children: React.ReactNode
icon?: React.ReactNode
disabled?: boolean
onClick?: (event: Event) => void
rightSlot?: React.ReactNode
}
export function Item({ className, children, icon, disabled, onClick, rightSlot }: ItemProps) {
export function Item({ icon, className, ...props }: ItemProps) {
let __styles = styleHandler('dropdown')
return (
<RadixDropdown.Item
className={`${__styles.item} ${className}`}
disabled={disabled}
onSelect={onClick}
>
<RadixDropdown.Item className={cn(__styles.item, className)} {...props}>
{icon && icon}
<span>{children}</span>
<span>{props.children}</span>
</RadixDropdown.Item>
)
}
@@ -123,33 +125,33 @@ export function Misc({ children, icon }: ItemProps) {
)
}
interface CheckboxProps {
children: React.ReactNode
checked?: boolean
onChange?(x: boolean): void
disabled?: boolean
ItemIndicator?: React.ReactNode
}
export function Separator() {
let __styles = styleHandler('dropdown')
return <RadixDropdown.Separator className={__styles.separator} />
}
// to do : remove onChange omit in favor of using onCheckedChange
interface CheckboxProps extends Omit<RadixDropdownTypes.DropdownMenuCheckboxItemProps, 'onChange'> {
ItemIndicator?: React.ReactNode
onChange?(x: boolean): void
}
export function Checkbox({
children,
checked: propsChecked,
onChange,
disabled,
ItemIndicator,
onChange,
className,
...props
}: CheckboxProps) {
const [checked, setChecked] = useState(propsChecked ? propsChecked : false)
const [checked, setChecked] = useState(propsChecked ?? false)
let __styles = styleHandler('dropdown')
const handleChange = (e: boolean) => {
const handleChange = (e: any) => {
// to do : remove onChange in favor of using onCheckedChange
if (onChange) onChange(e)
if (props.onCheckedChange) props.onCheckedChange(e)
setChecked(e)
}
@@ -157,71 +159,59 @@ export function Checkbox({
<RadixDropdown.CheckboxItem
checked={checked}
onCheckedChange={handleChange}
className={`${__styles.item} ${__styles.input}`}
disabled={disabled}
className={cn(__styles.item, __styles.input, className)}
{...props}
>
<RadixDropdown.ItemIndicator className={__styles.check}>
{ItemIndicator ? ItemIndicator : <IconCheck size="tiny" strokeWidth={3} />}
<RadixDropdown.CheckboxItem />
</RadixDropdown.ItemIndicator>
<span>{children}</span>
<span>{props.children}</span>
</RadixDropdown.CheckboxItem>
)
}
interface RadioProps {
children: React.ReactNode
value: string
interface RadioProps extends RadixDropdownTypes.DropdownMenuRadioItemProps {
ItemIndicator?: React.ReactNode
disabled?: boolean
}
export function Radio({ children, value, ItemIndicator, disabled }: RadioProps) {
export function Radio({ ItemIndicator, className, ...props }: RadioProps) {
let __styles = styleHandler('dropdown')
return (
<RadixDropdown.RadioItem
value={value}
disabled={disabled}
className={`${__styles.item} ${__styles.input}`}
>
<RadixDropdown.RadioItem className={cn(__styles.item, __styles.input, className)} {...props}>
<RadixDropdown.ItemIndicator className={__styles.check}>
{ItemIndicator ? ItemIndicator : <IconTarget strokeWidth={6} size={10} />}
</RadixDropdown.ItemIndicator>
<span>{children}</span>
<span>{props.children}</span>
</RadixDropdown.RadioItem>
)
}
interface RadioGroupProps {
children: React.ReactNode
value: string
// to do : remove onChange omit in favor of using onValueChange
interface RadioGroupProps extends Omit<RadixDropdownTypes.DropdownMenuRadioGroupProps, 'onChange'> {
onChange?(x: string): void
}
export function RadioGroup({ children, value: propsValue, onChange }: RadioGroupProps) {
export function RadioGroup({ value: propsValue, onChange, ...props }: RadioGroupProps) {
const [value, setValue] = useState(propsValue ? propsValue : '')
const handleChange = (e: string) => {
const handleChange = (e: any) => {
// to do : remove onChange in favor of using onValueChange
if (onChange) onChange(e)
if (props.onValueChange) props.onValueChange(e)
setValue(e)
}
return (
<RadixDropdown.RadioGroup value={value} onValueChange={handleChange}>
{children}
</RadixDropdown.RadioGroup>
)
return <RadixDropdown.RadioGroup value={value} onValueChange={handleChange} {...props} />
}
interface LabelProps {
children: React.ReactNode
}
interface LabelProps extends RadixDropdownTypes.DropdownMenuLabelProps {}
export function Label({ children }: LabelProps) {
export function Label({ className, ...props }: LabelProps) {
let __styles = styleHandler('dropdown')
return <RadixDropdown.Label className={__styles.label}>{children}</RadixDropdown.Label>
return <RadixDropdown.Label className={cn(__styles.label, className)} {...props} />
}
Dropdown.Item = Item
@@ -4,7 +4,7 @@ interface ContextProps {
formContextOnChange: any
values: any
errors: any
handleBlur: React.FocusEventHandler | null
handleBlur: ((e: React.FocusEvent<any, any>) => void) | null
touched: any
fieldLevelValidation: any
}
@@ -25,14 +25,7 @@ const FormContext = createContext<ContextProps>({
})
export const FormContextProvider = (props: Provider) => {
const {
formContextOnChange,
values,
errors,
handleBlur,
touched,
fieldLevelValidation,
} = props
const { formContextOnChange, values, errors, handleBlur, touched, fieldLevelValidation } = props
const value = {
formContextOnChange: formContextOnChange,
@@ -43,18 +36,14 @@ export const FormContextProvider = (props: Provider) => {
fieldLevelValidation: fieldLevelValidation,
}
return (
<FormContext.Provider value={value}>{props.children}</FormContext.Provider>
)
return <FormContext.Provider value={value}>{props.children}</FormContext.Provider>
}
// context helper to avoid using a consumer component
export const useFormContext = () => {
const context = useContext(FormContext)
if (context === undefined) {
throw new Error(
`useFormContextOnChange must be used within a FormContextProvider.`
)
throw new Error(`useFormContextOnChange must be used within a FormContextProvider.`)
}
return context
}
+2 -1
View File
@@ -159,8 +159,9 @@ const Modal = ({
<Dialog.Overlay className={__styles.overlay} />
<Dialog.Overlay className={__styles.scroll_overlay}>
<Dialog.Content
className={[__styles.base, __styles.size[size]].join(' ')}
className={[__styles.base, __styles.size[size], className].join(' ')}
onInteractOutside={props.onInteractOutside}
onEscapeKeyDown={props.onEscapeKeyDown}
>
{header && <div className={__styles.header}>{header}</div>}
{/* <div
@@ -1,10 +1,7 @@
import { Button } from '../Button'
import { Divider } from '../Divider'
import { IconChevronDown } from '../../index'
import Typography from '../Typography'
import { IconChevronDown } from '../Icon/icons/IconChevronDown'
import { Popover } from '.'
import { IconLogIn } from '../Icon/icons/IconLogIn'
import { Input } from '../Input'
export default {
@@ -19,7 +16,6 @@ export const Default = (args: any) => (
side="bottom"
align="end"
size="content"
portalled
showClose
header={
<div className="flex justify-between items-center">
+19 -20
View File
@@ -16,7 +16,6 @@ interface RootProps {
onOpenChange?: RadixPopoverTypes.PopoverProps['onOpenChange']
open?: boolean
overlay?: React.ReactNode
portalled?: boolean
showClose?: boolean
side?: RadixPopoverTypes.PopoverContentProps['side']
sideOffset?: RadixPopoverTypes.PopoverContentProps['sideOffset']
@@ -37,7 +36,6 @@ function Popover({
onOpenChange,
open,
overlay,
portalled,
side = 'bottom',
sideOffset = 6,
style,
@@ -67,24 +65,25 @@ function Popover({
{children}
</RadixPopover.Trigger>
<RadixPopover.Content
sideOffset={sideOffset}
side={side}
align={align}
className={classes.join(' ')}
style={style}
portalled={portalled}
>
{arrow && (
<RadixPopover.Arrow
// className={DropdownStyles['sbui-popover__arrow']}
offset={10}
></RadixPopover.Arrow>
)}
{header && <div className={__styles.header}>{header}</div>}
{overlay}
{footer && <div className={__styles.footer}>{footer}</div>}
</RadixPopover.Content>
<RadixPopover.Portal>
<RadixPopover.Content
sideOffset={sideOffset}
side={side}
align={align}
className={classes.join(' ')}
style={style}
>
{arrow && (
<RadixPopover.Arrow
// className={DropdownStyles['sbui-popover__arrow']}
offset={10}
></RadixPopover.Arrow>
)}
{header && <div className={__styles.header}>{header}</div>}
{overlay}
{footer && <div className={__styles.footer}>{footer}</div>}
</RadixPopover.Content>
</RadixPopover.Portal>
</RadixPopover.Root>
)
}
+6 -2
View File
@@ -972,6 +972,7 @@ export default {
sidepanel: {
base: `
z-50
bg-scale-100 dark:bg-scale-300
flex flex-col
fixed
@@ -1023,6 +1024,7 @@ export default {
bg-scale-300 dark:bg-scale-500
`,
overlay: `
z-50
fixed
bg-scale-300
dark:bg-scale-100
@@ -1210,12 +1212,12 @@ export default {
data-open:text-scale-1200
`,
content: `
z-50
bg-scale-100 dark:bg-scale-300
border border-scale-300 dark:border-scale-500
rounded
shadow-lg
py-1.5
origin-dropdown
data-open:animate-dropdown-content-show
data-closed:animate-dropdown-content-hide
@@ -1297,6 +1299,7 @@ export default {
`,
content: `
z-50
bg-scale-100 dark:bg-scale-300
border border-scale-300 dark:border-scale-500
rounded
@@ -1469,6 +1472,7 @@ export default {
xxxlarge: `sm:align-middle sm:w-full max-w-7xl`,
},
overlay: `
z-50
fixed
bg-scale-300
dark:bg-scale-100
@@ -1480,13 +1484,13 @@ export default {
data-open:animate-fade-in-overlay-bg
`,
scroll_overlay: `
z-50
fixed
inset-0
grid
place-items-center
overflow-y-auto
data-open:animate-overlay-show data-closed:animate-overlay-hide
z-50
`,
separator: `
w-full
+12
View File
@@ -0,0 +1,12 @@
declare module 'sse.js' {
export type SSEOptions = EventSourceInit & {
headers?: Record<string, string>
payload?: string
method?: string
}
export class SSE extends EventSource {
constructor(url: string | URL, sseOptions?: SSEOptions)
stream(): void
}
}
+6
View File
@@ -0,0 +1,6 @@
import { ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
+14
View File
@@ -0,0 +1,14 @@
{
"extends": "tsconfig/react-library.json",
"compilerOptions": {
"resolveJsonModule": true
},
"include": [
"."
],
"exclude": [
"dist",
"build",
"node_modules"
]
}
@@ -104,17 +104,19 @@ export function JsonEditor<TRow, TSummaryRow = unknown>({
<IconMaximize size={12} strokeWidth={2} />
</div>
</Tooltip.Trigger>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">Expand editor</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">Expand editor</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
</div>
</BlockKeys>
@@ -52,17 +52,21 @@ export const ForeignKeyFormatter = (props: PropsWithChildren<FormatterProps<Supa
</a>
</Link>
</Tooltip.Trigger>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">View referencing record</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">View referencing record</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Portal>
</Tooltip.Root>
)}
</div>
@@ -130,17 +130,19 @@ export function ColumnHeader<R>({
<IconKey size="tiny" strokeWidth={2} />
</div>
</Tooltip.Trigger>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">Primary key</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">Primary key</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
)}
<span className="sb-grid-column-header__inner__name" title={hoverValue}>
@@ -152,17 +154,19 @@ export function ColumnHeader<R>({
<Tooltip.Trigger>
<IconLock size="tiny" strokeWidth={2} />
</Tooltip.Trigger>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">Encrypted column</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">Encrypted column</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
)}
</div>
@@ -184,32 +188,34 @@ function renderColumnIcon(
<Tooltip.Trigger>
<IconLink size="tiny" strokeWidth={2} />
</Tooltip.Trigger>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<div>
<p className="text-xs text-scale-1100">Foreign key relation:</p>
<div className="flex items-center space-x-1">
<p className="text-xs text-scale-1200">{name}</p>
<IconArrowRight size="tiny" strokeWidth={1.5} />
<p className="text-xs text-scale-1200">
{foreignKey?.targetTableSchema}.{foreignKey?.targetTableName}.
{foreignKey?.targetColumnName}
</p>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<div>
<p className="text-xs text-scale-1100">Foreign key relation:</p>
<div className="flex items-center space-x-1">
<p className="text-xs text-scale-1200">{name}</p>
<IconArrowRight size="tiny" strokeWidth={1.5} />
<p className="text-xs text-scale-1200">
{foreignKey?.targetTableSchema}.{foreignKey?.targetTableName}.
{foreignKey?.targetColumnName}
</p>
</div>
{foreignKey?.deletionAction !== FOREIGN_KEY_DELETION_ACTION.NO_ACTION && (
<p className="text-xs text-scale-1200 mt-1">
On delete: {getForeignKeyDeletionAction(foreignKey?.deletionAction)}
</p>
)}
</div>
{foreignKey?.deletionAction !== FOREIGN_KEY_DELETION_ACTION.NO_ACTION && (
<p className="text-xs text-scale-1200 mt-1">
On delete: {getForeignKeyDeletionAction(foreignKey?.deletionAction)}
</p>
)}
</div>
</div>
</Tooltip.Content>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
)
default:
@@ -55,19 +55,21 @@ const ColumnMenu: React.FC<ColumnMenuProps> = ({ column, isEncrypted }) => {
</Dropdown.Item>
</Tooltip.Trigger>
{isEncrypted && (
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
Encrypted columns cannot be edited
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
Encrypted columns cannot be edited
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
)}
</Tooltip.Root>
)}
@@ -169,19 +169,21 @@ const ProviderForm: FC<Props> = ({ provider }) => {
</Button>
</Tooltip.Trigger>
{!canUpdateConfig && (
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to update provider settings
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to update provider settings
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
)}
</Tooltip.Root>
</div>
@@ -43,28 +43,31 @@ const PolicyDefinition: FC<Props> = ({
<Tooltip.Trigger>
<IconHelpCircle className="text-scale-1100" size={16} strokeWidth={1.5} />
</Tooltip.Trigger>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'w-[300px] space-y-2 border border-scale-200',
].join(' ')}
>
<p className="text-xs text-scale-1200">
This expression will be added to queries that refer to the table if row-level
security is enabled.
</p>
<p className="text-xs text-scale-1200">
Rows for which the expression returns true will be visible. Any rows for which
the expression returns false or null will not be visible to the user (in a
SELECT), and will not be available for modification (in an UPDATE or DELETE).
</p>
<p className="text-xs text-scale-1200">
Such rows are silently suppressed - no error is reported.
</p>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'w-[300px] space-y-2 border border-scale-200',
].join(' ')}
>
<p className="text-xs text-scale-1200">
This expression will be added to queries that refer to the table if
row-level security is enabled.
</p>
<p className="text-xs text-scale-1200">
Rows for which the expression returns true will be visible. Any rows for
which the expression returns false or null will not be visible to the user
(in a SELECT), and will not be available for modification (in an UPDATE or
DELETE).
</p>
<p className="text-xs text-scale-1200">
Such rows are silently suppressed - no error is reported.
</p>
</div>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
</div>
<p className="text-sm text-scale-900">
@@ -87,29 +90,31 @@ const PolicyDefinition: FC<Props> = ({
<Tooltip.Trigger>
<IconHelpCircle className="text-scale-1100" size={16} strokeWidth={1.5} />
</Tooltip.Trigger>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'w-[300px] space-y-2 border border-scale-200',
].join(' ')}
>
<p className="text-xs text-scale-1200">
This expression will be used in INSERT and UPDATE queries against the table if
row-level security is enabled.
</p>
<p className="text-xs text-scale-1200">
Only rows for which the expression evaluates to true will be allowed. An error
will be thrown if the expression evaluates to false or null for any of the
records inserted or any of the records that result from the update.
</p>
<p className="text-xs text-scale-1200">
Note that this expression is evaluated against the proposed new contents of
the row, not the original contents.
</p>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'w-[300px] space-y-2 border border-scale-200',
].join(' ')}
>
<p className="text-xs text-scale-1200">
This expression will be used in INSERT and UPDATE queries against the table
if row-level security is enabled.
</p>
<p className="text-xs text-scale-1200">
Only rows for which the expression evaluates to true will be allowed. An
error will be thrown if the expression evaluates to false or null for any of
the records inserted or any of the records that result from the update.
</p>
<p className="text-xs text-scale-1200">
Note that this expression is evaluated against the proposed new contents of
the row, not the original contents.
</p>
</div>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
</div>
<p className="text-sm text-scale-900">
@@ -82,19 +82,21 @@ const PolicyRow: FC<Props> = ({
/>
</Tooltip.Trigger>
{!canUpdatePolicies && (
<Tooltip.Content side="left">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to edit RLS policies
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="left">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to edit RLS policies
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
)}
</Tooltip.Root>
)}
@@ -52,19 +52,21 @@ const PolicyTableRowHeader: FC<Props> = ({
</Button>
</Tooltip.Trigger>
{!canCreatePolicies && (
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to create RLS policies
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to create RLS policies
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
)}
</Tooltip.Root>
<Tooltip.Root delayDuration={0}>
@@ -78,19 +80,21 @@ const PolicyTableRowHeader: FC<Props> = ({
</Button>
</Tooltip.Trigger>
{!canToggleRLS && (
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to toggle RLS
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to toggle RLS
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
)}
</Tooltip.Root>
</div>
@@ -97,19 +97,21 @@ const RedirectDomains = () => {
</Button>
</Tooltip.Trigger>
{!canUpdateConfig && (
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to update redirect URLs
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to update redirect URLs
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
)}
</Tooltip.Root>
</div>
@@ -47,19 +47,21 @@ const AddUserDropdown = ({ projectKpsVersion }: AddUserDropdownProps) => {
</Dropdown.Item>
</Tooltip.Trigger>
{!canInviteUsers && (
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to invite users
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to invite users
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
)}
</Tooltip.Root>
)}
@@ -76,19 +78,21 @@ const AddUserDropdown = ({ projectKpsVersion }: AddUserDropdownProps) => {
</Tooltip.Trigger>
{!canCreateUsers && (
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to create users
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to create users
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
)}
</Tooltip.Root>
</>
@@ -184,19 +184,21 @@ const UserDropdown: FC<Props> = ({ user, canRemoveUser, canRemoveMFAFactors }) =
need to double check with KM / anyone familiar with ABAC
*/}
{!canRemoveMFAFactors && (
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to remove a user's authentication factors.
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to remove a user's authentication factors.
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
)}
</Tooltip.Root>
<Tooltip.Root delayDuration={0}>
@@ -210,19 +212,21 @@ const UserDropdown: FC<Props> = ({ user, canRemoveUser, canRemoveMFAFactors }) =
</Dropdown.Item>
</Tooltip.Trigger>
{!canRemoveUser && (
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to delete users
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to delete users
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
)}
</Tooltip.Root>
</>
@@ -48,41 +48,43 @@ const InvoiceStatusBadge: FC<Props> = ({ status }) => {
{statusMapping?.label || status}
</Badge>
</Tooltip.Trigger>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'w-[300px] space-y-2 border border-scale-200',
].join(' ')}
>
{[InvoiceStatus.OPEN, InvoiceStatus.UNCOLLECTIBLE].includes(status) && (
<p className="text-xs text-scale-1200">
We were not able to collect the money. Make sure you have a valid payment method and
enough funds. Outstanding invoices may cause restrictions. You can manually pay the
using the "Pay Now" button.
</p>
)}
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'w-[300px] space-y-2 border border-scale-200',
].join(' ')}
>
{[InvoiceStatus.OPEN, InvoiceStatus.UNCOLLECTIBLE].includes(status) && (
<p className="text-xs text-scale-1200">
We were not able to collect the money. Make sure you have a valid payment method and
enough funds. Outstanding invoices may cause restrictions. You can manually pay the
using the "Pay Now" button.
</p>
)}
{status === InvoiceStatus.DRAFT && (
<p className="text-xs text-scale-1200">
The invoice will soon be finalized and charged for.
</p>
)}
{status === InvoiceStatus.DRAFT && (
<p className="text-xs text-scale-1200">
The invoice will soon be finalized and charged for.
</p>
)}
{status === InvoiceStatus.PAID && (
<p className="text-xs text-scale-1200">
The invoice has been paid successfully. No action is required on your side.
</p>
)}
{status === InvoiceStatus.PAID && (
<p className="text-xs text-scale-1200">
The invoice has been paid successfully. No action is required on your side.
</p>
)}
{status === InvoiceStatus.VOID && (
<p className="text-xs text-scale-1200">
This invoice has been forgiven. No action is required on your side.
</p>
)}
</div>
</Tooltip.Content>
{status === InvoiceStatus.VOID && (
<p className="text-xs text-scale-1200">
This invoice has been forgiven. No action is required on your side.
</p>
)}
</div>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
)
}
@@ -372,20 +372,22 @@ const PaymentSummaryPanel: FC<Props> = ({
</Button>
</Tooltip.Trigger>
{!canUpdatePaymentMethods && (
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
'w-48 border border-scale-200 text-center', //border
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to add new payment methods to this
organization
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
'w-48 border border-scale-200 text-center', //border
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to add new payment methods to this
organization
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
)}
</Tooltip.Root>
</div>
@@ -448,31 +450,35 @@ const PaymentSummaryPanel: FC<Props> = ({
</Button>
</Tooltip.Trigger>
{!hasChangesToPlan ? (
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
No changes made to your subscription
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
No changes made to your subscription
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
) : !selectedPaymentMethod ? (
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">Please select a payment method</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">Please select a payment method</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
) : (
<></>
)}
@@ -54,17 +54,19 @@ const PaymentTotal: FC<Props> = ({
onClick={() => setShowCostBreakdown(true)}
/>
</Tooltip.Trigger>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
'border border-scale-200 ', //border
].join(' ')}
>
<span className="text-xs text-scale-1200">How is this calculated?</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
'border border-scale-200 ', //border
].join(' ')}
>
<span className="text-xs text-scale-1200">How is this calculated?</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
)}
</div>
@@ -112,19 +112,23 @@ const PlanCTAButton: FC<Props> = ({ plan, currentPlan, onSelectPlan }) => {
</Button>
</Tooltip.Trigger>
{!disabled && projectUpdateDisabled && (
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200 w-[260px] flex items-center justify-center',
].join(' ')}
>
<span className="text-xs text-scale-1200 text-center">
Subscription changes are currently disabled, our engineers are working on a fix
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200 w-[260px] flex items-center justify-center',
].join(' ')}
>
<span className="text-xs text-scale-1200 text-center">
Subscription changes are currently disabled, our engineers are working on a fix
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Portal>
)}
</Tooltip.Root>
</div>
@@ -55,20 +55,24 @@ const SpendCapModal: FC<Props> = ({ visible, onHide }) => {
className="transition opacity-50 cursor-pointer hover:opacity-100"
/>
</Tooltip.Trigger>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
'border border-scale-200 ', //border
].join(' ')}
>
<span className="text-xs text-scale-1200">
Monthly Active Users: A user that has made an API request in the last
month
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
'border border-scale-200 ', //border
].join(' ')}
>
<span className="text-xs text-scale-1200">
Monthly Active Users: A user that has made an API request in the
last month
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Portal>
</Tooltip.Root>
</div>
<p className="w-[25%] text-sm">100,000</p>
@@ -97,29 +97,31 @@ const Subscription: FC<Props> = ({
</Button>
</Tooltip.Trigger>
{!canUpdateSubscription || projectUpdateDisabled ? (
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'border border-scale-200 text-center', //border
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
].join(' ')}
>
<span className="text-xs text-scale-1200">
{projectUpdateDisabled ? (
<>
Subscription changes are currently disabled.
<br />
Our engineers are working on a fix.
</>
) : !canUpdateSubscription ? (
'You need additional permissions to amend subscriptions'
) : (
''
)}
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'border border-scale-200 text-center', //border
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
].join(' ')}
>
<span className="text-xs text-scale-1200">
{projectUpdateDisabled ? (
<>
Subscription changes are currently disabled.
<br />
Our engineers are working on a fix.
</>
) : !canUpdateSubscription ? (
'You need additional permissions to amend subscriptions'
) : (
''
)}
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
) : (
<></>
)}
@@ -34,19 +34,21 @@ const PITRNotice = ({}) => {
</Button>
</Tooltip.Trigger>
{!canUpdateSubscription && (
<Tooltip.Content side="left">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to amend subscriptions
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="left">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to amend subscriptions
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
)}
</Tooltip.Root>
</div>
@@ -192,19 +192,21 @@ const PITRSelection = ({}) => {
</Button>
</Tooltip.Trigger>
{isSelectedOutOfRange && (
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'bg-scale-100 rounded py-1 px-2 leading-none shadow',
'border-scale-200 border w-48 text-center',
].join(' ')}
>
<span className="text-scale-1200 text-xs">
Selected date is out of range where backups are available
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'bg-scale-100 rounded py-1 px-2 leading-none shadow',
'border-scale-200 border w-48 text-center',
].join(' ')}
>
<span className="text-scale-1200 text-xs">
Selected date is out of range where backups are available
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
)}
</Tooltip.Root>
</div>
@@ -55,19 +55,21 @@ const PITRStatus: FC<Props> = ({ selectedTimezone, onUpdateTimezone, onSetConfig
</Button>
</Tooltip.Trigger>
{!canTriggerPhysicalBackup && (
<Tooltip.Content side="left">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to trigger a PITR recovery
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="left">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to trigger a PITR recovery
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
)}
</Tooltip.Root>
</div>
@@ -110,17 +110,19 @@ const TimeInput: FC<Props> = ({ defaultTime, minimumTime, maximumTime, onChange
className="w-full text-sm bg-transparent p-0 text-center outline-none border-none focus:ring-0"
/>
</Tooltip.Trigger>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'bg-scale-100 rounded py-1 px-2 leading-none shadow',
'border-scale-200 border',
].join(' ')}
>
<span className="text-scale-1200 text-xs">Hours (HH)</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'bg-scale-100 rounded py-1 px-2 leading-none shadow',
'border-scale-200 border',
].join(' ')}
>
<span className="text-scale-1200 text-xs">Hours (HH)</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
<span>:</span>
<Tooltip.Root delayDuration={0}>
@@ -138,17 +140,19 @@ const TimeInput: FC<Props> = ({ defaultTime, minimumTime, maximumTime, onChange
className="w-full text-sm bg-transparent p-0 text-center outline-none border-none focus:ring-0"
/>
</Tooltip.Trigger>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'bg-scale-100 rounded py-1 px-2 leading-none shadow',
'border-scale-200 border',
].join(' ')}
>
<span className="text-scale-1200 text-xs">Minutes (MM)</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'bg-scale-100 rounded py-1 px-2 leading-none shadow',
'border-scale-200 border',
].join(' ')}
>
<span className="text-scale-1200 text-xs">Minutes (MM)</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
<span>:</span>
<Tooltip.Root delayDuration={0}>
@@ -166,17 +170,19 @@ const TimeInput: FC<Props> = ({ defaultTime, minimumTime, maximumTime, onChange
className="w-full text-sm bg-transparent p-0 text-center outline-none border-none focus:ring-0"
/>
</Tooltip.Trigger>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'bg-scale-100 rounded py-1 px-2 leading-none shadow',
'border-scale-200 border',
].join(' ')}
>
<span className="text-scale-1200 text-xs">Seconds (SS)</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'bg-scale-100 rounded py-1 px-2 leading-none shadow',
'border-scale-200 border',
].join(' ')}
>
<span className="text-scale-1200 text-xs">Seconds (SS)</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
</div>
{error && <p className="text-sm text-red-900">{error}</p>}
@@ -89,19 +89,21 @@ const FunctionList: FC<Props> = ({
<Tooltip.Trigger>
<Button as="span" disabled type="default" icon={<IconMoreVertical />} />
</Tooltip.Trigger>
<Tooltip.Content side="left">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to update functions
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="left">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to update functions
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
)}
</div>
@@ -90,19 +90,23 @@ const FunctionsList: FC<Props> = ({
</Button>
</Tooltip.Trigger>
{!canCreateFunctions && (
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to create functions
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to create functions
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Portal>
)}
</Tooltip.Root>
</div>
@@ -105,19 +105,21 @@ const HookList: FC<Props> = ({
<Tooltip.Trigger>
<Button as="span" disabled type="default" icon={<IconMoreVertical />} />
</Tooltip.Trigger>
<Tooltip.Content side="left">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to update webhooks
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="left">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to update webhooks
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
)}
</div>
@@ -107,19 +107,21 @@ const HooksList = ({
</Button>
</Tooltip.Trigger>
{!canCreateWebhooks && (
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to create webhooks
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to create webhooks
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
)}
</Tooltip.Root>
</div>
@@ -184,19 +184,21 @@ const RoleRow: FC<Props> = ({ role, disabled = false, onSelectDelete }) => {
className="ml-2 relative top-[3px]"
/>
</Tooltip.Trigger>
<Tooltip.Content align="center" side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200 space-y-1',
].join(' ')}
>
<span className="text-xs">
This privilege cannot be updated via the dashboard
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content align="center" side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200 space-y-1',
].join(' ')}
>
<span className="text-xs">
This privilege cannot be updated via the dashboard
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
)
}
@@ -72,19 +72,21 @@ const ColumnList: FC<Props> = ({
</Button>
</Tooltip.Trigger>
{!canUpdateColumns && (
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to create columns
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to create columns
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
)}
</Tooltip.Root>
</div>
@@ -140,19 +142,21 @@ const ColumnList: FC<Props> = ({
/>
</Tooltip.Trigger>
{!canUpdateColumns && (
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to edit columns
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to edit columns
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
)}
</Tooltip.Root>
@@ -167,19 +171,21 @@ const ColumnList: FC<Props> = ({
/>
</Tooltip.Trigger>
{!canUpdateColumns && (
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to delete columns
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to delete columns
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
)}
</Tooltip.Root>
</div>
@@ -134,19 +134,21 @@ const TableList: FC<Props> = ({
</Button>
</Tooltip.Trigger>
{!canUpdateTables && (
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to create tables
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to create tables
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
)}
</Tooltip.Root>
</div>
@@ -228,19 +230,21 @@ const TableList: FC<Props> = ({
/>
</Tooltip.Trigger>
{!canUpdateTables && (
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to edit tables
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to edit tables
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
)}
</Tooltip.Root>
@@ -255,19 +259,21 @@ const TableList: FC<Props> = ({
/>
</Tooltip.Trigger>
{!canUpdateTables && (
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to delete tables
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to delete tables
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
)}
</Tooltip.Root>
@@ -82,19 +82,21 @@ const TriggerList: FC<Props> = ({ filterString, schema, editTrigger, deleteTrigg
<Tooltip.Trigger>
<Button as="span" disabled type="default" icon={<IconMoreVertical />} />
</Tooltip.Trigger>
<Tooltip.Content side="left">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to update triggers
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="left">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to update triggers
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
)}
</div>
@@ -80,19 +80,21 @@ const TriggersList: FC<any> = ({
</Button>
</Tooltip.Trigger>
{!canCreateTriggers && (
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to create triggers
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to create triggers
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
)}
</Tooltip.Root>
</div>
@@ -181,19 +181,21 @@ const WrapperRow: FC<Props> = ({ wrappers = [], wrapperMeta, isOpen, onOpen }) =
/>
</Tooltip.Trigger>
{!canManageWrappers && (
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to edit wrappers
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to edit wrappers
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
)}
</Tooltip.Root>
)}
@@ -208,19 +210,21 @@ const WrapperRow: FC<Props> = ({ wrappers = [], wrapperMeta, isOpen, onOpen }) =
/>
</Tooltip.Trigger>
{!canManageWrappers && (
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to add wrappers
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to add wrappers
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
)}
</Tooltip.Root>
</div>
@@ -131,19 +131,21 @@ const WrappersDisabledState = () => {
</Button>
</Tooltip.Trigger>
{!canToggleWrappers && (
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to enable Wrappers for this project
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to enable Wrappers for this project
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
)}
</Tooltip.Root>
</div>
@@ -26,19 +26,21 @@ const WrapperDropdown: FC<Props> = ({ buttonText = 'Add wrapper', align = 'end'
{buttonText}
</Button>
</Tooltip.Trigger>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to add wrappers
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to add wrappers
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
)
}
@@ -276,19 +276,21 @@ const EdgeFunctionDetails: FC<Props> = () => {
</Button>
</Tooltip.Trigger>
{!canUpdateEdgeFunction && (
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to delete an edge function
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to delete an edge function
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
)}
</Tooltip.Root>
</Alert>
@@ -78,19 +78,21 @@ const EdgeFunctionsListItem: FC<Props> = ({ function: item }) => {
<p className="text-sm text-scale-1000">{dayjs(item.updated_at).fromNow()}</p>
</div>
</Tooltip.Trigger>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
Last updated on {dayjs(item.updated_at).format('DD MMM, YYYY HH:mm')}
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
Last updated on {dayjs(item.updated_at).format('DD MMM, YYYY HH:mm')}
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
</Table.td>
<Table.td className="lg:table-cell">
@@ -143,7 +143,7 @@ const TerminalInstructions: FC<Props> = ({ closable = false, removeBorder = fals
href="https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions"
>
<a target="_blank" rel="noreferrer">
<Button as="a" type="default" iconRight={<IconCode />}>
<Button type="default" iconRight={<IconCode />}>
Examples
</Button>
</a>
@@ -187,19 +187,21 @@ const PaymentMethods: FC<Props> = ({
<Tooltip.Trigger>
<Button disabled as="span" type="outline" icon={<IconX />} />
</Tooltip.Trigger>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
'w-48 border border-scale-200 text-center', //border
].join(' ')}
>
<span className="text-xs text-scale-1200">
Your default payment method cannot be deleted
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
'w-48 border border-scale-200 text-center', //border
].join(' ')}
>
<span className="text-xs text-scale-1200">
Your default payment method cannot be deleted
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
) : (
<Dropdown
@@ -101,19 +101,21 @@ const InviteMemberButton = ({
</Button>
</Tooltip.Trigger>
{!canInviteMembers && (
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to invite a member to this organization
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to invite a member to this organization
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
)}
</Tooltip.Root>
<Modal
@@ -133,19 +133,21 @@ const MemberActions: FC<Props> = ({ member, roles }) => {
<Tooltip.Trigger>
<Button as="span" type="text" icon={<IconMoreHorizontal />} />
</Tooltip.Trigger>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
'border border-scale-200 ', //border
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to manage this team member
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
'border border-scale-200 ', //border
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to manage this team member
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
</div>
)
@@ -214,33 +214,38 @@ const MembersView = ({ searchString }: MembersViewProps) => {
</Listbox>
</Tooltip.Trigger>
{memberIsPendingInvite ? (
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
'border border-scale-200 ', //border
].join(' ')}
>
<span className="text-xs text-scale-1200">
Role can only be changed after the user has accepted the invite
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
'border border-scale-200 ', //border
].join(' ')}
>
<span className="text-xs text-scale-1200">
Role can only be changed after the user has accepted the
invite
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
) : !memberIsUser && !canRemoveRole ? (
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
'border border-scale-200 ', //border
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to manage this team member
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
'border border-scale-200 ', //border
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to manage this team member
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
) : (
<></>
)}
@@ -252,20 +257,22 @@ const MembersView = ({ searchString }: MembersViewProps) => {
<Tooltip.Trigger>
<IconAlertCircle size={16} strokeWidth={1.5} />
</Tooltip.Trigger>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
'border border-scale-200 ', //border
].join(' ')}
>
<span className="text-xs text-scale-1200">
This user has an invalid role, please reach out to us via
support
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
'border border-scale-200 ', //border
].join(' ')}
>
<span className="text-xs text-scale-1200">
This user has an invalid role, please reach out to us via
support
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
</div>
)}
@@ -17,17 +17,19 @@ const RolesHelperModal: FC<Props> = ({}) => {
<Tooltip.Trigger>
<IconInfo size={14} strokeWidth={2} />
</Tooltip.Trigger>
<Tooltip.Content side="top">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
'border border-scale-200 ', //border
].join(' ')}
>
<span className="text-xs text-scale-1200">Only available in Team/Enterprise plan.</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="top">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
'border border-scale-200 ', //border
].join(' ')}
>
<span className="text-xs text-scale-1200">Only available in Team/Enterprise plan.</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
)
@@ -97,19 +97,21 @@ const TeamSettings = () => {
</Button>
</Tooltip.Trigger>
{!canLeave && (
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
An organization requires at least 1 owner
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
An organization requires at least 1 owner
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
)}
</Tooltip.Root>
</div>
@@ -99,7 +99,7 @@ const ReportFilterBar: FC<Props> = ({
]
const [showAdder, setShowAdder] = useState(false)
const [currentProductFilter, setCurrentProductFilter] = useState<
null | typeof PRODUCT_FILTERS[number]
null | (typeof PRODUCT_FILTERS)[number]
>(null)
const [addFilterValues, setAddFilterValues] = useState<ReportFilterItem>({
key: filterKeys[0],
@@ -116,7 +116,7 @@ const ReportFilterBar: FC<Props> = ({
}
const handleProductFilterChange = async (
nextProductFilter: null | typeof PRODUCT_FILTERS[number]
nextProductFilter: null | (typeof PRODUCT_FILTERS)[number]
) => {
const toRemove = PRODUCT_FILTERS.map(
(productFilter) =>
@@ -156,6 +156,7 @@ const ReportFilterBar: FC<Props> = ({
All Requests
</Dropdown.Item>
<Dropdown.Separator />
{PRODUCT_FILTERS.map((productFilter) => {
const Icon = productFilter.icon
return (
@@ -163,7 +164,6 @@ const ReportFilterBar: FC<Props> = ({
key={productFilter.key}
disabled={productFilter.key === currentProductFilter?.key}
onClick={() => handleProductFilterChange(productFilter)}
className="hover:bg-scale-600"
icon={<Icon size={20} className="mr-2" />}
>
<p
@@ -46,17 +46,19 @@ const ReportWidget: React.FC<ReportWidgetProps> = (props) => {
<Tooltip.Trigger>
<IconHelpCircle className="text-scale-1100" size="tiny" strokeWidth={1.5} />
</Tooltip.Trigger>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 max-w-xs leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">{props.tooltip}</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 max-w-xs leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">{props.tooltip}</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
)}
</div>
@@ -80,17 +82,19 @@ const ReportWidget: React.FC<ReportWidgetProps> = (props) => {
}}
/>
</Tooltip.Trigger>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 max-w-xs leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">Open in Logs Explorer</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 max-w-xs leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">Open in Logs Explorer</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
</div>
@@ -275,7 +275,7 @@ const Reports = () => {
return (
<Dropdown.Checkbox
key={metric.key}
checked={config.layout?.find((x: any) => x.attribute === metric.key)}
checked={config.layout?.some((x: any) => x.attribute === metric.key)}
onChange={(e) => handleChartSelection({ metric, value: e })}
>
<div className="flex flex-col space-y-0">
@@ -360,19 +360,21 @@ const Reports = () => {
Add / Remove charts
</Button>
</Tooltip.Trigger>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to update this project's report
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to update this project's report
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
)}
</div>
@@ -112,20 +112,22 @@ const FavouriteButton = () => {
return (
<>
<Tooltip.Root delayDuration={0}>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
{isFavorite ? 'Remove' : 'Add'} this query {isFavorite ? 'from' : 'to'} your
Favourites
</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">
{isFavorite ? 'Remove' : 'Add'} this query {isFavorite ? 'from' : 'to'} your
Favourites
</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
<Tooltip.Trigger type="button">
<Button
type="text"
@@ -64,17 +64,19 @@ const SavingIndicator: FC<Props> = ({ updateSqlSnippet }) => {
<Tooltip.Trigger>
<IconLoader className="animate-spin" size={14} strokeWidth={2} />
</Tooltip.Trigger>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">Saving changes...</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200',
].join(' ')}
>
<span className="text-xs text-scale-1200">Saving changes...</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
)}
{(content.savingState === 'CREATING_FAILED' || content.savingState === 'UPDATING_FAILED') && (
@@ -85,17 +87,19 @@ const SavingIndicator: FC<Props> = ({ updateSqlSnippet }) => {
<Tooltip.Trigger>
<IconCheck className="text-brand-900" size={14} strokeWidth={3} />
</Tooltip.Trigger>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200 ',
].join(' ')}
>
<span className="text-xs text-scale-1200">All changes saved</span>
</div>
</Tooltip.Content>
<Tooltip.Portal>
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow',
'border border-scale-200 ',
].join(' ')}
>
<span className="text-xs text-scale-1200">All changes saved</span>
</div>
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
)}
<span className="text-sm text-scale-1000">
Loaded 100 of 151 files, more files were not shown because too many files have changed in this diff. Show more