From 25d374be355d94fc40aef57c825e610e85c9247e Mon Sep 17 00:00:00 2001 From: Aditya Dhawan Date: Sun, 11 Sep 2022 21:08:36 +0100 Subject: [PATCH 01/26] Support storage and buckets for self hosted --- docker/docker-compose.yml | 5 ++-- studio/.env | 2 +- .../NavigationBar/NavigationBar.utils.tsx | 18 ++++++------- .../layouts/StorageLayout/StorageMenu.tsx | 27 ++++++++++++------- studio/hooks/misc/withAuth.tsx | 2 +- .../storageExplorer/StorageExplorerStore.js | 3 ++- studio/package.json | 2 +- studio/pages/api/projects/[ref]/index.ts | 2 +- studio/pages/api/props/project/[ref]/api.ts | 4 +-- 9 files changed, 35 insertions(+), 30 deletions(-) diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index d879f3ba834..2eed64571ff 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -18,7 +18,7 @@ services: POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} SUPABASE_URL: http://kong:8000 - SUPABASE_REST_URL: ${PUBLIC_REST_URL} + SUPABASE_REST_URL: ${SUPABASE_PUBLIC_URL} SUPABASE_ANON_KEY: ${ANON_KEY} SUPABASE_SERVICE_KEY: ${SERVICE_ROLE_KEY} @@ -112,8 +112,7 @@ services: SLOT_NAME: supabase_realtime_rls TEMPORARY_SLOT: "true" command: > - bash -c "./prod/rel/realtime/bin/realtime eval Realtime.Release.migrate - && ./prod/rel/realtime/bin/realtime start" + bash -c "./prod/rel/realtime/bin/realtime eval Realtime.Release.migrate && ./prod/rel/realtime/bin/realtime start" storage: container_name: supabase-storage diff --git a/studio/.env b/studio/.env index db284f3411e..633099bfb61 100644 --- a/studio/.env +++ b/studio/.env @@ -2,7 +2,7 @@ STUDIO_PG_META_URL=http://localhost:8000/pg POSTGRES_PASSWORD=your-super-secret-and-long-postgres-password SUPABASE_URL=http://localhost:8000 -SUPABASE_REST_URL=http://localhost:8000/rest/v1/ +SUPABASE_REST_URL=http://localhost:8000 SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE SUPABASE_SERVICE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q diff --git a/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.utils.tsx b/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.utils.tsx index b08d1dbc460..98fa7181c6b 100644 --- a/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.utils.tsx +++ b/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.utils.tsx @@ -39,16 +39,14 @@ export const generateProductRoutes = (ref: string, project?: ProjectBase): Route icon: , link: isProjectBuilding ? buildingUrl : `/project/${ref}/auth/users`, }, - ...(IS_PLATFORM - ? [ - { - key: 'storage', - label: 'Storage', - icon: , - link: isProjectBuilding ? buildingUrl : `/project/${ref}/storage/buckets`, - }, - ] - : []), + + { + key: 'storage', + label: 'Storage', + icon: , + link: isProjectBuilding ? buildingUrl : `/project/${ref}/storage/buckets`, + }, + { key: 'sql', label: 'SQL Editor', diff --git a/studio/components/layouts/StorageLayout/StorageMenu.tsx b/studio/components/layouts/StorageLayout/StorageMenu.tsx index d2bc0302cfd..496b41ec4a7 100644 --- a/studio/components/layouts/StorageLayout/StorageMenu.tsx +++ b/studio/components/layouts/StorageLayout/StorageMenu.tsx @@ -19,6 +19,7 @@ import { import ProductMenuItem from 'components/ui/ProductMenu/ProductMenuItem' import { STORAGE_ROW_STATUS } from 'components/to-be-cleaned/Storage/Storage.constants' import { useStorageStore } from 'localStores/storageExplorer/StorageExplorerStore' +import { IS_PLATFORM } from 'lib/constants' interface Props {} @@ -94,21 +95,27 @@ const StorageMenu: FC = () => {
- - - Settings - - + {IS_PLATFORM && ( + + + Settings + + + )} + Policies - - - Usage - - + + {IS_PLATFORM && ( + + + Usage + + + )}
diff --git a/studio/hooks/misc/withAuth.tsx b/studio/hooks/misc/withAuth.tsx index 3ced511a8f2..ce5c5263d5d 100644 --- a/studio/hooks/misc/withAuth.tsx +++ b/studio/hooks/misc/withAuth.tsx @@ -4,7 +4,7 @@ import { NextRouter, useRouter } from 'next/router' import { IS_PLATFORM } from 'lib/constants' import { useProfile, useStore, usePermissions } from 'hooks' -const PLATFORM_ONLY_PAGES = ['storage', 'reports', 'settings'] +const PLATFORM_ONLY_PAGES = ['reports', 'settings'] export function withAuth( WrappedComponent: ComponentType, diff --git a/studio/localStores/storageExplorer/StorageExplorerStore.js b/studio/localStores/storageExplorer/StorageExplorerStore.js index 93a00a3b945..92b4488620c 100644 --- a/studio/localStores/storageExplorer/StorageExplorerStore.js +++ b/studio/localStores/storageExplorer/StorageExplorerStore.js @@ -112,7 +112,7 @@ class StorageExplorerStore { /* Methods which are commonly used + For better readability */ initializeSupabaseClient = (serviceKey, serviceEndpoint) => { - this.supabaseClient = createClient(`https://${serviceEndpoint}`, serviceKey, { + this.supabaseClient = createClient(`${serviceEndpoint}`, serviceKey, { auth: { persistSession: false, autoRefreshToken: false, @@ -469,6 +469,7 @@ class StorageExplorerStore { fetchBuckets = async () => { const { data: buckets, error } = await this.supabaseClient.storage.listBuckets() + if (error) return this.ui.setNotification({ message: error.message, category: 'error' }) const formattedBuckets = buckets.map((bucket) => { diff --git a/studio/package.json b/studio/package.json index 8715e7c0f45..6d7caac6c58 100644 --- a/studio/package.json +++ b/studio/package.json @@ -3,7 +3,7 @@ "version": "0.0.9", "private": true, "scripts": { - "dev": "next dev -p 8082", + "dev": "next dev -p 3000", "build": "next build", "start": "next start", "test": "jest", diff --git a/studio/pages/api/projects/[ref]/index.ts b/studio/pages/api/projects/[ref]/index.ts index 5d0e34ff354..f009a9e94f1 100644 --- a/studio/pages/api/projects/[ref]/index.ts +++ b/studio/pages/api/projects/[ref]/index.ts @@ -34,7 +34,7 @@ const handleGet = async (req: NextApiRequest, res: NextApiResponse) => { db_ssl: false, }), kpsVersion: 'kps-v1.0.0', - restUrl: process.env.SUPABASE_REST_URL || 'http://localhost:8000/rest/v1/', + restUrl: `${process.env.SUPABASE_REST_URL}/rest/v1/` || 'http://localhost:8000/rest/v1/', } return res.status(200).json(response) diff --git a/studio/pages/api/props/project/[ref]/api.ts b/studio/pages/api/props/project/[ref]/api.ts index 556f4deb40b..f5752d27374 100644 --- a/studio/pages/api/props/project/[ref]/api.ts +++ b/studio/pages/api/props/project/[ref]/api.ts @@ -42,7 +42,7 @@ const handleGetAll = async (req: NextApiRequest, res: NextApiResponse) => { realtime_enabled: true, }, endpoint: process.env.SUPABASE_URL || 'http://localhost:8000', - restUrl: process.env.SUPABASE_REST_URL || 'http://localhost:8000/rest/v1/', + restUrl: `${process.env.SUPABASE_REST_URL}/rest/v1/` || 'http://localhost:8000/rest/v1/', defaultApiKey: process.env.SUPABASE_ANON_KEY, serviceApiKey: process.env.SUPABASE_SERVICE_KEY, service_api_keys: [ @@ -71,7 +71,7 @@ const handleGetAll = async (req: NextApiRequest, res: NextApiResponse) => { realtime_enabled: true, }, endpoint: process.env.SUPABASE_URL, - restUrl: process.env.SUPABASE_REST_URL, + restUrl: `${process.env.SUPABASE_REST_URL}/rest/v1/`, defaultApiKey: process.env.SUPABASE_ANON_KEY, serviceApiKey: process.env.SUPABASE_SERVICE_KEY, service_api_keys: [ From 44e882c0a7098012cb37ec8dae57ff9470c5da17 Mon Sep 17 00:00:00 2001 From: Aditya Dhawan Date: Sun, 11 Sep 2022 21:10:05 +0100 Subject: [PATCH 02/26] Update .env.example --- docker/.env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/.env.example b/docker/.env.example index b6aa9a0fd95..7700710f95b 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -71,4 +71,4 @@ ENABLE_PHONE_AUTOCONFIRM=true ############ STUDIO_PORT=3000 -PUBLIC_REST_URL=http://localhost:8000/rest/v1/ # replace if you intend to use Studio outside of localhost +SUPABASE_PUBLIC_URL=http://localhost:8000 # replace if you intend to use Studio outside of localhost From 3b560cf886a15c50113ba649e6c950fcc0afc77a Mon Sep 17 00:00:00 2001 From: Jon Meyers Date: Fri, 28 Oct 2022 22:43:49 +1100 Subject: [PATCH 03/26] wip: next13 blog --- ...ing-data-from-supabase-with-next-js-13.mdx | 159 ++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 apps/www/_blog/2022-10-30-fetching-data-from-supabase-with-next-js-13.mdx diff --git a/apps/www/_blog/2022-10-30-fetching-data-from-supabase-with-next-js-13.mdx b/apps/www/_blog/2022-10-30-fetching-data-from-supabase-with-next-js-13.mdx new file mode 100644 index 00000000000..592babcdd06 --- /dev/null +++ b/apps/www/_blog/2022-10-30-fetching-data-from-supabase-with-next-js-13.mdx @@ -0,0 +1,159 @@ +--- +title: 'Fetching and caching Supabase data with Next.js 13' +description: 'Next.js 13 introduces new data fetching and caching methods to enable React Server Components and Suspense.' +author: jonmeyers +image: TODO +thumb: TODO +tags: + - Next.js 13 + - Data Fetching + - Caching +date: '2022-10-30' +toc_depth: 3 +--- + +The PostgreSQL community has a specific, effective, and time-tested method for contributing to the core code. + +Proposals and changes to PostgreSQL (called patches) are submitted via email to the common Postgres hackers' mailing list pgsql-hackers@postgresql.org. Then you can track all activity simply by reading your mailbox. You can also follow this mailing list online at [postgresql.org/list/pgadmin-hackers](https://www.postgresql.org/list/pgadmin-hackers/). + +The mailing list contains a lot of different discussions. Some of the patches are preliminary POCs (proof-of-concept) not to be committed to the core directly, and some are urgent bug fixes. When a feature is intended for merging this should be done by creating a Commitfest entry, which resembles a pull request. + +## What is Commitfest? + +Commitfest is a month-long activity where Postgres contributors and core staff (committers) dedicate to looking at the patches, reviewing them, improving, and committing to the master branch. Effectively these are the months you most probably get feedback on your patches. + +There are 5 commitfests each year. You can see the schedule at [commitfest.postgresql.org](https://commitfest.postgresql.org/). You can only attach a patch to a commitfest **that hasn't started yet** (i.e. before 1st day of a commitfest month). + +## Why participate in Commitfest? + +There are numerous benefits: + +- You gain knowledge of PostgreSQL internals by reviewing other hackers' patches, and grow your profile within the community. +- The process of reviewing involves more senior PostgreSQL experts and as a rule, the code proposed becomes better. +- Lower risk for your code. If you're running a fork or extension that diverges from PG core then only you are responsible for its maintenance. Once it is merged, the entire community will help with maintenance and the API becomes the "official" way to do it. +- PostgreSQL commits are likely to survive longer than you do. Like a book manuscript, you get to leave a small history in the world. +- To re-enforce the community. Many features, like performance, have diverse options and many people discuss how to make them better. This is more effective when you join the party. + +## Which patches are welcome on a Commitfest? + +Generally, all patches that touch the core code are welcome. It's not necessary that they are complex. In fact, it's an excellent strategy to start by fixing or improving something small. + +The main topics to propose are: bugfixes, performance, server features, clients, security, SQL commands, testing improvements, replication and recovery, procedural languages, code refactoring (yes, it's very important!), monitoring and control, and documentation. + +Some ideas on what to submit: + +- Very small and uncontroversial fixes (especially bug fixes) have a good chance to be reviewed and committed even before Commitfest. If they aren't, just attach it to commitfest. +- If you make your feature an extension, the chance of committing it to the core is small. There are nice extensions like Pageinspect and Amcheck, that are committed into PG code as contribs but it is rare (contribs - are extensions that are inside the main PG package) +- There is no need to propose patches to refine translations, .po files etc. This is done by specialists. If you have user-visible messages in your patch just leave it English-only. +- Refining documents, readmes, and comments are welcome if they are clear and precise enough. You can propose a fix for only one line of comment but please don't do nitpicking. +- Performance-related features should better demonstrate performance increase at least in some use cases and no performance degradation in others. +- If your patch changes user behavior be ready to discuss and rework the interface. It's not easy to reach an agreement among many hackers with varying opinions. + +You can find a list of patches for the upcoming commitfest here: [commitfest.postgresql.org/40](https://commitfest.postgresql.org/40/) + +## Quick steps on how to make a patch + +Make a git branch that is based on the most recent master branch ([github.com/postgres/postgres](https://github.com/postgres/postgres)): + +```bash +git clone https://github.com/postgres/postgres +git checkout master +git checkout -b your_branch_name +``` + +Add your code to the branch (i.e. cherry-pick your commits from another branch). + +Rebase and squash your patch code to be 1 commit against master: + +``` +git rebase -i HEAD~5 + +# This will take the last 5 commits in a branch +# and you can do anything with them: +# squash, change order, change commit message, etc +``` + +Edit your commit message to be meaningful and clear. Be concise, but don't be too short. Reviewers enjoy understanding the purpose of a patch and what it does. This is especially important if the patchset consists of several patches. The first line of the commit message will become the patch name on a step later so make that line particularly clear and concise. + +Don't forget to add all the patch authors with their emails to the commit message. + +``` +git format-patch -1 -v13 + +# 1 = patch depth. +# The number of topmost patches to include +# v13 = the patch version. +# Make new version number anytime you want to +# send updated patch to the same thread) +``` + +Attach the file(s) to an email message (they look like `v13-0001-Naming-of-a-patch.patch`) + +Compose email message to pgsql-hackers@postgresql.org. Write all rationale and details of your proposal. The main thing to bear in mind is that attracting attention to your proposal is like 50% of success. Also, please be polite. + +It's encouraged to split complicated patches into several independently working parts as it's much easier to review (although not all complicated patches are easy to decompose). + +## Submitting to a Commitfest thread + +After you have created a patch, you can attach it to a Commitfest thread: + +- Make sure that you have sent email with your patch to the pgsql-hackers@postgresql.org mailing list +- Open commitfests page [commitfest.postgresql.org](https://commitfest.postgresql.org/) and choose the nearest commitfest with the status "Open" +- Click "New patch" button. +- Create a concise description, chose the relevant topic, and fill in the msgid in the hackers mailing list to attach a thread (which can be found in a full header of a mail message e.g.`Message-ID: `) +- Click "Create patch" button. + +That's all. Congrats! + +## After your patch is submitted to commitfest + +After you have a submitted your patch, several things will happen: + +- It will appear on CI page [cfbot.cputube.org](http://cfbot.cputube.org/) which will check its health against several architectures and OSes. If the patch has any errors then you should fix/rebase it. New versions of patches sent in messages replying to the mail thread are taken by CFBot automatically. Send updated versions of all patches in a patchset even if only one changed, otherwise cfbot will not take it properly. Clean status patches will attract reviewers' and committers' attention first. +- It will get "Needs review" status and anyone could add a review and change it to "Ready for committer" or "Waiting on author". +- You are supposed to **answer the questions** and communicate on your patch regularly during commitfest. That includes fixing the things the other hackers propose. It's likely better to follow their opinion and it almost always takes much less effort than arguing. Be polite, even if comments seem like nitpicking (probably they are not, actually). +- Give back. You are supposed to **review** the same number of patches that you proposed to commitfest and with similar complexity. You can choose what to review based on your interest, your knowledge of a particular part of Postgres (e.g. indexes or planner things), or even your personal acquaintance with the author. As a rule of thumb, you are supposed to specify if you are from the same company as the author (it is not prohibited but this should be clear, the best way is just to add your company to the email signature). + +The commit process is a two-step: + +1. Preliminary review. It could be done by anyone (including you). A detailed guide on how to do this [wiki.postgresql.org/wiki/Reviewing_a_Patch](https://wiki.postgresql.org/wiki/Reviewing_a_Patch) If there are no doubts reviewer sets the status to "Ready for committer". If the patch needs some changes a reviewer can set "Waiting on author". Reviewers can leave a favorable review but that's only a signal. After other reviewers join the conversation, if there are no objections then one of them will make it "Ready for committer". It's pretty normal if there is more than one reviewer. In fact the more reviewers, the better. +2. Final review by a committer. Committers are the most senior members of a community and it's a formal status. The list of committers: [wiki.postgresql.org/wiki/Committers](https://wiki.postgresql.org/wiki/Committers) As a result a committer can commit it or push back to "Waiting on author", or "Need review". + +The time and attention of committers are limited, which is why there is a two-step process. Some patches that pass the initial review won't receive reviewers' attention. In that case, they will be transferred to the next commitfest automatically. + +## When does the commitfest finish? + +CF finishes around the 1st day of the next month (i.e. November CF starts on November 1 and finishes on December 1). For any patches that do not get merged: + +- Patches with the status "Ready for committer" or "Need review" will be transferred to the next CF. +- Patches with the status "Waiting on author" are transferred automatically if the questions and proposals in the thread are not unaddressed for more than 2 weeks. Otherwise, it will be dropped from CF with "Returned with feedback". + +All these "automatic" things are actually done by commitfest managers (volunteers), so if you have any requests, doubts, or questions you can write them personally. + +Please note that hackers almost never reject patches! They just return it and the author if he is still willing and has time to work on the patch further can attach it (the same thread) to the next commitfest anytime. + +## What is feature-freeze? + +Feature-freeze is an annual event that targets patches released into a major PostgreSQL version. Feature-freeze for the "current-year" release is at the end of a March commitfest. All patches committed on and before will be released in the PG major release (in October). Everything committed after March (July, September, etc) will go into next year's major release. + +For that reason, the March Commitfest takes **10-15 days longer** than the usual 1 month, finishing in mid-April. It's important for the author to continue watching your patches and feedback until that time. Commits in early April are big! + +## Important tips + +- If you want to discuss something with hackers without a thread overhead (which makes it more difficult to read by other hackers) you can wipe out pgsql-hackers@postgresql.org from the reply list and leave only the participants you want. If you do this, you should add `[offlist]` to the message subject to prevent confusion. +- The hackers mail list receives _a lot_ of messages. It's advisable to set up mail filtering so that hackers' messages are sorted into separate places and shown as threads. But ensure that all messages that contain **your address** will NOT go into this place so that you can read all answers to your proposal among your prioritized emails. +- Patches that need minor rework like rebase or comments refine are pushed back to "Waiting on author". Therefore, almost-ready patch very often stall, waiting for a new review, during which it will need another rebase and it goes into a vicious circle. If you are certain that the change is minor and doesn't affect functionality, you can return the patch to the same state it had before (if it was "Ready-for-committer" it should be ready for committer even after rebase). It's good manners to mention this change in a hacker's thread to remove suspicions that you want to hijack the patch status. +- It’s very good to be proactive with your patch not only change something on request. Sometimes it’s hard to describe in detail what needs improving and hackers may give only approximate direction of work. If you see criticism it’s very good to answer this with possible proposals on how can you change the code, or what measurements you can do to drop possible doubts about functionality or performance. +- It’s also very nice to keep watching activity in the patch thread after it is committed. Sometimes small problems arise later and you can propose a separate fix and get a reputation as a responsible contributor. Also, you know your code better and it would be very valuable for everyone though other hackers could help you with this. + +If you're interested in contributing to PostgreSQL full time, [Supabase is hiring Postgres Core Contributors](https://boards.greenhouse.io/supabase/jobs/4307456004). + +## More Postgres resources + +- [Postgres Full Text Search vs the rest](https://supabase.com/blog/postgres-full-text-search-vs-the-rest) +- [Postgres WASM by Snaplet and Supabase](https://supabase.com/blog/postgres-wasm) +- [Choosing a Postgres Primary Key](https://supabase.com/blog/choosing-a-postgres-primary-key) +- [Implementing "seen by" functionality with Postgres](https://supabase.com/blog/seen-by-in-postgresql) +- [Partial data dumps using Postgres Row Level Security](https://supabase.com/blog/partial-postgresql-data-dumps-with-rls) +- [Postgres Views](https://supabase.com/blog/postgresql-views) +- [Realtime Postgres RLS on Supabase](https://supabase.com/blog/realtime-row-level-security-in-postgresql) From 0fd5b733f36d522b91a5ac205c821da2ee8f170a Mon Sep 17 00:00:00 2001 From: Jon Meyers Date: Wed, 16 Nov 2022 11:48:53 +1100 Subject: [PATCH 04/26] add server components article --- ...ing-data-from-supabase-with-next-js-13.mdx | 159 ------ ...base-data-in-next-js-server-components.mdx | 466 ++++++++++++++++++ 2 files changed, 466 insertions(+), 159 deletions(-) delete mode 100644 apps/www/_blog/2022-10-30-fetching-data-from-supabase-with-next-js-13.mdx create mode 100644 apps/www/_blog/2022-11-18-fetching-and-caching-supabase-data-in-next-js-server-components.mdx diff --git a/apps/www/_blog/2022-10-30-fetching-data-from-supabase-with-next-js-13.mdx b/apps/www/_blog/2022-10-30-fetching-data-from-supabase-with-next-js-13.mdx deleted file mode 100644 index 592babcdd06..00000000000 --- a/apps/www/_blog/2022-10-30-fetching-data-from-supabase-with-next-js-13.mdx +++ /dev/null @@ -1,159 +0,0 @@ ---- -title: 'Fetching and caching Supabase data with Next.js 13' -description: 'Next.js 13 introduces new data fetching and caching methods to enable React Server Components and Suspense.' -author: jonmeyers -image: TODO -thumb: TODO -tags: - - Next.js 13 - - Data Fetching - - Caching -date: '2022-10-30' -toc_depth: 3 ---- - -The PostgreSQL community has a specific, effective, and time-tested method for contributing to the core code. - -Proposals and changes to PostgreSQL (called patches) are submitted via email to the common Postgres hackers' mailing list pgsql-hackers@postgresql.org. Then you can track all activity simply by reading your mailbox. You can also follow this mailing list online at [postgresql.org/list/pgadmin-hackers](https://www.postgresql.org/list/pgadmin-hackers/). - -The mailing list contains a lot of different discussions. Some of the patches are preliminary POCs (proof-of-concept) not to be committed to the core directly, and some are urgent bug fixes. When a feature is intended for merging this should be done by creating a Commitfest entry, which resembles a pull request. - -## What is Commitfest? - -Commitfest is a month-long activity where Postgres contributors and core staff (committers) dedicate to looking at the patches, reviewing them, improving, and committing to the master branch. Effectively these are the months you most probably get feedback on your patches. - -There are 5 commitfests each year. You can see the schedule at [commitfest.postgresql.org](https://commitfest.postgresql.org/). You can only attach a patch to a commitfest **that hasn't started yet** (i.e. before 1st day of a commitfest month). - -## Why participate in Commitfest? - -There are numerous benefits: - -- You gain knowledge of PostgreSQL internals by reviewing other hackers' patches, and grow your profile within the community. -- The process of reviewing involves more senior PostgreSQL experts and as a rule, the code proposed becomes better. -- Lower risk for your code. If you're running a fork or extension that diverges from PG core then only you are responsible for its maintenance. Once it is merged, the entire community will help with maintenance and the API becomes the "official" way to do it. -- PostgreSQL commits are likely to survive longer than you do. Like a book manuscript, you get to leave a small history in the world. -- To re-enforce the community. Many features, like performance, have diverse options and many people discuss how to make them better. This is more effective when you join the party. - -## Which patches are welcome on a Commitfest? - -Generally, all patches that touch the core code are welcome. It's not necessary that they are complex. In fact, it's an excellent strategy to start by fixing or improving something small. - -The main topics to propose are: bugfixes, performance, server features, clients, security, SQL commands, testing improvements, replication and recovery, procedural languages, code refactoring (yes, it's very important!), monitoring and control, and documentation. - -Some ideas on what to submit: - -- Very small and uncontroversial fixes (especially bug fixes) have a good chance to be reviewed and committed even before Commitfest. If they aren't, just attach it to commitfest. -- If you make your feature an extension, the chance of committing it to the core is small. There are nice extensions like Pageinspect and Amcheck, that are committed into PG code as contribs but it is rare (contribs - are extensions that are inside the main PG package) -- There is no need to propose patches to refine translations, .po files etc. This is done by specialists. If you have user-visible messages in your patch just leave it English-only. -- Refining documents, readmes, and comments are welcome if they are clear and precise enough. You can propose a fix for only one line of comment but please don't do nitpicking. -- Performance-related features should better demonstrate performance increase at least in some use cases and no performance degradation in others. -- If your patch changes user behavior be ready to discuss and rework the interface. It's not easy to reach an agreement among many hackers with varying opinions. - -You can find a list of patches for the upcoming commitfest here: [commitfest.postgresql.org/40](https://commitfest.postgresql.org/40/) - -## Quick steps on how to make a patch - -Make a git branch that is based on the most recent master branch ([github.com/postgres/postgres](https://github.com/postgres/postgres)): - -```bash -git clone https://github.com/postgres/postgres -git checkout master -git checkout -b your_branch_name -``` - -Add your code to the branch (i.e. cherry-pick your commits from another branch). - -Rebase and squash your patch code to be 1 commit against master: - -``` -git rebase -i HEAD~5 - -# This will take the last 5 commits in a branch -# and you can do anything with them: -# squash, change order, change commit message, etc -``` - -Edit your commit message to be meaningful and clear. Be concise, but don't be too short. Reviewers enjoy understanding the purpose of a patch and what it does. This is especially important if the patchset consists of several patches. The first line of the commit message will become the patch name on a step later so make that line particularly clear and concise. - -Don't forget to add all the patch authors with their emails to the commit message. - -``` -git format-patch -1 -v13 - -# 1 = patch depth. -# The number of topmost patches to include -# v13 = the patch version. -# Make new version number anytime you want to -# send updated patch to the same thread) -``` - -Attach the file(s) to an email message (they look like `v13-0001-Naming-of-a-patch.patch`) - -Compose email message to pgsql-hackers@postgresql.org. Write all rationale and details of your proposal. The main thing to bear in mind is that attracting attention to your proposal is like 50% of success. Also, please be polite. - -It's encouraged to split complicated patches into several independently working parts as it's much easier to review (although not all complicated patches are easy to decompose). - -## Submitting to a Commitfest thread - -After you have created a patch, you can attach it to a Commitfest thread: - -- Make sure that you have sent email with your patch to the pgsql-hackers@postgresql.org mailing list -- Open commitfests page [commitfest.postgresql.org](https://commitfest.postgresql.org/) and choose the nearest commitfest with the status "Open" -- Click "New patch" button. -- Create a concise description, chose the relevant topic, and fill in the msgid in the hackers mailing list to attach a thread (which can be found in a full header of a mail message e.g.`Message-ID: `) -- Click "Create patch" button. - -That's all. Congrats! - -## After your patch is submitted to commitfest - -After you have a submitted your patch, several things will happen: - -- It will appear on CI page [cfbot.cputube.org](http://cfbot.cputube.org/) which will check its health against several architectures and OSes. If the patch has any errors then you should fix/rebase it. New versions of patches sent in messages replying to the mail thread are taken by CFBot automatically. Send updated versions of all patches in a patchset even if only one changed, otherwise cfbot will not take it properly. Clean status patches will attract reviewers' and committers' attention first. -- It will get "Needs review" status and anyone could add a review and change it to "Ready for committer" or "Waiting on author". -- You are supposed to **answer the questions** and communicate on your patch regularly during commitfest. That includes fixing the things the other hackers propose. It's likely better to follow their opinion and it almost always takes much less effort than arguing. Be polite, even if comments seem like nitpicking (probably they are not, actually). -- Give back. You are supposed to **review** the same number of patches that you proposed to commitfest and with similar complexity. You can choose what to review based on your interest, your knowledge of a particular part of Postgres (e.g. indexes or planner things), or even your personal acquaintance with the author. As a rule of thumb, you are supposed to specify if you are from the same company as the author (it is not prohibited but this should be clear, the best way is just to add your company to the email signature). - -The commit process is a two-step: - -1. Preliminary review. It could be done by anyone (including you). A detailed guide on how to do this [wiki.postgresql.org/wiki/Reviewing_a_Patch](https://wiki.postgresql.org/wiki/Reviewing_a_Patch) If there are no doubts reviewer sets the status to "Ready for committer". If the patch needs some changes a reviewer can set "Waiting on author". Reviewers can leave a favorable review but that's only a signal. After other reviewers join the conversation, if there are no objections then one of them will make it "Ready for committer". It's pretty normal if there is more than one reviewer. In fact the more reviewers, the better. -2. Final review by a committer. Committers are the most senior members of a community and it's a formal status. The list of committers: [wiki.postgresql.org/wiki/Committers](https://wiki.postgresql.org/wiki/Committers) As a result a committer can commit it or push back to "Waiting on author", or "Need review". - -The time and attention of committers are limited, which is why there is a two-step process. Some patches that pass the initial review won't receive reviewers' attention. In that case, they will be transferred to the next commitfest automatically. - -## When does the commitfest finish? - -CF finishes around the 1st day of the next month (i.e. November CF starts on November 1 and finishes on December 1). For any patches that do not get merged: - -- Patches with the status "Ready for committer" or "Need review" will be transferred to the next CF. -- Patches with the status "Waiting on author" are transferred automatically if the questions and proposals in the thread are not unaddressed for more than 2 weeks. Otherwise, it will be dropped from CF with "Returned with feedback". - -All these "automatic" things are actually done by commitfest managers (volunteers), so if you have any requests, doubts, or questions you can write them personally. - -Please note that hackers almost never reject patches! They just return it and the author if he is still willing and has time to work on the patch further can attach it (the same thread) to the next commitfest anytime. - -## What is feature-freeze? - -Feature-freeze is an annual event that targets patches released into a major PostgreSQL version. Feature-freeze for the "current-year" release is at the end of a March commitfest. All patches committed on and before will be released in the PG major release (in October). Everything committed after March (July, September, etc) will go into next year's major release. - -For that reason, the March Commitfest takes **10-15 days longer** than the usual 1 month, finishing in mid-April. It's important for the author to continue watching your patches and feedback until that time. Commits in early April are big! - -## Important tips - -- If you want to discuss something with hackers without a thread overhead (which makes it more difficult to read by other hackers) you can wipe out pgsql-hackers@postgresql.org from the reply list and leave only the participants you want. If you do this, you should add `[offlist]` to the message subject to prevent confusion. -- The hackers mail list receives _a lot_ of messages. It's advisable to set up mail filtering so that hackers' messages are sorted into separate places and shown as threads. But ensure that all messages that contain **your address** will NOT go into this place so that you can read all answers to your proposal among your prioritized emails. -- Patches that need minor rework like rebase or comments refine are pushed back to "Waiting on author". Therefore, almost-ready patch very often stall, waiting for a new review, during which it will need another rebase and it goes into a vicious circle. If you are certain that the change is minor and doesn't affect functionality, you can return the patch to the same state it had before (if it was "Ready-for-committer" it should be ready for committer even after rebase). It's good manners to mention this change in a hacker's thread to remove suspicions that you want to hijack the patch status. -- It’s very good to be proactive with your patch not only change something on request. Sometimes it’s hard to describe in detail what needs improving and hackers may give only approximate direction of work. If you see criticism it’s very good to answer this with possible proposals on how can you change the code, or what measurements you can do to drop possible doubts about functionality or performance. -- It’s also very nice to keep watching activity in the patch thread after it is committed. Sometimes small problems arise later and you can propose a separate fix and get a reputation as a responsible contributor. Also, you know your code better and it would be very valuable for everyone though other hackers could help you with this. - -If you're interested in contributing to PostgreSQL full time, [Supabase is hiring Postgres Core Contributors](https://boards.greenhouse.io/supabase/jobs/4307456004). - -## More Postgres resources - -- [Postgres Full Text Search vs the rest](https://supabase.com/blog/postgres-full-text-search-vs-the-rest) -- [Postgres WASM by Snaplet and Supabase](https://supabase.com/blog/postgres-wasm) -- [Choosing a Postgres Primary Key](https://supabase.com/blog/choosing-a-postgres-primary-key) -- [Implementing "seen by" functionality with Postgres](https://supabase.com/blog/seen-by-in-postgresql) -- [Partial data dumps using Postgres Row Level Security](https://supabase.com/blog/partial-postgresql-data-dumps-with-rls) -- [Postgres Views](https://supabase.com/blog/postgresql-views) -- [Realtime Postgres RLS on Supabase](https://supabase.com/blog/realtime-row-level-security-in-postgresql) diff --git a/apps/www/_blog/2022-11-18-fetching-and-caching-supabase-data-in-next-js-server-components.mdx b/apps/www/_blog/2022-11-18-fetching-and-caching-supabase-data-in-next-js-server-components.mdx new file mode 100644 index 00000000000..c660a5b183f --- /dev/null +++ b/apps/www/_blog/2022-11-18-fetching-and-caching-supabase-data-in-next-js-server-components.mdx @@ -0,0 +1,466 @@ +--- +title: 'Fetching and caching Supabase data in Next.js 13 Server Components' +description: 'Next.js 13 introduces new data fetching and caching methods to enable React Server Components and Suspense.' +author: jonmeyers +image: TODO +thumb: TODO +tags: + - Next.js 13 + - React Server Components + - app directory + - data fetching + - caching + - async components + - Suspense +date: '2022-11-18' +toc_depth: 3 +--- + +The biggest announcement from Next.js Conf 2022 was the release of Next.js 13)[https://nextjs.org/blog/next-13](https://nextjs.org/blog/next-13), which introduces a collection of improvements, most exciting of which is Server Components. The combination of Server Components and Suspense allow for a more streamlined, reimagined way to fetch and cache data. This provides excellent DX improvements — such as async components — and aligns the Next framework even closer with the future of React. + +This article is going to look at how we can use these brand new async components to simplify fetching and caching data from Supabase. We will look at authentication and using the Next.js Auth Helpers to make authenticated Supabase queries from Server Components in a separate article. Coming soon! + +> To learn more about any of the concepts covered in this article, check out [Next.js' beta docs](https://beta.nextjs.org/docs). + +A good distinction to understand is that Next.js 13 is stable and ready for production, however, the `app` directory is still in beta and likely to change. This article will be focusing on the app directory, Server Components and Suspense so let's get experimental! + +> For an example of the code covered in this tutorial, check out [this repo](https://github.com/dijonmusters/fetching-and-caching-supabase-data-in-next-js-13-server-components). + +If you prefer video, check out our recent live stream where we stepped through a similar example. + +
+ +
+ +Let’s get started by creating a brand new Next.js 13 app using the `create-next-app` package: + +```bash +npx create-next-app@latest --experimental-app next13 +``` + +Now we can run our app in development mode: + +```bash +npm run dev +``` + +And navigate to [http://localhost:3000](http://localhost:3000). + +This should look pretty familiar, and scanning the folder structure for the app, it should look almost identical to Next.js 12, but with a new folder called `app`. This is where the new data fetching and caching magic takes place. 🪄 + +Each folder within the `app` directory represents a route in our application. Each folder must have a `page` component, which is rendered when the user navigates to the route, and an optional `layout`, `loading` and `error` component. + +> Learn more about page, layout, loading and error components in the Next.js docs [TODO link to each of those docs]. + +Before we jump into fetching data, we need some data to fetch. Let’s [create a new Supabase project](https://app.supabase.com). + +Once your instance is up and running, head over to the [SQL Editor](https://app.supabase.com/project/_/sql), paste in the following snippet and click `RUN`. + +```sql +create table if not exists posts ( + id uuid default uuid_generate_v4() primary key, + created_at timestamp with time zone default timezone('utc'::text, now()) not null, + title text, + content text, + is_published boolean default false +); + +insert into posts(title, content, is_published) +values + ('My first post', 'Wow! What a great post.', true), + ('My second post', 'This one needs a little work!', false); +``` + +This will create a table called `posts`, and populate it with some example data. + +Let’s install the [`supabase-js` library](https://www.npmjs.com/package/@supabase/supabase-js) to fetch our `posts`. + +```bash +npm install @supabase/supabase-js +``` + +And add a `.env.local` file with the following environment variables. + +``` title=".env.local" +NEXT_PUBLIC_SUPABASE_URL=your-supabase-url +NEXT_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-key +``` + +> The values for these can be found in [your project’s API settings](https://app.supabase.com/project/_/settings/api). + +Lastly, we need to create a Supabase client. Create a file at `utils/supabase.ts` with the following content: + +```tsx title="utils/supabase.ts" +import { createClient } from "@supabase/supabase-js"; + +export default createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! +); +``` + +Okay, let’s look at some different data fetching and caching strategies. + +# Static + +By default any page component in the `app` folder is a Server Component, and its data is fetched and cached by Next.js every time we build a new version of our application. This is the equivalent of exporting a `getStaticProps` function from a component in the `pages` directory. + +Let’s create a new file at `app/static/page.tsx` and populate with the following: + +```jsx title="app/static/page.tsx" +import supabase from "../../utils/supabase"; + +export default async function Posts() { + const { data: posts } = await supabase.from("posts").select(); + return
{JSON.stringify(posts, null, 2)}
; +} +``` + +Server Components support async/await by default, and suspend the rendering of the component until the data has been fetched. This means we don’t need to handle error or loading states in our component, keeping our rendering logic clean. + +> To learn more about displaying `loading` and `error` states, check out [the documentation](https://beta.nextjs.org/docs/api-reference/file-conventions/loading). + +Let’s modify this component to render out a collection of `` components, that navigate to a dedicated page for each post. + +```tsx title="app/static/page.tsx" +import Link from "next/link"; +import supabase from "../../utils/supabase"; + +export default async function Posts() { + const { data: posts } = await supabase.from("posts").select("id, title"); + + if (!posts) { + return

No posts found.

; + } + + return posts.map((post) => ( +

+ {post.title} +

+ )); +} +``` + +> Since we are only using `id` and `title` in our component, we can scope our query down to only return these two columns for each post. + +Let’s create a dynamic route to handle displaying an individual post. Create a new file at `app/static/[id]/page.tsx` and populate with the following: + +```tsx title="app/static/[id]/page.tsx" +import supabase from "../../../utils/supabase"; +import { notFound } from "next/navigation"; + +export default async function Post({ + params: { id }, +}: { + params: { id: string }; +}) { + const { data } = await supabase.from("posts").select().match({ id }).single(); + + if (!data) { + notFound(); + } + + return
{JSON.stringify(data, null, 2)}
; +} +``` + +Currently, this page is generated on-demand and then cached. This means the first person who visits the page will need to wait for the server to get the post data from Supabase. This won’t take long at all, because Supabase is Supa awesome! But, we can still make this *slightly* more efficient by telling Next.js a finite collection of paths that we want to generate at build time. + +We do this by exporting out a `generateStaticParams` function from our dynamic StaticPost page. + +```tsx title="app/static/[id]/page.tsx" +export async function generateStaticParams() { + const { data: posts } = await supabase.from("posts").select("id"); + + return posts?.map(({ id }) => ({ + id, + })); +} +``` + +> This is similar to `getStaticPaths` in a pages component. Learn more [here](https://beta.nextjs.org/docs/data-fetching/generating-static-params). + +The full component should look something like this: + +```tsx title="app/static/[id]/page.tsx" +import supabase from "../../../utils/supabase"; +import { notFound } from "next/navigation"; + +export async function generateStaticParams() { + const { data: posts } = await supabase.from("posts").select("id"); + + return posts?.map(({ id }) => ({ + id, + })); +} + +export default async function Post({ + params: { id }, +}: { + params: { id: string }; +}) { + const { data: post } = await supabase.from("posts").select().match({ id }).single(); + + if (!post) { + notFound(); + } + + return
{JSON.stringify(post, null, 2)}
; +} +``` + +Awesome! We now have a Supa snappy blog! The user never needs to wait for data to be fetched. All pages are statically generated at build time, and cached at CDN nodes close to our users! 🎉 + +Unfortunately, this means any changes we make in Supabase — adding, updating or deleting posts etc — will not be reflected in our blog. If we want to refresh this data on a regular basis, we need to tell Next.js when to *revalidate*. + +# Static with Revalidation + +By exporting a `revalidate` variable from our component, we can specify how many seconds we consider this data to be “fresh”. + +```tsx +export const revalidate = 60; +``` + +So, for 60 seconds Next.js will continue to respond with the static version of our page. After 60 seconds, it will fetch fresh data from Supabase and generate a new static page. However, there is no downtime while this happens, as the previous static page will continue to be served until the “fresh” one has been successfully generated. + +The `Posts` component should now look like this: + +```tsx title="app/static-with-revalidate/page.tsx" +import Link from "next/link"; +import supabase from "../../utils/supabase"; + +export const revalidate = 60; + +export default async function Posts() { + const { data: posts } = await supabase.from("posts").select("id, title"); + + if (!posts) { + return

No posts found.

; + } + + return posts.map((post) => ( +

+ {post.title} +

+ )); +} +``` + +And the `Post` component should look like this: + +```tsx title="app/static-with-revalidate/[id]/page.tsx" +import supabase from "../../../utils/supabase"; +import { notFound } from "next/navigation"; + +export const revalidate = 60; + +export async function generateStaticParams() { + const { data: posts } = await supabase.from("posts").select("id"); + + return posts?.map(({ id }) => ({ + id, + })); +} + +export default async function Post({ + params: { id }, +}: { + params: { id: string }; +}) { + const { data: post } = await supabase.from("posts").select().match({ id }).single(); + + if (!post) { + notFound(); + } + + return
{JSON.stringify(post, null, 2)}
; +} +``` + +# Dynamic + +If we want fresh data to be fetched on every single request, we can simply set our `revalidate` value to `0`. + +**Posts Component** + +```tsx title="app/server-rendered/page.tsx" +import Link from "next/link"; +import supabase from "../../utils/supabase"; + +export const revalidate = 60; + +export default async function Posts() { + const { data: posts } = await supabase.from("posts").select("id, title"); + + if (!posts) { + return

No posts found.

; + } + + return posts.map((post) => ( +

+ {post.title} +

+ )); +} +``` + +**Single Post Component** + +```tsx title="app/server-rendered/[id]/page.tsx" +import supabase from "../../../utils/supabase"; +import { notFound } from "next/navigation"; + +export const revalidate = 60; + +export async function generateStaticParams() { + const { data: posts } = await supabase.from("posts").select("id"); + + return posts?.map(({ id }) => ({ + id, + })); +} + +export default async function Post({ + params: { id }, +}: { + params: { id: string }; +}) { + const { data: post } = await supabase.from("posts").select().match({ id }).single(); + + if (!post) { + notFound(); + } + + return
{JSON.stringify(post, null, 2)}
; +} +``` + +This is similar to exporting a `getServerSideProps` function from a component in the `pages` directory. + +# Client-side + +There are many use-cases where you need to use Supabase client-side: + +1. Authentication + + Supabase Auth does a bunch of stuff behind the scenes — handling 3rd party OAuth flows, for example. This will break if you try to sign users in and out on the server. + +2. Realtime + + Supabase manage the awesome power of websockets on your behalf — something that is not yet solved with this new serverless world. + +3. You prefer it … there is nothing wrong with this! + + You do you! + + +To use Supabase client-side, we need to tell Next.js that this is a client component. We do this by specifying the `use client` directive at the top of our component. This opts into a similar flow to the `pages` directory — the component is rendered on the server and hydrated client-side. + +> The React team is [working on an awesome new hook called `use`](https://github.com/acdlite/rfcs/blob/first-class-promises/text/0000-first-class-support-for-promises.md#example-use-in-client-components-and-hooks), which will drastically simplify fetching data client-side, but for now, we still need to rely on the combination of `useState` and `useEffect`. + +Let’s implement client-side data fetching. + +```tsx title="app/client-side/page.tsx" +"use client"; + +import { useEffect, useState } from "react"; +import supabase from "../../utils/supabase"; + +export default function ClientPosts() { + const [isLoading, setIsLoading] = useState(true); + const [posts, setPosts] = useState([]); + + useEffect(() => { + const fetchPosts = async () => { + const { data } = await supabase.from("posts").select(); + setPosts(data); + setIsLoading(false); + }; + + fetchPosts(); + }, []); + + return isLoading ? ( +

Loading

+ ) : ( +
{JSON.stringify(posts, null, 2)}
+ ); +} +``` + +But now we have loading spinners! Yuck! + +# Realtime + +Realtime allows us to subscribe to changes in Supabase — inserted, updated or deleted posts — and update our UI dynamically. In order to receive realtime events, we need to [enable replication](https://app.supabase.com/project/lzdftaiyqrgmgqunxlwf/database/replication) on the posts table. + +Let’s merge the two previous concepts and fetch the initial state of our posts in a Server Component, and then render a Client Component to do client-y things — like subscribe to changes in the DB and update the UI dynamically: + +**Server Component** + +```tsx title="app/realtime/page.tsx" +import supabase from "../../utils/supabase"; +import RealtimePosts from "./realtime-posts"; + +export const revalidate = 0; + +export default async function Realtime() { + const { data } = await supabase.from("posts").select("*"); + return ; +} +``` + +**Client Component** + +```tsx title="app/realtime/realtime-posts.tsx" +"use client"; + +import { useEffect, useState } from "react"; +import supabase from "../../utils/supabase"; + +export default function RealtimePosts({ serverPosts }: { serverPosts: any }) { + const [posts, setPosts] = useState(serverPosts); + + useEffect(() => { + setPosts(serverPosts); + }, [serverPosts]); + + useEffect(() => { + const channel = supabase + .channel("*") + .on( + "postgres_changes", + { event: "INSERT", schema: "public", table: "posts" }, + (payload) => setPosts((posts: any) => [...posts, payload.new]) + ) + .subscribe(); + + return () => { + supabase.removeChannel(channel); + }; + }, [serverPosts]); + + return
{JSON.stringify(posts, null, 2)}
; +} +``` + +> `useEffect` is used to subscribe to changes to the `serverPosts` prop. Without this, our component would not display fresh server-side results when the parent component is re-rendered, only on the first render. + + +This is a great pattern for fetching initial data server-side and subscribing to realtime changes client-side. This will likely be replaced by the `use` hook once it is stable with Next.js — as it also uses suspense to *suspend* the rendering of a component while fetching data. + +# Conclusion + +Next.js 13 Server Components and Suspense are awesome! Async components allow us to think about data fetching and caching as separate concerns — rather than specifying completely different data fetching functions like `getStaticProps` and `getServerSideProps`. If our caching requirements for a component change, we simply update the caching value, rather than refactoring our data fetching logic. + +Allowing any component in the tree to be either a server or client component, that is responsible for its own data, and suspends rendering until it is ready, drastically simplifies our code, and provides much more flexible patterns for maintainable applications. + +# More Next.js 13 Resources +- [Next.js 13 Beta Docs](https://beta.nextjs.org/docs) +- [Creating an authenticated Supabase client in Next.js 13 Server Component](https://github.com/supabase/auth-helpers/tree/main/examples/nextjs-server-components) +- [Data fetching and caching with Next.js 13 - Supabase Happy Hour #26](https://www.youtube.com/watch?v=QH0P5xZt5wY) From f93996dcc3afc49ceb7f8ad6aab09a2ec3f9aa4f Mon Sep 17 00:00:00 2001 From: Jon Meyers Date: Wed, 16 Nov 2022 12:32:58 +1100 Subject: [PATCH 05/26] fix typo in development.md --- DEVELOPERS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEVELOPERS.md b/DEVELOPERS.md index e427b76ed6f..c9e45c9d5e9 100644 --- a/DEVELOPERS.md +++ b/DEVELOPERS.md @@ -79,7 +79,7 @@ Then visit, and edit, any of the following sites: #### Running sites individually -You can run any of the sites indiviudally by using the scope name. For example: +You can run any of the sites individually by using the scope name. For example: ```sh npm run dev:www From 95163650deb99497cab012607cdc778aa309ec20 Mon Sep 17 00:00:00 2001 From: Jon Meyers Date: Wed, 16 Nov 2022 12:33:32 +1100 Subject: [PATCH 06/26] add data fetching and caching blog --- ...base-data-in-next-js-server-components.mdx | 257 +++++++++--------- 1 file changed, 122 insertions(+), 135 deletions(-) diff --git a/apps/www/_blog/2022-11-18-fetching-and-caching-supabase-data-in-next-js-server-components.mdx b/apps/www/_blog/2022-11-18-fetching-and-caching-supabase-data-in-next-js-server-components.mdx index c660a5b183f..6024141781f 100644 --- a/apps/www/_blog/2022-11-18-fetching-and-caching-supabase-data-in-next-js-server-components.mdx +++ b/apps/www/_blog/2022-11-18-fetching-and-caching-supabase-data-in-next-js-server-components.mdx @@ -1,7 +1,7 @@ --- title: 'Fetching and caching Supabase data in Next.js 13 Server Components' description: 'Next.js 13 introduces new data fetching and caching methods to enable React Server Components and Suspense.' -author: jonmeyers +author: jonmeyers_io image: TODO thumb: TODO tags: @@ -16,13 +16,13 @@ date: '2022-11-18' toc_depth: 3 --- -The biggest announcement from Next.js Conf 2022 was the release of Next.js 13)[https://nextjs.org/blog/next-13](https://nextjs.org/blog/next-13), which introduces a collection of improvements, most exciting of which is Server Components. The combination of Server Components and Suspense allow for a more streamlined, reimagined way to fetch and cache data. This provides excellent DX improvements — such as async components — and aligns the Next framework even closer with the future of React. +The biggest announcement from Next.js Conf 2022 was the [release of Next.js 13](https://nextjs.org/blog/next-13), which introduces a collection of improvements, most exciting of which is Server Components. The combination of Server Components and Suspense allow for a more streamlined, reimagined way to fetch and cache data in Next.js applications. This provides excellent DX improvements — such as async components — and aligns the Next framework even closer with the future of React. -This article is going to look at how we can use these brand new async components to simplify fetching and caching data from Supabase. We will look at authentication and using the Next.js Auth Helpers to make authenticated Supabase queries from Server Components in a separate article. Coming soon! +This article is going to look at how we can use these brand new async components to simplify fetching and caching data from Supabase. We will look all things auth in a separate article. Check out the [Server Components example in the Auth Helpers repo](https://github.com/supabase/auth-helpers/tree/main/examples/nextjs-server-components) if you just can't wait! -> To learn more about any of the concepts covered in this article, check out [Next.js' beta docs](https://beta.nextjs.org/docs). +> To learn more about any of the concepts covered in this article, check out the [Next.js beta docs](https://beta.nextjs.org/docs). -A good distinction to understand is that Next.js 13 is stable and ready for production, however, the `app` directory is still in beta and likely to change. This article will be focusing on the app directory, Server Components and Suspense so let's get experimental! +A good distinction to understand at this point, is that Next.js 13 is stable and ready for production, however, the `app` directory is still in beta and likely to change. This article will be focusing on the app directory, Server Components and Suspense so let's get experimental! > For an example of the code covered in this tutorial, check out [this repo](https://github.com/dijonmusters/fetching-and-caching-supabase-data-in-next-js-13-server-components). @@ -53,9 +53,9 @@ And navigate to [http://localhost:3000](http://localhost:3000). This should look pretty familiar, and scanning the folder structure for the app, it should look almost identical to Next.js 12, but with a new folder called `app`. This is where the new data fetching and caching magic takes place. 🪄 -Each folder within the `app` directory represents a route in our application. Each folder must have a `page` component, which is rendered when the user navigates to the route, and an optional `layout`, `loading` and `error` component. +Each folder within the `app` directory represents a route in our application. Each folder must have a `page` component, which is rendered when the user navigates to the route, and optional `layout`, `loading` and `error` components. -> Learn more about page, layout, loading and error components in the Next.js docs [TODO link to each of those docs]. +> Learn more about [Page](https://beta.nextjs.org/docs/api-reference/file-conventions/page), [Layout](https://beta.nextjs.org/docs/api-reference/file-conventions/layout), [Loading](https://beta.nextjs.org/docs/api-reference/file-conventions/loading) and [Error](https://beta.nextjs.org/docs/api-reference/file-conventions/error) components in the [Next.js beta docs](https://beta.nextjs.org/docs). Before we jump into fetching data, we need some data to fetch. Let’s [create a new Supabase project](https://app.supabase.com). @@ -78,15 +78,15 @@ values This will create a table called `posts`, and populate it with some example data. -Let’s install the [`supabase-js` library](https://www.npmjs.com/package/@supabase/supabase-js) to fetch our `posts`. +Let’s install the [supabase-js library](https://www.npmjs.com/package/@supabase/supabase-js) to fetch our `posts`. ```bash npm install @supabase/supabase-js ``` -And add a `.env.local` file with the following environment variables. +And add a `.env.local` file with the following environment variables: -``` title=".env.local" +```title=".env.local" NEXT_PUBLIC_SUPABASE_URL=your-supabase-url NEXT_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-key ``` @@ -96,28 +96,28 @@ NEXT_PUBLIC_SUPABASE_ANON_KEY=your-supabase-anon-key Lastly, we need to create a Supabase client. Create a file at `utils/supabase.ts` with the following content: ```tsx title="utils/supabase.ts" -import { createClient } from "@supabase/supabase-js"; +import { createClient } from '@supabase/supabase-js' export default createClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! -); +) ``` Okay, let’s look at some different data fetching and caching strategies. # Static -By default any page component in the `app` folder is a Server Component, and its data is fetched and cached by Next.js every time we build a new version of our application. This is the equivalent of exporting a `getStaticProps` function from a component in the `pages` directory. +By default any page component in the `app` folder is a Server Component, and its data is fetched and cached by Next.js every time we build a new version of our application. This is equivalent to exporting a `getStaticProps` function from a component in the `pages` directory. Let’s create a new file at `app/static/page.tsx` and populate with the following: ```jsx title="app/static/page.tsx" -import supabase from "../../utils/supabase"; +import supabase from '../../utils/supabase' export default async function Posts() { - const { data: posts } = await supabase.from("posts").select(); - return
{JSON.stringify(posts, null, 2)}
; + const { data: posts } = await supabase.from('posts').select() + return
{JSON.stringify(posts, null, 2)}
} ``` @@ -128,21 +128,21 @@ Server Components support async/await by default, and suspend the rendering of t Let’s modify this component to render out a collection of `` components, that navigate to a dedicated page for each post. ```tsx title="app/static/page.tsx" -import Link from "next/link"; -import supabase from "../../utils/supabase"; +import Link from 'next/link' +import supabase from '../../utils/supabase' export default async function Posts() { - const { data: posts } = await supabase.from("posts").select("id, title"); + const { data: posts } = await supabase.from('posts').select('id, title') if (!posts) { - return

No posts found.

; + return

No posts found.

} return posts.map((post) => (

{post.title}

- )); + )) } ``` @@ -151,35 +151,31 @@ export default async function Posts() { Let’s create a dynamic route to handle displaying an individual post. Create a new file at `app/static/[id]/page.tsx` and populate with the following: ```tsx title="app/static/[id]/page.tsx" -import supabase from "../../../utils/supabase"; -import { notFound } from "next/navigation"; +import supabase from '../../../utils/supabase' +import { notFound } from 'next/navigation' -export default async function Post({ - params: { id }, -}: { - params: { id: string }; -}) { - const { data } = await supabase.from("posts").select().match({ id }).single(); +export default async function Post({ params: { id } }: { params: { id: string } }) { + const { data } = await supabase.from('posts').select().match({ id }).single() if (!data) { - notFound(); + notFound() } - return
{JSON.stringify(data, null, 2)}
; + return
{JSON.stringify(data, null, 2)}
} ``` -Currently, this page is generated on-demand and then cached. This means the first person who visits the page will need to wait for the server to get the post data from Supabase. This won’t take long at all, because Supabase is Supa awesome! But, we can still make this *slightly* more efficient by telling Next.js a finite collection of paths that we want to generate at build time. +Currently, this page is generated on-demand and then cached. This means the first person who visits the page will need to wait for the server to get the post data from Supabase. This won’t take long at all, because Supabase is Supa awesome! But, we can still make this _slightly_ more efficient by telling Next.js a finite collection of paths that we want to generate at build time. -We do this by exporting out a `generateStaticParams` function from our dynamic StaticPost page. +We do this by exporting out a `generateStaticParams` function from our dynamic page. ```tsx title="app/static/[id]/page.tsx" export async function generateStaticParams() { - const { data: posts } = await supabase.from("posts").select("id"); + const { data: posts } = await supabase.from('posts').select('id') return posts?.map(({ id }) => ({ id, - })); + })) } ``` @@ -188,100 +184,98 @@ export async function generateStaticParams() { The full component should look something like this: ```tsx title="app/static/[id]/page.tsx" -import supabase from "../../../utils/supabase"; -import { notFound } from "next/navigation"; +import supabase from '../../../utils/supabase' +import { notFound } from 'next/navigation' export async function generateStaticParams() { - const { data: posts } = await supabase.from("posts").select("id"); + const { data: posts } = await supabase.from('posts').select('id') return posts?.map(({ id }) => ({ id, - })); + })) } -export default async function Post({ - params: { id }, -}: { - params: { id: string }; -}) { - const { data: post } = await supabase.from("posts").select().match({ id }).single(); +export default async function Post({ params: { id } }: { params: { id: string } }) { + const { data: post } = await supabase.from('posts').select().match({ id }).single() if (!post) { - notFound(); + notFound() } - return
{JSON.stringify(post, null, 2)}
; + return
{JSON.stringify(post, null, 2)}
} ``` Awesome! We now have a Supa snappy blog! The user never needs to wait for data to be fetched. All pages are statically generated at build time, and cached at CDN nodes close to our users! 🎉 -Unfortunately, this means any changes we make in Supabase — adding, updating or deleting posts etc — will not be reflected in our blog. If we want to refresh this data on a regular basis, we need to tell Next.js when to *revalidate*. +Unfortunately, this means any changes we make in Supabase — adding, updating or deleting posts etc — will not be reflected in our blog. If we want to refresh this data on a regular basis, we need to tell Next.js when to _revalidate_. # Static with Revalidation By exporting a `revalidate` variable from our component, we can specify how many seconds we consider this data to be “fresh”. ```tsx -export const revalidate = 60; +export const revalidate = 60 ``` +> This is similar to returning a `revalidate` key from the `getStaticProps` function in a component from the `pages` directory. + So, for 60 seconds Next.js will continue to respond with the static version of our page. After 60 seconds, it will fetch fresh data from Supabase and generate a new static page. However, there is no downtime while this happens, as the previous static page will continue to be served until the “fresh” one has been successfully generated. The `Posts` component should now look like this: ```tsx title="app/static-with-revalidate/page.tsx" -import Link from "next/link"; -import supabase from "../../utils/supabase"; +import Link from 'next/link' +import supabase from '../../utils/supabase' -export const revalidate = 60; +export const revalidate = 60 export default async function Posts() { - const { data: posts } = await supabase.from("posts").select("id, title"); + const { data: posts } = await supabase.from('posts').select('id, title') if (!posts) { - return

No posts found.

; + return

No posts found.

} return posts.map((post) => (

{post.title}

- )); + )) } ``` And the `Post` component should look like this: ```tsx title="app/static-with-revalidate/[id]/page.tsx" -import supabase from "../../../utils/supabase"; -import { notFound } from "next/navigation"; +import supabase from '../../../utils/supabase' +import { notFound } from 'next/navigation' -export const revalidate = 60; +export const revalidate = 60 export async function generateStaticParams() { - const { data: posts } = await supabase.from("posts").select("id"); + const { data: posts } = await supabase.from('posts').select('id') return posts?.map(({ id }) => ({ id, - })); + })) } -export default async function Post({ - params: { id }, -}: { - params: { id: string }; -}) { - const { data: post } = await supabase.from("posts").select().match({ id }).single(); +export default async function Post({ params: { id } }: { params: { id: string } }) { + const { data: post } = await supabase.from('posts').select().match({ id }).single() if (!post) { - notFound(); + notFound() } - return
{JSON.stringify(post, null, 2)}
; + return
{JSON.stringify(post, null, 2)}
} ``` +We now get all the benefits of static — users not waiting around while data is fetched at request time — but we also get the benefits of dynamic data, as it is being refreshed on a regular basis. + +Very cool! 😎 + # Dynamic If we want fresh data to be fetched on every single request, we can simply set our `revalidate` value to `0`. @@ -289,107 +283,100 @@ If we want fresh data to be fetched on every single request, we can simply set o **Posts Component** ```tsx title="app/server-rendered/page.tsx" -import Link from "next/link"; -import supabase from "../../utils/supabase"; +import Link from 'next/link' +import supabase from '../../utils/supabase' -export const revalidate = 60; +export const revalidate = 0 export default async function Posts() { - const { data: posts } = await supabase.from("posts").select("id, title"); + const { data: posts } = await supabase.from('posts').select('id, title') if (!posts) { - return

No posts found.

; + return

No posts found.

} return posts.map((post) => (

{post.title}

- )); + )) } ``` **Single Post Component** ```tsx title="app/server-rendered/[id]/page.tsx" -import supabase from "../../../utils/supabase"; -import { notFound } from "next/navigation"; +import supabase from '../../../utils/supabase' +import { notFound } from 'next/navigation' -export const revalidate = 60; +export const revalidate = 0 export async function generateStaticParams() { - const { data: posts } = await supabase.from("posts").select("id"); + const { data: posts } = await supabase.from('posts').select('id') return posts?.map(({ id }) => ({ id, - })); + })) } -export default async function Post({ - params: { id }, -}: { - params: { id: string }; -}) { - const { data: post } = await supabase.from("posts").select().match({ id }).single(); +export default async function Post({ params: { id } }: { params: { id: string } }) { + const { data: post } = await supabase.from('posts').select().match({ id }).single() if (!post) { - notFound(); + notFound() } - return
{JSON.stringify(post, null, 2)}
; + return
{JSON.stringify(post, null, 2)}
} ``` This is similar to exporting a `getServerSideProps` function from a component in the `pages` directory. +All this server stuff is great, but what if you want to use Supabase client-side? 🤔 + # Client-side There are many use-cases where you need to use Supabase client-side: 1. Authentication - Supabase Auth does a bunch of stuff behind the scenes — handling 3rd party OAuth flows, for example. This will break if you try to sign users in and out on the server. + Supabase Auth does a bunch of stuff behind the scenes — handling 3rd party OAuth flows, for example. This will break if you try to sign users in and out on the server. 2. Realtime - Supabase manage the awesome power of websockets on your behalf — something that is not yet solved with this new serverless world. + Supabase manages the awesome power of websockets on your behalf — something that is not yet solved in this serverless world. -3. You prefer it … there is nothing wrong with this! +3. You prefer it - You do you! + There is nothing wrong with this! You do you! +To use Supabase client-side, we need to tell Next.js that this is a [Client Component](https://beta.nextjs.org/docs/rendering/server-and-client-components#client-components). We do this by specifying the `use client` directive at the top of our component. This opts into a similar flow to the `pages` directory — the component is rendered on the server and hydrated client-side. -To use Supabase client-side, we need to tell Next.js that this is a client component. We do this by specifying the `use client` directive at the top of our component. This opts into a similar flow to the `pages` directory — the component is rendered on the server and hydrated client-side. - -> The React team is [working on an awesome new hook called `use`](https://github.com/acdlite/rfcs/blob/first-class-promises/text/0000-first-class-support-for-promises.md#example-use-in-client-components-and-hooks), which will drastically simplify fetching data client-side, but for now, we still need to rely on the combination of `useState` and `useEffect`. +> The React team is [working on an awesome new hook called "use"](https://github.com/acdlite/rfcs/blob/first-class-promises/text/0000-first-class-support-for-promises.md#example-use-in-client-components-and-hooks), which will drastically simplify fetching data client-side, but for now, we still need to rely on the combination of `useState` and `useEffect`. Let’s implement client-side data fetching. ```tsx title="app/client-side/page.tsx" -"use client"; +'use client' -import { useEffect, useState } from "react"; -import supabase from "../../utils/supabase"; +import { useEffect, useState } from 'react' +import supabase from '../../utils/supabase' export default function ClientPosts() { - const [isLoading, setIsLoading] = useState(true); - const [posts, setPosts] = useState([]); + const [isLoading, setIsLoading] = useState(true) + const [posts, setPosts] = useState([]) useEffect(() => { const fetchPosts = async () => { - const { data } = await supabase.from("posts").select(); - setPosts(data); - setIsLoading(false); - }; + const { data } = await supabase.from('posts').select() + setPosts(data) + setIsLoading(false) + } - fetchPosts(); - }, []); + fetchPosts() + }, []) - return isLoading ? ( -

Loading

- ) : ( -
{JSON.stringify(posts, null, 2)}
- ); + return isLoading ?

Loading

:
{JSON.stringify(posts, null, 2)}
} ``` @@ -404,63 +391,63 @@ Let’s merge the two previous concepts and fetch the initial state of our posts **Server Component** ```tsx title="app/realtime/page.tsx" -import supabase from "../../utils/supabase"; -import RealtimePosts from "./realtime-posts"; +import supabase from '../../utils/supabase' +import RealtimePosts from './realtime-posts' -export const revalidate = 0; +export const revalidate = 0 export default async function Realtime() { - const { data } = await supabase.from("posts").select("*"); - return ; + const { data } = await supabase.from('posts').select('*') + return } ``` **Client Component** ```tsx title="app/realtime/realtime-posts.tsx" -"use client"; +'use client' -import { useEffect, useState } from "react"; -import supabase from "../../utils/supabase"; +import { useEffect, useState } from 'react' +import supabase from '../../utils/supabase' export default function RealtimePosts({ serverPosts }: { serverPosts: any }) { - const [posts, setPosts] = useState(serverPosts); + const [posts, setPosts] = useState(serverPosts) useEffect(() => { - setPosts(serverPosts); - }, [serverPosts]); + setPosts(serverPosts) + }, [serverPosts]) useEffect(() => { const channel = supabase - .channel("*") - .on( - "postgres_changes", - { event: "INSERT", schema: "public", table: "posts" }, - (payload) => setPosts((posts: any) => [...posts, payload.new]) + .channel('*') + .on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'posts' }, (payload) => + setPosts((posts: any) => [...posts, payload.new]) ) - .subscribe(); + .subscribe() return () => { - supabase.removeChannel(channel); - }; - }, [serverPosts]); + supabase.removeChannel(channel) + } + }, [serverPosts]) - return
{JSON.stringify(posts, null, 2)}
; + return
{JSON.stringify(posts, null, 2)}
} ``` > `useEffect` is used to subscribe to changes to the `serverPosts` prop. Without this, our component would not display fresh server-side results when the parent component is re-rendered, only on the first render. - -This is a great pattern for fetching initial data server-side and subscribing to realtime changes client-side. This will likely be replaced by the `use` hook once it is stable with Next.js — as it also uses suspense to *suspend* the rendering of a component while fetching data. +This is a great pattern for fetching initial data server-side and subscribing to realtime changes client-side. This will likely be replaced by the `use` hook once it is stable with Next.js — it also uses suspense to _suspend_ the rendering of a component while fetching data, and cleans up those loading and error states. # Conclusion -Next.js 13 Server Components and Suspense are awesome! Async components allow us to think about data fetching and caching as separate concerns — rather than specifying completely different data fetching functions like `getStaticProps` and `getServerSideProps`. If our caching requirements for a component change, we simply update the caching value, rather than refactoring our data fetching logic. +Next.js 13 Server Components are awesome! Suspense is awesome! Async components are awesome! -Allowing any component in the tree to be either a server or client component, that is responsible for its own data, and suspends rendering until it is ready, drastically simplifies our code, and provides much more flexible patterns for maintainable applications. +The combination of these concepts allow us to think about data fetching and caching as separate concerns, rather than specifying completely different data fetching functions like `getStaticProps` and `getServerSideProps`. If our caching requirements for a component change, we simply update the caching value, rather than refactoring our data fetching logic. + +Additionally, by allowing any component in the tree to be either a server or client component — that is responsible for its own data and suspends rendering until it is ready — drastically simplifies our code, and provides much more flexible patterns for creating maintainable applications as complexity grows. # More Next.js 13 Resources + - [Next.js 13 Beta Docs](https://beta.nextjs.org/docs) - [Creating an authenticated Supabase client in Next.js 13 Server Component](https://github.com/supabase/auth-helpers/tree/main/examples/nextjs-server-components) - [Data fetching and caching with Next.js 13 - Supabase Happy Hour #26](https://www.youtube.com/watch?v=QH0P5xZt5wY) From 6a6acc1dc249242695be986bf4cdc4efa966aa5a Mon Sep 17 00:00:00 2001 From: Jon Meyers Date: Wed, 16 Nov 2022 14:08:10 +1100 Subject: [PATCH 07/26] remove is_published field --- ...aching-supabase-data-in-next-js-server-components.mdx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/www/_blog/2022-11-18-fetching-and-caching-supabase-data-in-next-js-server-components.mdx b/apps/www/_blog/2022-11-18-fetching-and-caching-supabase-data-in-next-js-server-components.mdx index 6024141781f..e819b684120 100644 --- a/apps/www/_blog/2022-11-18-fetching-and-caching-supabase-data-in-next-js-server-components.mdx +++ b/apps/www/_blog/2022-11-18-fetching-and-caching-supabase-data-in-next-js-server-components.mdx @@ -66,14 +66,13 @@ create table if not exists posts ( id uuid default uuid_generate_v4() primary key, created_at timestamp with time zone default timezone('utc'::text, now()) not null, title text, - content text, - is_published boolean default false + content text ); -insert into posts(title, content, is_published) +insert into posts(title, content) values - ('My first post', 'Wow! What a great post.', true), - ('My second post', 'This one needs a little work!', false); + ('My first post', 'Wow! What a great post.'), + ('My second post', 'This one needs a little work!'); ``` This will create a table called `posts`, and populate it with some example data. From 04cef3f93ffcdcd84fb71bb816d7a5339878021b Mon Sep 17 00:00:00 2001 From: Jon Meyers Date: Wed, 16 Nov 2022 14:08:28 +1100 Subject: [PATCH 08/26] add CTAs for generating ts types --- ...and-caching-supabase-data-in-next-js-server-components.mdx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/www/_blog/2022-11-18-fetching-and-caching-supabase-data-in-next-js-server-components.mdx b/apps/www/_blog/2022-11-18-fetching-and-caching-supabase-data-in-next-js-server-components.mdx index e819b684120..e713a38ca07 100644 --- a/apps/www/_blog/2022-11-18-fetching-and-caching-supabase-data-in-next-js-server-components.mdx +++ b/apps/www/_blog/2022-11-18-fetching-and-caching-supabase-data-in-next-js-server-components.mdx @@ -103,6 +103,8 @@ export default createClient( ) ``` +> For automatically adding types to the Supabase client, check out [how to generate types](https://supabase.com/docs/reference/javascript/typescript-support). + Okay, let’s look at some different data fetching and caching strategies. # Static @@ -379,6 +381,8 @@ export default function ClientPosts() { } ``` +> Again, check out [how to generate types](https://supabase.com/docs/reference/javascript/typescript-support) to add proper typing for Post. + But now we have loading spinners! Yuck! # Realtime From 9c63fc53bb397e16b700b933c58c4ca5bd68f322 Mon Sep 17 00:00:00 2001 From: Qiao Han Date: Wed, 16 Nov 2022 11:11:30 +0800 Subject: [PATCH 09/26] chore: use supabase public url --- docker/.env.example | 2 +- docker/docker-compose.yml | 11 +++++++---- studio/.env | 3 +-- .../layouts/StorageLayout/StorageMenu.tsx | 1 - .../storageExplorer/StorageExplorerStore.js | 3 +-- studio/package.json | 2 +- studio/pages/api/constants.ts | 4 ++++ studio/pages/api/projects/[ref]/index.ts | 3 ++- studio/pages/api/props/project/[ref]/api.ts | 13 +++++++------ studio/pages/api/props/project/[ref]/settings.ts | 3 ++- 10 files changed, 26 insertions(+), 19 deletions(-) create mode 100644 studio/pages/api/constants.ts diff --git a/docker/.env.example b/docker/.env.example index 3d68946481e..2469e01fa6b 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -74,4 +74,4 @@ STUDIO_DEFAULT_ORGANIZATION=Default Organization STUDIO_DEFAULT_PROJECT=Default Project STUDIO_PORT=3000 -SUPABASE_PUBLIC_URL=http://localhost:8000 # replace if you intend to use Studio outside of localhost +SUPABASE_PUBLIC_URL=https://localhost:8443 # replace if you intend to use Studio outside of localhost diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 2bf444e5aa6..cfb99e54238 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -21,7 +21,9 @@ services: DEFAULT_PROJECT: ${STUDIO_DEFAULT_PROJECT} SUPABASE_URL: http://kong:8000 - SUPABASE_REST_URL: ${SUPABASE_PUBLIC_URL} + SUPABASE_PUBLIC_URL: ${SUPABASE_PUBLIC_URL} + # Kept for backwards compatibility with studio:0.22.08 + SUPABASE_REST_URL: ${API_EXTERNAL_URL}/rest/v1/ SUPABASE_ANON_KEY: ${ANON_KEY} SUPABASE_SERVICE_KEY: ${SERVICE_ROLE_KEY} @@ -45,7 +47,7 @@ services: auth: container_name: supabase-auth - image: supabase/gotrue:v2.19.4 + image: supabase/gotrue:v2.25.1 depends_on: db: # Disable this if you are using an external Postgres database condition: service_healthy @@ -122,7 +124,8 @@ services: SLOT_NAME: supabase_realtime_rls TEMPORARY_SLOT: "true" command: > - bash -c "./prod/rel/realtime/bin/realtime eval Realtime.Release.migrate && ./prod/rel/realtime/bin/realtime start" + bash -c "./prod/rel/realtime/bin/realtime eval Realtime.Release.migrate + && ./prod/rel/realtime/bin/realtime start" storage: container_name: supabase-storage @@ -167,7 +170,7 @@ services: # Comment out everything below this point if you are using an external Postgres database db: container_name: supabase-db - image: supabase/postgres:14.1.0.82 + image: supabase/postgres:14.1.0.89 healthcheck: test: pg_isready -U postgres -h localhost interval: 5s diff --git a/studio/.env b/studio/.env index dd9c8a3205b..ee686048dfc 100644 --- a/studio/.env +++ b/studio/.env @@ -5,7 +5,6 @@ DEFAULT_ORGANIZATION_NAME=Default Organization DEFAULT_PROJECT_NAME=Default Project SUPABASE_URL=http://localhost:8000 -SUPABASE_REST_URL=http://localhost:8000 +SUPABASE_PUBLIC_URL=https://localhost:8443 SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE SUPABASE_SERVICE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q - diff --git a/studio/components/layouts/StorageLayout/StorageMenu.tsx b/studio/components/layouts/StorageLayout/StorageMenu.tsx index 218408594c2..dc41cdacb64 100644 --- a/studio/components/layouts/StorageLayout/StorageMenu.tsx +++ b/studio/components/layouts/StorageLayout/StorageMenu.tsx @@ -18,7 +18,6 @@ import { import ProductMenuItem from 'components/ui/ProductMenu/ProductMenuItem' import { STORAGE_ROW_STATUS } from 'components/to-be-cleaned/Storage/Storage.constants' import { useStorageStore } from 'localStores/storageExplorer/StorageExplorerStore' -import { IS_PLATFORM } from 'lib/constants' interface Props {} diff --git a/studio/localStores/storageExplorer/StorageExplorerStore.js b/studio/localStores/storageExplorer/StorageExplorerStore.js index 9c668113683..f3bcfd79a4b 100644 --- a/studio/localStores/storageExplorer/StorageExplorerStore.js +++ b/studio/localStores/storageExplorer/StorageExplorerStore.js @@ -112,7 +112,7 @@ class StorageExplorerStore { /* Methods which are commonly used + For better readability */ initializeSupabaseClient = (serviceKey, serviceEndpoint) => { - this.supabaseClient = createClient(`${serviceEndpoint}`, serviceKey, { + this.supabaseClient = createClient(`https://${serviceEndpoint}`, serviceKey, { auth: { persistSession: false, autoRefreshToken: false, @@ -478,7 +478,6 @@ class StorageExplorerStore { fetchBuckets = async () => { const { data: buckets, error } = await this.supabaseClient.storage.listBuckets() - if (error) return this.ui.setNotification({ message: error.message, category: 'error' }) const formattedBuckets = buckets.map((bucket) => { diff --git a/studio/package.json b/studio/package.json index bba48626c3c..587ba186cfe 100644 --- a/studio/package.json +++ b/studio/package.json @@ -3,7 +3,7 @@ "version": "0.0.9", "private": true, "scripts": { - "dev": "next dev -p 3000", + "dev": "next dev -p 8082", "build": "next build", "start": "next start", "test": "jest", diff --git a/studio/pages/api/constants.ts b/studio/pages/api/constants.ts new file mode 100644 index 00000000000..3531bad779e --- /dev/null +++ b/studio/pages/api/constants.ts @@ -0,0 +1,4 @@ +const PUBLIC_URL = new URL(process.env.SUPABASE_PUBLIC_URL || 'https://localhost:8443') + +export const PROJECT_REST_URL = `${PUBLIC_URL.origin}/rest/v1/` +export const PROJECT_ENDPOINT = PUBLIC_URL.host diff --git a/studio/pages/api/projects/[ref]/index.ts b/studio/pages/api/projects/[ref]/index.ts index 204167ae379..7ae98b66a38 100644 --- a/studio/pages/api/projects/[ref]/index.ts +++ b/studio/pages/api/projects/[ref]/index.ts @@ -1,6 +1,7 @@ import { NextApiRequest, NextApiResponse } from 'next' import apiWrapper from 'lib/api/apiWrapper' +import { PROJECT_REST_URL } from 'pages/api/constants' export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler) @@ -35,7 +36,7 @@ const handleGet = async (req: NextApiRequest, res: NextApiResponse) => { db_ssl: false, }), kpsVersion: 'kps-v1.0.0', - restUrl: `${process.env.SUPABASE_REST_URL}/rest/v1/` || 'http://localhost:8000/rest/v1/', + restUrl: PROJECT_REST_URL, } return res.status(200).json(response) diff --git a/studio/pages/api/props/project/[ref]/api.ts b/studio/pages/api/props/project/[ref]/api.ts index 9736e3cdc41..9e6cb871c40 100644 --- a/studio/pages/api/props/project/[ref]/api.ts +++ b/studio/pages/api/props/project/[ref]/api.ts @@ -1,6 +1,7 @@ import { NextApiRequest, NextApiResponse } from 'next' import apiWrapper from 'lib/api/apiWrapper' +import { PROJECT_ENDPOINT, PROJECT_REST_URL } from 'pages/api/constants' export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler) @@ -39,11 +40,11 @@ const handleGetAll = async (req: NextApiRequest, res: NextApiResponse) => { app: { id: 1, name: 'Auto API' }, app_config: { db_schema: 'public', - endpoint: process.env.SUPABASE_URL, + endpoint: PROJECT_ENDPOINT, realtime_enabled: true, }, - endpoint: process.env.SUPABASE_URL || 'http://localhost:8000', - restUrl: `${process.env.SUPABASE_REST_URL}/rest/v1/` || 'http://localhost:8000/rest/v1/', + endpoint: PROJECT_ENDPOINT, + restUrl: PROJECT_REST_URL, defaultApiKey: process.env.SUPABASE_ANON_KEY, serviceApiKey: process.env.SUPABASE_SERVICE_KEY, service_api_keys: [ @@ -68,11 +69,11 @@ const handleGetAll = async (req: NextApiRequest, res: NextApiResponse) => { app: { id: 1, name: 'Auto API' }, app_config: { db_schema: 'public', - endpoint: process.env.SUPABASE_URL, + endpoint: PROJECT_ENDPOINT, realtime_enabled: true, }, - endpoint: process.env.SUPABASE_URL, - restUrl: `${process.env.SUPABASE_REST_URL}/rest/v1/`, + endpoint: PROJECT_ENDPOINT, + restUrl: PROJECT_REST_URL, defaultApiKey: process.env.SUPABASE_ANON_KEY, serviceApiKey: process.env.SUPABASE_SERVICE_KEY, service_api_keys: [ diff --git a/studio/pages/api/props/project/[ref]/settings.ts b/studio/pages/api/props/project/[ref]/settings.ts index 5677414b23a..433ee7f95c8 100644 --- a/studio/pages/api/props/project/[ref]/settings.ts +++ b/studio/pages/api/props/project/[ref]/settings.ts @@ -1,6 +1,7 @@ import { NextApiRequest, NextApiResponse } from 'next' import apiWrapper from 'lib/api/apiWrapper' +import { PROJECT_ENDPOINT } from 'pages/api/constants' export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler) @@ -56,7 +57,7 @@ const handleGetAll = async (req: NextApiRequest, res: NextApiResponse) => { app: { id: 1, name: 'Auto API' }, app_config: { db_schema: 'public', - endpoint: process.env.SUPABASE_URL, + endpoint: PROJECT_ENDPOINT, realtime_enabled: true, }, }, From 34360a54cb05cfc3222557d50611726e2afb09fb Mon Sep 17 00:00:00 2001 From: Qiao Han Date: Wed, 16 Nov 2022 16:48:54 +0800 Subject: [PATCH 10/26] fix: docker release workflow --- .github/workflows/publish_image.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish_image.yml b/.github/workflows/publish_image.yml index 84c8b8a88c2..6106dad6b42 100644 --- a/.github/workflows/publish_image.yml +++ b/.github/workflows/publish_image.yml @@ -52,7 +52,8 @@ jobs: - uses: docker/build-push-action@v3 with: push: true - context: '{{defaultContext}}:studio' + context: '{{defaultContext}}' + file: studio/Dockerfile target: production platforms: linux/amd64,linux/arm64 tags: ${{ steps.meta.outputs.tags }} From 113ad40b6a9ae2756b1101ee47f3a07868f2ac94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ramiro=20Nu=C3=B1ez=20Dosio?= Date: Wed, 16 Nov 2022 14:32:36 +0000 Subject: [PATCH 11/26] Couple of updates. --- ...ng-supabase-data-in-next-js-server-components.mdx} | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) rename apps/www/_blog/{2022-11-18-fetching-and-caching-supabase-data-in-next-js-server-components.mdx => 2022-11-16-fetching-and-caching-supabase-data-in-next-js-server-components.mdx} (99%) diff --git a/apps/www/_blog/2022-11-18-fetching-and-caching-supabase-data-in-next-js-server-components.mdx b/apps/www/_blog/2022-11-16-fetching-and-caching-supabase-data-in-next-js-server-components.mdx similarity index 99% rename from apps/www/_blog/2022-11-18-fetching-and-caching-supabase-data-in-next-js-server-components.mdx rename to apps/www/_blog/2022-11-16-fetching-and-caching-supabase-data-in-next-js-server-components.mdx index e713a38ca07..71456066963 100644 --- a/apps/www/_blog/2022-11-18-fetching-and-caching-supabase-data-in-next-js-server-components.mdx +++ b/apps/www/_blog/2022-11-16-fetching-and-caching-supabase-data-in-next-js-server-components.mdx @@ -5,14 +5,8 @@ author: jonmeyers_io image: TODO thumb: TODO tags: - - Next.js 13 - - React Server Components - - app directory - - data fetching - - caching - - async components - - Suspense -date: '2022-11-18' + - Next.js +date: '2022-11-16' toc_depth: 3 --- @@ -451,6 +445,7 @@ Additionally, by allowing any component in the tree to be either a server or cli # More Next.js 13 Resources +- [Next.js Quickstart](https://supabase.com/docs/guides/with-nextjs) - [Next.js 13 Beta Docs](https://beta.nextjs.org/docs) - [Creating an authenticated Supabase client in Next.js 13 Server Component](https://github.com/supabase/auth-helpers/tree/main/examples/nextjs-server-components) - [Data fetching and caching with Next.js 13 - Supabase Happy Hour #26](https://www.youtube.com/watch?v=QH0P5xZt5wY) From 9b82d50e42b2787d6c44b0703a8e6339e84bb097 Mon Sep 17 00:00:00 2001 From: thorwebdev Date: Thu, 17 Nov 2022 01:21:08 +0800 Subject: [PATCH 12/26] chore: add wildcard redirect docs. --- apps/docs/docs/guides/auth.mdx | 37 +++++++++++++++++++ .../guides/auth/server-side-rendering.mdx | 2 +- spec/supabase_js_v2.yml | 4 +- spec/supabase_js_v2_temp.yml | 4 +- 4 files changed, 42 insertions(+), 5 deletions(-) diff --git a/apps/docs/docs/guides/auth.mdx b/apps/docs/docs/guides/auth.mdx index 1d60ab88fbb..95dd1dbf3ad 100644 --- a/apps/docs/docs/guides/auth.mdx +++ b/apps/docs/docs/guides/auth.mdx @@ -49,6 +49,43 @@ You can enable third-party providers with the click of a button by navigating to ![OAuth Logins.](/docs/img/supabase-oauth-logins.png) +### Redirect URLs and wildcards + +When using third-party providers the [Supabase client library](/docs/reference/javascript/auth-signinwithoauth#sign-in-using-a-third-party-provider-with-redirect) will redirect the user to the provider. When the third-party provider successfully authenticates the user, the provider will redirect the user to the Supabase Auth callback URL, from where they will be further redirected to the URL specified in the `redirectTo` parameter. This parameter defaults to the [`SITE_URL`](/docs/reference/auth/config#site_url). You can modify the `SITE_URL` or add additional redirect urls in [your project](https://app.supabase.com/project/_/auth/settings). + +You can use wildcard match patterns to support preview URLs from providers like Netlify and Vercel. + +A full list of supported pattern syntax can be found [here](https://pkg.go.dev/github.com/gobwas/glob#Compile). + +#### Netlify preview URLs + +For deployments with Netlify, set the `SITE_URL` to your official site URL. Add the following additional redirect URLs for local development and deployment previews: + +- `http://localhost:3000/*/*` +- `https://**--my_org.netlify.app/*` + +#### Vercel preview URLs + +For deployments with Vercel, set the `SITE_URL` to your official site URL. Add the following additional redirect URLs for local development and deployment previews: + +- `http://localhost:3000/*/*` +- `https://**vercel.app/*/*` + +Vercel provides an environment variable for the URL of the deployment called `NEXT_PUBLIC_VERCEL_URL`. See the [Vercel docs](https://vercel.com/docs/concepts/projects/environment-variables#system-environment-variables) for more details. You can use this variable to dynamically redirect depending on the environment: + +```js +const { data, error } = await supabase.auth.signInWithOAuth({ + provider: 'github' + options: { + redirectTo: `${ + process.env.NEXT_PUBLIC_VERCEL_URL + ? 'https://' + process.env.NEXT_PUBLIC_VERCEL_URL + : 'http://localhost:3000' + }` + } +} +``` + ## Authorization When you need granular authorization rules, nothing beats PostgreSQL's Row Level Security (RLS). diff --git a/apps/docs/docs/guides/auth/server-side-rendering.mdx b/apps/docs/docs/guides/auth/server-side-rendering.mdx index c80ff1ce825..102ff4fbe78 100644 --- a/apps/docs/docs/guides/auth/server-side-rendering.mdx +++ b/apps/docs/docs/guides/auth/server-side-rendering.mdx @@ -48,7 +48,7 @@ server redirects the user back to your single-page app. -You can configure [redirects URLs](https://app.supabase.com/project/_/auth/url-configuration) in the Supabase Dashboard. You can use wildcard match patterns +You can configure [redirects URLs](https://app.supabase.com/project/_/auth/url-configuration) in the Supabase Dashboard. You can use [wildcard match patterns](/docs/guides/auth#redirect-urls-and-wildcards) like `*` and `**` to allow redirects to different forms of URLs. diff --git a/spec/supabase_js_v2.yml b/spec/supabase_js_v2.yml index c3ee9260119..9df7075f297 100644 --- a/spec/supabase_js_v2.yml +++ b/spec/supabase_js_v2.yml @@ -214,8 +214,8 @@ pages: - name: Sign in using a third-party provider with redirect isSpotlight: false description: | - When the third-party provider successfully authenticates the user, the provider will redirect the user to the URL specified in the `redirectTo` parameter. This parameter defaults to the [`SITE_URL`](https://supabase.com/docs/reference/auth/config#site_url). It does not redirect the user immediately after invoking this method. - You can modify the `SITE_URL` or add additional redirect urls in [your project](https://app.supabase.com/project/_/auth/settings). + When the third-party provider successfully authenticates the user, the provider will redirect the user to the Supabase Auth callback URL from where they will be further redirected to the URL specified in the `redirectTo` parameter. This parameter defaults to the [`SITE_URL`](https://supabase.com/docs/reference/auth/config#site_url). + You can modify the `SITE_URL` or add additional redirect urls in [your project](https://app.supabase.com/project/_/auth/settings). You can use [wildcard match patterns](/docs/guides/auth#redirect-urls-and-wildcards) to support preview URLs from providers like Netlify and Vercel. js: | ```js const { data, error } = await supabase.auth.signInWithOAuth({ diff --git a/spec/supabase_js_v2_temp.yml b/spec/supabase_js_v2_temp.yml index 0a42193dfd8..79b40bfc863 100644 --- a/spec/supabase_js_v2_temp.yml +++ b/spec/supabase_js_v2_temp.yml @@ -214,8 +214,8 @@ pages: - name: Sign in using a third-party provider with redirect isSpotlight: false description: | - When the third-party provider successfully authenticates the user, the provider will redirect the user to the URL specified in the `redirectTo` parameter. This parameter defaults to the [`SITE_URL`](https://supabase.com/docs/reference/auth/config#site_url). It does not redirect the user immediately after invoking this method. - You can modify the `SITE_URL` or add additional redirect urls in [your project](https://app.supabase.com/project/_/auth/settings). + When the third-party provider successfully authenticates the user, the provider will redirect the user to the Supabase Auth callback URL from where they will be further redirected to the URL specified in the `redirectTo` parameter. This parameter defaults to the [`SITE_URL`](https://supabase.com/docs/reference/auth/config#site_url). + You can modify the `SITE_URL` or add additional redirect urls in [your project](https://app.supabase.com/project/_/auth/settings). You can use [wildcard match patterns](/docs/guides/auth#redirect-urls-and-wildcards) to support preview URLs from providers like Netlify and Vercel. js: | ```js const { data, error } = await supabase.auth.signInWithOAuth({ From 927223dd2514abf3af340b8cac49a0acd52b3959 Mon Sep 17 00:00:00 2001 From: dshukertjr <18113850+dshukertjr@users.noreply.github.com> Date: Thu, 17 Nov 2022 11:59:40 +0900 Subject: [PATCH 13/26] fix image url on readme for flutter example --- examples/user-management/flutter-user-management/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/user-management/flutter-user-management/README.md b/examples/user-management/flutter-user-management/README.md index 13d06cbf26e..38f7169d129 100644 --- a/examples/user-management/flutter-user-management/README.md +++ b/examples/user-management/flutter-user-management/README.md @@ -7,7 +7,7 @@ This repo will demonstrate how to: - store and retrieve data with [Supabase database](https://supabase.io/docs/guides/database) - store image files in [Supabase storage](https://supabase.io/docs/guides/storage) -Supabase User Management example +Supabase User Management example ## Getting Started From aaddeb54d8a11c9de154d638b9882ea4b1005b7c Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Thu, 17 Nov 2022 10:14:33 +0800 Subject: [PATCH 14/26] Put MFA settings behind feature flag --- .../interfaces/Auth/AutoSchemaForm.tsx | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/studio/components/interfaces/Auth/AutoSchemaForm.tsx b/studio/components/interfaces/Auth/AutoSchemaForm.tsx index 36fc149755d..ee8d8dce9c6 100644 --- a/studio/components/interfaces/Auth/AutoSchemaForm.tsx +++ b/studio/components/interfaces/Auth/AutoSchemaForm.tsx @@ -4,7 +4,7 @@ import { boolean, number, object, string } from 'yup' import { PermissionAction } from '@supabase/shared-types/out/constants' import { Button, Form, Input, IconEye, IconEyeOff, InputNumber, Toggle } from 'ui' -import { useStore, checkPermissions } from 'hooks' +import { useStore, checkPermissions, useFlag } from 'hooks' import { FormActions, FormHeader, @@ -21,6 +21,7 @@ const AutoSchemaForm = observer(() => { const formId = 'auth-config-general-form' const [hidden, setHidden] = useState(true) + const showMfaSso = false const canUpdateConfig = checkPermissions(PermissionAction.UPDATE, 'custom_config_gotrue') const INITIAL_VALUES = { @@ -187,18 +188,20 @@ const AutoSchemaForm = observer(() => { )} - Multi Factor Authentication (MFA)} - > - - - - + {showMfaSso && ( + Multi Factor Authentication (MFA)} + > + + + + + )} ) From e3ac9e193c3df8849dcf0fc458210d2869364c79 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Thu, 17 Nov 2022 10:15:45 +0800 Subject: [PATCH 15/26] Use flag --- studio/components/interfaces/Auth/AutoSchemaForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/components/interfaces/Auth/AutoSchemaForm.tsx b/studio/components/interfaces/Auth/AutoSchemaForm.tsx index ee8d8dce9c6..d553fcf588d 100644 --- a/studio/components/interfaces/Auth/AutoSchemaForm.tsx +++ b/studio/components/interfaces/Auth/AutoSchemaForm.tsx @@ -21,7 +21,7 @@ const AutoSchemaForm = observer(() => { const formId = 'auth-config-general-form' const [hidden, setHidden] = useState(true) - const showMfaSso = false + const showMfaSso = useFlag('mfaSso') const canUpdateConfig = checkPermissions(PermissionAction.UPDATE, 'custom_config_gotrue') const INITIAL_VALUES = { From 8b88e7914482bd53f105d498a3e09bfda1b7f9af Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 16 Nov 2022 17:16:13 +0800 Subject: [PATCH 16/26] Small updates to email logins --- .../SignIn/PasswordConditionsHelper.tsx | 12 ++-- .../interfaces/SignIn/SignInForm.tsx | 2 +- .../interfaces/SignIn/SignUpForm.tsx | 1 + studio/pages/account/me.tsx | 55 +++++++++---------- 4 files changed, 33 insertions(+), 37 deletions(-) diff --git a/studio/components/interfaces/SignIn/PasswordConditionsHelper.tsx b/studio/components/interfaces/SignIn/PasswordConditionsHelper.tsx index 98df3524fd0..a2436717341 100644 --- a/studio/components/interfaces/SignIn/PasswordConditionsHelper.tsx +++ b/studio/components/interfaces/SignIn/PasswordConditionsHelper.tsx @@ -11,11 +11,11 @@ const PasswordConditionsHelper = ({ password }: PasswordConditionsHelperProps) = return (
- - - - - + + + + +
) } @@ -31,7 +31,7 @@ const PasswordCondition = ({ title, isMet }: PasswordConditionProps) => { return (
diff --git a/studio/components/interfaces/SignIn/SignInForm.tsx b/studio/components/interfaces/SignIn/SignInForm.tsx index bf4874b23f1..6498075f943 100644 --- a/studio/components/interfaces/SignIn/SignInForm.tsx +++ b/studio/components/interfaces/SignIn/SignInForm.tsx @@ -61,7 +61,7 @@ const SignInForm = () => { return ui.setNotification({ id: toastId, category: 'error', - message: 'Please click the link sent to your email to confirm your account', + message: 'Account has not been verified, please check the link sent to your email', }) } diff --git a/studio/components/interfaces/SignIn/SignUpForm.tsx b/studio/components/interfaces/SignIn/SignUpForm.tsx index 88c5883a3bc..df461d12d0a 100644 --- a/studio/components/interfaces/SignIn/SignUpForm.tsx +++ b/studio/components/interfaces/SignIn/SignUpForm.tsx @@ -111,6 +111,7 @@ const SignUpForm = () => { + + +
+ + )} From 88e911608ba4d065656a473a9e33a644aa5622be Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Thu, 17 Nov 2022 12:22:41 +0800 Subject: [PATCH 17/26] Show password condition on focus --- studio/components/interfaces/SignIn/SignUpForm.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/studio/components/interfaces/SignIn/SignUpForm.tsx b/studio/components/interfaces/SignIn/SignUpForm.tsx index df461d12d0a..e7777da7377 100644 --- a/studio/components/interfaces/SignIn/SignUpForm.tsx +++ b/studio/components/interfaces/SignIn/SignUpForm.tsx @@ -16,6 +16,7 @@ const signUpSchema = passwordSchema.shape({ const SignUpForm = () => { const { ui } = useStore() const captchaRef = useRef(null) + const [showConditions, setShowConditions] = useState(false) const [isSubmitted, setIsSubmitted] = useState(false) const [passwordHidden, setPasswordHidden] = useState(true) const [captchaToken, setCaptchaToken] = useState(null) @@ -107,17 +108,24 @@ const SignUpForm = () => { placeholder="••••••••" disabled={isSubmitting} autoComplete="new-password" + onFocus={() => setShowConditions(true)} actions={