No files matched your search
@@ -1,53 +1,21 @@
|
||||
import providers from '../data/authProviders'
|
||||
import ButtonCard from './ButtonCard'
|
||||
import { IconPanel } from 'ui'
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function AuthProviders({ type }: { type: string }) {
|
||||
const filterProviders = providers.filter((item) => item.authType === type)
|
||||
|
||||
export default function AuthProviders() {
|
||||
return (
|
||||
<div className="grid md:grid-cols-12 gap-4">
|
||||
{providers.map((x) => (
|
||||
<div key={x.name} className="col-span-6">
|
||||
<ButtonCard to={x.href} title={x.name}>
|
||||
<div className="px-6 py-4">
|
||||
<div className="flex justify-between">
|
||||
<p className="mt-0">{x.name}</p>
|
||||
<p className="mt-0">
|
||||
{x.official ? (
|
||||
<span className={`badge badge--official`}>Official</span>
|
||||
) : (
|
||||
<span className={`badge badge--unofficial`}>Unofficial</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
|
||||
<div
|
||||
className="code-block"
|
||||
style={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
fontSize: '0.7rem',
|
||||
}}
|
||||
>
|
||||
<span>Platform:</span>
|
||||
<span>{x.platform.toString()}</span>
|
||||
</div>
|
||||
<div
|
||||
className="code-block"
|
||||
style={{
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
fontSize: '0.7rem',
|
||||
}}
|
||||
>
|
||||
<span>Self-Hosted:</span>
|
||||
<span>{x.selfHosted.toString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ButtonCard>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<>
|
||||
<div className="grid grid-cols-12 gap-10 not-prose py-8">
|
||||
{filterProviders.map((x) => (
|
||||
<Link href={`${x.href}`} key={x.name} passHref>
|
||||
<a className="col-span-6 lg:col-span-4 xl:col-span-3">
|
||||
<IconPanel title={x.name} icon={x.logo} />
|
||||
</a>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -30,12 +30,12 @@ const questions = [
|
||||
'How do I connect to my database?',
|
||||
'How do I run migrations? ',
|
||||
'How do I listen to changes in a table?',
|
||||
'How do I setup authentication?',
|
||||
'How do I set up authentication?',
|
||||
]
|
||||
|
||||
function getEdgeFunctionUrl() {
|
||||
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL.replace(/\/$/, '')
|
||||
const isPlatform = supabaseUrl.match(/(supabase\.co)|(supabase\.in)/)
|
||||
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL?.replace(/\/$/, '')
|
||||
const isPlatform = supabaseUrl?.match(/(supabase\.co)|(supabase\.in)/)
|
||||
|
||||
if (isPlatform) {
|
||||
const [schemeAndProjectId, domain, tld] = supabaseUrl.split('.')
|
||||
|
||||
@@ -25,9 +25,10 @@ interface Props {
|
||||
* expected tocList format (text, link,level).
|
||||
*
|
||||
* In tsx files, we can generate this tocList directly. For these files, we don't
|
||||
* need to parse the <a> and generate anchors
|
||||
* need to parse the <a> and generate anchors. Custom anchors are used in tsx files.
|
||||
* (see: /pages/reference/cli/config.tsx)
|
||||
*/
|
||||
const Heading: React.FC<Props> = ({ tag, parseAnchors = false, customAnchor, children }) => {
|
||||
const Heading: React.FC<Props> = ({ tag, customAnchor, children }) => {
|
||||
const HeadingTag = `${tag}` as any
|
||||
const anchor = customAnchor ? customAnchor : getAnchor(children)
|
||||
const link = `#${anchor}`
|
||||
@@ -43,7 +44,7 @@ const Heading: React.FC<Props> = ({ tag, parseAnchors = false, customAnchor, chi
|
||||
|
||||
return (
|
||||
<HeadingTag id={anchor} ref={ref} className="group scroll-mt-24">
|
||||
{parseAnchors ? removeAnchor(children) : children}
|
||||
{removeAnchor(children)}
|
||||
{anchor && (
|
||||
<a href={link} className="ml-2 opacity-0 group-hover:opacity-100 transition">
|
||||
#
|
||||
|
||||
@@ -1,34 +1,33 @@
|
||||
import { FC, useEffect, useState } from 'react'
|
||||
import createConfigCatClient from 'configcat-js'
|
||||
// import createConfigCatClient from 'configcat-js'
|
||||
import FlagContext from './FlagContext'
|
||||
|
||||
const FlagProvider: FC = ({ children }) => {
|
||||
const [store, setStore] = useState({})
|
||||
|
||||
const { Provider } = FlagContext
|
||||
|
||||
useEffect(() => {
|
||||
getFlags()
|
||||
}, [])
|
||||
// useEffect(() => {
|
||||
// getFlags()
|
||||
// }, [])
|
||||
|
||||
const getFlags = async () => {
|
||||
const setFlagValues = async () => {
|
||||
const flagValues = await client.getAllValuesAsync()
|
||||
const flagStore: any = {}
|
||||
// const getFlags = async () => {
|
||||
// const setFlagValues = async () => {
|
||||
// const flagValues = await client.getAllValuesAsync()
|
||||
// const flagStore: any = {}
|
||||
|
||||
flagValues.forEach((item: any) => {
|
||||
flagStore[item.settingKey] = item.settingValue
|
||||
})
|
||||
setStore(flagStore)
|
||||
}
|
||||
// flagValues.forEach((item: any) => {
|
||||
// flagStore[item.settingKey] = item.settingValue
|
||||
// })
|
||||
// setStore(flagStore)
|
||||
// }
|
||||
|
||||
const client = createConfigCatClient(process.env.NEXT_PUBLIC_CONFIGCAT_SDK_KEY ?? '', {
|
||||
configChanged: setFlagValues,
|
||||
pollIntervalSeconds: 600,
|
||||
})
|
||||
// const client = createConfigCatClient(process.env.NEXT_PUBLIC_CONFIGCAT_SDK_KEY ?? '', {
|
||||
// configChanged: setFlagValues,
|
||||
// pollIntervalSeconds: 600,
|
||||
// })
|
||||
|
||||
await setFlagValues()
|
||||
}
|
||||
// await setFlagValues()
|
||||
// }
|
||||
|
||||
return <Provider value={store}>{children}</Provider>
|
||||
}
|
||||
|
||||
@@ -400,7 +400,7 @@ export const database = {
|
||||
},
|
||||
{ name: 'http: RESTful Client', url: '/guides/database/extensions/http', items: [] },
|
||||
{
|
||||
name: 'PGRoonga: Multilingual Full Text Search',
|
||||
name: 'PGroonga: Multilingual Full Text Search',
|
||||
url: '/guides/database/extensions/pgroonga',
|
||||
items: [],
|
||||
},
|
||||
@@ -658,6 +658,11 @@ export const resources = {
|
||||
url: '/guides/resources/migrating-to-supabase/heroku',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
name: 'Render',
|
||||
url: '/guides/resources/migrating-to-supabase/render',
|
||||
items: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -720,6 +725,7 @@ export const migrate = {
|
||||
{ name: 'Firestore Data', url: '/guides/migrations/firestore-data', items: [] },
|
||||
{ name: 'Firebase Storage', url: '/guides/migrations/firebase-storage', items: [] },
|
||||
{ name: 'Heroku', url: '/guides/migrations/heroku', items: [] },
|
||||
{ name: 'Render', url: '/guides/migrations/render', items: [] },
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@@ -1,172 +1,193 @@
|
||||
const authProviders = [
|
||||
{
|
||||
name: 'Apple',
|
||||
// logo: '/img/libraries/dart-icon.svg',
|
||||
logo: '/docs/img/icons/apple-icon',
|
||||
href: '/guides/auth/social-login/auth-apple',
|
||||
official: true,
|
||||
supporter: 'Supabase',
|
||||
platform: true,
|
||||
selfHosted: true,
|
||||
authType: 'social',
|
||||
},
|
||||
{
|
||||
name: 'Azure',
|
||||
// logo: '/img/libraries/dart-icon.svg',
|
||||
logo: '/docs/img/icons/microsoft-icon',
|
||||
href: '/guides/auth/social-login/auth-azure',
|
||||
official: false,
|
||||
supporter: 'TBD',
|
||||
platform: true,
|
||||
selfHosted: true,
|
||||
authType: 'social',
|
||||
},
|
||||
{
|
||||
name: 'Bitbucket',
|
||||
// logo: '/img/libraries/dart-icon.svg',
|
||||
logo: '/docs/img/icons/bitbucket-icon',
|
||||
href: '/guides/auth/social-login/auth-bitbucket',
|
||||
official: true,
|
||||
supporter: 'Supabase',
|
||||
platform: true,
|
||||
selfHosted: true,
|
||||
authType: 'social',
|
||||
},
|
||||
{
|
||||
name: 'Discord',
|
||||
// logo: '/img/libraries/dart-icon.svg',
|
||||
logo: '/docs/img/icons/discord-icon',
|
||||
href: '/guides/auth/social-login/auth-discord',
|
||||
official: true,
|
||||
supporter: 'Supabase',
|
||||
platform: true,
|
||||
selfHosted: true,
|
||||
authType: 'social',
|
||||
},
|
||||
{
|
||||
name: 'Facebook',
|
||||
// logo: '/img/libraries/dart-icon.svg',
|
||||
logo: '/docs/img/icons/facebook-icon',
|
||||
href: '/guides/auth/social-login/auth-facebook',
|
||||
official: true,
|
||||
supporter: 'Supabase',
|
||||
platform: true,
|
||||
selfHosted: true,
|
||||
authType: 'social',
|
||||
},
|
||||
{
|
||||
name: 'GitHub',
|
||||
// logo: '/img/libraries/dart-icon.svg',
|
||||
logo: '/docs/img/icons/github-icon',
|
||||
href: '/guides/auth/social-login/auth-github',
|
||||
official: true,
|
||||
supporter: 'Supabase',
|
||||
platform: true,
|
||||
selfHosted: true,
|
||||
authType: 'social',
|
||||
},
|
||||
{
|
||||
name: 'GitLab',
|
||||
// logo: '/img/libraries/dart-icon.svg',
|
||||
logo: '/docs/img/icons/gitlab-icon',
|
||||
href: '/guides/auth/social-login/auth-gitlab',
|
||||
official: true,
|
||||
supporter: 'Supabase',
|
||||
platform: true,
|
||||
selfHosted: true,
|
||||
authType: 'social',
|
||||
},
|
||||
{
|
||||
name: 'Google',
|
||||
// logo: '/img/libraries/dart-icon.svg',
|
||||
logo: '/docs/img/icons/google-icon',
|
||||
href: '/guides/auth/social-login/auth-google',
|
||||
official: true,
|
||||
supporter: 'Supabase',
|
||||
platform: true,
|
||||
selfHosted: true,
|
||||
authType: 'social',
|
||||
},
|
||||
{
|
||||
name: 'Keycloak',
|
||||
logo: '/docs/img/icons/keycloak-icon',
|
||||
href: '/guides/auth/social-login/auth-keycloak',
|
||||
official: true,
|
||||
supporter: 'Supabase',
|
||||
platform: true,
|
||||
selfHosted: true,
|
||||
authType: 'social',
|
||||
},
|
||||
{
|
||||
name: 'LinkedIn',
|
||||
// logo: '/img/libraries/dart-icon.svg',
|
||||
logo: '/docs/img/icons/linkedin-icon',
|
||||
href: '/guides/auth/social-login/auth-linkedin',
|
||||
official: true,
|
||||
supporter: 'Supabase',
|
||||
platform: true,
|
||||
selfHosted: true,
|
||||
authType: 'social',
|
||||
},
|
||||
{
|
||||
name: 'MessageBird',
|
||||
// logo: '/img/libraries/dart-icon.svg',
|
||||
logo: '/docs/img/icons/messagebird-icon',
|
||||
href: '/guides/auth/phone-login/messagebird',
|
||||
official: false,
|
||||
supporter: 'MessageBird',
|
||||
platform: true,
|
||||
selfHosted: true,
|
||||
authType: 'phone'
|
||||
},
|
||||
{
|
||||
name: 'Notion',
|
||||
// logo: '/img/libraries/notion-icon.svg',
|
||||
logo: '/docs/img/icons/notion-icon',
|
||||
href: '/guides/auth/social-login/auth-notion',
|
||||
official: true,
|
||||
supporter: 'Supabase',
|
||||
platform: true,
|
||||
selfHosted: true,
|
||||
authType: 'social',
|
||||
},
|
||||
{
|
||||
name: 'Slack',
|
||||
// logo: '/img/libraries/dart-icon.svg',
|
||||
logo: '/docs/img/icons/slack-icon',
|
||||
href: '/guides/auth/social-login/auth-slack',
|
||||
official: true,
|
||||
supporter: 'Supabase',
|
||||
platform: true,
|
||||
selfHosted: true,
|
||||
authType: 'social',
|
||||
},
|
||||
{
|
||||
name: 'Spotify',
|
||||
// logo: '/img/libraries/dart-icon.svg',
|
||||
logo: '/docs/img/icons/spotify-icon',
|
||||
href: '/guides/auth/social-login/auth-spotify',
|
||||
official: true,
|
||||
supporter: 'Supabase',
|
||||
platform: true,
|
||||
selfHosted: true,
|
||||
authType: 'social',
|
||||
},
|
||||
{
|
||||
name: 'Twitter',
|
||||
// logo: '/img/libraries/dart-icon.svg',
|
||||
logo: '/docs/img/icons/twitter-icon',
|
||||
href: '/guides/auth/social-login/auth-twitter',
|
||||
official: true,
|
||||
supporter: 'Supabase',
|
||||
platform: true,
|
||||
selfHosted: true,
|
||||
authType: 'social',
|
||||
},
|
||||
{
|
||||
name: 'Twitch',
|
||||
// logo: '/img/libraries/dart-icon.svg',
|
||||
logo: '/docs/img/icons/twitch-icon',
|
||||
href: '/guides/auth/social-login/auth-twitch',
|
||||
official: true,
|
||||
supporter: 'Supabase',
|
||||
platform: true,
|
||||
selfHosted: true,
|
||||
authType: 'social',
|
||||
},
|
||||
{
|
||||
name: 'Zoom',
|
||||
// logo: '/img/libraries/dart-icon.svg',
|
||||
logo: '/docs/img/icons/zoom-icon',
|
||||
href: '/guides/auth/social-login/auth-zoom',
|
||||
official: true,
|
||||
supporter: 'Supabase',
|
||||
platform: true,
|
||||
selfHosted: true,
|
||||
authType: 'social',
|
||||
},
|
||||
{
|
||||
name: 'Twilio',
|
||||
// logo: '/img/libraries/dart-icon.svg',
|
||||
logo: '/docs/img/icons/twilio-icon',
|
||||
href: '/guides/auth/phone-login/twilio',
|
||||
official: true,
|
||||
supporter: 'Supabase',
|
||||
platform: true,
|
||||
selfHosted: true,
|
||||
authType: 'phone',
|
||||
},
|
||||
{
|
||||
name: 'Vonage',
|
||||
logo: '/docs/img/icons/vonage-icon',
|
||||
href: '/guides/auth/phone-login/vonage',
|
||||
official: false,
|
||||
supporter: 'Supabase',
|
||||
platform: true,
|
||||
selfHosted: true,
|
||||
authType: 'phone',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@ const nextConfig = {
|
||||
'github.com',
|
||||
'user-images.githubusercontent.com',
|
||||
'raw.githubusercontent.com',
|
||||
'weweb-changelog.ghost.io',
|
||||
],
|
||||
},
|
||||
experimental: {
|
||||
|
||||
@@ -257,7 +257,7 @@ Wrap an Action to check that the user has a valid session. If they're not logged
|
||||
```ts title=src/routes/posts/+page.server.ts
|
||||
import type { Actions } from './$types'
|
||||
import { getSupabase } from '@supabase/auth-helpers-sveltekit'
|
||||
import { error, invalid } from '@sveltejs/kit'
|
||||
import { error, fail } from '@sveltejs/kit'
|
||||
|
||||
export const actions: Actions = {
|
||||
createPost: async (event) => {
|
||||
@@ -276,7 +276,7 @@ export const actions: Actions = {
|
||||
.insert({ content })
|
||||
|
||||
if (createPostError) {
|
||||
return invalid(500, {
|
||||
return fail(500, {
|
||||
supabaseErrorMessage: createPostError.message,
|
||||
})
|
||||
}
|
||||
@@ -293,7 +293,7 @@ If you try to submit a form with the action `?/createPost` without a valid sessi
|
||||
|
||||
```ts
|
||||
import type { Actions } from './$types'
|
||||
import { invalid, redirect } from '@sveltejs/kit'
|
||||
import { fail, redirect } from '@sveltejs/kit'
|
||||
import { getSupabase } from '@supabase/auth-helpers-sveltekit'
|
||||
import { AuthApiError } from '@supabase/supabase-js'
|
||||
|
||||
@@ -313,14 +313,14 @@ export const actions: Actions = {
|
||||
|
||||
if (error) {
|
||||
if (error instanceof AuthApiError && error.status === 400) {
|
||||
return invalid(400, {
|
||||
return fail(400, {
|
||||
error: 'Invalid credentials.',
|
||||
values: {
|
||||
email,
|
||||
},
|
||||
})
|
||||
}
|
||||
return invalid(500, {
|
||||
return fail(500, {
|
||||
error: 'Server error. Try again later.',
|
||||
values: {
|
||||
email,
|
||||
|
||||
@@ -44,8 +44,16 @@ You can authenticate your users in several ways:
|
||||
|
||||
We provide a suite of Providers and login methods, as well as [Auth helpers](/docs/guides/auth/auth-helpers/).
|
||||
|
||||
#### Social Auth
|
||||
|
||||
<div className="container" style={{ padding: 0 }}>
|
||||
<AuthProviders />
|
||||
<AuthProviders type="social" />
|
||||
</div>
|
||||
|
||||
#### Phone Auth
|
||||
|
||||
<div className="container" style={{ padding: 0 }}>
|
||||
<AuthProviders type="phone" />
|
||||
</div>
|
||||
|
||||
### Configure third-party providers
|
||||
|
||||
@@ -209,12 +209,12 @@ curl '<SUPABASE_URL>/rest/v1/todos' \
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
JS Reference: [select()](../reference/javascript/select),
|
||||
[insert()](../reference/javascript/insert),
|
||||
[update()](../reference/javascript/update),
|
||||
[upsert()](../reference/javascript/upsert),
|
||||
[delete()](../reference/javascript/delete),
|
||||
[rpc()](../reference/javascript/rpc) (call Postgres functions).
|
||||
JS Reference: [select()](/reference/javascript/select),
|
||||
[insert()](/reference/javascript/insert),
|
||||
[update()](/reference/javascript/update),
|
||||
[upsert()](/reference/javascript/upsert),
|
||||
[delete()](/reference/javascript/delete),
|
||||
[rpc()](/reference/javascript/rpc) (call Postgres functions).
|
||||
|
||||
### GraphQL API
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ Edge Functions supports `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, and `OPTIONS`.
|
||||
|
||||
- Deno Deploy limitations
|
||||
- Deno does not support outgoing connections to ports `25`, `465`, and `587`.
|
||||
- Cannot write to File System
|
||||
- Cannot read or write to File System
|
||||
- Edge Functions
|
||||
- Local development - only one function at a time
|
||||
- Serving of HTML content is not supported (`GET` requests that return `text/html` will be rewritten to `text/plain`).
|
||||
|
||||
@@ -6,37 +6,52 @@ export const meta = {
|
||||
description: 'Understanding how database usage applies to your subscription.',
|
||||
}
|
||||
|
||||
Database storage refers to the _monthly average storage usage_, as reported by Postgres. This metric is reported in your project's [billing usage](https://app.supabase.com/project/_/settings/billing/usage) and is updated daily.
|
||||
Database size refers to the _monthly average storage usage_, as reported by Postgres. This metric is reported in your project's [billing usage](https://app.supabase.com/project/_/settings/billing/usage) and is updated daily.
|
||||
Database size is the total size of used storage from your database, whereas disk size describes the size of the underlying available storage.
|
||||
|
||||
For an instantaneous live view of the DB storage usage, you can execute in Postgres:
|
||||
For an instantaneous live view of the DB size, you can execute in Postgres:
|
||||
|
||||
```sql
|
||||
select sum(pg_database_size(pg_database.datname)) / (1024 * 1024) as db_size_mb
|
||||
from pg_database;
|
||||
select
|
||||
sum(pg_database_size (pg_database.datname)) / (1024 * 1024) as db_size_mb
|
||||
from
|
||||
pg_database;
|
||||
```
|
||||
|
||||
This value is also reported in the [database settings page](https://app.supabase.com/project/_/settings/database).
|
||||
|
||||
## Database storage management
|
||||
|
||||
Supabase uses network-attached storage to balance performance with scalability. For Pro and Enterprise projects, database storage expands ~1.5x automatically (e.g., 8GB -> 12GB) when you reach 90% of the database storage quota. Automatic database storage expansion can only occur once every six hours. Pro projects can store up to 1024TB.
|
||||
Supabase uses network-attached storage to balance performance with scalability.
|
||||
For Pro and Enterprise projects, disk size expands ~1.5x automatically (e.g., 8GB -> 12GB) when you reach 90% of the disk size.
|
||||
Automatic disk size expansion can only occur once every six hours.
|
||||
Pro projects can store up to 1024TB.
|
||||
|
||||
All projects enter read-only mode when you reach 95% of the database storage quota. In read-only mode, clients will encounter errors such as `cannot execute INSERT in a read-only transaction`. Regular operation (read-write mode) is automatically re-enabled once usage is below 95% of the database storage quota.
|
||||
All projects enter read-only mode when you reach 95% of the disk size. In read-only mode, clients will encounter errors such as `cannot execute INSERT in a read-only transaction`.
|
||||
Regular operation (read-write mode) is automatically re-enabled once usage is below 95% of the disk size.
|
||||
|
||||
If you need more than 1024TB of database storage or require multiple storage expansions in a short period of time, [contact us](https://app.supabase.com/support/new) to learn more about the Enterprise plan.
|
||||
If you need more than 1024TB of disk size or require multiple storage expansions in a short period of time, [contact us](https://app.supabase.com/support/new) to learn more about the Enterprise plan.
|
||||
|
||||
### Increasing available database storage
|
||||
### Increasing available disk size
|
||||
|
||||
1. [Upgrade to the Pro or Enterprise plan](https://app.supabase.com/project/_/settings/billing/subscription) to increase your storage quota.
|
||||
1. [Upgrade to the Pro or Enterprise plan](https://app.supabase.com/project/_/settings/billing/subscription) to increase your quota and expand your disk size automatically.
|
||||
|
||||
2. Delete data from your project's database to lower its disk usage. If your database is already in read-only mode, run the following command to change the transaction mode to read-write for your session:
|
||||
|
||||
```sql
|
||||
SET default_transaction_read_only = 'off';
|
||||
SET
|
||||
default_transaction_read_only = 'off';
|
||||
```
|
||||
|
||||
This allows you to delete data from within the session.
|
||||
|
||||
### Preoccupied Space
|
||||
|
||||
When launching a new project, your database size will be roughly ~40-60mb.
|
||||
The space is used up by preinstalled extensions, schema/data used by our services that are offered with each project and default Postgres data.
|
||||
|
||||
When installing additional extensions, even if you don't actively use them, additional database size is used.
|
||||
|
||||
## Vacuum operations
|
||||
|
||||
Postgres does not immediately reclaim the physical space used by dead tuples (i.e., deleted rows) in the DB. Instead, they are internally marked as removed until a [vacuum operation](https://www.postgresql.org/docs/current/routine-vacuuming.html) is executed.
|
||||
|
||||
@@ -135,6 +135,12 @@ export const migrationGuides = [
|
||||
href: '/guides/resources/migrating-to-supabase/heroku',
|
||||
description: 'Migrate your Heroku Postgres database to Supabase.',
|
||||
},
|
||||
{
|
||||
title: 'Render',
|
||||
icon: '/docs/img/icons/render-icon',
|
||||
href: '/guides/resources/migrating-to-supabase/render',
|
||||
description: 'Migrate your Render Postgres database to Supabase.',
|
||||
},
|
||||
]
|
||||
|
||||
export const postgres = [
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
|
||||
export const meta = {
|
||||
title: 'Migrate from Render to Supabase',
|
||||
description: 'Migrate your Render Postgres database to Supabase.',
|
||||
}
|
||||
|
||||
Render is a popular Web Hosting service in the online services category that also has a managed Postgres service. Render has a great developer experience, allowing users to deploy straight from GitHub or GitLab. This is the core of their product and they do it really well. However, when it comes to Postgres databases, it may not be the best option.
|
||||
|
||||
Supabase is one of the best free alternative to Render Postgres. Supabase provide all the backend features developers need to build a product: a Postgres database, authentication, instant APIs, edge functions, realtime subscriptions, and storage. Postgres is the core of Supabase—for example, you can use row-level security and there are more than 40 Postgres extensions available.
|
||||
|
||||
This guide demonstrates how to migrate from Render to Supabase to get the most out of Postgres while gaining access to all the features you need to build a project.
|
||||
|
||||
## Retrieve your Render database credentials [#retrieve-render-credentials]
|
||||
|
||||
1. Log in to your [Render account](https://render.com) and select the project you want to migrate.
|
||||
1. Click **Dashboard** in the menu and click in your **Postgres** database.
|
||||
1. Scroll down in the **Info** tab.
|
||||
1. Click on **PSQL Command** and edit it adding the content after `PSQL_COMMAND=`.
|
||||
|
||||

|
||||
Example:
|
||||
|
||||
```bash
|
||||
%env PSQL_COMMAND=PGPASSWORD=RgaMDfTS_password_FTPa7 psql -h dpg-a_server_in.oregon-postgres.render.com -U my_db_pxl0_user my_db_pxl0
|
||||
```
|
||||
|
||||
## Retrieve your Supabase Host [#retrieve-supabase-host]
|
||||
|
||||
1. If you're new to Supabase, [create a project](https://app.supabase.com).
|
||||
1. Go to the [Database settings](https://app.supabase.com/project/_/settings/database) for your project in the Supabase Dashboard.
|
||||
1. Under **Connection Info**, note your Host (`$SUPABASE_HOST`).
|
||||
1. Save your password or [reset it](https://app.supabase.com/project/_/settings/database) if you forgot it.
|
||||
|
||||

|
||||
|
||||
## Migrate the database
|
||||
|
||||
The fastest way to migrate your database is with the Supabase migration tool on [Google Colab](https://colab.research.google.com/github/mansueli/Supa-Migrate/blob/main/Migrate_Postgres_Supabase.ipynb). Alternatively, you can use the [pg_dump](https://www.postgresql.org/docs/current/app-pgdump.html) and [psql](https://www.postgresql.org/docs/current/app-psql.html) command line tools, which are included in a full PostgreSQL installation.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="colab"
|
||||
>
|
||||
<TabPanel id="colab" label="Migrate using Colab">
|
||||
|
||||
1. Set the environment variables (`PSQL_COMMAND`, `SUPABASE_HOST`, `SUPABASE_PASSWORD`) in the Colab notebook.
|
||||
1. Run the first two steps in [the notebook](https://colab.research.google.com/github/mansueli/Supa-Migrate/blob/main/Migrate_Postgres_Supabase.ipynb) in order. The first sets the variables and the second installs PSQL and the migration script.
|
||||
1. Run the third step to start the migration. This will take a few minutes.
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="cli" label="Migrate using CLI tools">
|
||||
|
||||
### Export your Render database to a file in console [#export-render-database]
|
||||
|
||||
Use `pg_dump` with your Render credentials to export your Render database to a file (e.g., `render_dump.sql`).
|
||||
|
||||
```bash
|
||||
pg_dump --clean --if-exists --quote-all-identifiers \
|
||||
-h $RENDER_HOST -U $RENDER_USER -d $RENDER_DATABASE \
|
||||
--no-owner --no-privileges > render_dump.sql
|
||||
```
|
||||
|
||||
### Import the database to your Supabase project [#import-database-to-supabase]
|
||||
|
||||
Use `psql` to import the Render database file to your Supabase project.
|
||||
|
||||
```bash
|
||||
psql -h $SUPA_URL -U postgres --file render_dump.sql -p 6543 -d postgres
|
||||
```
|
||||
|
||||
### Additional options
|
||||
|
||||
- To only migrate a single database schema, add the `--schema=PATTERN` parameter to your `pg_dump` command.
|
||||
- To exclude a schema: `--exclude-schema=PATTERN`.
|
||||
- To only migrate a single table: `--table=PATTERN`.
|
||||
- To exclude a table: `--exclude-table=PATTERN`.
|
||||
|
||||
Run `pg_dump --help` for a full list of options.
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
@@ -13,7 +13,6 @@ Bo Lu
|
||||
Bobbie Soedirgo
|
||||
Chase Granberry
|
||||
Chris Copplestone
|
||||
Danny Ng
|
||||
Dave Wilson
|
||||
Div Arora
|
||||
Egor Romanov
|
||||
|
||||
|
After Width: | Height: | Size: 789 KiB |
|
After Width: | Height: | Size: 764 KiB |
@@ -0,0 +1,13 @@
|
||||
<svg width="21" height="21" viewBox="0 0 21 21" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="17.4653" y="17.6948" width="2.81974" height="2.81974" fill="#46E7AA"/>
|
||||
<rect x="13.1709" y="17.6948" width="2.81974" height="2.81974" fill="#46E7AA"/>
|
||||
<rect x="13.1709" y="0.828125" width="2.81974" height="2.81974" fill="#46E7AA"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.285156 7.2979C0.285156 3.72312 3.18309 0.825195 6.75786 0.825195H11.6109V3.65042H6.75786C4.74341 3.65042 3.11038 5.28345 3.11038 7.2979V16.259H0.285156V7.2979Z" fill="#46E7AA"/>
|
||||
<rect x="8.87671" y="17.6948" width="2.81974" height="2.81974" fill="#46E7AA"/>
|
||||
<rect x="4.58228" y="17.6948" width="2.81974" height="2.81974" fill="#46E7AA"/>
|
||||
<rect x="0.288086" y="17.6948" width="2.81974" height="2.81974" fill="#46E7AA"/>
|
||||
<rect x="17.4653" y="13.478" width="2.81974" height="2.81974" fill="#46E7AA"/>
|
||||
<rect x="17.4653" y="9.26123" width="2.81974" height="2.81974" fill="#46E7AA"/>
|
||||
<rect x="17.4653" y="5.04492" width="2.81974" height="2.81974" fill="#46E7AA"/>
|
||||
<rect x="17.4653" y="0.828125" width="2.81974" height="2.81974" fill="#46E7AA"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 18 KiB |
@@ -7,6 +7,7 @@
|
||||
.DocSearch-Container {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
@apply fixed top-0 left-0 z-[200] flex h-screen w-screen flex-col p-4 backdrop-blur-sm sm:p-6 md:p-[10vh] lg:p-[12vh];
|
||||
@apply cursor-auto #{!important};
|
||||
}
|
||||
|
||||
.DocSearch-LoadingIndicator svg {
|
||||
|
||||
@@ -119,9 +119,9 @@ begin
|
||||
select
|
||||
id,
|
||||
content,
|
||||
(documents.embedding <=> query_embedding) as similarity
|
||||
1 - (documents.embedding <=> query_embedding) as similarity
|
||||
from documents
|
||||
where (documents.embedding <=> query_embedding) > similarity_threshold
|
||||
where 1 - (documents.embedding <=> query_embedding) > similarity_threshold
|
||||
order by documents.embedding <=> query_embedding
|
||||
limit match_count;
|
||||
end;
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Database space",
|
||||
"tooltip": "Billing is based on the average database size in GB throughout the billing period.",
|
||||
"title": "Database size",
|
||||
"tooltip": "Billing is based on the average daily database size in GB throughout the billing period.",
|
||||
"tiers": {
|
||||
"free": "Up to 500 MB",
|
||||
"pro": "8 GB included, then $0.125 per GB",
|
||||
|
||||
|
Before Width: | Height: | Size: 661 KiB After Width: | Height: | Size: 667 KiB |
@@ -2,7 +2,7 @@ import { Application, Router } from 'https://deno.land/x/oak/mod.ts'
|
||||
|
||||
const router = new Router()
|
||||
router
|
||||
// Note: path will be prefixed with function name
|
||||
// Note: path should be prefixed with function name
|
||||
.get('/oak-server', (context) => {
|
||||
context.response.body = 'This is an example Oak server running on Edge Functions!'
|
||||
})
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
#Use PolyScale connection string with application_name parameter to get code-free database caching
|
||||
DATABASE_URL="postgresql:/DB_USERNAME:DB_PASSWORD@psedge.global:5432/postgres?application_name=POLYSCALE_CACHE_ID"
|
||||
@@ -0,0 +1,18 @@
|
||||
# postgres-on-the-edge
|
||||
|
||||
This function allows you to access your Supabase database directly via TCP from an edge function.
|
||||
|
||||
You can add a global cache to your database for fast access to your data, globally by simply connecting your Supabase database to [PolyScale](https://polyscale.ai).
|
||||
|
||||
## Setup
|
||||
1. Rename `.env.example` to `.env`
|
||||
2. Insert Supabase database connection string for `DATABASE_URL` in `.env` file
|
||||
3. (Optional) Create PolyScale cache - see [instructions](https://supabase.com/docs/guides/integrations/polyscale) for adding a PolyScale cache.
|
||||
4. Replace `DATABASE_URL` with PolyScale connection string.
|
||||
|
||||
## Deploy
|
||||
|
||||
1. Run `supabase functions deploy --no-verify-jwt postgres-on-the-edge`
|
||||
2. Run `supabase secrets set --env-file supabase/functions/postgres-on-the-edge/.env`
|
||||
|
||||
Learn more about creating this function [here](https://www.youtube.com/watch?v=cl7EuF1-RsY).
|
||||
@@ -0,0 +1,43 @@
|
||||
import * as postgres from 'https://deno.land/x/postgres@v0.14.2/mod.ts'
|
||||
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
|
||||
|
||||
// Get the connection string from the environment variable "DATABASE_URL"
|
||||
const databaseUrl = Deno.env.get('DATABASE_URL')!
|
||||
|
||||
// Create a database pool with three connections that are lazily established
|
||||
const pool = new postgres.Pool(databaseUrl, 3, true)
|
||||
|
||||
serve(async (_req) => {
|
||||
try {
|
||||
// Grab a connection from the pool
|
||||
const connection = await pool.connect()
|
||||
|
||||
try {
|
||||
// Run a query
|
||||
const result = await connection.queryObject`SELECT * FROM animals`
|
||||
const animals = result.rows // [{ id: 1, name: "Lion" }, ...]
|
||||
console.log(animals)
|
||||
|
||||
// Encode the result as pretty printed JSON
|
||||
const body = JSON.stringify(
|
||||
animals,
|
||||
(_key, value) => (typeof value === 'bigint' ? value.toString() : value),
|
||||
2
|
||||
)
|
||||
|
||||
// Return the response with the correct content type header
|
||||
return new Response(body, {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
},
|
||||
})
|
||||
} finally {
|
||||
// Release the connection back into the pool
|
||||
connection.release()
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
return new Response(String(err?.message ?? err), { status: 500 })
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,36 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
@@ -0,0 +1,81 @@
|
||||
# Supabase Realtime Presence API Sample Program
|
||||
|
||||
This is an example program for Supabase Realtime Presence APIs.
|
||||
User get authenticated using Supabase Auth API. Once Logged-in you can see which users are 'present' and viewing the page.
|
||||
|
||||
- Frontend:
|
||||
- Next.js.
|
||||
- [Supabase.js v2 (realtime presence support)](https://supabase.io/docs/library/getting-started)
|
||||
- Backend:
|
||||
- [app.supabase.io](https://app.supabase.io/): hosted postgres database with realtime support.
|
||||
|
||||
## Real time Presence APIs used.
|
||||
|
||||
This program shows usage of channel presence API calls such as , channel.on('presence', ...), channel.subscribe function usage.
|
||||
|
||||
## Getting Started
|
||||
|
||||
### 1. Create new project
|
||||
|
||||
Sign up to Supabase - [https://app.supabase.io](https://app.supabase.io) and create a new project. Wait for your database to start.
|
||||
|
||||
### 2. Run "User Management Starter" Quickstart
|
||||
|
||||
This will create user tables and profile tables for user management.
|
||||
|
||||
### 3. Get the URL and Key
|
||||
|
||||
Go to the Project Settings (the cog icon), open the API tab, and find your API URL and `anon` key, you'll need these in the next step.
|
||||
|
||||
The `anon` key is your client-side API key. It allows "anonymous access" to your database, until the user has logged in. Once they have logged in, the keys will switch to the user's own login token.
|
||||
|
||||

|
||||
|
||||
### 4. Pull this example git repository
|
||||
|
||||
`git clone <<this repository url>> `
|
||||
|
||||
### 5. Create a .env.local file
|
||||
|
||||
Create a .env.local file and add following environment variables.
|
||||
|
||||
```
|
||||
NEXT_PUBLIC_SUPABASE_URL=<<insert-your-db-url-here>>
|
||||
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=<<insert-your-anon-key-here>>
|
||||
```
|
||||
|
||||
### 5. Now run the development server!
|
||||
|
||||
Now run
|
||||
First, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
|
||||
## How to Test?
|
||||
|
||||
When you visit http://localhost:3000, you will be redirected to auth login/signup screen.
|
||||
|
||||
Signup if you haven't and then login.
|
||||
|
||||
You will see your email listed on the page.
|
||||
|
||||
Open another browser window. And sign-in as another user.
|
||||
|
||||
As you login from other browser window, you will see list of current users updated.
|
||||
|
||||
## Deployment
|
||||
|
||||
Since this is next.js application, simplest method to deploy this repository is on Vercel.
|
||||
|
||||
## Conclusion/Next Steps
|
||||
|
||||
- Need to implement Profile page
|
||||
- Need to implement ability to upload User Avatars
|
||||
- Need ability to read user avatar from social media.
|
||||
@@ -0,0 +1,7 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: false,
|
||||
swcMinify: true,
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "nextjs-auth-presence",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"format": "prettier --write \"pages/**/*.{js,jsx,ts,tsx,css,md,json}\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@supabase/auth-helpers-nextjs": "^0.5.4",
|
||||
"@supabase/auth-helpers-react": "^0.3.1",
|
||||
"@supabase/auth-ui-react": "^0.2.7",
|
||||
"@supabase/supabase-js": "^2.7.1",
|
||||
"next": "^13.1.6",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "18.11.0",
|
||||
"@types/react": "18.0.28",
|
||||
"@types/react-dom": "18.0.10",
|
||||
"eslint": "8.25.0",
|
||||
"eslint-config-next": "12.3.1",
|
||||
"typescript": "4.8.4"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { createBrowserSupabaseClient, Session } from '@supabase/auth-helpers-nextjs'
|
||||
import { SessionContextProvider } from '@supabase/auth-helpers-react'
|
||||
import type { AppProps } from 'next/app'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useEffect, useState } from 'react'
|
||||
import '../styles/globals.css'
|
||||
|
||||
function MyApp({
|
||||
Component,
|
||||
pageProps,
|
||||
}: AppProps<{
|
||||
initialSession: Session
|
||||
}>) {
|
||||
const router = useRouter()
|
||||
const [supabaseClient] = useState(() => createBrowserSupabaseClient())
|
||||
|
||||
useEffect(() => {
|
||||
const {
|
||||
data: { subscription },
|
||||
} = supabaseClient.auth.onAuthStateChange((event, session) => {
|
||||
switch (event) {
|
||||
case 'SIGNED_IN':
|
||||
router.push('/')
|
||||
return
|
||||
case 'SIGNED_OUT':
|
||||
router.push('/login')
|
||||
return
|
||||
}
|
||||
})
|
||||
return subscription.unsubscribe
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<SessionContextProvider
|
||||
supabaseClient={supabaseClient}
|
||||
initialSession={pageProps.initialSession}
|
||||
>
|
||||
<Component {...pageProps} />
|
||||
</SessionContextProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default MyApp
|
||||
@@ -0,0 +1,80 @@
|
||||
import { createServerSupabaseClient } from '@supabase/auth-helpers-nextjs'
|
||||
import { useSupabaseClient, useUser } from '@supabase/auth-helpers-react'
|
||||
import { RealtimePresenceState } from '@supabase/supabase-js'
|
||||
import type { GetServerSidePropsContext, NextPage } from 'next'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
const HomePage: NextPage = () => {
|
||||
const supabaseClient = useSupabaseClient()
|
||||
const this_user = useUser()
|
||||
const [userState, setUserState] = useState<RealtimePresenceState>({})
|
||||
|
||||
useEffect(() => {
|
||||
console.log('user: ', this_user)
|
||||
|
||||
const channel = supabaseClient.channel('online-users', {
|
||||
config: {
|
||||
presence: {
|
||||
key: this_user?.email ? this_user?.email : 'Unknown',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
channel.on('presence', { event: 'sync' }, () => {
|
||||
const presentState = channel.presenceState()
|
||||
|
||||
console.log('inside presence: ', presentState)
|
||||
|
||||
setUserState({ ...presentState })
|
||||
})
|
||||
|
||||
channel.on('presence', { event: 'join' }, ({ newPresences }) => {
|
||||
console.log('New users have joined: ', newPresences)
|
||||
})
|
||||
|
||||
channel.subscribe(async (status) => {
|
||||
if (status === 'SUBSCRIBED') {
|
||||
const status = await channel.track({
|
||||
user_name: this_user?.email ? this_user?.email : 'Unknown',
|
||||
})
|
||||
console.log('status: ', status)
|
||||
}
|
||||
})
|
||||
}, [])
|
||||
return (
|
||||
<>
|
||||
<button onClick={() => supabaseClient.auth.signOut()}>Sign out</button>
|
||||
|
||||
<p> List of Currently Logged in Users: </p>
|
||||
{Object.keys(userState).map((key) => (
|
||||
<p key={key}>Hi {key}</p>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
||||
// Create authenticated Supabase Client
|
||||
const supabase = createServerSupabaseClient(ctx)
|
||||
// Check if we have a session
|
||||
const {
|
||||
data: { session },
|
||||
} = await supabase.auth.getSession()
|
||||
|
||||
if (!session)
|
||||
return {
|
||||
redirect: {
|
||||
destination: '/login',
|
||||
permanent: false,
|
||||
},
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
initialSession: session,
|
||||
user: session.user,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default HomePage
|
||||
@@ -0,0 +1,31 @@
|
||||
import { useSupabaseClient, useUser } from '@supabase/auth-helpers-react'
|
||||
import { Auth, ThemeSupa } from '@supabase/auth-ui-react'
|
||||
import type { NextPage } from 'next'
|
||||
import styles from '../styles/Home.module.css'
|
||||
|
||||
const LoginPage: NextPage = () => {
|
||||
const supabaseClient = useSupabaseClient()
|
||||
const user = useUser()
|
||||
|
||||
if (!user) {
|
||||
return (
|
||||
<main className={styles.main}>
|
||||
<Auth
|
||||
redirectTo="http://localhost:3000/"
|
||||
appearance={{ theme: ThemeSupa }}
|
||||
supabaseClient={supabaseClient}
|
||||
/>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<button onClick={() => supabaseClient.auth.signOut()}>Sign out</button>
|
||||
<p>user:</p>
|
||||
<pre>{JSON.stringify(user, null, 2)}</pre>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default LoginPage
|
||||
@@ -0,0 +1,39 @@
|
||||
import { createServerSupabaseClient, User } from '@supabase/auth-helpers-nextjs'
|
||||
import { GetServerSidePropsContext } from 'next'
|
||||
import Link from 'next/link'
|
||||
|
||||
export default function Profile({ user }: { user: User }) {
|
||||
return (
|
||||
<>
|
||||
<p>
|
||||
[<Link href="/">Home</Link>] | [<Link href="/protected-page">supabaseServerClient</Link>]
|
||||
</p>
|
||||
<div>Hello {user.email}</div>
|
||||
<pre>{JSON.stringify(user, null, 2)}</pre>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
||||
// Create authenticated Supabase Client
|
||||
const supabase = createServerSupabaseClient(ctx)
|
||||
// Check if we have a session
|
||||
const {
|
||||
data: { session },
|
||||
} = await supabase.auth.getSession()
|
||||
|
||||
if (!session)
|
||||
return {
|
||||
redirect: {
|
||||
destination: '/login',
|
||||
permanent: false,
|
||||
},
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
initialSession: session,
|
||||
user: session.user,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 25 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg width="283" height="64" viewBox="0 0 283 64" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" fill="#000"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,2 @@
|
||||
NEXT_PUBLIC_SUPABASE_URL="replace-this-with-your-supabase-instance"
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY="replace-this-with-your-supabasedb-anon-key"
|
||||
@@ -0,0 +1,129 @@
|
||||
.container {
|
||||
padding: 0 2rem;
|
||||
}
|
||||
|
||||
.main {
|
||||
min-height: 100vh;
|
||||
padding: 4rem 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
padding: 2rem 0;
|
||||
border-top: 1px solid #eaeaea;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.title a {
|
||||
color: #0070f3;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.title a:hover,
|
||||
.title a:focus,
|
||||
.title a:active {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
line-height: 1.15;
|
||||
font-size: 4rem;
|
||||
}
|
||||
|
||||
.title,
|
||||
.description {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 4rem 0;
|
||||
line-height: 1.5;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.code {
|
||||
background: #fafafa;
|
||||
border-radius: 5px;
|
||||
padding: 0.75rem;
|
||||
font-size: 1.1rem;
|
||||
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
|
||||
Bitstream Vera Sans Mono, Courier New, monospace;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.card {
|
||||
margin: 1rem;
|
||||
padding: 1.5rem;
|
||||
text-align: left;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
border: 1px solid #eaeaea;
|
||||
border-radius: 10px;
|
||||
transition: color 0.15s ease, border-color 0.15s ease;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.card:hover,
|
||||
.card:focus,
|
||||
.card:active {
|
||||
color: #0070f3;
|
||||
border-color: #0070f3;
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
margin: 0 0 1rem 0;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.card p {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 1em;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.grid {
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.card,
|
||||
.footer {
|
||||
border-color: #222;
|
||||
}
|
||||
.code {
|
||||
background: #111;
|
||||
}
|
||||
.logo img {
|
||||
filter: invert(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
html,
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
html {
|
||||
color-scheme: dark;
|
||||
}
|
||||
body {
|
||||
color: white;
|
||||
background: black;
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 50 KiB |
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
@@ -14115,20 +14115,6 @@
|
||||
"resolved": "packages/config",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/configcat-common": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/configcat-common/-/configcat-common-5.3.0.tgz",
|
||||
"integrity": "sha512-yM9SNuG8XQa0aHNK5eUhtzAD+a6yFEOQw0qg6f/ZvIWGHkY+7FYvDbSkvMHW1ERQaUmAIt2QLZB6rMJzFCDCmg=="
|
||||
},
|
||||
"node_modules/configcat-js": {
|
||||
"version": "5.10.0",
|
||||
"resolved": "https://registry.npmjs.org/configcat-js/-/configcat-js-5.10.0.tgz",
|
||||
"integrity": "sha512-ta827/ONGd9sIKIRItQPS+URTb90BYjy+YXv5F0dF9T0X7tUZWt0b9V4Liy2Vlq+VaJR3DqrjyZyeD9vG7Z0Bg==",
|
||||
"deprecated": "Please upgrade to version 6 or higher.",
|
||||
"dependencies": {
|
||||
"configcat-common": "^5.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/console-browserify": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz",
|
||||
@@ -38931,7 +38917,7 @@
|
||||
"clipboard": "^2.0.8",
|
||||
"common": "*",
|
||||
"config": "*",
|
||||
"configcat-js": "^5.6.0",
|
||||
"configcat-js": "^7.0.0",
|
||||
"dayjs": "^1.11.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"generate-password": "^1.7.0",
|
||||
@@ -39196,6 +39182,23 @@
|
||||
"postcss": "^8.1.0"
|
||||
}
|
||||
},
|
||||
"studio/node_modules/configcat-common": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/configcat-common/-/configcat-common-7.0.1.tgz",
|
||||
"integrity": "sha512-9SxX4/6NzK1l+2iDWLyr/0tsli/eKzSJhBIepptF+GPy/7CprnA0coQz8fF8Gx1IF3wBZsJg0vRaC3LtNa0vRg==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.1"
|
||||
}
|
||||
},
|
||||
"studio/node_modules/configcat-js": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/configcat-js/-/configcat-js-7.0.0.tgz",
|
||||
"integrity": "sha512-ECVS5rV4wzU+r+81Fc1Rho1pvbSMpZXUKpO/Ei0Ms+Qmo21U8x1NVMWF0hIplHmDdEsCtEIa5AgXVceDURGAuA==",
|
||||
"dependencies": {
|
||||
"configcat-common": "^7.0.1",
|
||||
"tslib": "^2.4.1"
|
||||
}
|
||||
},
|
||||
"studio/node_modules/d3-array": {
|
||||
"version": "2.12.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz",
|
||||
@@ -49533,19 +49536,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"configcat-common": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/configcat-common/-/configcat-common-5.3.0.tgz",
|
||||
"integrity": "sha512-yM9SNuG8XQa0aHNK5eUhtzAD+a6yFEOQw0qg6f/ZvIWGHkY+7FYvDbSkvMHW1ERQaUmAIt2QLZB6rMJzFCDCmg=="
|
||||
},
|
||||
"configcat-js": {
|
||||
"version": "5.10.0",
|
||||
"resolved": "https://registry.npmjs.org/configcat-js/-/configcat-js-5.10.0.tgz",
|
||||
"integrity": "sha512-ta827/ONGd9sIKIRItQPS+URTb90BYjy+YXv5F0dF9T0X7tUZWt0b9V4Liy2Vlq+VaJR3DqrjyZyeD9vG7Z0Bg==",
|
||||
"requires": {
|
||||
"configcat-common": "^5.3.0"
|
||||
}
|
||||
},
|
||||
"console-browserify": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz",
|
||||
@@ -65797,7 +65787,7 @@
|
||||
"clipboard": "^2.0.8",
|
||||
"common": "*",
|
||||
"config": "*",
|
||||
"configcat-js": "^5.6.0",
|
||||
"configcat-js": "^7.0.0",
|
||||
"dayjs": "^1.11.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"generate-password": "^1.7.0",
|
||||
@@ -65960,6 +65950,23 @@
|
||||
"postcss-value-parser": "^4.2.0"
|
||||
}
|
||||
},
|
||||
"configcat-common": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/configcat-common/-/configcat-common-7.0.1.tgz",
|
||||
"integrity": "sha512-9SxX4/6NzK1l+2iDWLyr/0tsli/eKzSJhBIepptF+GPy/7CprnA0coQz8fF8Gx1IF3wBZsJg0vRaC3LtNa0vRg==",
|
||||
"requires": {
|
||||
"tslib": "^2.4.1"
|
||||
}
|
||||
},
|
||||
"configcat-js": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/configcat-js/-/configcat-js-7.0.0.tgz",
|
||||
"integrity": "sha512-ECVS5rV4wzU+r+81Fc1Rho1pvbSMpZXUKpO/Ei0Ms+Qmo21U8x1NVMWF0hIplHmDdEsCtEIa5AgXVceDURGAuA==",
|
||||
"requires": {
|
||||
"configcat-common": "^7.0.1",
|
||||
"tslib": "^2.4.1"
|
||||
}
|
||||
},
|
||||
"d3-array": {
|
||||
"version": "2.12.1",
|
||||
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz",
|
||||
|
||||
@@ -18,8 +18,7 @@ function classNames(...classes: any) {
|
||||
return classes.filter(Boolean).join(' ')
|
||||
}
|
||||
|
||||
export interface Props
|
||||
extends Omit<React.InputHTMLAttributes<HTMLButtonElement>, 'size'> {
|
||||
export interface Props extends Omit<React.InputHTMLAttributes<HTMLButtonElement>, 'size'> {
|
||||
className?: string
|
||||
children: React.ReactNode
|
||||
descriptionText?: string
|
||||
@@ -73,14 +72,8 @@ function Listbox({
|
||||
|
||||
const triggerRef = useRef<HTMLButtonElement>(null)
|
||||
|
||||
const {
|
||||
formContextOnChange,
|
||||
values,
|
||||
errors,
|
||||
handleBlur,
|
||||
touched,
|
||||
fieldLevelValidation,
|
||||
} = useFormContext()
|
||||
const { formContextOnChange, values, errors, handleBlur, touched, fieldLevelValidation } =
|
||||
useFormContext()
|
||||
|
||||
if (values && !value) {
|
||||
defaultValue = values[id || name]
|
||||
@@ -158,7 +151,7 @@ function Listbox({
|
||||
/*
|
||||
* if no selected value (including a `defaultvalue`), then use first child
|
||||
*/
|
||||
setSelectedNode(content[0].props)
|
||||
setSelectedNode(content[0]?.props)
|
||||
return
|
||||
}
|
||||
}, [selected])
|
||||
@@ -257,9 +250,7 @@ function Listbox({
|
||||
className={__styles.options_container}
|
||||
>
|
||||
<div>
|
||||
<SelectContext.Provider
|
||||
value={{ onChange: handleOnChange, selected }}
|
||||
>
|
||||
<SelectContext.Provider value={{ onChange: handleOnChange, selected }}>
|
||||
{children}
|
||||
</SelectContext.Provider>
|
||||
</div>
|
||||
@@ -313,9 +304,7 @@ function SelectOption({
|
||||
<div className={__styles.option_inner}>
|
||||
{addOnBefore && addOnBefore({ active, selected })}
|
||||
<span>
|
||||
{typeof children === 'function'
|
||||
? children({ active, selected })
|
||||
: children}
|
||||
{typeof children === 'function' ? children({ active, selected }) : children}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -326,10 +315,7 @@ function SelectOption({
|
||||
active ? __styles.option_check_active : ''
|
||||
)}
|
||||
>
|
||||
<IconCheck
|
||||
className={__styles.option_check_icon}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<IconCheck className={__styles.option_check_icon} aria-hidden="true" />
|
||||
</span>
|
||||
) : null}
|
||||
</DropdownMenuPrimitive.Item>
|
||||
|
||||
@@ -244,7 +244,7 @@ functions:
|
||||
options: {
|
||||
redirectTo: 'https://example.com/welcome'
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
- id: sign-in-with-scopes
|
||||
name: Sign in with scopes
|
||||
|
||||
@@ -287,7 +287,7 @@ functions:
|
||||
options: {
|
||||
redirectTo: 'https://example.com/welcome'
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
- id: sign-in-with-scopes
|
||||
name: Sign in with scopes
|
||||
|
||||
@@ -55,7 +55,7 @@ functions:
|
||||
- If **Confirm email** is enabled, a `user` is returned but `session` is null.
|
||||
- If **Confirm email** is disabled, both a `user` and a `session` are returned.
|
||||
- By default, when the user confirms their email address, they are redirected to the [`SITE_URL`](https://supabase.com/docs/reference/auth/config#site_url). You can modify your `SITE_URL` or add additional redirect URLs in [your project](https://app.supabase.com/project/_/auth/url-configuration).
|
||||
- If signUp() is called for an existing confirmed user:
|
||||
- If sign_up() is called for an existing confirmed user:
|
||||
- If **Confirm email** is enabled in [your project](https://app.supabase.com/project/_/auth/providers), an obfuscated/fake user object is returned.
|
||||
- If **Confirm email** is disabled, the error message, `User already registered` is returned.
|
||||
- To fetch the currently logged-in user, refer to [`getUser()`](/docs/reference/javascript/auth-getuser).
|
||||
@@ -64,26 +64,131 @@ functions:
|
||||
name: Sign up
|
||||
code: |
|
||||
```
|
||||
res = supabase.auth.signUp({
|
||||
email: 'example@email.com',
|
||||
password: 'example-password',
|
||||
res = supabase.auth.sign_up({
|
||||
"email": 'example@email.com',
|
||||
"password": 'example-password',
|
||||
})
|
||||
```
|
||||
- id: sign-up-with-additional-user-metadata
|
||||
name: Sign up with additional user metadata
|
||||
code: |
|
||||
```
|
||||
res = supabase.auth.signUp({
|
||||
email: 'example@email.com',
|
||||
password: 'example-password',
|
||||
options: {
|
||||
data: {
|
||||
first_name: 'John',
|
||||
age: 27,
|
||||
res = supabase.auth.sign_up({
|
||||
"email": 'example@email.com',
|
||||
"password": 'example-password',
|
||||
"options": {
|
||||
"data": {
|
||||
"first_name": 'John',
|
||||
"age": 27,
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
- id: sign-in-with-password
|
||||
title: 'sign_in_with_password'
|
||||
notes: |
|
||||
- Requires either an email and password or a phone number and password.
|
||||
examples:
|
||||
- id: sign-in-with-email-and-password
|
||||
name: Sign in with email and password
|
||||
isSpotlight: true
|
||||
code: |
|
||||
```
|
||||
data = supabase.auth.sign_in_with_password({"email": "j0@supabase.io", "password": "testsupabasenow"})
|
||||
```
|
||||
- id: sign-in-with-phone-and-password
|
||||
name: Sign in with phone and password
|
||||
isSpotlight: false
|
||||
code: |
|
||||
```
|
||||
data = supabase.auth.sign_in_with_password({"phone": "+1234566", password": "testsupabasenow"})
|
||||
# After receiving a SMS with a OTP.
|
||||
data = supabase.auth.verify_otp({
|
||||
"phone": '+13334445555',
|
||||
"token": '123456',
|
||||
})
|
||||
```
|
||||
- id: sign-in-with-otp
|
||||
title: 'sign_in_with_otp'
|
||||
notes: |
|
||||
- Requires either an email or phone number.
|
||||
- This method is used for passwordless sign-ins where a OTP is sent to the user's email or phone number.
|
||||
- If the user doesn't exist, `sign_in_with_otp()` will signup the user instead. To restrict this behavior, you can set `should_create_user` in `SignInWithPasswordlessCredentials.options` to `false`.
|
||||
- If you're using an email, you can configure whether you want the user to receive a magiclink or a OTP.
|
||||
- If you're using phone, you can configure whether you want the user to receive a OTP.
|
||||
- The magic link's destination URL is determined by the [`SITE_URL`](/docs/reference/auth/config#site_url).
|
||||
- See [redirect URLs and wildcards](/docs/guides/auth/overview#redirect-urls-and-wildcards) to add additional redirect URLs to your project.
|
||||
- Magic links and OTPs share the same implementation. To send users a one-time code instead of a magic link, [modify the magic link email template](https://app.supabase.com/project/_/auth/templates) to include `{{ .Token }}` instead of `{{ .ConfirmationURL }}`.
|
||||
examples:
|
||||
- id: sign-in-with-email
|
||||
name: Sign in with email
|
||||
isSpotlight: true
|
||||
description: The user will be sent an email which contains either a magiclink or a OTP or both. By default, a given user can only request a OTP once every 60 seconds.
|
||||
code: |
|
||||
```
|
||||
data = supabase.auth.sign_in_with_otp({
|
||||
"email": 'example@email.com',
|
||||
"options": {
|
||||
"email_redirect_to": 'https://example.com/welcome'
|
||||
}
|
||||
})
|
||||
```
|
||||
- id: sign-in-with-sms-otp
|
||||
name: Sign in with SMS OTP
|
||||
isSpotlight: false
|
||||
description: The user will be sent a SMS which contains a OTP. By default, a given user can only request a OTP once every 60 seconds.
|
||||
code: |
|
||||
```
|
||||
data = supabase.auth.sign_in_with_otp({
|
||||
phone: '+13334445555',
|
||||
})
|
||||
```
|
||||
- id: sign-in-with-oauth
|
||||
title: 'sign_in_with_oauth'
|
||||
notes: |
|
||||
- This method is used for signing in using a third-party provider.
|
||||
- Supabase supports many different [third-party providers](https://supabase.com/docs/guides/auth#providers).
|
||||
examples:
|
||||
- id: sign-in-using-a-third-party-provider
|
||||
name: Sign in using a third-party provider
|
||||
isSpotlight: true
|
||||
code: |
|
||||
```
|
||||
data = supabase.auth.sign_in_with_oauth({
|
||||
"provider": 'github'
|
||||
})
|
||||
```
|
||||
- id: sign-in-using-a-third-party-provider-with-redirect
|
||||
name: Sign in using a third-party provider with redirect
|
||||
isSpotlight: false
|
||||
description: |
|
||||
- When the third-party provider successfully authenticates the user, the provider redirects the user to the URL specified in the `redirectTo` parameter. This parameter defaults to the [`SITE_URL`](/docs/reference/auth/config#site_url). It does not redirect the user immediately after invoking this method.
|
||||
- See [redirect URLs and wildcards](/docs/guides/auth/overview#redirect-urls-and-wildcards) to add additional redirect URLs to your project.
|
||||
code: |
|
||||
```
|
||||
data = supabase.auth.sign_in_with_oauth({
|
||||
"provider": 'github',
|
||||
"options": {
|
||||
"redirect_to": 'https://example.com/welcome'
|
||||
}
|
||||
})
|
||||
```
|
||||
- id: sign-in-with-scopes
|
||||
name: Sign in with scopes
|
||||
isSpotlight: false
|
||||
description: |
|
||||
If you need additional data from an OAuth provider, you can include a space-separated list of scopes in your request to get back an OAuth provider token.
|
||||
You may also need to specify the scopes in the provider's OAuth app settings, depending on the provider. The list of scopes will be documented by the third-party provider you are using and specifying scopes will enable you to use the OAuth provider token to call additional APIs supported by the third-party provider to get more information.
|
||||
code: |
|
||||
```
|
||||
data = supabase.auth.sign_in_with_oauth({
|
||||
"provider": 'github',
|
||||
"options": {
|
||||
"scopes": 'repo gist notifications'
|
||||
}
|
||||
})
|
||||
oauth_token = data.session.provider_token # use to access provider API
|
||||
```
|
||||
- id: sign-out
|
||||
title: 'sign_out()'
|
||||
notes: |
|
||||
@@ -96,10 +201,10 @@ functions:
|
||||
res = supabase.auth.sign_out()
|
||||
```
|
||||
- id: verify-otp
|
||||
title: 'verify_otp()'
|
||||
title: 'verify_otp'
|
||||
notes: |
|
||||
- The `verifyOtp` method takes in different verification types. If a phone number is used, the type can either be `sms` or `phone_change`. If an email address is used, the type can be one of the following: `signup`, `magiclink`, `recovery`, `invite` or `email_change`.
|
||||
- The verification type used should be determined based on the corresponding auth method called before `verifyOtp` to sign up / sign-in a user.
|
||||
- The `verify_otp` method takes in different verification types. If a phone number is used, the type can either be `sms` or `phone_change`. If an email address is used, the type can be one of the following: `signup`, `magiclink`, `recovery`, `invite` or `email_change`.
|
||||
- The verification type used should be determined based on the corresponding auth method called before `verify_otp` to sign up / sign-in a user.
|
||||
examples:
|
||||
- id: verify-sms-one-time-password
|
||||
name: Verify SMS One-Time Password (OTP)
|
||||
@@ -107,15 +212,35 @@ functions:
|
||||
```
|
||||
res = supabase.auth.verify_otp(phone, token)
|
||||
```
|
||||
|
||||
- id: get-session
|
||||
title: 'get_session_from_url'
|
||||
- id: get-user
|
||||
title: 'get_user'
|
||||
notes: |
|
||||
- This method gets the user object from the current session.
|
||||
- Fetches the user object from the database instead of local session.
|
||||
examples:
|
||||
- id: get-session
|
||||
name: Get the session data from URL
|
||||
- id: get-the-logged-in-user-with-the-current-existing-session
|
||||
name: Get the logged in user with the current existing session
|
||||
isSpotlight: true
|
||||
code: |
|
||||
```
|
||||
res = supabase.auth.get_session_from_url(url)
|
||||
data = supabase.auth.get_user()
|
||||
```
|
||||
- id: get-the-logged-in-user-with-a-custom-access-token-jwt
|
||||
name: Get the logged in user with a custom access token jwt
|
||||
isSpotlight: false
|
||||
code: |
|
||||
```
|
||||
data = supabase.auth.get_user(jwt)
|
||||
```
|
||||
|
||||
- id: get-session
|
||||
title: 'get_session'
|
||||
examples:
|
||||
- id: get-session
|
||||
name: Get the session data
|
||||
code: |
|
||||
```
|
||||
res = supabase.auth.get_session()
|
||||
```
|
||||
|
||||
- id: set-session
|
||||
@@ -132,10 +257,10 @@ functions:
|
||||
description: Sets the session data from refresh_token and returns current session or an error if the refresh_token is invalid.
|
||||
code: |
|
||||
```
|
||||
res = supabase.auth.set_session(refresh_token)
|
||||
res = supabase.auth.set_session({"access_token": access_token, "refresh_token": refresh_token)
|
||||
```
|
||||
- id: refresh-session
|
||||
title: 'refresh_ession()'
|
||||
title: 'refresh_session()'
|
||||
notes: |
|
||||
- This method will refresh the session whether the current one is expired or not.
|
||||
- Both examples destructure `user` and `session` from `data`. This is not required; so `const { data, error } =` is also valid.
|
||||
@@ -158,7 +283,7 @@ functions:
|
||||
```
|
||||
r = supabase
|
||||
.from('countries')
|
||||
.select("*")
|
||||
.select("*").execute()
|
||||
```
|
||||
- id: selecting-specific-columns
|
||||
name: Selecting specific columns
|
||||
@@ -166,7 +291,7 @@ functions:
|
||||
```
|
||||
r = await supabase
|
||||
.from('countries')
|
||||
.select('name')
|
||||
.select('name').execute()
|
||||
```
|
||||
|
||||
- id: invoke
|
||||
|
||||
@@ -203,11 +203,10 @@ function isFilterSql(filter: Filter) {
|
||||
|
||||
function filterLiteral(value: any) {
|
||||
if (typeof value === 'string') {
|
||||
const temp = value.trim()
|
||||
if (temp?.startsWith('ARRAY[') && temp?.endsWith(']')) {
|
||||
return temp
|
||||
if (value?.startsWith('ARRAY[') && value?.endsWith(']')) {
|
||||
return value
|
||||
} else {
|
||||
return literal(temp)
|
||||
return literal(value)
|
||||
}
|
||||
}
|
||||
return value
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { FC } from 'react'
|
||||
import { Button } from 'ui'
|
||||
import { Button, IconExternalLink } from 'ui'
|
||||
|
||||
interface Props {
|
||||
showTemplates: boolean
|
||||
@@ -8,15 +8,20 @@ interface Props {
|
||||
}
|
||||
|
||||
const PolicyEditorFooter: FC<Props> = ({ showTemplates, onViewTemplates, onReviewPolicy }) => (
|
||||
<div className="flex w-full items-center justify-end gap-2 border-t px-6 py-4 dark:border-dark">
|
||||
{showTemplates && (
|
||||
<Button type="default" onClick={onViewTemplates}>
|
||||
View templates
|
||||
<div className="flex justify-between items-center border-t px-6 py-4 dark:border-dark">
|
||||
<a href='https://supabase.com/docs/learn/auth-deep-dive/auth-policies' target="_blank">
|
||||
<Button type='link' icon={<IconExternalLink size={14} strokeWidth={1.5} />}>Documentation</Button>
|
||||
</a>
|
||||
<div className="flex w-full items-center justify-end gap-2">
|
||||
{showTemplates && (
|
||||
<Button type="default" onClick={onViewTemplates}>
|
||||
View templates
|
||||
</Button>
|
||||
)}
|
||||
<Button type="primary" onClick={onReviewPolicy}>
|
||||
Review
|
||||
</Button>
|
||||
)}
|
||||
<Button type="primary" onClick={onReviewPolicy}>
|
||||
Review
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
|
||||
@@ -26,7 +26,9 @@ const PolicyTableRow: FC<Props> = ({
|
||||
onSelectDeletePolicy = () => {},
|
||||
}) => {
|
||||
const { meta } = useStore()
|
||||
const policies = meta.policies.list((x: PostgresPolicy) => x.table === table.name)
|
||||
const policies = meta.policies.list(
|
||||
(policy: PostgresPolicy) => policy.schema === table.schema && policy.table === table.name
|
||||
)
|
||||
|
||||
return (
|
||||
<Panel
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IconArchive, IconCode, IconDatabase, IconKey } from 'ui'
|
||||
import { Badge, IconArchive, IconCode, IconDatabase, IconKey } from 'ui'
|
||||
|
||||
export const CANCELLATION_REASONS = [
|
||||
'Pricing',
|
||||
@@ -25,13 +25,15 @@ export const USAGE_BASED_PRODUCTS = [
|
||||
{
|
||||
key: 'db_size',
|
||||
attribute: 'total_db_size_bytes',
|
||||
title: 'Database space',
|
||||
title: 'Database size',
|
||||
units: 'bytes',
|
||||
costPerUnit: 0.125,
|
||||
tooltip: (
|
||||
<span>
|
||||
We continuously monitor the total size of your database. Billing is based on the average
|
||||
database size in GB throughout the billing period.
|
||||
Billing is based on the average daily database size in GB throughout the billing period.{' '}
|
||||
<a href="https://supabase.com/docs/guides/platform/database-usage" target="_blank">
|
||||
Docs
|
||||
</a>
|
||||
</span>
|
||||
),
|
||||
},
|
||||
@@ -130,8 +132,9 @@ export const USAGE_BASED_PRODUCTS = [
|
||||
costPerUnit: 0.1,
|
||||
tooltip: (
|
||||
<span>
|
||||
Every single serverless function invocation independent of response status is counted.
|
||||
Billing is based on the sum of all invocations throughout your billing period.
|
||||
We continuously monitor the amount of serverless functions in your project. Billing is
|
||||
based on the maximum amount of functions at any point in time throughout your billing
|
||||
period.
|
||||
</span>
|
||||
),
|
||||
},
|
||||
@@ -144,9 +147,8 @@ export const USAGE_BASED_PRODUCTS = [
|
||||
costPerUnit: 0.000002,
|
||||
tooltip: (
|
||||
<span>
|
||||
We continuously monitor the amount of serverless functions in your project. Billing is
|
||||
based on the maximum amount of functions at any point in time throughout your billing
|
||||
period.
|
||||
Every single serverless function invocation independent of response status is counted.
|
||||
Billing is based on the sum of all invocations throughout your billing period.
|
||||
</span>
|
||||
),
|
||||
},
|
||||
|
||||
@@ -394,7 +394,7 @@ const ProUpgrade: FC<Props> = ({
|
||||
</div>
|
||||
<div className="py-1">
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<p className="w-[50%] text-sm">Database space</p>
|
||||
<p className="w-[50%] text-sm">Database size</p>
|
||||
<p className="w-[25%] text-sm">8GB</p>
|
||||
<p className="w-[25%] text-sm">$0.125/GB</p>
|
||||
</div>
|
||||
|
||||
@@ -14,7 +14,7 @@ import CostBreakdownRow from './CostBreakdownRow'
|
||||
import { StripeSubscription } from './Subscription.types'
|
||||
import NoPermission from 'components/ui/NoPermission'
|
||||
import { USAGE_BASED_PRODUCTS } from 'components/interfaces/Billing/Billing.constants'
|
||||
import { ProjectUsageResponse, useProjectUsageQuery } from 'data/usage/project-usage-query'
|
||||
import { ProjectUsageResponseUsageKeys, useProjectUsageQuery } from 'data/usage/project-usage-query'
|
||||
|
||||
interface Props {
|
||||
project: any
|
||||
@@ -62,7 +62,7 @@ const Subscription: FC<Props> = ({
|
||||
? 0
|
||||
: Object.keys(usage)
|
||||
.map((productKey) => {
|
||||
return usage[productKey as keyof ProjectUsageResponse].cost ?? 0
|
||||
return usage[productKey as ProjectUsageResponseUsageKeys].cost ?? 0
|
||||
})
|
||||
.reduce((prev, current) => prev + current, 0)
|
||||
|
||||
@@ -183,10 +183,10 @@ const Subscription: FC<Props> = ({
|
||||
{isPayg &&
|
||||
USAGE_BASED_PRODUCTS.map((product) => {
|
||||
return product.features.map((feature) => {
|
||||
const amount = usage?.[feature.key as keyof ProjectUsageResponse]?.usage ?? 0
|
||||
const limit = usage?.[feature.key as keyof ProjectUsageResponse]?.limit ?? 0
|
||||
const amount = usage?.[feature.key as ProjectUsageResponseUsageKeys]?.usage ?? 0
|
||||
const limit = usage?.[feature.key as ProjectUsageResponseUsageKeys]?.limit ?? 0
|
||||
const cost = (
|
||||
usage?.[feature.key as keyof ProjectUsageResponse]?.cost ?? 0
|
||||
usage?.[feature.key as ProjectUsageResponseUsageKeys]?.cost ?? 0
|
||||
).toFixed(2)
|
||||
|
||||
return (
|
||||
|
||||
@@ -31,9 +31,9 @@ const Wrappers = () => {
|
||||
|
||||
const isWrappersEnabled =
|
||||
wrappersExtension !== undefined &&
|
||||
wrappersExtension?.installed_version !== null &&
|
||||
wrappersExtension.installed_version !== null &&
|
||||
vaultExtension !== undefined &&
|
||||
vaultExtension?.installed_version !== null
|
||||
vaultExtension.installed_version !== null
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { IconLoader, IconAlertCircle } from 'ui'
|
||||
import { useParams } from 'hooks'
|
||||
import { ProjectUsage, NewProjectPanel } from 'components/interfaces/Home'
|
||||
import InformationBox from 'components/ui/InformationBox'
|
||||
import { ProjectUsageResponse, useProjectUsageQuery } from 'data/usage/project-usage-query'
|
||||
import { ProjectUsageResponseUsageKeys, useProjectUsageQuery } from 'data/usage/project-usage-query'
|
||||
|
||||
const ProjectUsageSection: FC = observer(({}) => {
|
||||
const { ref: projectRef } = useParams()
|
||||
@@ -24,7 +24,7 @@ const ProjectUsageSection: FC = observer(({}) => {
|
||||
|
||||
const hasProjectData = usage
|
||||
? Object.keys(usage)
|
||||
.map((key) => usage[key as keyof ProjectUsageResponse].usage)
|
||||
.map((key) => usage[key as ProjectUsageResponseUsageKeys].usage)
|
||||
.some((usage) => (usage ?? 0) > 0)
|
||||
: false
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ const CustomDomainVerify = ({ projectRef, customDomain, settings }: CustomDomain
|
||||
>
|
||||
Please add a CAA record allowing "digicert.com" to issue certificates for{' '}
|
||||
<code className="text-xs">{customDomain.hostname}</code>. For example:{' '}
|
||||
<code className="text-xs">0 issue "digitcert.com"</code>
|
||||
<code className="text-xs">0 issue "digicert.com"</code>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { FC, useEffect } from 'react'
|
||||
import { Badge, Button, IconAlertCircle, IconInfo, Loading } from 'ui'
|
||||
import { Badge, Button, IconAlertCircle, IconInfo, Loading, IconExternalLink } from 'ui'
|
||||
|
||||
import { useStore } from 'hooks'
|
||||
import { formatBytes } from 'lib/helpers'
|
||||
@@ -8,7 +8,7 @@ import SparkBar from 'components/ui/SparkBar'
|
||||
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
|
||||
import InformationBox from 'components/ui/InformationBox'
|
||||
import { USAGE_BASED_PRODUCTS } from 'components/interfaces/Billing/Billing.constants'
|
||||
import { ProjectUsageResponse, useProjectUsageQuery } from 'data/usage/project-usage-query'
|
||||
import { ProjectUsageResponseUsageKeys, useProjectUsageQuery } from 'data/usage/project-usage-query'
|
||||
import { useRouter } from 'next/router'
|
||||
import * as Tooltip from '@radix-ui/react-tooltip'
|
||||
|
||||
@@ -60,6 +60,26 @@ const ProjectUsage: FC<Props> = ({ projectRef }) => {
|
||||
)
|
||||
}
|
||||
|
||||
const isPaidTier = subscriptionTier !== PRICING_TIER_PRODUCT_IDS.FREE
|
||||
|
||||
const featureFootnotes: Record<string, JSX.Element> = {
|
||||
db_size: (
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex flex-row space-x-4 text-scale-1000">
|
||||
{usage?.disk_volume_size_gb && <span>Disk Size: {usage.disk_volume_size_gb} GB</span>}
|
||||
|
||||
{isPaidTier && <Badge>Auto-Scaling</Badge>}
|
||||
</div>
|
||||
|
||||
<Button type="default" icon={<IconExternalLink size={14} strokeWidth={1.5} />}>
|
||||
<a target="_blank" href="https://supabase.com/docs/guides/platform/database-usage">
|
||||
What is disk size?
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
|
||||
return (
|
||||
<Loading active={isLoading}>
|
||||
{usage && (
|
||||
@@ -69,7 +89,7 @@ const ProjectUsage: FC<Props> = ({ projectRef }) => {
|
||||
showUsageExceedMessage &&
|
||||
product.features
|
||||
.map((feature) => {
|
||||
const featureUsage = usage[feature.key as keyof ProjectUsageResponse]
|
||||
const featureUsage = usage[feature.key as ProjectUsageResponseUsageKeys]
|
||||
return (featureUsage.usage ?? 0) / featureUsage.limit > 1
|
||||
})
|
||||
.some((x) => x === true)
|
||||
@@ -116,7 +136,7 @@ const ProjectUsage: FC<Props> = ({ projectRef }) => {
|
||||
) : (
|
||||
<tbody>
|
||||
{product.features.map((feature) => {
|
||||
const featureUsage = usage[feature.key as keyof ProjectUsageResponse]
|
||||
const featureUsage = usage[feature.key as ProjectUsageResponseUsageKeys]
|
||||
const usageValue = featureUsage.usage || 0
|
||||
const usageRatio = usageValue / featureUsage.limit
|
||||
const isApproaching = usageRatio >= USAGE_APPROACHING_THRESHOLD
|
||||
@@ -171,7 +191,7 @@ const ProjectUsage: FC<Props> = ({ projectRef }) => {
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
return [
|
||||
<tr
|
||||
key={feature.title}
|
||||
className="border-t border-panel-border-light dark:border-panel-border-dark"
|
||||
@@ -217,8 +237,18 @@ const ProjectUsage: FC<Props> = ({ projectRef }) => {
|
||||
</td>
|
||||
</>
|
||||
)}
|
||||
</tr>
|
||||
)
|
||||
</tr>,
|
||||
featureFootnotes[feature.key] && (
|
||||
<tr key={`${feature.title}-footnote`}>
|
||||
<td
|
||||
className="whitespace-nowrap px-6 py-3 text-sm text-scale-1200"
|
||||
colSpan={3}
|
||||
>
|
||||
{featureFootnotes[feature.key]}
|
||||
</td>
|
||||
</tr>
|
||||
),
|
||||
]
|
||||
})}
|
||||
</tbody>
|
||||
)}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { FC } from 'react'
|
||||
import { Loading } from 'ui'
|
||||
|
||||
import { ProjectUsageResponse, useProjectUsageQuery } from 'data/usage/project-usage-query'
|
||||
import { ProjectUsageResponseUsageKeys, useProjectUsageQuery } from 'data/usage/project-usage-query'
|
||||
import { useProjectSubscriptionQuery } from 'data/subscriptions/project-subscription-query'
|
||||
import { formatBytes } from 'lib/helpers'
|
||||
import { PRICING_TIER_PRODUCT_IDS, USAGE_APPROACHING_THRESHOLD } from 'lib/constants'
|
||||
@@ -24,7 +24,7 @@ const ProjectUsageMinimal: FC<ProjectUsageMinimalProps> = ({ projectRef, filter
|
||||
if (
|
||||
subscription?.tier?.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.PAYG ||
|
||||
subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.ENTERPRISE ||
|
||||
subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.TEAM
|
||||
subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.TEAM
|
||||
) {
|
||||
return <></>
|
||||
}
|
||||
@@ -41,7 +41,7 @@ const ProjectUsageMinimal: FC<ProjectUsageMinimalProps> = ({ projectRef, filter
|
||||
{usage && (
|
||||
<div className="space-y-8">
|
||||
{product.features.map((feature) => {
|
||||
const featureUsage = usage[feature.key as keyof ProjectUsageResponse]
|
||||
const featureUsage = usage[feature.key as ProjectUsageResponseUsageKeys]
|
||||
const usageRatio = (featureUsage.usage ?? 0) / featureUsage.limit
|
||||
const isApproaching = usageRatio >= USAGE_APPROACHING_THRESHOLD
|
||||
const isExceeded = usageRatio >= 1
|
||||
|
||||
@@ -375,7 +375,6 @@ const ColumnEditor: FC<Props> = ({
|
||||
)}
|
||||
|
||||
<ForeignKeySelector
|
||||
tables={tables}
|
||||
column={columnFields}
|
||||
visible={isEditingRelation}
|
||||
closePanel={() => setIsEditingRelation(false)}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import React, { FC, useEffect, useState } from 'react'
|
||||
import { get, find, isEmpty, sortBy } from 'lodash'
|
||||
import { Dictionary } from 'components/grid'
|
||||
import { SidePanel, Input, Listbox, IconHelpCircle } from 'ui'
|
||||
import type { PostgresTable, PostgresColumn } from '@supabase/postgres-meta'
|
||||
import { SidePanel, Input, Listbox, IconHelpCircle, IconDatabase } from 'ui'
|
||||
import type { PostgresTable, PostgresColumn, PostgresSchema } from '@supabase/postgres-meta'
|
||||
|
||||
import { useStore } from 'hooks'
|
||||
import ActionBar from '../ActionBar'
|
||||
import { ForeignKey } from './ForeignKeySelector.types'
|
||||
import { ColumnField } from '../SidePanelEditor.types'
|
||||
import InformationBox from 'components/ui/InformationBox'
|
||||
|
||||
interface Props {
|
||||
tables: PostgresTable[]
|
||||
column: ColumnField
|
||||
metadata?: any
|
||||
visible: boolean
|
||||
@@ -18,15 +18,19 @@ interface Props {
|
||||
saveChanges: (value: { table: PostgresTable; column: PostgresColumn } | undefined) => void
|
||||
}
|
||||
|
||||
const ForeignKeySelector: FC<Props> = ({
|
||||
tables = [] as PostgresTable[],
|
||||
column,
|
||||
visible = false,
|
||||
closePanel,
|
||||
saveChanges,
|
||||
}) => {
|
||||
const ForeignKeySelector: FC<Props> = ({ column, visible = false, closePanel, saveChanges }) => {
|
||||
const { meta } = useStore()
|
||||
const [errors, setErrors] = useState<any>({})
|
||||
const [selectedForeignKey, setSelectedForeignKey] = useState<ForeignKey>()
|
||||
const [selectedForeignKey, setSelectedForeignKey] = useState<ForeignKey>({
|
||||
schema: 'public',
|
||||
table: '',
|
||||
column: '',
|
||||
})
|
||||
|
||||
const schemas = meta.schemas.list()
|
||||
const tables = meta.tables.list(
|
||||
(table: PostgresTable) => table.schema === selectedForeignKey.schema
|
||||
)
|
||||
|
||||
const foreignKey = column?.foreignKey
|
||||
const selectedTable: PostgresTable | undefined = find(tables, {
|
||||
@@ -41,6 +45,7 @@ const ForeignKeySelector: FC<Props> = ({
|
||||
// Reset the state of the side panel
|
||||
if (visible) {
|
||||
setErrors({})
|
||||
|
||||
if (foreignKey) {
|
||||
setSelectedForeignKey({
|
||||
schema: foreignKey.target_table_schema,
|
||||
@@ -49,7 +54,7 @@ const ForeignKeySelector: FC<Props> = ({
|
||||
})
|
||||
} else {
|
||||
setSelectedForeignKey({
|
||||
schema: '',
|
||||
schema: 'public',
|
||||
table: '',
|
||||
column: '',
|
||||
})
|
||||
@@ -57,6 +62,12 @@ const ForeignKeySelector: FC<Props> = ({
|
||||
}
|
||||
}, [visible])
|
||||
|
||||
const updateSelectedSchema = (schema: string) => {
|
||||
meta.tables.loadBySchema(schema)
|
||||
const updatedForeignKey = { ...selectedForeignKey, schema, table: '', column: '' } as ForeignKey
|
||||
setSelectedForeignKey(updatedForeignKey)
|
||||
}
|
||||
|
||||
const updateSelectedTable = (tableId: number) => {
|
||||
setErrors({})
|
||||
if (!tableId) {
|
||||
@@ -87,6 +98,9 @@ const ForeignKeySelector: FC<Props> = ({
|
||||
|
||||
const onSaveChanges = (resolve: () => void) => {
|
||||
const errors = {} as Dictionary<any>
|
||||
if (!selectedForeignKey?.table) {
|
||||
errors['table'] = 'Please select a table'
|
||||
}
|
||||
if (selectedForeignKey?.table && !selectedForeignKey.column) {
|
||||
errors['column'] = `The table ${selectedForeignKey.table} has no columns`
|
||||
}
|
||||
@@ -141,12 +155,38 @@ const ForeignKeySelector: FC<Props> = ({
|
||||
/>
|
||||
|
||||
<Listbox
|
||||
id="schema"
|
||||
label="Select a schema"
|
||||
value={selectedForeignKey.schema}
|
||||
error={errors.schema}
|
||||
onChange={(value: string) => updateSelectedSchema(value)}
|
||||
>
|
||||
{schemas.map((schema: PostgresSchema) => {
|
||||
return (
|
||||
<Listbox.Option
|
||||
key={schema.id}
|
||||
value={schema.name}
|
||||
label={schema.name}
|
||||
addOnBefore={() => <IconDatabase size={16} strokeWidth={1.5} />}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
{/* For aria searching to target the schema name instead of schema */}
|
||||
<span className="hidden">{schema.name}</span>
|
||||
<span className="text-scale-1200">{schema.name}</span>
|
||||
</div>
|
||||
</Listbox.Option>
|
||||
)
|
||||
})}
|
||||
</Listbox>
|
||||
|
||||
<Listbox
|
||||
id="table"
|
||||
label="Select a table to reference to"
|
||||
value={selectedTable?.id}
|
||||
value={selectedTable?.id ?? 1}
|
||||
error={errors.table}
|
||||
onChange={(value: string) => updateSelectedTable(Number(value))}
|
||||
>
|
||||
<Listbox.Option key="empty" value="" label="---">
|
||||
<Listbox.Option key="empty" value={1} label="---">
|
||||
---
|
||||
</Listbox.Option>
|
||||
{/* @ts-ignore */}
|
||||
@@ -179,6 +219,7 @@ const ForeignKeySelector: FC<Props> = ({
|
||||
/>
|
||||
) : (
|
||||
<Listbox
|
||||
id="column"
|
||||
value={selectedColumn?.id}
|
||||
// @ts-ignore
|
||||
label={
|
||||
|
||||
@@ -353,7 +353,6 @@ const ColumnManagement: FC<Props> = ({
|
||||
</div>
|
||||
</div>
|
||||
<ForeignKeySelector
|
||||
tables={tables}
|
||||
column={selectedColumnToEditRelation as ColumnField}
|
||||
visible={!isUndefined(selectedColumnToEditRelation)}
|
||||
closePanel={() => setSelectedColumnToEditRelation(undefined)}
|
||||
|
||||
@@ -22,6 +22,9 @@ const DatabaseLayout: FC<Props> = ({ title, children }) => {
|
||||
const router = useRouter()
|
||||
const page = router.pathname.split('/')[4]
|
||||
|
||||
const vaultExtension = meta.extensions.byId('supabase_vault')
|
||||
const isVaultEnabled = vaultExtension !== undefined && vaultExtension.installed_version !== null
|
||||
|
||||
const [loaded, setLoaded] = useState<boolean>(isInitialized)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -38,10 +41,15 @@ const DatabaseLayout: FC<Props> = ({ title, children }) => {
|
||||
if (IS_PLATFORM) {
|
||||
backups.load()
|
||||
}
|
||||
vault.load()
|
||||
}
|
||||
}, [ui.selectedProject?.ref])
|
||||
|
||||
useEffect(() => {
|
||||
if (isVaultEnabled) {
|
||||
vault.load()
|
||||
}
|
||||
}, [ui.selectedProject?.ref, isVaultEnabled])
|
||||
|
||||
// Optimization required: load logic should be at the page level
|
||||
// e.g backups page is waiting for meta.tables to load finish when it doesnt even need that data
|
||||
useEffect(() => {
|
||||
|
||||
@@ -37,7 +37,7 @@ const TableEditorLayout: FC<Props> = ({
|
||||
const canReadTables = checkPermissions(PermissionAction.TENANT_SQL_ADMIN_READ, 'tables')
|
||||
|
||||
const vaultExtension = meta.extensions.byId('supabase_vault')
|
||||
const isVaultEnabled = vaultExtension !== undefined && vaultExtension?.installed_version !== null
|
||||
const isVaultEnabled = vaultExtension !== undefined && vaultExtension.installed_version !== null
|
||||
|
||||
useEffect(() => {
|
||||
if (ui.selectedProject?.ref) {
|
||||
|
||||
@@ -43,7 +43,7 @@ const TableEditorMenu: FC<Props> = ({
|
||||
onDeleteTable = () => {},
|
||||
onDuplicateTable = () => {},
|
||||
}) => {
|
||||
const { meta, ui } = useStore()
|
||||
const { meta } = useStore()
|
||||
const { id, ref } = useParams()
|
||||
|
||||
const schemas: PostgresSchema[] = meta.schemas.list()
|
||||
|
||||
@@ -51,8 +51,11 @@ export type ProjectUsageResponse = {
|
||||
storage_size: StorageSize
|
||||
storage_egress: StorageEgress
|
||||
monthly_active_users: MonthlyActiveUsers
|
||||
disk_volume_size_gb: number
|
||||
}
|
||||
|
||||
export type ProjectUsageResponseUsageKeys = keyof Omit<ProjectUsageResponse, 'disk_volume_size_gb'>
|
||||
|
||||
export async function getProjectUsage({ projectRef }: ProjectUsageVariables, signal?: AbortSignal) {
|
||||
if (!projectRef) {
|
||||
throw new Error('projectRef is required')
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
"clipboard": "^2.0.8",
|
||||
"common": "*",
|
||||
"config": "*",
|
||||
"configcat-js": "^5.6.0",
|
||||
"configcat-js": "^7.0.0",
|
||||
"dayjs": "^1.11.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"generate-password": "^1.7.0",
|
||||
|
||||
@@ -138,14 +138,14 @@ const AuthPoliciesPage: NextPageWithLayout = () => {
|
||||
icon={<IconSearch size="tiny" />}
|
||||
/>
|
||||
</div>
|
||||
<Button type="link" icon={<IconExternalLink size={14} strokeWidth={1.5} />}>
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://supabase.com/docs/learn/auth-deep-dive/auth-row-level-security"
|
||||
>
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://supabase.com/docs/learn/auth-deep-dive/auth-row-level-security"
|
||||
>
|
||||
<Button type="link" icon={<IconExternalLink size={14} strokeWidth={1.5} />}>
|
||||
What is RLS?
|
||||
</a>
|
||||
</Button>
|
||||
</Button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<Policies tables={filteredTables} hasTables={tables.length > 0} isLocked={isLocked} />
|
||||
|
||||
@@ -15,7 +15,7 @@ const VaultSettingsSecrets: NextPageWithLayout = () => {
|
||||
const { ref } = useParams()
|
||||
|
||||
const vaultExtension = meta.extensions.byId('supabase_vault')
|
||||
const isEnabled = vaultExtension !== undefined && vaultExtension?.installed_version !== null
|
||||
const isEnabled = vaultExtension !== undefined && vaultExtension.installed_version !== null
|
||||
|
||||
useEffect(() => {
|
||||
if (isEnabled) {
|
||||
|
||||
@@ -17,7 +17,7 @@ const VaultSettingsSecrets: NextPageWithLayout = () => {
|
||||
|
||||
const vaultExtension = meta.extensions.byId('supabase_vault')
|
||||
const isLoading = meta.extensions.isLoading
|
||||
const isEnabled = vaultExtension !== undefined && vaultExtension?.installed_version !== null
|
||||
const isEnabled = vaultExtension !== undefined && vaultExtension.installed_version !== null
|
||||
|
||||
useEffect(() => {
|
||||
if (isEnabled) {
|
||||
|
||||