diff --git a/.dockerignore b/.dockerignore index b5924c32836..ff561a782b4 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,7 +1,20 @@ -node_modules/ +# Root folder of this turbo monorepo +# Doesn't send useless context to Docker daemon +.git +.github +about +apps +docker +examples +i18n +**/node_modules +spec +supabase +tests + +**/.next .vercel -.next -.env.local -.env.production -.env.dev -.env.* \ No newline at end of file +.env.* + +.dockerignore +studio/Dockerfile diff --git a/.github/workflows/avoid-typos.yml b/.github/workflows/avoid-typos.yml new file mode 100644 index 00000000000..cd8d982f4aa --- /dev/null +++ b/.github/workflows/avoid-typos.yml @@ -0,0 +1,20 @@ +name: reviewdog +on: + pull_request: + types: [opened, synchronize, reopened, edited] + +jobs: + misspell: + name: runner / misspell + runs-on: ubuntu-latest + steps: + - name: Check out code. + uses: actions/checkout@v1 + - name: misspell + uses: reviewdog/action-misspell@v1 + with: + github_token: ${{ secrets.github_token }} + locale: "US" + reporter: github-pr-review + level: error + exclude: "*.css" diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml deleted file mode 100644 index 0c9a3c46ebd..00000000000 --- a/.github/workflows/integration-tests.yml +++ /dev/null @@ -1,92 +0,0 @@ -name: Tests - -# Controls when the workflow will run -on: - schedule: - - cron: '0 4/6 * * *' - workflow_dispatch: - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - autotests: - name: Run tests and generate Allure Report - strategy: - matrix: - node: ['14'] - - runs-on: ubuntu-latest - defaults: - run: - working-directory: ./tests - - steps: - - uses: actions/checkout@v2 - - - name: Set up JDK - uses: actions/setup-java@v1 - with: - java-version: 1.8 - - - name: Set up Node - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node }} - - - name: Install dependencies - run: npm ci - - - uses: supabase/setup-cli@v1 - - run: supabase start - - - name: Run Test - run: npm run test:local - env: - SUPABASE_DB_PORT: 54322 - SUPABASE_DB_PASS: postgres - SUPABASE_DB_HOST: localhost - SUPABASE_GOTRUE: http://localhost:54321 - SUPABASE_URL: http://localhost:54321 - SUPABASE_KEY_ANON: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24ifQ.625_WdcF3KHqz5amU0x2X5WWHP-OEs_4qj0ssLNHzTs - SUPABASE_KEY_ADMIN: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSJ9.vI9obAHOGyVVKa3pD--kJlyxp-Z2zV9UUMAhKpNLAcU - - - name: Stop infrastructure - if: always() - run: supabase stop - - - name: Get Allure history - uses: actions/checkout@v2 - if: always() - continue-on-error: true - with: - repository: supabase/test-reports - ref: gh-pages - path: gh-pages - - - name: Allure Report action - uses: simple-elf/allure-report-action@master - if: always() - with: - github_repo: test-reports - allure_results: tests/allure-results - allure_history: allure-history - subfolder: supabase-integration - keep_reports: 50 - - - name: Deploy report to Github Pages - if: always() - uses: peaceiris/actions-gh-pages@v2 - env: - EXTERNAL_REPOSITORY: supabase/test-reports - ACTIONS_DEPLOY_KEY: ${{ secrets.DEPLOY_TO_TEST_REPORTS_KEY }} - PUBLISH_BRANCH: gh-pages - PUBLISH_DIR: allure-history - - - name: Post the link to the report - if: always() - uses: Sibz/github-status-action@v1 - with: - authToken: ${{ secrets.GITHUB_TOKEN }} - context: 'Test report' - state: 'success' - sha: ${{ github.event.pull_request.head.sha || github.sha }} - target_url: https://supabase.github.io/test-reports/supabase-integration/${{ github.run_number }} diff --git a/.github/workflows/mirror.yml b/.github/workflows/mirror.yml index 0a4d1b1fd57..47563fba1ef 100644 --- a/.github/workflows/mirror.yml +++ b/.github/workflows/mirror.yml @@ -19,12 +19,16 @@ jobs: permissions: contents: read packages: write + id-token: write steps: + - name: configure aws credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + role-to-assume: ${{ secrets.PROD_AWS_ROLE }} + aws-region: us-east-1 - uses: docker/login-action@v2 with: registry: public.ecr.aws - username: ${{ secrets.PROD_ACCESS_KEY_ID }} - password: ${{ secrets.PROD_SECRET_ACCESS_KEY }} - uses: docker/login-action@v2 with: registry: ghcr.io diff --git a/.github/workflows/og_images.yml b/.github/workflows/og_images.yml new file mode 100644 index 00000000000..2e1461461d2 --- /dev/null +++ b/.github/workflows/og_images.yml @@ -0,0 +1,28 @@ +name: Deploy OG Images + +on: + push: + branches: + - main + paths: + - 'supabase/functions/og-images' + workflow_dispatch: + +jobs: + deploy: + runs-on: ubuntu-latest + + env: + SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }} + PROJECT_ID: ${{ secrets.PROJECT_ID }} + + steps: + - name: Check out repo + - uses: actions/checkout@v3 + + - name: Setup the Supabase CLI + - uses: supabase/setup-cli@v1 + with: + version: 1.0.0 + + - run: supabase functions deploy og-images --project-ref $PROJECT_ID diff --git a/.github/workflows/studio-build.yml b/.github/workflows/studio-build.yml index 570baa1aa61..35d1f97b8b1 100644 --- a/.github/workflows/studio-build.yml +++ b/.github/workflows/studio-build.yml @@ -7,6 +7,7 @@ on: - studio paths: - 'studio/**' + - 'package-lock.json' jobs: build: runs-on: ubuntu-latest diff --git a/.github/workflows/studio-tests.yml b/.github/workflows/studio-tests.yml index 1a99a50182d..72a84549568 100644 --- a/.github/workflows/studio-tests.yml +++ b/.github/workflows/studio-tests.yml @@ -8,10 +8,12 @@ on: branches: [master] paths: - 'studio/**' + - 'package-lock.json' pull_request: branches: [master] paths: - 'studio/**' + - 'package-lock.json' jobs: build: runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index f536bae18d7..c67a911193a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ node_modules out .docz tmp +.swp coverage allure-results @@ -119,4 +120,4 @@ typings/ **/supabase/.branches **/supabase/.temp -apps/new-docs/* \ No newline at end of file +apps/new-docs/* diff --git a/.prettierignore b/.prettierignore index 9ab0849d504..db287264935 100644 --- a/.prettierignore +++ b/.prettierignore @@ -3,4 +3,5 @@ node_modules package-lock.json docker* -apps/**/out \ No newline at end of file +apps/**/out +**/**.mdx \ No newline at end of file diff --git a/DEVELOPERS.md b/DEVELOPERS.md index 0281b1b0bd7..1845aa29ca5 100644 --- a/DEVELOPERS.md +++ b/DEVELOPERS.md @@ -57,7 +57,7 @@ To contribute code to [Supabase](https://supabase.com), you must fork the [Supab [Supabase](https://supabase.com) uses [Turborepo](https://turborepo.org/docs) to manage and run this monorepo. -1. Install the dependences in the root of the repo. +1. Install the dependencies in the root of the repo. ```sh npm install # install dependencies @@ -113,7 +113,7 @@ Following the changes to the [Supabase docs](https://supabase.com/blog/new-supab - Inside of `apps/docs` create a `.env.local` file with the following: `NEXT_PUBLIC_NEW_DOCS=true` -Now when you run a local devlopment docs server you will see the new docs site. +Now when you run a local development docs server you will see the new docs site. --- diff --git a/LICENSE b/LICENSE index 438f239eb5d..e029233d99c 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2022 Supabase + Copyright 2023 Supabase Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 304eceed468..d6498236c88 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

- - + +

--- @@ -12,9 +12,9 @@ - [x] Hosted Postgres Database. [Docs](https://supabase.com/docs/guides/database) - [x] Authentication and Authorization. [Docs](https://supabase.com/docs/guides/auth) - [x] Auto-generated APIs. - - [x] REST. [Docs](https://supabase.com/docs/guides/api#rest-api) - - [x] Realtime subscriptions. [Docs](https://supabase.com/docs/guides/api#realtime-api) - - [x] GraphQL (Beta). [Docs](https://supabase.com/docs/guides/api#graphql-api) + - [x] REST. [Docs](https://supabase.com/docs/guides/database/api#rest-api) + - [x] Realtime subscriptions. [Docs](https://supabase.com/docs/guides/database/api#realtime-api) + - [x] GraphQL (Beta). [Docs](https://supabase.com/docs/guides/database/api#graphql-api) - [x] Functions. - [x] Database Functions. [Docs](https://supabase.com/docs/guides/database/functions) - [x] Edge Functions [Docs](https://supabase.com/docs/guides/functions) diff --git a/apps/docs/.env.example b/apps/docs/.env.example new file mode 100644 index 00000000000..24f1544c0e9 --- /dev/null +++ b/apps/docs/.env.example @@ -0,0 +1,10 @@ +# PUBLIC +NEXT_PUBLIC_SUPABASE_URL=http://localhost:54321 +NEXT_PUBLIC_SUPABASE_ANON_KEY= + +# PRIVATE +OPENAI_KEY= +SUPABASE_SERVICE_ROLE_KEY= + +# Set IS_PLATFORM to true if you have the api keys and urls +NEXT_PUBLIC_IS_PLATFORM=false diff --git a/apps/docs/DEVELOPERS.md b/apps/docs/DEVELOPERS.md new file mode 100644 index 00000000000..3935d5e0ce0 --- /dev/null +++ b/apps/docs/DEVELOPERS.md @@ -0,0 +1,99 @@ +# Developing Supabase Docs + +## Getting started + +Thanks for your interest in [Supabase docs](https://supabase.com/docs) and for wanting to contribute! Before you begin, read the +[code of conduct](https://github.com/supabase/.github/blob/main/CODE_OF_CONDUCT.md) and check out the +[existing issues](https://github.com/supabase/supabase/issues). +This document describes how to set up your development environment to contribute to [Supabase docs](https://supabase.com/docs). + +For a complete run-down on how all of our tools work together, see the main DEVELOPERS.md. That readme describes how to get set up locally in lots of detail, including minimum requirements, our Turborepo setup, installing packages, sharing components across projects, and more. This readme deals specifically with the docs site. + +## Local setup + +[supabase.com/docs](https://supabase.com/docs) is a Next.JS site. You can get setup by following the same steps for all of our other Next.JS projects: + +1. Follow the steps outlined in the Local Development section of the main [DEVELOPERS.md](https://github.com/supabase/supabase/blob/master/DEVELOPERS.md) +2. If you work at Supabase, run `dev:secrets:pull` to pull down the internal environment variables. If you're a community member, create a `.env` file and add this line to it: `NEXT_PUBLIC_IS_PLATFORM=false` +3. Start the local docs site by navigating to `/apps/docs` and running `npm run dev` +4. Visit http://localhost:3001/docs in your browser - don't forget to append the `/docs` to the end +5. Your local site should look exactly like [https://supabase.com/docs](https://supabase.com/docs) + +## Types of documentation + +[https://supabase.com/docs](https://supabase.com/docs) has several different kinds of documentation, all coming from different sources. + +### Guides + +The primary, instructional type of content. Basically anything that lives on the `https://supabase.com/docs/guides` route. This includes Guides for Auth, Database, Storage, Realtime, Edge Functions, as well as general resources, self-hosting instructions, and integrations. These are all [`.mdx`](https://mdxjs.com/) files — a combination of Markdown and Javascript. + +#### Things to know + +Here's a simple [example](https://supabase.com/docs/guides/functions) `.mdx` Guide, and here is [the source on Github](https://raw.githubusercontent.com/supabase/supabase/master/apps/docs/pages/guides/functions.mdx). + +Some things to note: + +1. The files need to import a Layout at the top +2. The files need to export a `Page` at the bottom with the `` component +3. The files frontmatter is stored in `const meta = {}`. You should always include `title` and `description`. +4. You can write Markdown as you normally would, but you can also write regular Javascript and JSX. Note the `examples` array that we iterate over. +5. Any Javascript variables you use in these files need to be exported in order to be used (i.e., `export const examples = []`). + +##### Using components + +You can use any standard React components in these `.mdx` files without having to explicitly import them in each file. All components get imported in a [common components](https://github.com/supabase/supabase/blob/master/apps/docs/components/index.tsx) file and can be used in any `.mdx` file. Components can also be "intercepted" and modified via this file. Note how we're intercepting the `h2`, `h3` and `code` tags and modifying them before converting the `mdx` to `html`. + +### Reference docs for client libraries + +We maintain client libraries for [Javascript](https://supabase.com/docs/reference/javascript) and [Flutter/Dart](https://supabase.com/docs/reference/dart) (with more to come). These reference docs document every object and method available for developers to use. The are assembled from different sources and work much differently than the `.mdx` Guides we just looked at. + +The client libraries are essentially wrappers around the clients for the various tools we use — GoTrue, PostgREST, Storage, Functions, and Realtime. The easiest way to describe how the things fit together is to look at an example and trace where the various pieces of information are coming from. + +#### Example + +Let's look at the `updateUser()` function in the `supabase-js` library. + +#### Common file + +Several pieces of information for this function come from a [common file](https://github.com/supabase/supabase/blob/3d774b3b7bcdcb410e25726d832467584ebea686/spec/common-client-libs-sections.json#L548) where we store information shared by all libraries. + +1. id — used to identify this function +2. title - the human-readable title +3. slug — the url slug +4. product - the Supabase tool or product that "owns" this function. Since `updateUser()` is an auth function, its product is `auth` +5. type — `updateUser()` is a function and marked as such, but we can also have sections of markdown interspersed with these function definitions. + +When a new function is added, this info would need to be manually added to the common file. + +#### Function Parameters + +The `updateUser()` function takes one parameter: `attributes`. The details for this parameter live in the GoTrue client library, referenced via a `$ref` property in the `supabase-js` [spec file](https://github.com/supabase/supabase/blob/cb04d85262db6a371539dda7df9b00ba5a901e87/spec/supabase_js_v2.yml#L357). Here, the `$ref` property is pointing to the [actual function definition](https://github.com/supabase/gotrue-js/blob/2d60e79073b96ae8c97a6ce18e2601ed1e2a2712/src/GoTrueClient.ts#L590) in the `gotrue-js` library. The accepted values for the `attributes` parameter come from the [type definition](https://github.com/supabase/gotrue-js/blob/16d3deb822097e8640a3a15b94a5690b3beaf11b/src/lib/types.ts#L233). + +These individual library spec files are fetched via this [Makefile](https://github.com/supabase/supabase/blob/master/spec/Makefile), and get [transformed](https://github.com/supabase/supabase/blob/master/spec/enrichments/tsdoc_v2/supabase_dereferenced.json) to combine the information we need (params, types, etc). Unless you're a library maintainer, you shouldn't need to worry about this part of the process. + +If you are a library maintainer, the last important note about these library files is that the [Makefile](https://github.com/supabase/supabase/blob/master/spec/Makefile) pulls from the `gh-pages` branch of the client library repo. Here's an example of the [`realtime-js` spec file](https://github.com/supabase/realtime-js/blob/gh-pages/v2/spec.json). Updating something like function params or returns, the process is: + +1. Get your changes merged to `master` in your library +2. This will kick off an action that automatically updates the spec file in the library's `gh-pages` branch +3. Run `make` in `/spec` of the `supabase/supabase` repo. This will regenerate all of the `tsdoc` files that the docs site uses +4. You should now see the changes you've made in the docs site locally + +#### Function Examples + +The `updateUser()` function has three examples listed with it. The examples are stored along with the `$ref` property in the [supabase_js_v2 spec file](https://github.com/supabase/supabase/blob/master/spec/supabase_js_v2.yml). + +#### Rendering in Next.JS + +These reference docs are rendered by Next.JS via a dynamic route using a [`[...slug.tsx]`](https://github.com/supabase/supabase/blob/master/apps/docs/pages/reference/javascript/%5B...slug%5D.tsx). Here, we use the library [spec file](https://github.com/supabase/supabase/blob/bd0514553c627db8f1e8d0b3ae440ccb6759d228/apps/docs/pages/reference/javascript/%5B...slug%5D.tsx#L4) and the [common file](https://github.com/supabase/supabase/blob/bd0514553c627db8f1e8d0b3ae440ccb6759d228/apps/docs/pages/reference/javascript/%5B...slug%5D.tsx#L1) to output the info you see on the page. + +### Other reference docs + +The reference docs for the [Supabase Management API](https://supabase.com/docs/reference/api) and the [Supabase CLI](https://supabase.com/docs/reference/cli) are a little more straightforward than the client libraries. Both files also have a [common file](https://github.com/supabase/supabase/blob/master/spec/common-cli-sections.json) which handles things like `title`, `id` and `slug`. Both also have a spec file detailing things like parameters, descriptions, and responses ([Management API](https://github.com/supabase/supabase/blob/master/spec/api_v0_openapi.json) / [CLI](https://github.com/supabase/supabase/blob/master/spec/cli_v1_commands.yaml)) + +On the Next.JS side of things, these work almost exactly the same as the client libaries with a dynamic [`[...slug.tsx]`](https://github.com/supabase/supabase/blob/master/apps/docs/pages/reference/cli/%5B...slug%5D.tsx). + +### Misc + +#### 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. diff --git a/apps/docs/components/AuthProviders.tsx b/apps/docs/components/AuthProviders.tsx index a1903636904..82a57180a4b 100644 --- a/apps/docs/components/AuthProviders.tsx +++ b/apps/docs/components/AuthProviders.tsx @@ -1,53 +1,21 @@ import providers from '../data/authProviders' -import ButtonCard from './ButtonCard' +import { IconPanel } from 'ui' +import Link from 'next/link' + +export default function AuthProviders({ type }: { type: string }) { + const filterProviders = providers.filter((item) => item.authType === type) -export default function AuthProviders() { return ( -
- {providers.map((x) => ( -
- -
-
-

{x.name}

-

- {x.official ? ( - Official - ) : ( - Unofficial - )} -

-
-
-
- Platform: - {x.platform.toString()} -
-
- Self-Hosted: - {x.selfHosted.toString()} -
-
-
-
-
- ))} -
+ <> +
+ {filterProviders.map((x) => ( + + + + + + ))} +
+ ) } diff --git a/apps/docs/components/Clippy/Clippy.tsx b/apps/docs/components/Clippy/Clippy.tsx new file mode 100644 index 00000000000..ce22accbe09 --- /dev/null +++ b/apps/docs/components/Clippy/Clippy.tsx @@ -0,0 +1,37 @@ +import { useTheme } from 'common/Providers' +import Image from 'next/image' +import { FC } from 'react' +import { createPortal } from 'react-dom' +import { IS_PLATFORM } from '~/lib/constants' +import clippyImageDark from '../../public/img/clippy-dark.png' +import clippyImage from '../../public/img/clippy.png' +import ClippyBubble from './ClippyBubble' +import ClippyModal from './ClippyModal' +import { useClippy } from './ClippyProvider' + +const Clippy: FC = () => { + const { isDarkMode } = useTheme() + const { isOpen, open, close } = useClippy() + + if (!IS_PLATFORM) { + return null + } else { + return ( + <> + {!isOpen && ( +
+ {/* <> + +
+ Clippy +
+ */} +
+ )} + {isOpen && createPortal(, document.body)} + + ) + } +} + +export default Clippy diff --git a/apps/docs/components/Clippy/ClippyBubble.tsx b/apps/docs/components/Clippy/ClippyBubble.tsx new file mode 100644 index 00000000000..92286cb85c6 --- /dev/null +++ b/apps/docs/components/Clippy/ClippyBubble.tsx @@ -0,0 +1,36 @@ +import { FC } from 'react' +import { IconCommand } from 'ui' +import Image from 'next/image' +interface Props { + className?: string + onClick?: () => void +} + +const ClippyBubble: FC = ({ className, onClick }) => { + return ( +
+
+ Clippy blur +
+ What can I help you with? +
+
+ + / +
+
+
+ + + +
+
+ ) +} + +export default ClippyBubble diff --git a/apps/docs/components/Clippy/ClippyModal.tsx b/apps/docs/components/Clippy/ClippyModal.tsx new file mode 100644 index 00000000000..282f9cc7c9f --- /dev/null +++ b/apps/docs/components/Clippy/ClippyModal.tsx @@ -0,0 +1,270 @@ +import type { CreateCompletionResponse } from 'openai' +import { FC, useCallback, useState } from 'react' +import ReactMarkdown from 'react-markdown' +import remarkGfm from 'remark-gfm' +import { SSE } from 'sse.js' +import clippyImageDark from '../../public/img/clippy-dark.png' +import clippyImage from '../../public/img/clippy.png' + +import { useTheme } from 'common/Providers' +import Image from 'next/image' +import { + Button, + IconAlertCircle, + IconAlertTriangle, + IconLoader, + IconSearch, + Input, + Loading, + Modal, +} from 'ui' +import components from '~/components' +import { IS_PLATFORM } from '~/lib/constants' + +type Props = { + onClose?: () => void +} + +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?', +] + +function getEdgeFunctionUrl() { + const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL?.replace(/\/$/, '') + + if (IS_PLATFORM) { + const [schemeAndProjectId, domain, tld] = supabaseUrl.split('.') + return `${schemeAndProjectId}.functions.${domain}.${tld}` + } else { + return `${supabaseUrl}/functions/v1` + } +} + +const edgeFunctionUrl = IS_PLATFORM ? getEdgeFunctionUrl() : undefined + +const ClippyModal: FC = ({ onClose }) => { + const { isDarkMode } = useTheme() + const [query, setQuery] = useState('') + const [answer, setAnswer] = useState('') + const [isLoading, setIsLoading] = useState(false) + const [isResponding, setIsResponding] = useState(false) + const [hasError, setHasError] = useState(false) + + 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 || hasError + ? 'Clippy has failed you' + : undefined + + const handleConfirm = useCallback(async (query: string) => { + setHasError(false) + setAnswer(undefined) + 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(err: T) { + setIsLoading(false) + setIsResponding(false) + setHasError(true) + console.error(err) + } + + eventSource.addEventListener('error', handleError) + eventSource.addEventListener('message', (e) => { + try { + setIsLoading(false) + + if (e.data === '[DONE]') { + setIsResponding(false) + return + } + + setIsResponding(true) + + const completionResponse: CreateCompletionResponse = JSON.parse(e.data) + const [{ text }] = completionResponse.choices + + setAnswer((answer) => { + return (answer ?? '') + text + }) + } catch (err) { + handleError(err) + } + }) + + eventSource.stream() + + setIsLoading(true) + }, []) + + function handleResetPrompt() { + setQuery('') + setAnswer(undefined) + setIsResponding(false) + setHasError(false) + } + + return ( + +
e.stopPropagation()} + > +
+ setQuery(e.target.value)} + icon={} + onKeyDown={(e) => { + switch (e.key) { + case 'Enter': + handleConfirm(query) + return + default: + return + } + }} + /> +
+ +
+ {!isLoading && answer && ( +
+ +
+ )} +
+ + {!isLoading && !answer && !hasError && ( +
+
+

Not sure where to start?

+ +
    + {questions.map((question) => { + const key = question.replace(/\s+/g, '_') + return ( +
  • + +
  • + ) + })} +
+
+
+ )} + {answer && ( +
+ {cantHelp ? ( +

+

+ +

Sorry, I don't know how to help with that.

+
+ +

+ ) : ( +
+ { + 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 + }} + components={components} + > + {answer} + +
+ )} +
+ )} + {isLoading && ( +
+ {} +

Searching for results

+
+ )} + {hasError && ( +
+ +

Sorry, looks like Clippy is having a hard time!

+

Please try again in a bit.

+ +
+ )} +
+
+
+ Powered by OpenAI. + + Read the blog post + +
+
+ {status ? ( + + {(isLoading || isResponding) && } + {status} + + ) : ( + <> + )} + Clippy +
+
+
+
+
+ ) +} + +export default ClippyModal diff --git a/apps/docs/components/Clippy/ClippyProvider.tsx b/apps/docs/components/Clippy/ClippyProvider.tsx new file mode 100644 index 00000000000..9bdfabea56e --- /dev/null +++ b/apps/docs/components/Clippy/ClippyProvider.tsx @@ -0,0 +1,72 @@ +import { + createContext, + FC, + PropsWithChildren, + useCallback, + useContext, + useEffect, + useState, +} from 'react' + +export type ClippyContextValue = { + isOpen: boolean + open: () => void + close: () => void +} + +export const ClippyContext = createContext(null) + +export const useClippy = () => { + const { isOpen, open, close } = useContext(ClippyContext) + + return { isOpen, open, close } +} + +const ClippyProvider: FC> = ({ children }) => { + const [isModalOpen, setIsModalOpen] = useState(false) + + const onOpen = useCallback(() => { + setIsModalOpen(true) + document.body.classList.add('DocSearch--active') + }, []) + + const onClose = useCallback(() => { + setIsModalOpen(false) + document.body.classList.remove('DocSearch--active') + }, []) + + useSearchKeyboardEvents({ + onOpen, + onClose, + }) + return ( + + {children} + + ) +} + +function useSearchKeyboardEvents({ onOpen, onClose }) { + useEffect(() => { + function onKeyDown(event: KeyboardEvent) { + switch (event.key) { + case 'Escape': + onClose() + return + case '/': + if (event.metaKey || event.ctrlKey) { + onOpen() + } + return + } + } + + window.addEventListener('keydown', onKeyDown) + + return () => { + window.removeEventListener('keydown', onKeyDown) + } + }, [onOpen, onClose]) +} + +export default ClippyProvider diff --git a/apps/docs/components/CodeBlock/CodeBlock.tsx b/apps/docs/components/CodeBlock/CodeBlock.tsx index f4e962bb2aa..b54e01b699f 100644 --- a/apps/docs/components/CodeBlock/CodeBlock.tsx +++ b/apps/docs/components/CodeBlock/CodeBlock.tsx @@ -1,4 +1,4 @@ -import { FC } from 'react' +import { Children, FC } from 'react' import CopyToClipboard from 'react-copy-to-clipboard' import { Light as SyntaxHighlighter } from 'react-syntax-highlighter' import monokaiCustomTheme from './CodeBlock.utils' @@ -6,6 +6,7 @@ import { Button, IconCheck, IconCopy } from 'ui' import js from 'react-syntax-highlighter/dist/cjs/languages/hljs/javascript' import ts from 'react-syntax-highlighter/dist/cjs/languages/hljs/typescript' +import csharp from 'react-syntax-highlighter/dist/cjs/languages/hljs/csharp' import py from 'react-syntax-highlighter/dist/cjs/languages/hljs/python' import sql from 'react-syntax-highlighter/dist/cjs/languages/hljs/sql' import bash from 'react-syntax-highlighter/dist/cjs/languages/hljs/bash' @@ -17,7 +18,7 @@ import { useTheme } from 'common/Providers' interface Props { title?: string - language: 'js' | 'jsx' | 'sql' | 'py' | 'bash' | 'ts' | 'dart' | 'json' + language: 'js' | 'jsx' | 'sql' | 'py' | 'bash' | 'ts' | 'dart' | 'json' | 'csharp' linesToHighlight?: number[] hideCopy?: boolean hideLineNumbers?: boolean @@ -48,10 +49,18 @@ const CodeBlock: FC = ({ }, 1000) } + // Extract string when `children` has a single string node + const childrenArray = Children.toArray(children) + const [singleChild] = childrenArray.length === 1 ? childrenArray : [] + const singleString = typeof singleChild === 'string' ? singleChild : undefined + + let codeValue = value ?? singleString ?? children + codeValue = codeValue?.trimEnd?.() ?? codeValue + // check the length of the string inside the tag // if it's fewer than 70 characters, add a white-space: pre so it doesn't wrap const shortCodeBlockClasses = - typeof children === 'string' && children.length < 70 ? 'short-inline-codeblock' : '' + typeof codeValue === 'string' && codeValue.length < 70 ? 'short-inline-codeblock' : '' let lang = language ? language : className ? className.replace('language-', '') : 'js' // force jsx to be js highlighted @@ -62,6 +71,7 @@ const CodeBlock: FC = ({ SyntaxHighlighter.registerLanguage('sql', sql) SyntaxHighlighter.registerLanguage('bash', bash) SyntaxHighlighter.registerLanguage('dart', dart) + SyntaxHighlighter.registerLanguage('csharp', csharp) SyntaxHighlighter.registerLanguage('json', json) const large = false @@ -116,7 +126,7 @@ const CodeBlock: FC = ({ paddingBottom: '4px', }} > - {(value || children)?.trimEnd()} + {codeValue} {!hideCopy && (value || children) && className ? (
= ({ `${!title ? 'top-2' : 'top-[3.25rem]'}`, ].join(' ')} > - {/* // + {/* // @ts-ignore */} +

+
+ + + + + + ) +} diff --git a/apps/docs/components/Flag/Flag.tsx b/apps/docs/components/Flag/Flag.tsx new file mode 100644 index 00000000000..ea95b33834f --- /dev/null +++ b/apps/docs/components/Flag/Flag.tsx @@ -0,0 +1,17 @@ +import React, { ReactNode } from 'react' +import { useFlag } from '~/hooks/useFlag' + +interface Props { + children: ReactNode + name: string +} + +function Flag({ children, name }: Props) { + const flagValue = useFlag(name) + if (!flagValue) { + return null + } + return <>{children} +} + +export default Flag diff --git a/apps/docs/components/Flag/FlagContext.ts b/apps/docs/components/Flag/FlagContext.ts new file mode 100644 index 00000000000..6c58ab54c84 --- /dev/null +++ b/apps/docs/components/Flag/FlagContext.ts @@ -0,0 +1,5 @@ +import { createContext } from 'react' + +const FlagContext = createContext({}) + +export default FlagContext diff --git a/apps/docs/components/Flag/FlagProvider.tsx b/apps/docs/components/Flag/FlagProvider.tsx new file mode 100644 index 00000000000..4cb3f362fa7 --- /dev/null +++ b/apps/docs/components/Flag/FlagProvider.tsx @@ -0,0 +1,35 @@ +import { FC, useEffect, useState } from 'react' +// import createConfigCatClient from 'configcat-js' +import FlagContext from './FlagContext' + +const FlagProvider: FC = ({ children }) => { + const [store, setStore] = useState({}) + const { Provider } = FlagContext + + // useEffect(() => { + // getFlags() + // }, []) + + // const getFlags = async () => { + // const setFlagValues = async () => { + // const flagValues = await client.getAllValuesAsync() + // const flagStore: any = {} + + // flagValues.forEach((item: any) => { + // flagStore[item.settingKey] = item.settingValue + // }) + // setStore(flagStore) + // } + + // const client = createConfigCatClient(process.env.NEXT_PUBLIC_CONFIGCAT_SDK_KEY ?? '', { + // configChanged: setFlagValues, + // pollIntervalSeconds: 600, + // }) + + // await setFlagValues() + // } + + return {children} +} + +export default FlagProvider diff --git a/apps/docs/components/FooterHelpCallout.tsx b/apps/docs/components/FooterHelpCallout.tsx index 2eb6421dc4c..89f3265ec16 100644 --- a/apps/docs/components/FooterHelpCallout.tsx +++ b/apps/docs/components/FooterHelpCallout.tsx @@ -1,12 +1,38 @@ -const FooterHelpCallout = () => { +import { ReactMarkdown } from 'react-markdown/lib/react-markdown' + +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).`, + }, + postgres: { + title: 'Looking for Serverless Postgres?', + description: `Supabase is the fastest way to get started with Postgres in a serverless environment. [Learn more](https://supabase.com/database?utm=postgres-helpers).`, + }, +} + +const FooterHelpCallout = ({ + footerHelpType = 'default', + title, +}: { + footerHelpType: FooterHelpCalloutType + title: any +}) => { return ( -
-
-
Need some help?
-

- Not to worry, our specialist engineers are here to help. Submit a support ticket through - the Dashboard. -

+
+
+
{content[footerHelpType].title}
+ {content[footerHelpType].description}
) diff --git a/apps/docs/components/JwtGenerator.js b/apps/docs/components/JwtGenerator.js index 46dd4758e31..4ac1b05e255 100644 --- a/apps/docs/components/JwtGenerator.js +++ b/apps/docs/components/JwtGenerator.js @@ -1,7 +1,7 @@ import React, { useState } from 'react' import KJUR from 'jsrsasign' import CodeBlock from './CodeBlock/CodeBlock' -import { Button } from 'ui' +import { Button, Select, Input } from 'ui' const JWT_HEADER = { alg: 'HS256', typ: 'JWT' } const now = new Date() @@ -46,9 +46,8 @@ export default function JwtGenerator({}) {
-
- +
-