diff --git a/.github/workflows/avoid-typos.yml b/.github/workflows/avoid-typos.yml index 68a17b5ba32..98b952ef4c2 100644 --- a/.github/workflows/avoid-typos.yml +++ b/.github/workflows/avoid-typos.yml @@ -14,11 +14,12 @@ jobs: uses: reviewdog/action-misspell@v1 with: github_token: ${{ secrets.github_token }} - locale: "US" + locale: 'US' reporter: github-pr-review level: error exclude: | - "*.css" - "**/package.json" - "**/package-lock.json" - ".git/*" + *.css + **/package.json + **/package-lock.json + .git/* + *.ipynb diff --git a/.github/workflows/docs-tests.yml b/.github/workflows/docs-tests.yml new file mode 100644 index 00000000000..4a059540f4a --- /dev/null +++ b/.github/workflows/docs-tests.yml @@ -0,0 +1,30 @@ +name: Docs Tests + +on: + pull_request: + branches: ['master'] + paths: + - 'apps/docs/**/*.ts*' + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [16.x] + + steps: + - uses: actions/checkout@v3 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + + - name: Install deps + run: npm ci + + - name: Run tests + run: npm run test:docs diff --git a/.github/workflows/og_images.yml b/.github/workflows/og_images.yml index c3cc6568e73..afec7f34149 100644 --- a/.github/workflows/og_images.yml +++ b/.github/workflows/og_images.yml @@ -14,7 +14,7 @@ jobs: env: SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }} - PROJECT_ID: ${{ secrets.PROJECT_ID }} + PROJECT_ID: ${{ secrets.OG_IMAGE_SUPABASE_PROJECT_ID }} steps: - name: Check out repo diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml index aef4d8f166d..f1314528f5b 100644 --- a/.github/workflows/prettier.yml +++ b/.github/workflows/prettier.yml @@ -11,9 +11,16 @@ jobs: steps: - name: Check out repo uses: actions/checkout@v3 + - name: Setup node + uses: actions/setup-node@v3 + with: + node-version: 18 + cache: 'npm' + - name: Download dependencies + run: npm ci - name: Run prettier run: |- - npx prettier -c 'apps/**/*.{js,jsx,ts,tsx,css,md,json}' + npx prettier -c 'apps/**/*.{js,jsx,ts,tsx,css,md,mdx,json}' # [Joshen] Temp disable the following due to an issue # https://github.com/EPMatt/reviewdog-action-prettier/issues/34 # uses: EPMatt/reviewdog-action-prettier@v1 @@ -42,9 +49,16 @@ jobs: steps: - name: Check out repo uses: actions/checkout@v3 + - name: Setup node + uses: actions/setup-node@v3 + with: + node-version: 18 + cache: 'npm' + - name: Download dependencies + run: npm ci - name: Run prettier run: |- - npx prettier -c 'i18n/**/*.{js,jsx,ts,tsx,css,md,json}' + npx prettier -c 'i18n/**/*.{js,jsx,ts,tsx,css,md,mdx,json}' # uses: EPMatt/reviewdog-action-prettier@v1 # with: # github_token: ${{ secrets.github_token }} diff --git a/.github/workflows/search.yml b/.github/workflows/search.yml new file mode 100644 index 00000000000..b4631ceca5d --- /dev/null +++ b/.github/workflows/search.yml @@ -0,0 +1,62 @@ +name: Generate Embeddings for Search + +on: + push: + branches: + - master + paths: + - '.github/workflows/search.yml' + - 'supabase/migrations/**' + - 'apps/docs/**' + - 'spec/**' + workflow_dispatch: + inputs: + refresh: + description: 'Refresh all pages' + required: false + type: boolean + schedule: + - cron: '0 0 * * *' + +jobs: + deploy: + runs-on: ubuntu-latest + + env: + OPENAI_KEY: ${{ secrets.OPENAI_KEY }} + SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }} + PROJECT_ID: ${{ secrets.SEARCH_SUPABASE_PROJECT_ID }} + NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.SEARCH_SUPABASE_URL }} + SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SEARCH_SUPABASE_SERVICE_ROLE_KEY }} + SUPABASE_DB_PASSWORD: ${{ secrets.SEARCH_SUPABASE_DB_PASSWORD }} + SEARCH_GITHUB_APP_ID: ${{ secrets.SEARCH_GITHUB_APP_ID }} + SEARCH_GITHUB_APP_INSTALLATION_ID: ${{ secrets.SEARCH_GITHUB_APP_INSTALLATION_ID }} + SEARCH_GITHUB_APP_PRIVATE_KEY: ${{ secrets.SEARCH_GITHUB_APP_PRIVATE_KEY }} + + steps: + - name: Check out repo + uses: actions/checkout@v3 + + - name: Setup node + uses: actions/setup-node@v3 + with: + node-version: 18 + + - name: Download dependencies + run: npm ci + + - name: Link Supabase project + run: npx supabase link --project-ref $PROJECT_ID + + - name: Run migrations + run: npx supabase db push + + - name: Update embeddings + working-directory: ./apps/docs + if: ${{ !inputs.refresh }} + run: npm run embeddings + + - name: Refresh embeddings + working-directory: ./apps/docs + if: ${{ inputs.refresh }} + run: npm run embeddings:refresh diff --git a/.github/workflows/studio-tests.yml b/.github/workflows/studio-tests.yml index 766fddf9226..eaf8efed679 100644 --- a/.github/workflows/studio-tests.yml +++ b/.github/workflows/studio-tests.yml @@ -34,5 +34,8 @@ jobs: run: npm ci working-directory: ./ - name: Run tests + env: + # Default is 2 GB, increase to have less frequent OOM errors + NODE_OPTIONS: "--max_old_space_size=3072" run: npm run test:studio working-directory: ./ diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml new file mode 100644 index 00000000000..0898d33a8c1 --- /dev/null +++ b/.github/workflows/ui-tests.yml @@ -0,0 +1,30 @@ +name: UI Tests + +on: + pull_request: + branches: [master] + paths: + - 'packages/ui/**' + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [16.x] + + steps: + - uses: actions/checkout@v3 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + + - name: Install deps + run: npm ci + + - name: Run tests + run: npm run test:ui diff --git a/.gitignore b/.gitignore index c78e6a2eb47..a2de2708e81 100644 --- a/.gitignore +++ b/.gitignore @@ -122,5 +122,8 @@ typings/ apps/new-docs/* +# UI tokens +packages/ui/tokens/**/*.json + # For self-hosted logs: https://github.com/supabase/supabase/blob/86e3ab20abfdb9c3e666334d3d2f8efeef9ccf2c/docker/docker-compose-logging.yml#L101 gcloud.json diff --git a/.misspell-fixer.ignore b/.misspell-fixer.ignore index 7c90f6e4788..105b2fccda8 100644 --- a/.misspell-fixer.ignore +++ b/.misspell-fixer.ignore @@ -1 +1 @@ -^./i18n +^./i18n \ No newline at end of file diff --git a/.prettierrc b/.prettierrc index b9bda117c38..c60d3ab91ef 100644 --- a/.prettierrc +++ b/.prettierrc @@ -5,5 +5,7 @@ "singleQuote": true, "printWidth": 100, "endOfLine": "lf", - "sqlKeywordCase": "lower" + "sqlKeywordCase": "lower", + "pluginSearchDirs": false, + "plugins": ["prettier-plugin-sql-cst"] } diff --git a/README.md b/README.md index f3506927e0a..37941659f32 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ - [x] Database Functions. [Docs](https://supabase.com/docs/guides/database/functions) - [x] Edge Functions [Docs](https://supabase.com/docs/guides/functions) - [x] File Storage. [Docs](https://supabase.com/docs/guides/storage) +- [x] AI + Vector/Embeddings Toolkit. [Docs](https://supabase.com/docs/guides/ai) - [x] Dashboard ![Supabase Dashboard](https://raw.githubusercontent.com/supabase/supabase/master/apps/www/public/images/github/supabase-dashboard.png) @@ -149,8 +150,8 @@ Our approach for client libraries is modular. Each sub-library is a standalone i Kotlin supabase-kt - postgrest-kt - gotrue-kt + postgrest-kt + gotrue-kt realtime-kt storage-kt functions-kt @@ -231,7 +232,7 @@ Our approach for client libraries is modular. Each sub-library is a standalone i - [Norwegian (Bokmål) / Norsk (Bokmål)](/i18n/README.nb-no.md) - [Persian / فارسی](/i18n/README.fa.md) - [Polish / Polski](/i18n/README.pl.md) -- [Portuguese / Portuguese](/i18n/README.pt.md) +- [Portuguese / Português](/i18n/README.pt.md) - [Portuguese (Brazilian) / Português Brasileiro](/i18n/README.pt-br.md) - [Romanian / Română](/i18n/README.ro.md) - [Russian / Pусский](/i18n/README.ru.md) @@ -247,8 +248,3 @@ Our approach for client libraries is modular. Each sub-library is a standalone i - [Vietnamese / Tiếng Việt](/i18n/README.vi-vn.md) - [List of translations](/i18n/languages.md) ---- - -## Sponsors - -[![New Sponsor](https://user-images.githubusercontent.com/10214025/90518111-e74bbb00-e198-11ea-8f88-c9e3c1aa4b5b.png)](https://github.com/sponsors/supabase) diff --git a/apps/docs/.env.example b/apps/docs/.env.example index 7fa24758cb1..81e97461ea0 100644 --- a/apps/docs/.env.example +++ b/apps/docs/.env.example @@ -5,6 +5,9 @@ NEXT_PUBLIC_SUPABASE_ANON_KEY= # PRIVATE OPENAI_KEY= SUPABASE_SERVICE_ROLE_KEY= +SEARCH_GITHUB_APP_ID= +SEARCH_GITHUB_APP_INSTALLATION_ID= +SEARCH_GITHUB_APP_PRIVATE_KEY= # Set IS_PLATFORM to true if you have the api keys and urls NEXT_PUBLIC_IS_PLATFORM=false diff --git a/apps/docs/.gitignore b/apps/docs/.gitignore index fd62e0e6867..ba0b52697ed 100644 --- a/apps/docs/.gitignore +++ b/apps/docs/.gitignore @@ -12,6 +12,7 @@ .env.local .env.development.local .env.test.local +.env.staging.local .env.production.local npm-debug.log* @@ -20,4 +21,4 @@ yarn-error.log* **/*/generated/**/* !**/*/generated/.gitkeep -!**/*/generated/**/.gitkeep \ No newline at end of file +!**/*/generated/**/.gitkeep diff --git a/apps/docs/DEVELOPERS.md b/apps/docs/DEVELOPERS.md index 5c5b74e39de..a998eaba7d2 100644 --- a/apps/docs/DEVELOPERS.md +++ b/apps/docs/DEVELOPERS.md @@ -96,4 +96,6 @@ On the Next.JS side of things, these work almost exactly the same as the client #### Search -Search is handled through Algolia. When the site is built, a [search script](https://github.com/supabase/supabase/blob/master/apps/docs/scripts/build-search.ts) runs through all of the types of content, generating search objects that are sent to Algolia to index. +Search is handled using a Supabase instance. During CI, [a script](https://github.com/supabase/supabase/blob/master/apps/docs/scripts/search/generate-embeddings.ts) aggregates all content sources (eg. guides, reference docs, etc), indexes them using OpenAI embeddings, and stores them in a Supabase database. + +At runtime, an [Edge Function](https://github.com/supabase/supabase/blob/master/supabase/functions) is executed that performs a similarity search between the user's query and the above content sources using [`pgvector`](https://github.com/pgvector/pgvector) embeddings. diff --git a/apps/docs/components/Admonition.tsx b/apps/docs/components/Admonition.tsx index 42f7049f864..8a85475d929 100644 --- a/apps/docs/components/Admonition.tsx +++ b/apps/docs/components/Admonition.tsx @@ -1,13 +1,12 @@ -import { FC } from 'react' -import { IconInfo, IconHelpCircle, IconAlertTriangle } from 'ui' +import { PropsWithChildren } from 'react' +import { IconAlertTriangle, IconHelpCircle, IconInfo } from 'ui' -interface Props { +export interface AdmonitionProps { type: 'note' | 'tip' | 'info' | 'caution' | 'danger' label?: string - children: any } -const Admonition: FC = ({ type = 'note', label, children }) => { +const Admonition = ({ type = 'note', label, children }: PropsWithChildren) => { return (
=> { + if (!kid) { + const match = file.name.match(/AuthKey_([^.]+)[.].*$/i) + if (match && match[1]) { + kid = match[1] + } + } + + if (!kid) { + throw new Error( + `No Key ID provided. The file "${file.name}" does not follow the AuthKey_XXXXXXXXXX.p8 pattern. Please provide a Key ID manually.` + ) + } + + const contents = await file.text() + + if (!contents.match(/^\s*-+BEGIN PRIVATE KEY-+[^-]+-+END PRIVATE KEY-+\s*$/i)) { + throw new Error(`Chosen file does not appear to be a PEM encoded PKCS8 private key file.`) + } + + // remove PEM headers and spaces + const pkcs8 = stringToArrayBuffer( + globalThis.atob(contents.replace(/-+[^-]+-+/g, '').replace(/\s+/g, '')) + ) + + const privateKey = await globalThis.crypto.subtle.importKey( + 'pkcs8', + pkcs8, + { + name: 'ECDSA', + namedCurve: 'P-256', + }, + true, + ['sign'] + ) + + const iat = Math.floor(Date.now() / 1000) + const exp = iat + 180 * 24 * 60 * 60 + + const jwt = [ + base64URL(JSON.stringify({ typ: 'JWT', kid, alg: 'ES256' })), + base64URL( + JSON.stringify({ + iss, + sub, + iat, + exp, + aud: 'https://appleid.apple.com', + }) + ), + ] + + const signature = await globalThis.crypto.subtle.sign( + { + name: 'ECDSA', + hash: 'SHA-256', + }, + privateKey, + stringToArrayBuffer(jwt.join('.')) + ) + + jwt.push(base64URL(arrayBufferToString(signature))) + + return { kid, jwt: jwt.join('.'), exp } +} + +const AppleSecretGenerator = () => { + const [file, setFile] = useState({ file: null as File | null }) + const [teamID, setTeamID] = useState('') + const [serviceID, setServiceID] = useState('') + const [keyID, setKeyID] = useState('') + const [secretKey, setSecretKey] = useState('') + const [expiresAt, setExpiresAt] = useState('') + const [error, setError] = useState('') + + return ( + <> + setTeamID(e.target.value.trim())} + /> + setServiceID(e.target.value.trim())} + /> + setKeyID(e.target.value.trim())} + /> +
+ { + setFile({ file: e.target.files[0] }) + }} + /> +
+
+ + + + {error && {error}} + + {secretKey && ( + <> +
+ + + )} + + ) +} + +export default AppleSecretGenerator diff --git a/apps/docs/components/CustomHTMLElements/CustomHTMLElements.utils.test.ts b/apps/docs/components/CustomHTMLElements/CustomHTMLElements.utils.test.ts new file mode 100644 index 00000000000..2d2d1285229 --- /dev/null +++ b/apps/docs/components/CustomHTMLElements/CustomHTMLElements.utils.test.ts @@ -0,0 +1,96 @@ +import { getAnchor, removeAnchor } from './CustomHTMLElements.utils' + +describe('CustomHTMLElementsUtils', () => { + describe('getAnchor', () => { + describe('when value is an object', () => { + it('returns slugified version of props.children', () => { + const value = { + props: { + children: 'Full Text Search', + }, + } + + const result = getAnchor(value) + + expect(result).toStrictEqual('full-text-search') + }) + }) + + describe('when value is an array', () => { + describe('when custom anchor exists', () => { + it('returns inner slug', () => { + const value = ['Proximity: <->', '[#proximity]'] + + const result = getAnchor(value) + + expect(result).toStrictEqual('proximity') + }) + + it('trims whitespace', () => { + const value = ['Proximity: <->', ' [#proximity] '] + + const result = getAnchor(value) + + expect(result).toStrictEqual('proximity') + }) + }) + + it('returns concatenated slug of elements', () => { + const value = ['Full', 'Text', 'Search'] + + const result = getAnchor(value) + + expect(result).toStrictEqual('full-text-search') + }) + + it('trims whitespace', () => { + const value = [' Full ', ' Text ', ' Search '] + + const result = getAnchor(value) + + expect(result).toStrictEqual('full-text-search') + }) + + it('removes special characters', () => { + const value = ['function()'] + + const result = getAnchor(value) + + expect(result).toStrictEqual('function') + }) + }) + + describe('when value is a string', () => { + it('returns slugified version of string', () => { + const value = 'My (Very) Awesome Heading' + + const result = getAnchor(value) + + expect(result).toStrictEqual('my-very-awesome-heading') + }) + }) + }) + + describe('removeAnchor', () => { + describe('when value is an array', () => { + it('filters out custom anchor elements', () => { + const value = ['My (Very) Awesome Heading', '[#my-custom-heading]'] + + const result = removeAnchor(value) + + expect(result).toStrictEqual(['My (Very) Awesome Heading']) + }) + }) + + describe('when value is a string', () => { + it('strips out custom anchor string', () => { + const value = 'My (Very) Awesome Heading [#my-custom-heading]' + + const result = removeAnchor(value) + + // Original implementation didn't trim the resulting string - not sure if it really matters + expect(result).toStrictEqual('My (Very) Awesome Heading ') + }) + }) + }) +}) diff --git a/apps/docs/components/CustomHTMLElements/CustomHTMLElements.utils.ts b/apps/docs/components/CustomHTMLElements/CustomHTMLElements.utils.ts index eb214e4963b..844724a5466 100644 --- a/apps/docs/components/CustomHTMLElements/CustomHTMLElements.utils.ts +++ b/apps/docs/components/CustomHTMLElements/CustomHTMLElements.utils.ts @@ -2,53 +2,60 @@ export const getAnchor = (text: any): string | undefined => { if (typeof text === 'object') { if (Array.isArray(text)) { - const customAnchor = text.find( - (x) => typeof x === 'string' && x.includes('[#') && x.endsWith(']') - ) - if (customAnchor !== undefined) return customAnchor.slice(2, customAnchor.indexOf(']')) + const customAnchor = text.find((x) => typeof x === 'string' && hasCustomAnchor(x)) + if (customAnchor !== undefined) { + return parseCustomAnchor(customAnchor) + } const formattedText = text .map((x) => { - if (typeof x !== 'string') return x.props.children - else return x.trim() + if (typeof x !== 'string') { + return x.props.children + } + + return x.trim() }) .map((x) => { - if (typeof x !== 'string') return x - else + if (typeof x !== 'string') { return x - .toLowerCase() - .replace(/[^a-z0-9- ]/g, '') - .replace(/[ ]/g, '-') + } + + return slugify(x) }) return formattedText.join('-').toLowerCase() } else { const anchor = text.props.children if (typeof anchor === 'string') { - return anchor - .toLowerCase() - .replace(/[^a-z0-9- ]/g, '') - .replace(/[ ]/g, '-') + return slugify(anchor) } return anchor } } else if (typeof text === 'string') { - if (text.includes('[#') && text.endsWith(']')) { - return text.slice(text.indexOf('[#') + 2, text.indexOf(']')) - } else { - return text - .toLowerCase() - .replace(/[^a-z0-9- ]/g, '') - .replace(/[ ]/g, '-') + if (hasCustomAnchor(text)) { + return parseCustomAnchor(text) } + return slugify(text) } else { return undefined } } +const hasCustomAnchor = (value: string): boolean => value.includes('[#') && value.includes(']') + +const parseCustomAnchor = (value: string): string => + value.slice(value.indexOf('[#') + 2, value.indexOf(']')) + +const slugify = (value: string): string => + value + .toLowerCase() + .trim() + .replace(/[^a-z0-9- ]/g, '') + .replace(/[ ]/g, '-') + export const removeAnchor = (text: any) => { if (typeof text === 'object' && Array.isArray(text)) { - return text.filter((x) => !(typeof x === 'string' && x.includes('[#') && x.endsWith(']'))) + return text.filter((x) => !(typeof x === 'string' && hasCustomAnchor(x))) } else if (typeof text === 'string') { if (text.indexOf('[#') > 0) return text.slice(0, text.indexOf('[#')) else return text @@ -79,7 +86,7 @@ export const unHighlightSelectedTocItems = () => { } export const highlightSelectedNavItem = (id: string) => { - const navMenuItems = document.querySelectorAll('.function-link-item a') + const navMenuItems = document.querySelectorAll('.function-link-item a') // find any currently active items and remove them const currentActiveItems = document.querySelectorAll('.function-link-list .text-brand-900') @@ -87,7 +94,6 @@ export const highlightSelectedNavItem = (id: string) => { // Add active class to the current item navMenuItems.forEach((item) => { - // @ts-ignore if (item.href.split('/').at(-1) === id) { item.classList.add('text-brand-900') } diff --git a/apps/docs/components/DocsCoverLogo.tsx b/apps/docs/components/DocsCoverLogo.tsx new file mode 100644 index 00000000000..b7065feb555 --- /dev/null +++ b/apps/docs/components/DocsCoverLogo.tsx @@ -0,0 +1,497 @@ +import React from 'react' +import { LazyMotion, domAnimation, m } from 'framer-motion' + +const DocsCoverLogo = () => { + const pathMotionConfig = { + initial: { pathLength: 0 }, + animate: { pathLength: 1 }, + transition: { + duration: 1, + delay: 0.2, + ease: [0.5, 0.11, 0.13, 1], + }, + } + const logoMotionConfig = { + initial: { fillOpacity: 0 }, + animate: { fillOpacity: 1 }, + transition: { + duration: 1, + delay: 0.5, + ease: [0.25, 0.25, 0, 1], + }, + } + + return ( +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ ) +} + +export default DocsCoverLogo diff --git a/apps/docs/components/FooterHelpCallout.tsx b/apps/docs/components/FooterHelpCallout.tsx index 89f3265ec16..429ed15d4e6 100644 --- a/apps/docs/components/FooterHelpCallout.tsx +++ b/apps/docs/components/FooterHelpCallout.tsx @@ -5,7 +5,7 @@ export type FooterHelpCalloutType = 'default' | 'postgres' const content = { default: { title: 'Need some help?', - description: `Not to worry, our specialist engineers are here to help. Submit a support ticket through the [Dashboard](https://app.supabase.com/support/new).`, + description: `Not to worry, our specialist engineers are here to help. Submit a support ticket through the [Dashboard](https://supabase.com/dashboard/support/new).`, }, postgres: { title: 'Looking for Serverless Postgres?', diff --git a/apps/docs/components/FunctionsExamples.tsx b/apps/docs/components/FunctionsExamples.tsx index a741c0c1751..d9326121c34 100644 --- a/apps/docs/components/FunctionsExamples.tsx +++ b/apps/docs/components/FunctionsExamples.tsx @@ -6,6 +6,12 @@ const examples = [ description: 'Use the Supabase client inside your Edge Function.', href: 'https://github.com/supabase/supabase/blob/master/examples/edge-functions/supabase/functions/select-from-table-with-auth-rls/index.ts', }, + { + name: 'Type-Safe SQL with Kysely', + description: + 'Combining Kysely with Deno Postgres gives you a convenient developer experience for interacting directly with your Postgres database.', + href: '/guides/functions/kysely-postgres', + }, { name: 'With CORS headers', description: 'Send CORS headers for invoking from the browser.', diff --git a/apps/docs/components/HomePageCover.tsx b/apps/docs/components/HomePageCover.tsx new file mode 100644 index 00000000000..ed1cf16df6b --- /dev/null +++ b/apps/docs/components/HomePageCover.tsx @@ -0,0 +1,129 @@ +import Link from 'next/link' +import { IconBackground, IconPanel, IconPlay } from 'ui' +import { useBreakpoint } from 'common' +import DocsCoverLogo from './DocsCoverLogo' + +const HomePageCover = (props) => { + const isXs = useBreakpoint(639) + const iconSize = isXs ? 'sm' : 'lg' + + const GettingStarted = () => ( +
+
+
+
+ + + +

Getting Started

+
+

+ Discover how to set up a database to an app making queries in just a few minutes. +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ ) + + return ( +
+
+
+ +
+

{props.meta?.title}

+

+ Learn how to get up and running with Supabase through tutorials, APIs and platform + resources. +

+
+
+
+ +
+
+
+ ) +} + +export default HomePageCover diff --git a/apps/docs/components/MDX/ai/quickstart_hf_deployment.mdx b/apps/docs/components/MDX/ai/quickstart_hf_deployment.mdx new file mode 100644 index 00000000000..595b1f8f25b --- /dev/null +++ b/apps/docs/components/MDX/ai/quickstart_hf_deployment.mdx @@ -0,0 +1,5 @@ +## Deployment + +If you have your own infrastructure for deploying Python apps, you can continue to use `vecs` as described in this guide. + +Alternatively if you would like to quickly deploy using Supabase, check out our guide on using the [Hugging Face Inference API](/docs/guides/ai/hugging-face) in Edge Functions using TypeScript. diff --git a/apps/docs/components/MDX/database_setup.mdx b/apps/docs/components/MDX/database_setup.mdx new file mode 100644 index 00000000000..2a6e853f9c2 --- /dev/null +++ b/apps/docs/components/MDX/database_setup.mdx @@ -0,0 +1,18 @@ +import { Tabs } from 'ui' +export const TabPanel = Tabs.Panel + +## Project setup + +Let's create a new Postgres database. This is as simple as starting a new Project in Supabase: + +1. [Create a new project](https://database.new/) in the Supabase dashboard. +1. Enter your project details. Remember to store your password somewhere safe. + +Your database will be available in less than a minute. + +**Finding your credentials:** + +You can find your project credentials inside the project [settings](https://app.supabase.com/project/_/settings/), including: + +- [Database credentials](https://app.supabase.com/project/_/settings/database): connection strings and connection pooler details. +- [API credentials](https://app.supabase.com/project/_/settings/database): your serverless API URL and `anon` / `service_role` keys. diff --git a/apps/docs/components/MDX/social_provider_settings_supabase.mdx b/apps/docs/components/MDX/social_provider_settings_supabase.mdx index 1cc3771e6f3..38e0d9e5bfe 100644 --- a/apps/docs/components/MDX/social_provider_settings_supabase.mdx +++ b/apps/docs/components/MDX/social_provider_settings_supabase.mdx @@ -3,4 +3,4 @@ - Click on [`Providers`](https://app.supabase.com/project/_/auth/providers) under the Configuration section - Click on **{props.provider}** from the accordion list to expand and turn **{props.provider} Enabled** to ON - Enter your **{props.provider} Client ID** and **{props.provider} Client Secret** saved in the previous step -- Click `Save` \ No newline at end of file +- Click `Save` diff --git a/apps/docs/components/Navigation/Footer.tsx b/apps/docs/components/Navigation/Footer.tsx index c21718dce72..e14892a8f39 100644 --- a/apps/docs/components/Navigation/Footer.tsx +++ b/apps/docs/components/Navigation/Footer.tsx @@ -1,66 +1,163 @@ -import Image from 'next/image' import Link from 'next/link' -import { Button, IconGitHub, IconTwitter, IconYoutube } from '~/../../packages/ui' -import footerData from '~/data/footer.json' -import { useTheme } from 'common/Providers' +import { Button } from 'ui' +import { primaryLinks, secondaryLinks } from '~/data/footer' +import { LayoutMainContent } from '../../layouts/DefaultLayout' -const Footer = () => { - const { isDarkMode } = useTheme() - return ( -
-
-
-
- Supabase 2022 - — - {footerData.map((item) => ( - - {item.title} - - ))} -
-
- - - + + + -
+ + + + + + + +
- ) -} + +) export default Footer diff --git a/apps/docs/components/Navigation/Navigation.types.ts b/apps/docs/components/Navigation/Navigation.types.ts index 76afaa7b1c6..3882fb26e77 100644 --- a/apps/docs/components/Navigation/Navigation.types.ts +++ b/apps/docs/components/Navigation/Navigation.types.ts @@ -9,8 +9,8 @@ export interface NavMenuGroup { export interface NavMenuSection { name: string - url?: string - items: NavMenuSection[] + url?: `/${string}` + items: Partial[] } export interface References { @@ -22,3 +22,21 @@ export interface References { currentVersion?: string } } + +type MenuItem = { + label: string + icon?: string + href?: `/${string}` | `https://${string}` + level?: string + hasLightIcon?: boolean + community?: boolean +} + +export type HomepageMenuItems = MenuItem[][] + +export type NavMenuConstant = Readonly<{ + title: string + icon: string + url?: `/${string}` + items: ReadonlyArray> +}> diff --git a/apps/docs/components/Navigation/NavigationMenu/HomeMenu.tsx b/apps/docs/components/Navigation/NavigationMenu/HomeMenu.tsx index db93e1616a2..fb51bb98ba7 100644 --- a/apps/docs/components/Navigation/NavigationMenu/HomeMenu.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/HomeMenu.tsx @@ -4,155 +4,18 @@ import Link from 'next/link' import { useRouter } from 'next/router' import { Fragment } from 'react' import { Badge } from '~/../../packages/ui' +import { cn } from 'ui/src/utils/cn' +import { HOMEPAGE_MENU_ITEMS } from './NavigationMenu.constants' +import HomeMenuIconPicker from './HomeMenuIconPicker' -const home = [ - [ - { - label: 'Home', - icon: '/img/icons/menu/home', - href: '/', - level: 'home', - }, - { - label: 'Getting Started', - icon: '/img/icons/menu/getting-started', - href: '/guides/getting-started', - level: 'gettingstarted', - }, - ], - [ - { - label: 'Database', - icon: '/img/icons/menu/database', - href: '/guides/database', - level: 'database', - }, - { - label: 'Serverless APIs', - icon: '/img/icons/menu/reference', - href: '/guides/api', - level: 'api', - }, - { - label: 'Auth', - icon: '/img/icons/menu/auth', - href: '/guides/auth', - level: 'auth', - }, - { - label: 'Edge Functions', - icon: '/img/icons/menu/functions', - href: '/guides/functions', - level: 'functions', - }, - { - label: 'Realtime', - icon: '/img/icons/menu/realtime', - href: '/guides/realtime', - level: 'realtime', - }, - { - label: 'Storage', - icon: '/img/icons/menu/storage', - href: '/guides/storage', - level: 'storage', - }, - ], - [ - { - label: 'Platform', - icon: '/img/icons/menu/platform', - href: '/guides/platform', - level: 'platform', - }, - { - label: 'Resources', - icon: '/img/icons/menu/platform', - href: '/guides/resources', - level: 'resources', - }, - { - label: 'Self-Hosting', - icon: '/img/icons/menu/platform', - href: '/guides/self-hosting', - level: 'self_hosting', - }, - { - label: 'Integrations', - icon: '/img/icons/menu/integrations', - hasLightIcon: true, - href: '/guides/integrations', - level: 'integrations', - }, - ], - [ - { - label: 'Client Library Reference', - }, - { - label: 'JavaScript', - icon: '/img/icons/menu/reference-javascript', - href: '/reference/javascript/introduction', - level: 'reference_javascript', - }, - { - label: 'Flutter', - icon: '/img/icons/menu/reference-dart', - href: '/reference/dart/introduction', - level: 'reference_dart', - }, - { - label: 'Python', - icon: '/img/icons/menu/reference-python', - href: '/reference/python/introduction', - level: 'reference_python', - community: true, - }, - { - label: 'C#', - icon: '/img/icons/menu/reference-csharp', - href: '/reference/csharp/introduction', - level: 'reference_csharp', - community: true, - }, - { - label: 'Swift', - icon: '/img/icons/menu/reference-swift', - href: '/reference/swift/introduction', - level: 'reference_swift', - community: true, - }, - { - label: 'Tools', - }, - { - label: 'Management API', - icon: '/img/icons/menu/reference-api', - href: '/reference/api/introduction', - level: 'reference_javascript', - }, - { - label: 'Supabase CLI', - icon: '/img/icons/menu/reference-cli', - href: '/guides/cli', - level: 'reference_javascript', - }, - ], -] - -const NavigationMenuHome = ({ active }) => { +const NavigationMenuHome = () => { const router = useRouter() const { isDarkMode } = useTheme() return ( -
-