Merge branch 'master' into docs/delete-old-directory

This commit is contained in:
dannykng committed 2022-11-17 08:14:24 -08:00
commit 0dc047316b
25 files changed
+596 -125

No files matched your search

+2 -1
View File
@@ -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 }}
+1 -1
View File
@@ -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
+37
View File
@@ -51,6 +51,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) redirects the user to the provider. When the third-party provider successfully authenticates the user, the provider redirects the user to the Supabase Auth callback URL where they are 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](https://app.supabase.com/project/_/auth/settings).
You can use wildcard match patterns to support preview URLs from providers like Netlify and Vercel. See the [full list of supported patterns](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"
}
}
```
#### Mobile deep linking URIs
For mobile applications you can use deep linking URIs. For example for your `SITE_URL` you can specify something like `com.supabase://login-callback/` and for additional redirect URLs something like `com.supabase.staging://login-callback/` if needed.
## Authorization
When you need granular authorization rules, nothing beats PostgreSQL's Row Level Security (RLS).
@@ -50,7 +50,7 @@ server redirects the user back to your single-page app.
<Admonition type="tip">
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.
</Admonition>
@@ -0,0 +1,451 @@
---
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_io
image: 2022-11-17-supabase-nextjs-13/nextjs-supabase.jpg
thumb: 2022-11-17-supabase-nextjs-13/nextjs-supabase.jpg
tags:
- Next.js
date: '2022-11-17'
toc_depth: 3
---
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 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 the [Next.js beta docs](https://beta.nextjs.org/docs).
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).
If you prefer video, check out our recent live stream where we stepped through a similar example.
<div class="video-container">
<iframe
src="https://www.youtube-nocookie.com/embed/QH0P5xZt5wY"
frameBorder="1"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
</div>
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 optional `layout`, `loading` and `error` components.
> 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).
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
);
insert into posts(title, content)
values
('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.
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!
)
```
> 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
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'
export default async function Posts() {
const { data: posts } = await supabase.from('posts').select()
return <pre>{JSON.stringify(posts, null, 2)}</pre>
}
```
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 `<Link />` 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 <p>No posts found.</p>
}
return posts.map((post) => (
<p key={post.id}>
<Link href={`/static/${post.id}`}>{post.title}</Link>
</p>
))
}
```
> 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 <pre>{JSON.stringify(data, null, 2)}</pre>
}
```
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 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 <pre>{JSON.stringify(post, null, 2)}</pre>
}
```
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
```
> 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'
export const revalidate = 60
export default async function Posts() {
const { data: posts } = await supabase.from('posts').select('id, title')
if (!posts) {
return <p>No posts found.</p>
}
return posts.map((post) => (
<p key={post.id}>
<Link href={`/static/${post.id}`}>{post.title}</Link>
</p>
))
}
```
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 <pre>{JSON.stringify(post, null, 2)}</pre>
}
```
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`.
**Posts Component**
```tsx title="app/server-rendered/page.tsx"
import Link from 'next/link'
import supabase from '../../utils/supabase'
export const revalidate = 0
export default async function Posts() {
const { data: posts } = await supabase.from('posts').select('id, title')
if (!posts) {
return <p>No posts found.</p>
}
return posts.map((post) => (
<p key={post.id}>
<Link href={`/static/${post.id}`}>{post.title}</Link>
</p>
))
}
```
**Single Post Component**
```tsx title="app/server-rendered/[id]/page.tsx"
import supabase from '../../../utils/supabase'
import { notFound } from 'next/navigation'
export const revalidate = 0
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 <pre>{JSON.stringify(post, null, 2)}</pre>
}
```
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.
2. Realtime
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! 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.
> 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<any>([])
useEffect(() => {
const fetchPosts = async () => {
const { data } = await supabase.from('posts').select()
setPosts(data)
setIsLoading(false)
}
fetchPosts()
}, [])
return isLoading ? <p>Loading</p> : <pre>{JSON.stringify(posts, null, 2)}</pre>
}
```
> 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
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/_/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 <RealtimePosts serverPosts={data} />
}
```
**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 <pre>{JSON.stringify(posts, null, 2)}</pre>
}
```
> `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 — 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 are awesome! Suspense is awesome! Async components are awesome!
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 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)
Binary file not shown.

After

Width:  |  Height:  |  Size: 1022 KiB

+1 -1
View File
@@ -74,4 +74,4 @@ STUDIO_DEFAULT_ORGANIZATION=Default Organization
STUDIO_DEFAULT_PROJECT=Default Project
STUDIO_PORT=3000
PUBLIC_REST_URL=http://localhost:8000/rest/v1/ # 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
+4 -2
View File
@@ -21,7 +21,9 @@ services:
DEFAULT_PROJECT: ${STUDIO_DEFAULT_PROJECT}
SUPABASE_URL: http://kong:8000
SUPABASE_REST_URL: ${PUBLIC_REST_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}
@@ -169,7 +171,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
@@ -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)
<img height="500px" src="https://raw.githubusercontent.com/supabase/examples/main/supabase-flutter-v1/user-management/supabase-flutter-demo.png" alt="Supabase User Management example" />
<img height="500px" src="https://raw.githubusercontent.com/supabase/supabase/master/examples/user-management/flutter-user-management/supabase-flutter-demo.png" alt="Supabase User Management example" />
## Getting Started
+2 -2
View File
@@ -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 redirects the user to the Supabase Auth callback URL where they are 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](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({
+6 -1
View File
@@ -5,7 +5,12 @@ DEFAULT_ORGANIZATION_NAME=Default Organization
DEFAULT_PROJECT_NAME=Default Project
SUPABASE_URL=http://localhost:8000
SUPABASE_REST_URL=http://localhost:8000/rest/v1/
SUPABASE_PUBLIC_URL=https://localhost:8443
SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE
SUPABASE_SERVICE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q
# TODO: evaluate these variables at runtime instead
# https://nextjs.org/docs/basic-features/environment-variables#loading-environment-variables
NEXT_PUBLIC_SITE_URL=http://localhost:3000
NEXT_PUBLIC_GOTRUE_URL=$SUPABASE_URL/auth/v1
NEXT_PUBLIC_HCAPTCHA_SITE_KEY=10000000-ffff-ffff-ffff-000000000001
@@ -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 = useFlag('mfaSso')
const canUpdateConfig = checkPermissions(PermissionAction.UPDATE, 'custom_config_gotrue')
const INITIAL_VALUES = {
@@ -187,18 +188,20 @@ const AutoSchemaForm = observer(() => {
)}
</FormSectionContent>
</FormSection>
<FormSection
header={<FormSectionLabel>Multi Factor Authentication (MFA)</FormSectionLabel>}
>
<FormSectionContent loading={!isLoaded}>
<InputNumber
id="MAX_ENROLLED_FACTORS"
size="small"
label="Maximum number of enrolled factors"
disabled={!canUpdateConfig}
/>
</FormSectionContent>
</FormSection>
{showMfaSso && (
<FormSection
header={<FormSectionLabel>Multi Factor Authentication (MFA)</FormSectionLabel>}
>
<FormSectionContent loading={!isLoaded}>
<InputNumber
id="MAX_ENROLLED_FACTORS"
size="small"
label="Maximum number of enrolled factors"
disabled={!canUpdateConfig}
/>
</FormSectionContent>
</FormSection>
)}
</FormPanel>
</>
)
@@ -11,11 +11,11 @@ const PasswordConditionsHelper = ({ password }: PasswordConditionsHelperProps) =
return (
<div className="text-sm">
<PasswordCondition title="One or more uppercase letters" isMet={hasUppercase} />
<PasswordCondition title="One or more lowercase letters" isMet={hasLowercase} />
<PasswordCondition title="One or more numbers" isMet={hasNumber} />
<PasswordCondition title="One or more special characters" isMet={hasSpecialCharacter} />
<PasswordCondition title="At least 8 characters long" isMet={isEightCharactersLong} />
<PasswordCondition title="Uppercase letter" isMet={hasUppercase} />
<PasswordCondition title="Lowercase letter" isMet={hasLowercase} />
<PasswordCondition title="Number" isMet={hasNumber} />
<PasswordCondition title="Special character (e.g. !?<>@#$%)" isMet={hasSpecialCharacter} />
<PasswordCondition title="> 7 characters" isMet={isEightCharactersLong} />
</div>
)
}
@@ -31,7 +31,7 @@ const PasswordCondition = ({ title, isMet }: PasswordConditionProps) => {
return (
<div
className={
'flex items-center gap-1 transition duration-200 ' +
'flex items-center gap-1 space-x-1.5 transition duration-200 ' +
(isMet ? 'text-scale-1100' : 'text-scale-900')
}
>
@@ -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',
})
}
@@ -16,6 +16,7 @@ const signUpSchema = passwordSchema.shape({
const SignUpForm = () => {
const { ui } = useStore()
const captchaRef = useRef<HCaptcha>(null)
const [showConditions, setShowConditions] = useState(false)
const [isSubmitted, setIsSubmitted] = useState(false)
const [passwordHidden, setPasswordHidden] = useState(true)
const [captchaToken, setCaptchaToken] = useState<string | null>(null)
@@ -107,16 +108,24 @@ const SignUpForm = () => {
placeholder="&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;"
disabled={isSubmitting}
autoComplete="new-password"
onFocus={() => setShowConditions(true)}
actions={
<Button
icon={passwordHidden ? <IconEye /> : <IconEyeOff />}
type="default"
className="!mr-1"
onClick={() => setPasswordHidden((prev) => !prev)}
/>
}
/>
<PasswordConditionsHelper password={values.password} />
<div
className={`${
showConditions ? 'max-h-[500px]' : 'max-h-[0px]'
} transition-all duration-400 overflow-y-hidden`}
>
<PasswordConditionsHelper password={values.password} />
</div>
<div className="self-center">
<HCaptcha
@@ -137,7 +146,7 @@ const SignUpForm = () => {
form="signUp-form"
htmlType="submit"
size="large"
disabled={isSubmitting}
disabled={values.password.length === 0 || isSubmitting}
loading={isSubmitting}
>
Sign Up
@@ -84,23 +84,18 @@ export const generateProductRoutes = (ref?: string, project?: ProjectBase): Rout
? buildingUrl
: `/project/${ref}/auth/users`),
},
...(IS_PLATFORM
? [
{
key: 'storage',
label: 'Storage',
icon: <IconArchive size={18} strokeWidth={2} />,
link:
ref &&
(isProjectPaused
? homeUrl
: isProjectBuilding
? buildingUrl
: `/project/${ref}/storage/buckets`),
},
]
: []),
{
key: 'storage',
label: 'Storage',
icon: <IconArchive size={18} strokeWidth={2} />,
link:
ref &&
(isProjectPaused
? homeUrl
: isProjectBuilding
? buildingUrl
: `/project/${ref}/storage/buckets`),
},
...(IS_PLATFORM
? [
{
+1 -1
View File
@@ -8,7 +8,7 @@ import { useProfile, useStore, usePermissions } from 'hooks'
import Error500 from '../../pages/500'
import { NextPageWithLayout } from 'types'
const PLATFORM_ONLY_PAGES = ['storage', 'reports', 'settings']
const PLATFORM_ONLY_PAGES = ['reports', 'settings']
export function withAuth<T>(
WrappedComponent: ComponentType<T> | NextPageWithLayout<T, T>,
+1 -4
View File
@@ -1,12 +1,9 @@
import { GoTrueClient, User } from '@supabase/gotrue-js'
export const GOTRUE_URL =
process.env.NEXT_PUBLIC_GOTRUE_URL || `${process.env.SUPABASE_URL}/auth/v1`
export const STORAGE_KEY = process.env.NEXT_PUBLIC_STORAGE_KEY || 'supabase.dashboard.auth.token'
export const auth = new GoTrueClient({
url: GOTRUE_URL,
url: process.env.NEXT_PUBLIC_GOTRUE_URL,
storageKey: STORAGE_KEY,
detectSessionInUrl: true,
})
+25 -30
View File
@@ -1,6 +1,6 @@
import { observer } from 'mobx-react-lite'
import { useEffect, useState } from 'react'
import { IconArrowRight, IconMoon, IconSun, Input, Listbox } from 'ui'
import { Button, IconArrowRight, IconMoon, IconSun, Input, Listbox } from 'ui'
import { Session } from '@supabase/supabase-js'
import { AccountLayout } from 'components/layouts'
@@ -60,46 +60,25 @@ const ProfileCard = observer(() => {
const [session, setSession] = useState<Session | null>(null)
useEffect(() => {
let cancel = false
;(async () => {
const {
data: { session },
} = await auth.getSession()
if (session) {
setSession(session)
}
if (session && !cancel) setSession(session)
})()
return () => {
cancel = true
}
}, [])
return (
<article className="max-w-4xl p-4">
<section>
<Profile />
<Profile session={session} />
</section>
{session?.user.app_metadata.provider === 'email' && (
<section>
<Panel
title={
<h5 key="panel-title" className="mb-0">
Password
</h5>
}
>
<Panel.Content>
<div>
You can change your password on the{' '}
<Link href="/reset-password">
<a className="text-blue-1000 hover:text-blue-1100 transition">
password reset page <IconArrowRight width={16} height={16} className="inline" />
</a>
</Link>
</div>
</Panel.Content>
</Panel>
</section>
)}
<section>
{/* @ts-ignore */}
<SchemaFormPanel
@@ -127,7 +106,7 @@ const ProfileCard = observer(() => {
)
})
const Profile = observer(() => {
const Profile = observer(({ session }: any) => {
const { ui } = useStore()
return (
@@ -154,6 +133,22 @@ const Profile = observer(() => {
layout="horizontal"
value={ui.profile?.primary_email ?? ''}
/>
{session?.user.app_metadata.provider === 'email' && (
<div className="text-sm grid gap-2 md:grid md:grid-cols-12 md:gap-x-4">
<div className="flex flex-col space-y-2 col-span-4 ">
<p className="text-scale-1100 break-all">Password</p>
</div>
<div className="col-span-8">
<Link href="/reset-password">
<a>
<Button type="default" size="medium">
Reset password
</Button>
</a>
</Link>
</div>
</div>
)}
</div>
</Panel.Content>
</Panel>
+4
View File
@@ -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
+2 -1
View File
@@ -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 || 'http://localhost:8000/rest/v1/',
restUrl: PROJECT_REST_URL,
}
return res.status(200).json(response)
+7 -6
View File
@@ -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 || '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,
endpoint: PROJECT_ENDPOINT,
restUrl: PROJECT_REST_URL,
defaultApiKey: process.env.SUPABASE_ANON_KEY,
serviceApiKey: process.env.SUPABASE_SERVICE_KEY,
service_api_keys: [
@@ -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,
},
},
+1 -1
View File
@@ -32,7 +32,7 @@ const JoinOrganizationPage = () => {
const { token_does_not_exist, email_match, expired_token, organization_name, invite_id } =
tokenValidationInfo || {}
const loginRedirectLink = `/?next=${encodeURIComponent(`/join?token=${token}&slug=${slug}`)}`
const loginRedirectLink = `/?returnTo=${encodeURIComponent(`/join?token=${token}&slug=${slug}`)}`
useEffect(() => {
const fetchTokenInfo = async () => {
+1 -32
View File
@@ -1,42 +1,11 @@
import SignInForm from 'components/interfaces/SignIn/SignInForm'
import SignInWithGitHub from 'components/interfaces/SignIn/SignInWithGitHub'
import { SignInLayout } from 'components/layouts'
import Connecting from 'components/ui/Loading'
import { auth } from 'lib/gotrue'
import Link from 'next/link'
import { NextRouter, useRouter } from 'next/router'
import { useEffect } from 'react'
import { NextPageWithLayout } from 'types'
// detect for redirect from 3rd party service like vercel, aws...
const isRedirectFromThirdPartyService = (router: NextRouter) => router.query.next !== undefined
const SignInPage: NextPageWithLayout = () => {
const router = useRouter()
const autoSignIn = isRedirectFromThirdPartyService(router)
useEffect(() => {
if (autoSignIn) {
const queryParams = (router.query as any) || {}
const params = new URLSearchParams(queryParams)
// trigger github signIn
auth.signInWithOAuth({
provider: 'github',
options: {
redirectTo: `${
process.env.NEXT_PUBLIC_VERCEL_ENV === 'preview'
? process.env.NEXT_PUBLIC_VERCEL_URL
: process.env.NEXT_PUBLIC_SITE_URL
}?${params.toString()}`,
},
})
}
}, [])
return autoSignIn ? (
<Connecting />
) : (
return (
<>
<div className="flex flex-col gap-5">
<SignInWithGitHub />