Merge branch 'master' into feat/docs2.0

This commit is contained in:
Jonathan Summers-Muir committed 2022-12-08 12:04:08 +08:00
commit d0bb7507c8
70 files changed
+1493 -1299

No files matched your search

+5 -7
View File
@@ -35,8 +35,6 @@ We are in the process of migrating this repository to monorepo, using [Turborepo
Eventually, all the apps will be run using [Turborepo](https://turborepo.org/docs), which will significantly improve the developer workflow.
If you are working on the ([docs site](https://supabase.com/docs)), please refer to this [developers guide](https://github.com/supabase/supabase/tree/master/apps/reference/DEVELOPERS.md).
### Fork the repository
To contribute code to [Supabase](https://supabase.com), you must fork the [Supabase Repository](https://github.com/supabase/supabase).
@@ -71,11 +69,11 @@ To contribute code to [Supabase](https://supabase.com), you must fork the [Supab
Then visit, and edit, any of the following sites:
| Site | Directory | Scope name | Description | Local development server |
| ---------------------------------------------- | -------------- | ---------- | ------------------------------------ | ------------------------ |
| [supabase.com](https://supabase.com) | `/apps/www` | www | The main website | http://localhost:3000 |
| [app.supabase.com](https://app.supabase.com) | `/studio` | studio | Studio dashboard | http://localhost:8082 |
| [supabase.com/docs](https://supabase.com/docs) | `/apps/docs` | docs | Guides and Reference (Next.js based) | http://localhost:3001 |
| Site | Directory | Scope name | Description | Local development server |
| ---------------------------------------------- | ------------ | ---------- | ------------------------------------ | -------------------------- |
| [supabase.com](https://supabase.com) | `/apps/www` | www | The main website | http://localhost:3000 |
| [app.supabase.com](https://app.supabase.com) | `/studio` | studio | Studio dashboard | http://localhost:8082 |
| [supabase.com/docs](https://supabase.com/docs) | `/apps/docs` | docs | Guides and Reference (Next.js based) | http://localhost:3001/docs |
#### Running sites individually
@@ -245,7 +245,9 @@ export const menuItems: NavMenu = {
label: 'Storage',
items: [
{ name: 'Overview', url: '/guides/storage', items: [] },
{ name: 'CDN', url: '/guides/storage-cdn', items: [] },
{ name: 'Quickstart', url: '/guides/storage/quickstart', items: [] },
{ name: 'Access Control', url: '/guides/storage/access-control', items: [] },
{ name: 'CDN', url: '/guides/storage/cdn', items: [] },
],
},
{
File diff suppressed because it is too large. Load diff
@@ -295,6 +295,7 @@ If you try to submit a form with the action `?/createPost` without a valid sessi
import type { Actions } from './$types'
import { invalid, redirect } from '@sveltejs/kit'
import { getSupabase } from '@supabase/auth-helpers-sveltekit'
import { AuthApiError } from '@supabase/supabase-js'
export const actions: Actions = {
signin: async (event) => {
@@ -8,7 +8,7 @@ export const meta = {
<Admonition type="caution">
The pg_net API is in beta. Functions signatures may change.
The pg_net API is in alpha. Functions signatures may change.
</Admonition>
-336
View File
@@ -34,342 +34,6 @@ keep all video files in a "video" bucket, and profile pictures in an "avatar" bu
></iframe>
</div>
## Getting started
This is a quick guide that shows the basic functionality of Supabase Storage. Find a full
[example application in GitHub](https://github.com/supabase/supabase/tree/master/examples/user-management/nextjs-ts-user-management),
which you can deploy yourself.
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https%3A%2F%2Fgithub.com%2Fsupabase%2Fsupabase%2Ftree%2Fmaster%2Fexamples%2Fuser-management%2Fnextjs-ts-user-management&project-name=supabase-user-management&repository-name=supabase-user-management&demo-title=Supabase%20User%20Management&demo-description=An%20example%20web%20app%20using%20Supabase%20and%20Next.js&demo-url=https%3A%2F%2Fsupabase-nextjs-ts-user-management.vercel.app&demo-image=https%3A%2F%2Fi.imgur.com%2FZ3HkQqe.png&integration-ids=oac_jUduyjQgOyzev1fjrW83NYOv&external-id=nextjs-user-management)
<Admonition type="note">
File, Folder, and Bucket names **must follow** [AWS object key naming guidelines](https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-keys.html) and avoid use of any other characters.
</Admonition>
### Create a bucket
You can create a bucket using the Supabase Dashboard.
Since the storage is interoperable with your Postgres database, you can also use SQL or our
client libraries. Here we create a bucket called "avatars":
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="dashboard"
>
<TabPanel id="dashboard" label="Dashboard">
1. Go to the [Storage](https://app.supabase.com/project/_/storage/buckets) page in the Dashboard.
2. Click **New Bucket** and enter a name for the bucket.
3. Click **Create Bucket**.
</TabPanel>
<TabPanel id="sql" label="SQL">
```sql
-- Use Postgres to create a bucket.
insert into storage.buckets (id, name)
values ('avatars', 'avatars');
```
</TabPanel>
<TabPanel id="javascript" label="JavaScript">
```js
// Use the JS library to create a bucket.
const { data, error } = await supabase.storage.createBucket('avatars')
```
[Reference.](/docs/reference/javascript/storage-createbucket)
</TabPanel>
<TabPanel id="dart" label="Dart">
```dart
void main() async {
final supabase = SupabaseClient('supabaseUrl', 'supabaseKey');
final storageResponse = await supabase
.storage
.createBucket('avatars');
}
```
[Reference.](https://pub.dev/documentation/storage_client/latest/storage_client/SupabaseStorageClient/createBucket.html)
</TabPanel>
</Tabs>
### Upload a file
You can upload a file from the Dashboard, or within a browser using our JS libraries.
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="dashboard"
>
<TabPanel id="dashboard" label="Dashboard">
1. Go to the [Storage](https://app.supabase.com/project/_/storage/buckets) page in the Dashboard.
2. Select the bucket you want to upload the file to.
3. Click **Upload File**.
4. Select the file you want to upload.
</TabPanel>
<TabPanel id="javascript" label="JavaScript">
```js
const avatarFile = event.target.files[0]
const { data, error } = await supabase.storage
.from('avatars')
.upload('public/avatar1.png', avatarFile)
```
[Reference.](/docs/reference/javascript/storage-from-upload)
</TabPanel>
<TabPanel id="dart" label="Dart">
```dart
void main() async {
final supabase = SupabaseClient('supabaseUrl', 'supabaseKey');
// Create file `example.txt` and upload it in `public` bucket
final file = File('example.txt');
file.writeAsStringSync('File content');
final storageResponse = await supabase
.storage
.from('public')
.upload('example.txt', file);
}
```
[Reference.](https://pub.dev/documentation/storage_client/latest/storage_client/SupabaseStorageClient/createBucket.html)
</TabPanel>
</Tabs>
### Download a file
You can download a file from the Dashboard, or within a browser using our JS libraries.
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="dashboard"
>
<TabPanel id="dashboard" label="Dashboard">
1. Go to the [Storage](https://app.supabase.com/project/_/storage/buckets) page in the Dashboard.
2. Select the bucket that contains the file.
3. Select the file that you want to download.
4. Click **Download**.
</TabPanel>
<TabPanel id="javascript" label="JavaScript">
```js
// Use the JS library to download a file.
const { data, error } = await supabase.storage.from('avatars').download('public/avatar1.png')
```
[Reference.](/docs/reference/javascript/storage-from-download)
</TabPanel>
<TabPanel id="dart" label="Dart">
```dart
void main() async {
final supabase = SupabaseClient('supabaseUrl', 'supabaseKey');
final storageResponse = await supabase
.storage
.from('public')
.download('example.txt');
}
```
[Reference.](/docs/reference/dart/storage-from-download)
</TabPanel>
</Tabs>
### Add security rules
To restrict access to your files you can use either the Dashboard or SQL.
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="dashboard"
>
<TabPanel id="dashboard" label="Dashboard">
1. Go to the [Storage](https://app.supabase.com/project/_/storage/buckets) page in the Dashboard.
2. Click **Policies** in the sidebar.
3. Click **Add Policies** in the `OBJECTS` table to add policies for Files. You can also create policies for Buckets.
4. Choose whether you want the policy to apply to downloads (SELECT), uploads (INSERT), updates (UPDATE), or deletes (DELETE).
5. Give your policy a unique name.
6. Write the policy using SQL.
</TabPanel>
<TabPanel id="sql" label="SQL">
```sql
-- Use SQL to create a policy.
create policy "Public Access"
on storage.objects for select
using ( bucket_id = 'public' );
```
</TabPanel>
</Tabs>
## Public and Private Buckets
Storage buckets are private by default.
For private buckets, you can access objects via the [download](/docs/reference/javascript/storage-from-download) method. This corresponds to `/object/auth/` API endpoint.
Alternatively, you can create a publicly shareable URL with an expiry date using the [createSignedUrl](/docs/reference/javascript/storage-from-createsignedurl) method
which calls the `/object/sign/` API.
For public buckets, you can access the assets directly without a token or an Authorisation header. The [getPublicUrl](/docs/reference/javascript/storage-from-getpublicurl)
helper method returns the full public URL for an asset. This calls the `/object/public/` API endpoint internally. While no authorization is required for accessing objects in a public bucket, proper [access control](#access-control) is required for other operations like uploading, deleting objects from public buckets as well.
Public buckets also tend to have [better performance](/docs/guides/storage-cdn#public-vs-private-buckets).
<details>
<summary>Advanced: reverse proxy</summary>
The URLs returned are proxied through the API Proxy. They are prefixed by <code>/storage/v1</code>.
For example, on the hosted Platform they will be
<code>https://[project_ref].supabase.co/storage/v1/object/public/[id]</code>
You can access the storage API directly with the same endpoint. See the <a href="https://supabase.github.io/storage-api">API docs</a> for a full list of operations available.
</details>
---
## Access control
Supabase Storage is integrated with your [Postgres Database](/docs/guides/database).
This means that you can use the same [Row Level Security Policies](/docs/guides/auth#policies)
for managing access to your files. Supabase Storage stores metadata in the `objects` and `buckets` table in the storage schema. To allow read access to files, the RLS policy must allow users to `SELECT` the `objects` table and for uploading a new object, the RLS policy must grant users access to `INSERT` into the `objects` table and so on. The mapping between the different API calls and the database permissions required is documented in the [Reference docs](/docs/reference/javascript/storage-createbucket).
<Admonition type="note">
Access control for Storage is mapped to CRUD operations on the `buckets` and `objects` table via RLS policies.
</Admonition>
<div className="video-container">
<iframe
src="https://www.youtube-nocookie.com/embed/4ERX__Y908k"
frameBorder="1"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
</div>
## Helpers
Supabase Storage provides SQL helper functions which you can use in your database queries and
policies.
---
#### `storage.filename()`
Returns the name of a file.
```sql
select storage.filename(name)
from storage.objects;
```
For example, if your file is stored in `public/subfolder/avatar.png` it would return:
`'avatar.png'`
---
#### `storage.foldername()`
Returns an array path, with all of the subfolders that a file belongs to.
```sql
select storage.foldername(name)
from storage.objects;
```
For example, if your file is stored in `public/subfolder/avatar.png` it would return:
`[ 'public', 'subfolder' ]`
---
#### `storage.extension()`
Returns the extension of a file.
```sql
select storage.extension(name)
from storage.objects;
```
For example, if your file is stored in `public/subfolder/avatar.png` it would return:
`'png'`
---
## Policy examples
Here are some examples of storage policies.
### Allow public access to a bucket
```sql
-- 1. Allow public access to any files in the "public" bucket
create policy "Public Access"
on storage.objects for select
using ( bucket_id = 'public' );
```
### Allow logged-in access to a bucket
```sql
-- 1. Allow logged-in access to any files in the "restricted" bucket
create policy "Restricted Access"
on storage.objects for select
using (
bucket_id = 'restricted'
and auth.role() = 'authenticated'
);
```
### Allow individual access to a file
```sql
-- 1. Allow a user to access their own files
create policy "Individual user Access"
on storage.objects for select
using ( auth.uid() = owner );
```
## See also
- Read more about Supabase Storage in the [blog post](https://supabase.com/blog/supabase-storage)
@@ -0,0 +1,137 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'storage-access-control',
title: 'Access Control',
description: 'Manage who can access your Supabase Storage files.',
}
Supabase Storage is integrated with your [Postgres Database](/docs/guides/database).
This means that you can use the same [Row Level Security Policies](/docs/guides/auth#policies)
for managing access to your files. Supabase Storage stores metadata in the `objects` and `buckets` table in the storage schema.
To allow read access to files, the RLS policy must allow users to `SELECT` the `objects` table and for uploading a new object, the RLS policy must grant users access to `INSERT` into the `objects` table and so on. The mapping between the different API calls and the database permissions required is documented in the [Reference docs](/docs/reference/javascript/storage-createbucket).
<Admonition type="note">
Access control for Storage is mapped to CRUD operations on the `buckets` and `objects` table via RLS policies.
</Admonition>
<div className="video-container">
<iframe
src="https://www.youtube-nocookie.com/embed/4ERX__Y908k"
frameBorder="1"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
</div>
## Public and private buckets
Storage buckets are private by default. For private buckets, you can access objects via the [download](/docs/reference/javascript/storage-from-download) method. This corresponds to `/object/auth/` API endpoint.
Alternatively, you can create a publicly shareable URL with an expiry date using the [createSignedUrl](/docs/reference/javascript/storage-from-createsignedurl) method
which calls the `/object/sign/` API.
For public buckets, you can access the assets directly without a token or an Authorisation header. The [getPublicUrl](/docs/reference/javascript/storage-from-getpublicurl)
helper method returns the full public URL for an asset. This calls the `/object/public/` API endpoint internally. While no authorization is required for accessing objects in a public bucket, proper [access control](#access-control) is required for other operations like uploading, deleting objects from public buckets as well. Public buckets also tend to have [better performance](/docs/guides/storage-cdn#public-vs-private-buckets).
<details>
<summary>Advanced: reverse proxy</summary>
The URLs returned are proxied through the API Proxy. They are prefixed by <code>/storage/v1</code>.
For example, on the hosted Platform they will be
<code>https://[project_ref].supabase.co/storage/v1/object/public/[id]</code>
You can access the storage API directly with the same endpoint. See the <a href="https://supabase.github.io/storage-api">API docs</a> for a full list of operations available.
</details>
## Helper functions
Supabase Storage provides SQL helper functions which you can use in your database queries and
policies.
---
#### `storage.filename()`
Returns the name of a file.
```sql
select storage.filename(name)
from storage.objects;
```
For example, if your file is stored in `public/subfolder/avatar.png` it would return:
`'avatar.png'`
---
#### `storage.foldername()`
Returns an array path, with all of the subfolders that a file belongs to.
```sql
select storage.foldername(name)
from storage.objects;
```
For example, if your file is stored in `public/subfolder/avatar.png` it would return:
`[ 'public', 'subfolder' ]`
---
#### `storage.extension()`
Returns the extension of a file.
```sql
select storage.extension(name)
from storage.objects;
```
For example, if your file is stored in `public/subfolder/avatar.png` it would return:
`'png'`
---
## Policy examples
### Allow public access to a bucket
```sql
-- 1. Allow public access to any files in the "public" bucket
create policy "Public Access"
on storage.objects for select
using ( bucket_id = 'public' );
```
### Allow logged-in access to a bucket
```sql
-- 1. Allow logged-in access to any files in the "restricted" bucket
create policy "Restricted Access"
on storage.objects for select
using (
bucket_id = 'restricted'
and auth.role() = 'authenticated'
);
```
### Allow individual access to a file
```sql
-- 1. Allow a user to access their own files
create policy "Individual user Access"
on storage.objects for select
using ( auth.uid() = owner );
```
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -3,27 +3,19 @@ import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'storage-cdn',
title: 'Storage CDN',
description: 'How Supabase Storage caches objects with a CDN',
description: 'Learn how Supabase Storage caches objects with a CDN.',
sidebar_label: 'CDN',
}
## CDN basics
All assets uploaded to Supabase Storage are cached on a Content Delivery Network (CDN) to improve the latency for users all around the world. CDNs are a geographically distributed set of servers or **nodes** which caches content from an **origin server**. For Supabase Storage, the origin is the storage server running in the [same region as your project](https://app.supabase.com/project/_/settings/general). Aside from performance, CDNs also help with security and availability by mitigating Distributed Denial of Service and other application attacks.
All assets uploaded to storage are cached on a Content Delivery Network (CDN).
To improve the latency for users all around the world, Supabase uses a CDN. CDNs are a geographically distributed set of servers or **nodes** which caches content from an **origin server**. For Supabase Storage, the origin is the storage server running in the [same region as your project](https://app.supabase.com/project/_/settings/general).
## Example
Let’s walk through an example of how a CDN helps with performance. A new bucket is created for a Supabase project launched in Singapore. All requests to the Supabase Storage API first hit the CDN. A user from the United States requests an object and is routed to the U.S. CDN. At this point, that CDN node does not have the object in its cache and pings the origin server in Singapore. Another user, also in the United States, requests the same object and is served directly from the CDN cache in the United States instead of routing the request back to Singapore.
Aside from performance, CDNs also help with security and availability by mitigating Distributed Denial of Service and other application attacks.
## Cache duration
By default, assets are cached both in the CDN and in the user's browser for 1 hour. After this, the CDN nodes ping the storage server to see if an object has been updated.
You can modify this cache time when you are [uploading](/docs/reference/javascript/storage-from-upload) or [updating](/docs/reference/javascript/storage-from-update) an object by modifying the `cacheControl` parameter.
If you expect the object to not change at a given URL, setting a longer cache duration is preferable.
By default, assets are cached both in the CDN and in the user's browser for 1 hour. After this, the CDN nodes ping the storage server to see if an object has been updated. You can modify this cache time when you are [uploading](/docs/reference/javascript/storage-from-upload) or [updating](/docs/reference/javascript/storage-from-update) an object by modifying the `cacheControl` parameter. If you expect the object to not change at a given URL, setting a longer cache duration is preferable.
If you need to update the version of the object stored in the CDN, there are various cache-busting techniques you can use. The most common way to do this is to add a version query parameter in the URL. For example, you can use a URL like `/storage/v1/object/sign/profile-pictures/cat.jpg?token=eyJh...&version=1` in your applications and set a long cache time of 1 year. When you want to update the cat picture, you can increment the version query parameter in the URL. The CDN will treat `/storage/v1/object/sign/profile-pictures/cat.jpg?token=eyJh...&version=2` as a new object and pings the origin for the updated version.
@@ -0,0 +1,216 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'storage-quickstart',
title: 'Storage Quickstart',
description: 'Learn how to use Supabase to store and serve files.',
sidebar_label: 'Quickstart',
}
This guide shows the basic functionality of Supabase Storage. Find a full [example application on GitHub](https://github.com/supabase/supabase/tree/master/examples/user-management/nextjs-ts-user-management) you can deploy yourself.
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https%3A%2F%2Fgithub.com%2Fsupabase%2Fsupabase%2Ftree%2Fmaster%2Fexamples%2Fuser-management%2Fnextjs-ts-user-management&project-name=supabase-user-management&repository-name=supabase-user-management&demo-title=Supabase%20User%20Management&demo-description=An%20example%20web%20app%20using%20Supabase%20and%20Next.js&demo-url=https%3A%2F%2Fsupabase-nextjs-ts-user-management.vercel.app&demo-image=https%3A%2F%2Fi.imgur.com%2FZ3HkQqe.png&integration-ids=oac_jUduyjQgOyzev1fjrW83NYOv&external-id=nextjs-user-management)
<Admonition type="note">
File, Folder, and Bucket names **must follow** [AWS object key naming guidelines](https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-keys.html) and avoid use of any other characters.
</Admonition>
## Create a bucket
You can create a bucket using the Supabase Dashboard.
Since the storage is interoperable with your Postgres database, you can also use SQL or our
client libraries. Here we create a bucket called "avatars":
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="dashboard"
>
<TabPanel id="dashboard" label="Dashboard">
1. Go to the [Storage](https://app.supabase.com/project/_/storage/buckets) page in the Dashboard.
2. Click **New Bucket** and enter a name for the bucket.
3. Click **Create Bucket**.
</TabPanel>
<TabPanel id="sql" label="SQL">
```sql
-- Use Postgres to create a bucket.
insert into storage.buckets (id, name)
values ('avatars', 'avatars');
```
</TabPanel>
<TabPanel id="javascript" label="JavaScript">
```js
// Use the JS library to create a bucket.
const { data, error } = await supabase.storage.createBucket('avatars')
```
[Reference.](/docs/reference/javascript/storage-createbucket)
</TabPanel>
<TabPanel id="dart" label="Dart">
```dart
void main() async {
final supabase = SupabaseClient('supabaseUrl', 'supabaseKey');
final storageResponse = await supabase
.storage
.createBucket('avatars');
}
```
[Reference.](https://pub.dev/documentation/storage_client/latest/storage_client/SupabaseStorageClient/createBucket.html)
</TabPanel>
</Tabs>
## Upload a file
You can upload a file from the Dashboard, or within a browser using our JS libraries.
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="dashboard"
>
<TabPanel id="dashboard" label="Dashboard">
1. Go to the [Storage](https://app.supabase.com/project/_/storage/buckets) page in the Dashboard.
2. Select the bucket you want to upload the file to.
3. Click **Upload File**.
4. Select the file you want to upload.
</TabPanel>
<TabPanel id="javascript" label="JavaScript">
```js
const avatarFile = event.target.files[0]
const { data, error } = await supabase.storage
.from('avatars')
.upload('public/avatar1.png', avatarFile)
```
[Reference.](/docs/reference/javascript/storage-from-upload)
</TabPanel>
<TabPanel id="dart" label="Dart">
```dart
void main() async {
final supabase = SupabaseClient('supabaseUrl', 'supabaseKey');
// Create file `example.txt` and upload it in `public` bucket
final file = File('example.txt');
file.writeAsStringSync('File content');
final storageResponse = await supabase
.storage
.from('public')
.upload('example.txt', file);
}
```
[Reference.](https://pub.dev/documentation/storage_client/latest/storage_client/SupabaseStorageClient/createBucket.html)
</TabPanel>
</Tabs>
## Download a file
You can download a file from the Dashboard, or within a browser using our JS libraries.
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="dashboard"
>
<TabPanel id="dashboard" label="Dashboard">
1. Go to the [Storage](https://app.supabase.com/project/_/storage/buckets) page in the Dashboard.
2. Select the bucket that contains the file.
3. Select the file that you want to download.
4. Click **Download**.
</TabPanel>
<TabPanel id="javascript" label="JavaScript">
```js
// Use the JS library to download a file.
const { data, error } = await supabase.storage.from('avatars').download('public/avatar1.png')
```
[Reference.](/docs/reference/javascript/storage-from-download)
</TabPanel>
<TabPanel id="dart" label="Dart">
```dart
void main() async {
final supabase = SupabaseClient('supabaseUrl', 'supabaseKey');
final storageResponse = await supabase
.storage
.from('public')
.download('example.txt');
}
```
[Reference.](/docs/reference/dart/storage-from-download)
</TabPanel>
</Tabs>
## Add security rules
To restrict access to your files you can use either the Dashboard or SQL.
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="dashboard"
>
<TabPanel id="dashboard" label="Dashboard">
1. Go to the [Storage](https://app.supabase.com/project/_/storage/buckets) page in the Dashboard.
2. Click **Policies** in the sidebar.
3. Click **Add Policies** in the `OBJECTS` table to add policies for Files. You can also create policies for Buckets.
4. Choose whether you want the policy to apply to downloads (SELECT), uploads (INSERT), updates (UPDATE), or deletes (DELETE).
5. Give your policy a unique name.
6. Write the policy using SQL.
</TabPanel>
<TabPanel id="sql" label="SQL">
```sql
-- Use SQL to create a policy.
create policy "Public Access"
on storage.objects for select
using ( bucket_id = 'public' );
```
</TabPanel>
</Tabs>
## See also
- [Supabase Storage on GitHub](https://github.com/supabase/storage-api)
- [Swagger API documentation](https://supabase.github.io/storage-api)
- Official [JavaScript](/docs/reference/javascript/storage-createbucket) and [Dart](/docs/reference/dart/storage-createbucket) documentation
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
+1
View File
@@ -29,6 +29,7 @@ Jon M
Jonny Summers-Muir
Joshen Lim
Kang Ming Tay
Karlo Ison
Kevin Grüneberg
Lakshmipathi G
Laura C
@@ -153,7 +153,7 @@ Notice also how there is a `DEFAULT` value for the `key_id`. In a way, this give
### One Key ID per Row with Nonce Support
The default cryptographic algorithm for the above approach uses a _nonceless_ encryption algorithm called `[crypto_aead_det_xchacha20()](https://github.com/jedisct1/libsodium-xchacha20-siv)`. This algorithm has the advantage that it does not require nonce values, the disadvantage is that duplicate plaintexts will produce duplicate ciphertexts.
The default cryptographic algorithm for the above approach uses a _nonceless_ encryption algorithm called [`crypto_aead_det_xchacha20()`](https://github.com/jedisct1/libsodium-xchacha20-siv). This algorithm has the advantage that it does not require nonce values, the disadvantage is that duplicate plaintexts will produce duplicate ciphertexts.
Nonces are some extra cryptographic context that is used in many cryptographic algorithms to produce different ciphertexts, even if the plaintexts are the same. The nonce does not have to be secret, but it _does_ have to be unique. **pgsodium** comes with a useful function `pgsodium.crypto_aead_det_noncegen()` that will generate a cryptographically secure nonce for you, and in almost all cases it's best to use that function unless you know specifically what you are doing. In password hashing approaches, this is often similar to how a “salt” value is used to deduplicate password hashes.
@@ -0,0 +1,58 @@
---
title: The Supabase Content Storm
description: We worked with +30 content creators to drop a mountain of content simultaneously.
author: ramiro
image: 2022-12-06-content-storm/content-storm.jpg
thumb: 2022-12-06-content-storm/content-storm.jpg
tags:
- announcements
date: '2022-12-06'
toc_depth: 3
---
We're always trying to innovate at Supabase, so when we started planning [Launch Week 6](https://supabase.com/launch-week), we brainstormed ideas for a campaign to promote it that would involve the community. We took a bit of inspiration from things like Weezer’s YouTube takeover and came up with the concept of a Content Storm ⛈.
## What is the Content Storm?
The idea was to get many different external content creators as possible to create Supabase-related content and *publish everything on the same day*. We contacted some well known Youtubers, community members, makers building with Supabase, the SupaSquad, and generally anyone within the Supabase community. We wanted this to be an opportunity for them to build their personal brand or promote their products and startups.
## The aftermath of the storm
More than 30 creators participated in creating content in different formats and everything was published today (Tuesday 6th of December). Here is the full list of every piece of content and its author:
- [A quick look at Supabase](https://twitter.com/Rorstro/status/1600047388852293632) by Ania Kubów.
- [Live streaming: Supabase and Sveltekit](https://www.youtube.com/@JamesQQuick/streams) by James Q Quick.
- [Perform Database Migration Using Github Actions & Supabase CLI](https://www.youtube.com/watch?v=iCkdtXSeq7A) by Kunal Kushwaha.
- [Supabase Authentication & Realtime Database with Flutter](https://www.youtube.com/watch?v=UTSSYPOd5A0) by Pooja Bhaumik.
- [Why I Switched From AWS Cognito To Supabase The Week Before My Startup Launched](https://medium.com/@sleeplessfox/why-i-switched-from-aws-cognito-to-supabase-the-week-before-my-startup-launched-67fdc1eccb8c) by Danilo à Tellinghusen.
- [Supabase Crash Course For Python Developers](https://www.youtube.com/watch?v=M6cfT2pqpSc) by Patrick Loeber.
- [Hoppscotch with GraphQL](https://www.youtube.com/watch?v=HoBXQng3aK4) by Jamie Barton.
- [Supabase with TypeScript: using tRPC and Prisma to achieve end-to-end typesafety](https://noahflk.com/blog/supabase-typescript-trpc) by Noah.
- [How to Connect Browser Extensions to Supabase](https://akoskm.com/how-to-connect-browser-extensions-to-supabase) by Ákos Kőműves.
- [A Chili Cookoff with Rust, Rocket, Render, and Supabase](bradcypert.com/chili-cookoff-with-rust-rocket-render-and-supabase/) by Brad Cypert.
- [Authenticating users with Remix and Supabase](https://makerkit.dev/blog/tutorials/remix-supabase-auth) by Giancarlo Buomprisco.
- [Automatic Spotify Playlist Creation with Pipedream and Supabase](https://www.ianwootten.co.uk/2022/12/06/automatic-spotify-playlist-creation-with-pipedream-and-supabase/) by Ian Wootten.
- [Accept Payments For Your African-Based Business Using Supabase Edge Functions and Paystack](https://blog.hijabicoder.dev/accept-payments-for-your-african-based-business-using-supabase-edge-functions-and-paystack) by Fatuma Abdullahi.
- [Creating AvatarAI.me using Supabase and Cerebrium](https://blog.cerebrium.ai/creating-avatarai-me-using-supabase-and-cerebrium-1bf69440c4a7) by Cerebrium.
- [From Zero to Hero: Building Next.js Projects](https://www.youtube.com/watch?v=LR1i_8QjXyY) by Gui Bibeau.
- [Set up a monorepo with Supabase and Turborepo](https://philipp.steinroetter.com/posts/supabase-turborepo) by Philipp Steinrötter.
- [Twitter clone with Supabase](https://www.youtube.com/watch?v=JnUzV-PpopU) by Code with Guillaume.
- [How Supabase Powers Mockury](https://blog.ikirill.com/how-supabase-powers-mockury) by Kirill Inoz.
- [How to add SIGN-IN WITH APPLE using IONIC ANGULAR and SUPABASE](https://www.youtube.com/watch?v=xhzVyso-9OM) by [Simon Grimm].
- [SvelteKit + Supabase v2 Auth with Magic Links, Providers, Email + Password, & more! 🔐🔴 LIVE](https://www.youtube.com/watch?v=XuyMr4pM4B8) by Johnny Magrippis
- [How we use Supabase at Brick](https://dev.to/vinzent/how-we-use-supabase-at-brick-3a5d) by Vinzent.
- [How Kuiq is using Supabase and serving around 100 APIs](https://kuiq.io/blog/how-kuiq-is-using-supabase-and-serving-around-100-apis) by Taishi
- [How I created an interactive website with React, Chakra UI + Supabase](https://medium.com/@limjoshen/how-i-created-a-personal-website-with-react-chakra-ui-supabase-39661852746a) by Joshen Lim.
- [5 benefits of using Supabase for your SaaS](https://supastarter.dev/blog/five-benefits-of-supabase) by Jonathan Wilke.
- [Getting into the holiday spirit with Astro, React, and Supabase](https://www.aleksandra.codes/astro-supabase) by Aleksandra
- [How I build register and login page with Supabase](https://surajondev.com/2022/12/06/how-i-build-register-and-login-page-using-supabase/) by Suraj Vishwakarma.
- [What the hell is Supabase?](https://www.youtube.com/watch?v=syOnFdFC_5U) by Ayesha Sahar.
- [Comment utiliser Supabase en No-Code avec Weweb ?](https://www.youtube.com/watch?v=g2TLd8vvs0E) by Thomas Buyle.
- [How I used Supabase and n8n for my background tasks](https://mathias.rocks/blog/2022-12-06-how-I-used-supabase/) by Mathias Mitchel
- [Build An Image Gallery With Supabase Storage and React (Upload Images with Supabase)](https://www.youtube.com/watch?v=8tfdY0Sf2rA) by Coopercodes
- [Twitter thread showing how to build a web app](https://twitter.com/thisisisheanesu/status/1600257146792181766) by Isheanesu.
- [This service will run your entire app](https://www.instagram.com/reel/Cl1KWbtDGMR/) by Simon Baker (@allthecode).
It's a lot! There are tons of great resources, going through everything would take a couple of days, so we recommend bookmarking this page! If you like the content, make sure to give feedback to the author and follow them on social.
And don't miss Launch Week 6, we've been working hard to make it our best Launch Week yet 🚀. Get your tickets [here](https://supabase.com/launch-week).
@@ -0,0 +1,104 @@
---
title: Supabase Beta November 2022
description: We are preparing everything for Launch Week 6, but we still had time to ship some goodies this month!
author: ant_wilson
image: 2022-12-07-november-beta-update/monthly-update-november-2022.jpg
thumb: 2022-12-07-november-beta-update/monthly-update-november-2022.jpg
tags:
- release-notes
date: '2022-12-07'
toc_depth: 3
---
Launch Week 6 is just around the corner! We’re saving most of November’s updated as a surprise for Launch Week, but we still had time to ship some goodies this month. Read on!
## Launch Week 6 tickets
![Launch Week 6 tickets](/images/blog/2022-12-07-november-beta-update/launch-week-6-ticket.jpg)
Next week, we go all out for LW6. It’s 5 days of shipping, including major features requested by the community. You don’t want to miss a thing, so make sure to claim your free ticket (and you might win some very special SupaSwag).
[Get your ticket](https://supabase.com/launch-week)
## Remix Auth Helpers
![Remix Auth Helpers](/images/blog/2022-12-07-november-beta-update/remix-auth-helpers.jpg)
It’s here! The much-awaited Remix Auth Helpers make server-side auth even easier and with a better experience for server-side rendering. Up to date with supabase-js V2 and can be used with Typescript.
[Read the docs](https://supabase.com/docs/guides/auth/auth-helpers/remix)
## Edgy Edge Functions
![Edgy Edge Function](/images/blog/2022-12-07-november-beta-update/edgy-edge-functions.jpg)
We launched a new YouTube series, [Edgy Edge Functions](https://www.youtube.com/playlist?list=PL5S4mPUpp4OulD3olUW8Eq1IYKpUbk5Ob), where we take a deep look at a new function every week.
Plus, we published three new functions examples: [Generate OG Images](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/opengraph), [Build API servers using Oak](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/oak-server), and [Generate Screenshots using Puppeteer](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/puppeteer).
## Quick product updates
- Fixed two issues with the `supabase.auth.signOut` function: [cookies not clearing properly](https://github.com/supabase/gotrue/pull/830) and [session not removed](https://github.com/supabase/gotrue-js/pull/541).
- You can now customize magic links by providing access to the `HashedToken`. [PR](https://github.com/supabase/gotrue/pull/707)
- Now you can do redirects from your functions. [PR](https://github.com/supabase/supabase/issues/10325)
- Conda support (`conda install -c conda-forge supabase` and everything else in py)
## Content storm ⛈
![The Supabase Content Storm](/images/blog/2022-12-07-november-beta-update/content-storm.jpg)
As a prelude to Launch Week 6, we invited dozens of developers from around the world to create content in different formats and publish everything simultaneously!
[Check the full list](https://supabase.com/blog/the-supabase-content-storm)
## Fireship’s Supabase course
![Fireship’s Supabase course](/images/blog/2022-12-07-november-beta-update/fireship-supabase-course.jpg)
That guy from the [Supaship Youtube](https://www.youtube.com/@Supaship6000) channel made a Supabase Course with Fireship! Zack DeRose did an amazing job with this project-based course that builds a Reddit-inspired web app from scratch with Supabase, PostgreSQL, and React.
[Get it while it's hot](https://fireship.io/courses/supabase/).
## Made with Supabase
![Made with Supabase](/images/blog/2022-12-07-november-beta-update/madewithsupabase.jpg)
[postedbyAI](https://postedby.ai/) | Allows users to create personalized postcards using advanced AI technology. Built with Vercel, OpenAI, and Supabase.
Discover other great projects: [Made with Supabase](https://www.madewithsupabase.com/).
## Extended Community Highlights
![Community](/images/blog/2022-june/community.jpg)
- Supabase in 100 Seconds, by Fireship. [Video](https://www.youtube.com/watch)
- NextAuth Supabase Adapter. [Docs](https://next-auth.js.org/adapters/supabase)
- Flutter Authentication and Authorization with RLS. [Tutorial](https://supabase.com/blog/flutter-authentication-and-authorization-with-rls)
- The pros and cons of using PostgREST. [Podcast](https://postgres.fm/episodes/postgrest)
- Querying Supabase from the edge with Netlify and PolyScale. [Tutorial](https://www.netlify.com/blog/querying-supabase-from-the-edge-with-polyscale/)
- JWT Authentication with Supabase and Zuplo for your API. [Blog](https://zuplo.com/blog/2022/11/15/api-authentication-with-supabase-jwt/) and [Video](https://www.youtube.com/watch?v=UEeSZkV7o_Y&feature=youtu.be)
- Shipping a public API backed by Supabase. [Blog](https://zuplo.com/blog/2022/11/18/shipping-a-public-api-backed-by-supabase) and [Video](https://www.youtube.com/watch?v=GJSkbxMnWxE)
- A Guide to testing on Supabase using pgTAP. [Guide](https://usebasejump.com/blog/testing-on-supabase-with-pgtap)
- [Using Bytebase to manage the database development lifecycle for Supabase databases](https://www.bytebase.com/docs/how-to/integrations/supabase)
- The Best Backend as a Service for your React App. [Article](https://www.freecodecamp.org/news/best-backend-service-react/)
- All You Need to Know About Serverless Functions and the Edge. [Article](https://dev.to/nefejames/all-you-need-to-know-about-serverless-functions-and-the-edge-2bl2)
- #1 Hack for Building JavaScript Portfolio Projects. [Video](https://www.youtube.com/watch?v=3K9VUT7H5sw)
- Livestreaming - Job Board App w/ SvelteKit, Supabase, & Prisma. [Youtube Playlist](https://www.youtube.com/playlist?list=PLq30BP0TIcqU8SlzDK2wX4IaXamQ26kpr)
- How to connect to and insert data into Supabase with Pipedream. [Article](https://pipedream.com/blog/how-to-connect-to-and-insert-data-into-supabase-with-pipedream/) and [Video](https://www.youtube.com/watch?v=l9B8F1n3duk)
- Google Sign-In using Supabase and React Native (Expo). [Tutorial](https://dev.to/fedorish/google-sign-in-using-supabase-and-react-native-expo-14jf)
- Fast and Intuitive analytics for Supabase with DB Zaps. [Video](https://www.youtube.com/watch?v=QlIlqm9eG3w)
- ReactJS + Supabase Fullstack CRUD Application Development. [Youtube Playlist](https://youtube.com/playlist?list=PLl6EcvA_AoxEFqwT_rNs1uZnnf7LSEoji)
- How to connect a ESP8266 to a Streamlit App using Supabase database. [Video](https://www.youtube.com/watch?v=wI209WK3-VM)
## Meme Zone
This month’s meme comes courtesy of Fireship. [Follow us on Twitter](https://twitter.com/supabase) for more.
![Supabase meme November 2022](/images/blog/2022-12-07-november-beta-update/november-beta-update-meme.png)
See you next month!
+3 -3
View File
@@ -30,7 +30,7 @@ const data: CommunityItem[] = [
},
{
title: 'GitHub',
stat: '40,000+',
stat: '42,000+',
statLabel: 'GitHub stars',
img: 'github.png',
invertImgDarkMode: true,
@@ -42,7 +42,7 @@ const data: CommunityItem[] = [
},
{
title: 'Twitter',
stat: '44,000+',
stat: '50,000+',
statLabel: 'Followers',
img: 'twitter.png',
detail: () => (
@@ -53,7 +53,7 @@ const data: CommunityItem[] = [
},
{
title: 'Discord',
stat: '8,000+',
stat: '10,000+',
statLabel: 'SupaTroopers',
img: 'discord.png',
detail: () => (
+7
View File
@@ -229,5 +229,12 @@
"position": "Postgres Engineer",
"author_url": "https://github.com/pashkinelfe",
"author_image_url": "https://avatars.githubusercontent.com/u/63344111?v=4"
},
{
"author_id": "ramiro",
"author": "Ramiro Nunez Dosio",
"position": "Growth Marketer",
"author_url": "https://twitter.com/ramiro__nd",
"author_image_url": "https://avatars.githubusercontent.com/u/20600542?v=4"
}
]
+3 -10
View File
@@ -30,20 +30,13 @@ export default function mdxComponents(type?: 'blog' | 'lp' | undefined) {
img: (props: any) => {
if (props.className !== ignoreClass) {
return (
<div
className={[
'next-image--dynamic-fill',
type === 'blog' && 'to-scale-400 from-scale-500 rounded-lg border bg-gradient-to-r',
].join(' ')}
>
<span className={['next-image--dynamic-fill'].join(' ')}>
<Image
{...props}
className={['next-image--dynamic-fill', type === 'blog' && 'rounded-md border'].join(
' '
)}
className={[type === 'blog' ? 'rounded-md border' : ''].join(' ')}
layout="fill"
/>
</div>
</span>
)
}
return <img {...props} />
+6 -1
View File
@@ -7,7 +7,7 @@ module.exports = [
{
permanent: true,
source: '/docs/guides/storage-caching',
destination: '/docs/guides/storage-cdn',
destination: '/docs/guides/storage/cdn',
},
{
permanent: true,
@@ -1560,6 +1560,11 @@ module.exports = [
source: '/forgot-password',
destination: 'https://app.supabase.com/forgot-password',
},
{
permanent: true,
source: '/docs/guides/storage-cdn',
destination: '/docs/guides/storage/cdn',
},
{
permanent: true,
source: '/docs/guides/functions/examples',
+110 -3
View File
@@ -1,6 +1,5 @@
import { NextSeo } from 'next-seo'
import _days from '~/components/LaunchWeek/days.json'
import { WeekDayProps } from '~/components/LaunchWeek/types'
import DefaultLayout from '~/components/Layouts/Default'
import SectionContainer from '~/components/Layouts/SectionContainer'
@@ -13,7 +12,20 @@ import classNames from 'classnames'
import styleUtils from '~/components/LaunchWeek/Ticket/utils.module.css'
import { SITE_ORIGIN } from '~/lib/constants'
const days = _days as WeekDayProps[]
import { Badge, IconExternalLink } from '~/../../packages/ui'
const constellation = [
[60, 8],
[13, 20],
[42, 24],
[68, 27],
[23, 42],
[52, 52],
[0, 55],
[33, 65],
[66, 70],
[55, 82],
]
export default function launchweek() {
const { isDarkMode } = useTheme()
@@ -25,6 +37,8 @@ export default function launchweek() {
createClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!)
)
const [session, setSession] = useState<Session | null>(null)
const [creators, setCreators] = useState<any>([])
const [activeCreator, setActiveCreator] = useState<any>(null)
const { query } = useRouter()
const ticketNumber = query.ticketNumber?.toString()
const defaultUserData = {
@@ -38,12 +52,36 @@ export default function launchweek() {
supabase.auth.onAuthStateChange((event, session) => {
setSession(session)
})
getCreators()
}, [])
useEffect(() => {
document.body.className = isDarkMode ? 'dark bg-[#121212]' : 'light bg-[#fff]'
}, [isDarkMode])
async function getCreators() {
try {
// setLoading(true)
let supa = await supabase.from('lw6_creators').select()
let { data, error, status } = supa
if (error && status !== 406) {
throw error
}
if (data) {
setCreators(data)
}
} catch (error) {
// alert('Error loading user data!')
console.log(error)
} finally {
// setLoading(false)
}
}
return (
<>
<NextSeo
@@ -60,7 +98,7 @@ export default function launchweek() {
}}
/>
<DefaultLayout>
<SectionContainer className="flex flex-col !pb-24 items-center lg:pt-32 gap-32">
<SectionContainer className="flex flex-col !pb-1 items-center lg:pt-32 gap-32">
<div
className={classNames(
styleUtils.appear,
@@ -116,6 +154,75 @@ export default function launchweek() {
)}
></div>
</div>
<SectionContainer
className={classNames(
'flex gap-6 min-h-[350px] !py-3 mb-11 flex-col-reverse md:flex-row',
styleUtils.appear,
styleUtils['appear-third']
)}
>
<div
className={`flex-1 bg-[url('/images/launchweek/orbit.svg')] bg-auto bg-no-repeat bg-bottom relative min-h-[360px]`} //grid grid-cols-5 grid-rows-5
>
{creators.map((creator: any, index: number) => {
return (
<div
className={`justify-self-center absolute overflow-visible`}
onMouseEnter={() => {
setActiveCreator(index)
}}
style={{
top: `${constellation[index][0]}%`,
left: `${constellation[index][1]}%`,
}}
>
<div className="relative group">
<div className="absolute -inset-0.5 bg-brand-1100 rounded-full opacity-75 group-hover:opacity-100 group-hover:blur-sm transition duration-500"></div>
<a href={creator.link} target="_blank" rel="noopener">
<img
className="relative rounded-full w-12 h-12 border border-brand-900 hover:shadow-md"
src={creator.profile_picture}
/>
</a>
</div>
</div>
)
})}
</div>
<div className="flex-1">
<Badge className="mb-6 bg-gradient-to-r from-[#0E3737C2] to-[#67947F] hover:to-[#39617D94] dark:hover:to-[#A6FFD899] text-whiteA-1200 dark:text-black font-normal !py-1 !px-4 dark:from-white dark:via-white dark:to-[#1a7a4c75] bg-slate-1200">
Currently happening
</Badge>
<h2 className="text-4xl dark:text-white mb-2">The Supabase Content Storm</h2>
<p className="text-slate-900 md:max-w-[80%] mb-16">
We worked with +30 content creators from around the world to drop a mountain of
content simultaneously!
<a rel="noopener" target="_blank" href="/blog/the-supabase-content-storm">
<div className="text-brand-900 flex items-center">
See all the content
<IconExternalLink size="small" className="inline-block ml-1" />
</div>
</a>
</p>
{activeCreator !== null && (
<div className="lg:max-w-[50%] min-h-[120px]">
<h3 className="dark:text-white">
{activeCreator !== null
? `${creators[activeCreator].first_name} ${creators[activeCreator].last_name}`
: 'Title'}
</h3>
<p className="text-brand-900">
<a rel="noopener" target="_blank" href={creators[activeCreator].link}>
<span>
{creators[activeCreator].link_title}
<IconExternalLink size="small" className="inline-block ml-2" />
</span>
</a>
</p>
</div>
)}
</div>
</SectionContainer>
</DefaultLayout>
</>
)
@@ -0,0 +1,3 @@
.mask {
mask-image: radial-gradient(black, transparent);
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 418 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

@@ -0,0 +1,39 @@
<svg width="709" height="355" viewBox="0 0 709 355" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_867_58310)">
<circle cx="355" cy="360" r="101" stroke="url(#paint0_linear_867_58310)" stroke-width="0.685121"/>
<circle cx="355" cy="360" r="157" stroke="url(#paint1_linear_867_58310)" stroke-width="0.685121"/>
<circle cx="355" cy="360" r="225" stroke="url(#paint2_linear_867_58310)" stroke-width="0.685121"/>
<circle cx="355" cy="360" r="285" stroke="url(#paint3_linear_867_58310)" stroke-width="0.685121"/>
<circle cx="355" cy="360" r="360" stroke="url(#paint4_linear_867_58310)" stroke-width="0.685121"/>
</g>
<defs>
<linearGradient id="paint0_linear_867_58310" x1="254" y1="259" x2="456" y2="259" gradientUnits="userSpaceOnUse">
<stop offset="0.135417" stop-color="#121212" stop-opacity="0"/>
<stop offset="0.510996" stop-color="#4F4F4F"/>
<stop offset="0.864583" stop-color="#121212" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint1_linear_867_58310" x1="198" y1="203" x2="512" y2="203" gradientUnits="userSpaceOnUse">
<stop offset="0.135417" stop-color="#121212" stop-opacity="0"/>
<stop offset="0.510996" stop-color="#4F4F4F"/>
<stop offset="0.864583" stop-color="#121212" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint2_linear_867_58310" x1="130" y1="135" x2="580" y2="135" gradientUnits="userSpaceOnUse">
<stop offset="0.135417" stop-color="#121212" stop-opacity="0"/>
<stop offset="0.510996" stop-color="#4F4F4F"/>
<stop offset="0.864583" stop-color="#121212" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint3_linear_867_58310" x1="70" y1="75" x2="640" y2="75" gradientUnits="userSpaceOnUse">
<stop offset="0.135417" stop-color="#121212" stop-opacity="0"/>
<stop offset="0.510996" stop-color="#4F4F4F"/>
<stop offset="0.864583" stop-color="#121212" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint4_linear_867_58310" x1="-5" y1="0" x2="715" y2="-2.73533e-06" gradientUnits="userSpaceOnUse">
<stop offset="0.135417" stop-color="#121212" stop-opacity="0"/>
<stop offset="0.510996" stop-color="#4F4F4F"/>
<stop offset="0.864583" stop-color="#121212" stop-opacity="0"/>
</linearGradient>
<clipPath id="clip0_867_58310">
<rect width="709" height="355" fill="white" transform="matrix(-1 -8.74228e-08 -8.74228e-08 1 709 0)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

+15 -1
View File
@@ -5,9 +5,23 @@
<link>https://supabase.com</link>
<description>Latest news from Supabase</description>
<language>en</language>
<lastBuildDate>Wed, 30 Nov 2022 16:04:00 +0000</lastBuildDate>
<lastBuildDate>Tue, 06 Dec 2022 16:04:00 +0000</lastBuildDate>
<atom:link href="https://supabase.com/rss.xml" rel="self" type="application/rss+xml"/>
<item>
<guid>https://supabase.com/blog/supabase-beta-november-2022</guid>
<title>Supabase Beta November 2022</title>
<link>https://supabase.com/blog/supabase-beta-november-2022</link>
<description>We are preparing everything for Launch Week 6, but we still had time to ship some goodies this month!</description>
<pubDate>Tue, 06 Dec 2022 16:04:00 +0000</pubDate>
</item>
<item>
<guid>https://supabase.com/blog/the-supabase-content-storm</guid>
<title>The Supabase Content Storm</title>
<link>https://supabase.com/blog/the-supabase-content-storm</link>
<description>We worked with +30 content creators to drop a mountain of content simultaneously.</description>
<pubDate>Mon, 05 Dec 2022 16:04:00 +0000</pubDate>
</item>
<item>
<guid>https://supabase.com/blog/transparent-column-encryption-with-postgres</guid>
<title>Transparent Column Encryption with Postgres</title>
<link>https://supabase.com/blog/transparent-column-encryption-with-postgres</link>
+1 -1
View File
@@ -426,7 +426,7 @@ launch week
}
.gradient-container {
min-height: 480px;
min-height: 360px;
position: relative;
max-width: 1600px;
margin: 0 auto;
@@ -0,0 +1,3 @@
# Upload files to Storage
This example shows how to process `multipart/form-data` in an Edge Function. Then, upload files in the request to Supabase Storage and insert other fields as a record to a database table.
@@ -0,0 +1,66 @@
// This example shows how to use Edge Functions to read incoming multipart/form-data request,
// and write files to Supabase Storage and other fields to a database table.
import { Application, Router } from 'https://deno.land/x/oak/mod.ts'
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
const MB = 1024 * 1024
const app = new Application()
app.use(async (ctx) => {
const body = ctx.request.body({ type: 'form-data' })
const formData = await body.value.read({
// Need to set the maxSize so files will be stored in memory.
// This is necessary as Edge Functions don't have disk write access.
// We are setting the max size as 10MB (an Edge Function has a max memory limit of 150MB)
// For more config options, check: https://deno.land/x/oak@v11.1.0/mod.ts?s=FormDataReadOptions
maxSize: 10 * MB,
})
if (!formData.files || !formData.files.length) {
ctx.response.status = 400
ctx.response.body = 'missing file'
return
}
const supabaseClient = createClient(
// Supabase API URL - env var exported by default.
Deno.env.get('SUPABASE_URL') ?? '',
// Supabase API ANON KEY - env var exported by default.
Deno.env.get('SUPABASE_ANON_KEY') ?? ''
)
//upload image to Storage
const file = formData.files[0]
const timestamp = +new Date()
const uploadName = `${file.name}-${timestamp}`
const { data: upload, error: uploadError } = await supabaseClient.storage
.from('images')
.upload(uploadName, file.content!.buffer, {
contentType: file.contentType,
cacheControl: '3600',
upsert: false,
})
if (uploadError) {
console.error(uploadError)
ctx.response.status = 500
ctx.response.body = 'Failed to upload the file'
return
}
// insert record to messages table
const { error } = await supabaseClient
.from('comments')
.insert({ message: formData.fields!.message || '', image_path: upload.path })
if (error) {
console.error(error)
ctx.response.status = 500
ctx.response.body = 'Fail to add the record'
return
}
ctx.response.status = 201
ctx.response.body = 'Success!'
})
await app.listen({ port: 8000 })
+41 -17
View File
@@ -27,11 +27,14 @@
Para documentação completa, visite [supabase.com/docs](https://supabase.com/docs)
Para ver como Contribuir, visite [Primeiros Passos](./DEVELOPERS.md)
## Comunidade & Suporte
- [Fórum da comunidade](https://github.com/supabase/supabase/discussions). Melhor para: ajuda com a construção, discussões sobre melhores práticas em banco de dados.
- [GitHub Issues](https://github.com/supabase/supabase/issues). Melhor para: problemas ou erros que você encontrar usando o Supabase.
- [Suporte por E-mail](https://supabase.com/docs/support#business-support). Melhor para: problemas com o seu banco de dados ou infraestrutura.
- [Discord](https://discord.supabase.com). Melhor para: compartilhar seus aplicativos e se divertir com a comunidade.
## Status
@@ -53,6 +56,7 @@ Supabase é uma combinação de ferramentas de código aberto. Nós estamos cons
**Arquitetura atual**
Supabase é uma [plataforma hospedada](https://app.supabase.com). Você pode se cadastrar e começar a usar Supabase sem instalar nada. Nós ainda estamos criando a experiência de trabalho local - esse é o nosso foco principal, junto com a estabilidade da plataforma.
Voce tambem pode [auto-hospedar](https://supabase.com/docs/guides/hosting/overview) e [develop locally](https://supabase.com/docs/guides/local-development).
![Arquitetura](https://user-images.githubusercontent.com/70828596/187547862-ffa9d058-0c3a-4851-a3e7-92ccfca4b596.png)
@@ -66,7 +70,7 @@ Supabase é uma [plataforma hospedada](https://app.supabase.com). Você pode se
#### Bibliotecas de cliente
Nossa biblioteca de cliente é modular. Cada sub-biblioteca é uma implementação independente para cada sistema externo. Esse é uma das formas de apoiar as ferramentas existentes.
Nossa biblioteca de cliente é modular. Cada sub-biblioteca é uma implementação independente para cada sistema externo. Essa é uma das formas de apoiar as ferramentas existentes.
<table style="table-layout:fixed; white-space: nowrap;">
<tr>
@@ -81,6 +85,7 @@ Nossa biblioteca de cliente é modular. Cada sub-biblioteca é uma implementaç
<th><a href="https://github.com/supabase/gotrue" target="_blank" rel="noopener noreferrer">GoTrue</a></th>
<th><a href="https://github.com/supabase/realtime" target="_blank" rel="noopener noreferrer">Realtime</a></th>
<th><a href="https://github.com/supabase/storage-api" target="_blank" rel="noopener noreferrer">Storage</a></th>
<th>Funções</th>
</tr>
<!-- TEMPLATE FOR NEW ROW -->
<!-- START ROW
@@ -93,7 +98,7 @@ Nossa biblioteca de cliente é modular. Cada sub-biblioteca é uma implementaç
<td><a href="https://github.com/supabase-community/storage-lang" target="_blank" rel="noopener noreferrer">storage-lang</a></td>
</tr>
END ROW -->
<th colspan="6">⚡️ Oficial ⚡️</th>
<th colspan="7">⚡️ Oficial ⚡️</th>
<tr>
<td>JavaScript (TypeScript)</td>
<td><a href="https://github.com/supabase/supabase-js" target="_blank" rel="noopener noreferrer">supabase-js</a></td>
@@ -101,8 +106,18 @@ Nossa biblioteca de cliente é modular. Cada sub-biblioteca é uma implementaç
<td><a href="https://github.com/supabase/gotrue-js" target="_blank" rel="noopener noreferrer">gotrue-js</a></td>
<td><a href="https://github.com/supabase/realtime-js" target="_blank" rel="noopener noreferrer">realtime-js</a></td>
<td><a href="https://github.com/supabase/storage-js" target="_blank" rel="noopener noreferrer">storage-js</a></td>
<td><a href="https://github.com/supabase/functions-js" target="_blank" rel="noopener noreferrer">functions-js</a></td>
</tr>
<th colspan="6">💚 Comunidade 💚</th>
<tr>
<td>Flutter</td>
<td><a href="https://github.com/supabase/supabase-flutter" target="_blank" rel="noopener noreferrer">supabase-flutter</a></td>
<td><a href="https://github.com/supabase/postgrest-dart" target="_blank" rel="noopener noreferrer">postgrest-dart</a></td>
<td><a href="https://github.com/supabase/gotrue-dart" target="_blank" rel="noopener noreferrer">gotrue-dart</a></td>
<td><a href="https://github.com/supabase/realtime-dart" target="_blank" rel="noopener noreferrer">realtime-dart</a></td>
<td><a href="https://github.com/supabase/storage-dart" target="_blank" rel="noopener noreferrer">storage-dart</a></td>
<td><a href="https://github.com/supabase/functions-dart" target="_blank" rel="noopener noreferrer">functions-dart</a></td>
</tr>
<th colspan="7">💚 Comunidade 💚</th>
<tr>
<td>C#</td>
<td><a href="https://github.com/supabase-community/supabase-csharp" target="_blank" rel="noopener noreferrer">supabase-csharp</a></td>
@@ -110,22 +125,16 @@ Nossa biblioteca de cliente é modular. Cada sub-biblioteca é uma implementaç
<td><a href="https://github.com/supabase-community/gotrue-csharp" target="_blank" rel="noopener noreferrer">gotrue-csharp</a></td>
<td><a href="https://github.com/supabase-community/realtime-csharp" target="_blank" rel="noopener noreferrer">realtime-csharp</a></td>
<td><a href="https://github.com/supabase-community/storage-csharp" target="_blank" rel="noopener noreferrer">storage-csharp</a></td>
</tr>
<tr>
<td>Flutter</td>
<td><a href="https://github.com/supabase/supabase-flutter" target="_blank" rel="noopener noreferrer">supabase-dart</a></td>
<td><a href="https://github.com/supabase/postgrest-dart" target="_blank" rel="noopener noreferrer">postgrest-dart</a></td>
<td><a href="https://github.com/supabase/gotrue-dart" target="_blank" rel="noopener noreferrer">gotrue-lang</a></td>
<td><a href="https://github.com/supabase/realtime-dart" target="_blank" rel="noopener noreferrer">realtime-dart</a></td>
<td><a href="https://github.com/supabase/storage-dart" target="_blank" rel="noopener noreferrer">storage-dart</a></td>
<td><a href="https://github.com/supabase-community/functions-csharp" target="_blank" rel="noopener noreferrer">functions-csharp</a></td>
</tr>
<tr>
<td>Go</td>
<td>-</td>
<td><a href="https://github.com/supabase-community/postgrest-go" target="_blank" rel="noopener noreferrer">postgrest-go</a></td>
<td><a href="https://github.com/supabase-community/gotrue-go" target="_blank" rel="noopener noreferrer">gotrue-go</a></td>
<td>-</td>
<td>-</td>
<td>-</td>
<td><a href="https://github.com/supabase-community/storage-go" target="_blank" rel="noopener noreferrer">storage-go</a></td>
<td><a href="https://github.com/supabase-community/functions-go" target="_blank" rel="noopener noreferrer">functions-go</a></td>
</tr>
<tr>
<td>Java</td>
@@ -134,14 +143,16 @@ Nossa biblioteca de cliente é modular. Cada sub-biblioteca é uma implementaç
<td><a href="https://github.com/supabase-community/gotrue-java" target="_blank" rel="noopener noreferrer">gotrue-java</a></td>
<td>-</td>
<td>-</td>
<td>-</td>
</tr>
<tr>
<td>Kotlin</td>
<td>-</td>
<td><a href="https://github.com/supabase-community/supabase-kt" target="_blank" rel="noopener noreferrer">supabase-kt</a></td>
<td><a href="https://github.com/supabase-community/postgrest-kt" target="_blank" rel="noopener noreferrer">postgrest-kt</a></td>
<td><a href="https://github.com/supabase-community/gotrue-kt" target="_blank" rel="noopener noreferrer">gotrue-kt</a></td>
<td>-</td>
<td>-</td>
<td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Realtime" target="_blank" rel="noopener noreferrer">realtime-kt</a></td>
<td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Storage" target="_blank" rel="noopener noreferrer">storage-kt</a></td>
<td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Functions" target="_blank" rel="noopener noreferrer">functions-kt</a></td>
</tr>
<tr>
<td>Python</td>
@@ -149,7 +160,8 @@ Nossa biblioteca de cliente é modular. Cada sub-biblioteca é uma implementaç
<td><a href="https://github.com/supabase-community/postgrest-py" target="_blank" rel="noopener noreferrer">postgrest-py</a></td>
<td><a href="https://github.com/supabase-community/gotrue-py" target="_blank" rel="noopener noreferrer">gotrue-py</a></td>
<td><a href="https://github.com/supabase-community/realtime-py" target="_blank" rel="noopener noreferrer">realtime-py</a></td>
<td>-</td>
<td><a href="https://github.com/supabase-community/storage-py" target="_blank" rel="noopener noreferrer">storage-py</a></td>
<td><a href="https://github.com/supabase-community/functions-py" target="_blank" rel="noopener noreferrer">functions-py</a></td>
</tr>
<tr>
<td>Ruby</td>
@@ -158,6 +170,7 @@ Nossa biblioteca de cliente é modular. Cada sub-biblioteca é uma implementaç
<td>-</td>
<td>-</td>
<td>-</td>
<td>-</td>
</tr>
<tr>
<td>Rust</td>
@@ -166,6 +179,7 @@ Nossa biblioteca de cliente é modular. Cada sub-biblioteca é uma implementaç
<td>-</td>
<td>-</td>
<td>-</td>
<td>-</td>
</tr>
<tr>
<td>Swift</td>
@@ -174,6 +188,16 @@ Nossa biblioteca de cliente é modular. Cada sub-biblioteca é uma implementaç
<td><a href="https://github.com/supabase-community/gotrue-swift" target="_blank" rel="noopener noreferrer">gotrue-swift</a></td>
<td><a href="https://github.com/supabase-community/realtime-swift" target="_blank" rel="noopener noreferrer">realtime-swift</a></td>
<td><a href="https://github.com/supabase-community/storage-swift" target="_blank" rel="noopener noreferrer">storage-swift</a></td>
<td><a href="https://github.com/supabase-community/functions-swift" target="_blank" rel="noopener noreferrer">functions-swift</a></td>
</tr>
<tr>
<td>Godot Engine (GDScript)</td>
<td><a href="https://github.com/supabase-community/godot-engine.supabase" target="_blank" rel="noopener noreferrer">supabase-gdscript</a></td>
<td><a href="https://github.com/supabase-community/postgrest-gdscript" target="_blank" rel="noopener noreferrer">postgrest-gdscript</a></td>
<td><a href="https://github.com/supabase-community/gotrue-gdscript" target="_blank" rel="noopener noreferrer">gotrue-gdscript</a></td>
<td><a href="https://github.com/supabase-community/realtime-gdscript" target="_blank" rel="noopener noreferrer">realtime-gdscript</a></td>
<td><a href="https://github.com/supabase-community/storage-gdscript" target="_blank" rel="noopener noreferrer">storage-gdscript</a></td>
<td><a href="https://github.com/supabase-community/functions-gdscript" target="_blank" rel="noopener noreferrer">functions-gdscript</a></td>
</tr>
</table>
@@ -137,7 +137,7 @@ const RowHeader: FC<RowHeaderProps> = ({ sorts, filters }) => {
} else {
const rowIdxs = Array.from(selectedRows) as number[]
const rows = allRows.filter((x) => rowIdxs.includes(x.idx))
const { error } = state.rowService!.delete(rows)
const { error } = await state.rowService!.delete(rows)
if (error) {
if (state.onError) state.onError(error)
} else {
@@ -17,13 +17,13 @@ const RowContextMenu: React.FC<RowContextMenuProps> = ({}) => {
confirmAlert({
title: 'Confirm to delete',
message: 'Are you sure you want to delete this row? This action cannot be undone.',
onConfirm: async () => {
onAsyncConfirm: async () => {
const { props } = p
const { rowIdx } = props
const row = state.rows[rowIdx]
if (!row) return
const { error } = state.rowService!.delete([row])
const { error } = await state.rowService!.delete([row])
if (error) {
if (state.onError) state.onError(error)
} else {
@@ -37,11 +37,7 @@ export class SqlRowService implements IRowService {
}
}
async create(row: SupaRow) {
return { error: { message: 'not implemented' } }
}
delete(rows: SupaRow[]) {
async delete(rows: SupaRow[]) {
const { primaryKeys, error } = this.getPrimaryKeys()
if (error) return { error }
@@ -53,14 +49,7 @@ export class SqlRowService implements IRowService {
})
const query = queryChains.toSql()
SupabaseGridQueue.add(async () => {
const { error } = await this.onSqlQuery(query)
if (error) throw error
}).catch((error) => {
this.onError(error)
})
return {}
return await this.onSqlQuery(query)
}
// For deleting all rows based on a given filter
@@ -3,8 +3,6 @@ import { Filter, ServiceError, Sort, SupaRow } from '../../types'
export interface IRowService {
count: (filters: Filter[]) => Promise<{ data?: number; error?: ServiceError }>
create: (row: SupaRow) => Promise<{ data?: SupaRow; error?: ServiceError }>
fetchPage: (
page: number,
rowsPerPage: number,
@@ -25,7 +23,7 @@ export interface IRowService {
* Right now, we remove rows immediately, so it's confused when delete operation fails
* and rows are already removed from the grid
*/
delete: (rows: SupaRow[]) => { error?: ServiceError }
delete: (rows: SupaRow[]) => Promise<{ error?: ServiceError }>
deleteAll: (filters: Filter[]) => Promise<{ error?: ServiceError }>
update: (
@@ -32,14 +32,16 @@ const PortalToast = () => (
{({ icon, message }) => (
<>
{icon}
{message}
{t.type !== 'loading' && (
<div className="ml-4">
<Button className="!p-1" type="text" onClick={() => toast.dismiss(t.id)}>
<IconX size={14} strokeWidth={2} />
</Button>
</div>
)}
<div className="flex items-center">
<div className="toast-message w-full">{message}</div>
{t.type !== 'loading' && (
<div className="ml-4">
<Button className="!p-1" type="text" onClick={() => toast.dismiss(t.id)}>
<IconX size={14} strokeWidth={2} />
</Button>
</div>
)}
</div>
</>
)}
</ToastBar>
@@ -1,12 +1,17 @@
import { DatabaseAddon } from './AddOns.types'
// e.g supabase-postgres-14.1.0.88 (There's 4 segments instead, so we can't use semver)
export const getSemanticVersion = (version: string) => {
if (!version) return 0
const segments = version.split('-')
// e.g supabase-postgres-14.1.0.88
// There's 4 segments instead so we can't use the semver package
const segments = version.split('supabase-postgres-')
const semver = segments[segments.length - 1]
return Number(semver.split('.').join(''))
// e.g supabase-postgres-14.1.0.99-vault-rc1
const formattedSemver = semver.split('-')[0]
return Number(formattedSemver.split('.').join(''))
}
export const formatComputeSizes = (addons: DatabaseAddon[]) => {
@@ -71,12 +71,11 @@ const PaymentSummaryPanel: FC<Props> = ({
}) => {
const { ui } = useStore()
const projectRegion = ui.selectedProject?.region
const isOwner = ui.selectedOrganization?.is_owner
const enablePermissions = useFlag('enablePermissions')
const canUpdatePaymentMethods = enablePermissions
? checkPermissions(PermissionAction.BILLING_WRITE, 'stripe.payment_methods')
: isOwner
const canUpdatePaymentMethods = checkPermissions(
PermissionAction.BILLING_WRITE,
'stripe.payment_methods'
)
const [showConfirmModal, setShowConfirmModal] = useState(false)
@@ -311,9 +310,8 @@ const PaymentSummaryPanel: FC<Props> = ({
].join(' ')}
>
<span className="text-xs text-scale-1200">
{enablePermissions
? 'You need additional permissions to add new payment methods to this organization'
: 'Only organization owners can add new payment methods'}
You need additional permissions to add new payment methods to this
organization
</span>
</div>
</Tooltip.Content>
@@ -36,13 +36,13 @@ const Subscription: FC<Props> = ({
const router = useRouter()
const { ref } = router.query
const enablePermissions = useFlag('enablePermissions')
const projectUpdateDisabled = useFlag('disableProjectCreationAndUpdate')
const canReadSubscription = checkPermissions(PermissionAction.READ, 'subscriptions')
const canUpdateSubscription = enablePermissions
? checkPermissions(PermissionAction.BILLING_WRITE, 'stripe.subscriptions')
: ui.selectedOrganization?.is_owner
const canUpdateSubscription = checkPermissions(
PermissionAction.BILLING_WRITE,
'stripe.subscriptions'
)
const { usage, isLoading: loadingUsage } = useProjectUsage(ref as string)
@@ -112,10 +112,8 @@ const Subscription: FC<Props> = ({
<br />
Our engineers are working on a fix.
</>
) : !canUpdateSubscription && enablePermissions ? (
) : !canUpdateSubscription ? (
'You need additional permissions to amend subscriptions'
) : !canUpdateSubscription && !enablePermissions ? (
'Only the organization owner can amend subscriptions'
) : (
''
)}
@@ -86,8 +86,8 @@ const PITRSelection = ({}) => {
// Start: Variables specifically for date picker component
// Required as it only works with vanilla Date object which is not timezone localized
const earliestAvailableBackupFormatted = new Date(earliestAvailableBackup.format('DD-MMM-YYYY'))
const latestAvailableBackupFormatted = new Date(latestAvailableBackup.format('DD-MMM-YYYY'))
const earliestAvailableBackupFormatted = new Date(earliestAvailableBackup.format('YYYY-MM-DD'))
const latestAvailableBackupFormatted = new Date(latestAvailableBackup.format('YYYY-MM-DD'))
const isSelectedOnEarliest = checkMatchingDates(selectedDate, earliestAvailableBackupFormatted)
const isSelectedOnLatest = checkMatchingDates(selectedDate, latestAvailableBackupFormatted)
const availableDates = getDatesBetweenRange(
@@ -4,7 +4,7 @@ import { Dictionary } from 'components/grid'
import { Form, Input, Button, Listbox } from 'ui'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { checkPermissions, useStore, useFlag } from 'hooks'
import { checkPermissions, useStore } from 'hooks'
import { patch } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import { COUNTRIES } from './BillingAddress.constants'
@@ -22,11 +22,11 @@ const BillingAddress: FC<Props> = ({ loading, address, onAddressUpdated }) => {
const orgSlug = ui.selectedOrganization?.slug ?? ''
const { city, country, line1, line2, postal_code, state } = address
const enablePermissions = useFlag('enablePermissions')
const canReadBillingAddress = checkPermissions(PermissionAction.BILLING_READ, 'stripe.customer')
const canUpdateBillingAddress = enablePermissions
? checkPermissions(PermissionAction.BILLING_WRITE, 'stripe.customer')
: ui.selectedOrganization?.is_owner
const canUpdateBillingAddress = checkPermissions(
PermissionAction.BILLING_WRITE,
'stripe.customer'
)
const [isDirty, setIsDirty] = useState(false)
const initialValues = {
@@ -1,17 +1,11 @@
import { useContext, useEffect } from 'react'
import { Form, Input } from 'ui'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useFlag, useStore, checkPermissions } from 'hooks'
import { useStore, checkPermissions } from 'hooks'
import { API_URL } from 'lib/constants'
import { patch } from 'lib/common/fetch'
import {
FormActions,
FormPanel,
FormSection,
FormSectionContent,
FormSectionLabel,
} from 'components/ui/Forms'
import { FormActions, FormPanel, FormSection, FormSectionContent } from 'components/ui/Forms'
import { PageContext } from 'pages/org/[slug]/settings'
@@ -24,12 +18,7 @@ const BillingEmail = () => {
billing_email: PageState.organization?.billing_email ?? '',
}
const enablePermissions = useFlag('enablePermissions')
const canUpdateOrganization = enablePermissions
? checkPermissions(PermissionAction.UPDATE, 'organizations')
: ui.selectedOrganization?.is_owner
const canUpdateOrganization = checkPermissions(PermissionAction.UPDATE, 'organizations')
const canReadBillingEmail = checkPermissions(PermissionAction.READ, 'organizations')
const onUpdateOrganization = async (values: any, { setSubmitting, resetForm }: any) => {
@@ -1,6 +1,6 @@
import { FC, useState, useEffect } from 'react'
import { useFlag, useStore, checkPermissions } from 'hooks'
import { useStore } from 'hooks'
import { API_URL } from 'lib/constants'
import { get } from 'lib/common/fetch'
@@ -9,7 +9,6 @@ import CreditBalance from './CreditBalance'
import PaymentMethods from './PaymentMethods'
import BillingAddress from './BillingAddress/BillingAddress'
import TaxID from './TaxID/TaxID'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import BillingEmail from './BillingEmail'
interface Props {
@@ -39,14 +38,6 @@ const BillingSettings: FC<Props> = ({ organization, projects = [] }) => {
? customerBalance.toString().replace('-', '')
: customerBalance
const enablePermissions = useFlag('enablePermissions')
const canUpdateOrganization = enablePermissions
? checkPermissions(PermissionAction.UPDATE, 'organizations')
: ui.selectedOrganization?.is_owner
const canReadBillingEmail = checkPermissions(PermissionAction.READ, 'organizations')
useEffect(() => {
getCustomerProfile()
getPaymentMethods()
@@ -46,15 +46,14 @@ const PaymentMethods: FC<Props> = ({
const [showAddPaymentMethodModal, setShowAddPaymentMethodModal] = useState(false)
const [isUpdatingPaymentMethod, setIsUpdatingPaymentMethod] = useState(false)
const enablePermissions = useFlag('enablePermissions')
const canReadPaymentMethods = checkPermissions(
PermissionAction.BILLING_READ,
'stripe.payment_methods'
)
const canUpdatePaymentMethods = enablePermissions
? checkPermissions(PermissionAction.BILLING_WRITE, 'stripe.payment_methods')
: isOwner
const canUpdatePaymentMethods = checkPermissions(
PermissionAction.BILLING_WRITE,
'stripe.payment_methods'
)
const onConfirmMakeDefaultPaymentMethod = async () => {
try {
@@ -120,9 +119,7 @@ const PaymentMethods: FC<Props> = ({
<div className="flex w-full justify-between">
{!canUpdatePaymentMethods ? (
<p className="text-sm text-scale-1000">
{enablePermissions
? "You need additional permissions to manage this organization's payment methods"
: 'Only organization owners can update payment methods'}
You need additional permissions to manage this organization's payment methods
</p>
) : (
<div />
@@ -50,12 +50,8 @@ const TaxID: FC<Props> = ({ loading, taxIds, onTaxIdsUpdated }) => {
}, [taxIds])
const hasChanges = !isEqual(taxIdValues, formattedTaxIds)
const enablePermissions = useFlag('enablePermissions')
const canReadTaxIds = checkPermissions(PermissionAction.BILLING_READ, 'stripe.tax_ids')
const canUpdateTaxIds = enablePermissions
? checkPermissions(PermissionAction.BILLING_WRITE, 'stripe.tax_ids')
: ui.selectedOrganization?.is_owner
const canUpdateTaxIds = checkPermissions(PermissionAction.BILLING_WRITE, 'stripe.tax_ids')
const onUpdateTaxId = (id: string, key: string, value: string) => {
const updatedTaxIds = taxIdValues.map((taxId: any) => {
@@ -3,7 +3,7 @@ import { observer } from 'mobx-react-lite'
import { Form, Input } from 'ui'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useFlag, useStore, checkPermissions } from 'hooks'
import { useStore, checkPermissions } from 'hooks'
import { API_URL } from 'lib/constants'
import { patch } from 'lib/common/fetch'
@@ -24,16 +24,8 @@ const GeneralSettings = observer(() => {
const formId = 'org-general-settings'
const initialValues = { name: PageState.organization.name }
const enablePermissions = useFlag('enablePermissions')
const canUpdateOrganization = enablePermissions
? checkPermissions(PermissionAction.UPDATE, 'organizations')
: ui.selectedOrganization?.is_owner
const canDeleteOrganization = enablePermissions
? checkPermissions(PermissionAction.UPDATE, 'organizations')
: ui.selectedOrganization?.is_owner
const canUpdateOrganization = checkPermissions(PermissionAction.UPDATE, 'organizations')
const canDeleteOrganization = checkPermissions(PermissionAction.UPDATE, 'organizations')
const onUpdateOrganization = async (values: any, { setSubmitting, resetForm }: any) => {
if (!canUpdateOrganization) {
@@ -1,12 +1,8 @@
import { IconAlertCircle } from 'ui'
import { useFlag } from 'hooks'
import InformationBox from 'components/ui/InformationBox'
// [Joshen] This can just use NoPermission component i think
const NotOrganizationOwnerWarning = () => {
const enablePermissions = useFlag('enablePermissions')
return (
<div className="mt-4">
<InformationBox
@@ -17,9 +13,7 @@ const NotOrganizationOwnerWarning = () => {
description={
<div className="space-y-3">
<p className="text-sm leading-normal">
{enablePermissions
? 'Contact your organization owner or adminstrator to create a new project.'
: 'Only the organization owner can create new projects. Contact your organization owner to create a new project for this organization.'}
Contact your organization owner or adminstrator to create a new project.
</p>
</div>
}
@@ -6,7 +6,7 @@ import * as Tooltip from '@radix-ui/react-tooltip'
import { Button, Form, IconMail, Input, Modal, Select } from 'ui'
import { Member, User, Role } from 'types'
import { checkPermissions, useFlag, useOrganizationDetail, useStore } from 'hooks'
import { checkPermissions, useOrganizationDetail, useStore } from 'hooks'
import { post } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import { PermissionAction } from '@supabase/shared-types/out/constants'
@@ -23,8 +23,6 @@ const InviteMemberButton: FC<Props> = ({ user, members = [], roles = [], rolesAd
const router = useRouter()
const { slug } = router.query
const enablePermissions = useFlag('enablePermissions')
const [isOpen, setIsOpen] = useState(false)
const { mutateOrgMembers } = useOrganizationDetail((slug as string) || '')
@@ -57,16 +55,14 @@ const InviteMemberButton: FC<Props> = ({ user, members = [], roles = [], rolesAd
}
}
const roleId = enablePermissions
? Number(values.role)
: roles.find((role) => role.name === 'Developer')?.id ?? roles[0].id
const roleId = Number(values.role)
setSubmitting(true)
const response = await post(`${API_URL}/organizations/${slug}/members/invite`, {
invited_email: values.email.toLowerCase(),
owner_id: user.id,
...(enablePermissions ? { role_id: roleId } : {}),
role_id: roleId,
})
if (response.error) {
@@ -141,9 +137,7 @@ const InviteMemberButton: FC<Props> = ({ user, members = [], roles = [], rolesAd
}, [roles])
const selectedRole = roles.find((role) => role.id === Number(values.role))
const invalidRoleSelected = enablePermissions
? values.role && !rolesAddable.includes(Number(values.role))
: false
const invalidRoleSelected = values.role && !rolesAddable.includes(Number(values.role))
return (
<>
@@ -151,7 +145,7 @@ const InviteMemberButton: FC<Props> = ({ user, members = [], roles = [], rolesAd
<div className="w-full py-4">
<div className="space-y-4">
<div className="space-y-2">
{roles && enablePermissions && (
{roles && (
<Select
name="role"
label="Member role"
@@ -5,7 +5,7 @@ import { PermissionAction } from '@supabase/shared-types/out/constants'
import { Button, Dropdown, IconTrash, IconMoreHorizontal } from 'ui'
import { Member, Role } from 'types'
import { useStore, useOrganizationDetail, useFlag, checkPermissions } from 'hooks'
import { useStore, useOrganizationDetail, checkPermissions } from 'hooks'
import { delete_, post } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import TextConfirmModal from 'components/ui/Modals/TextConfirmModal'
@@ -27,7 +27,6 @@ const MemberActions: FC<Props> = ({ members, member, roles }) => {
const { rolesRemovable } = getRolesManagementPermissions(roles)
const { app, ui } = useStore()
const enablePermissions = useFlag('enablePermissions')
const { mutateOrgMembers } = useOrganizationDetail(ui.selectedOrganization?.slug || '')
const [loading, setLoading] = useState(false)
@@ -37,9 +36,7 @@ const MemberActions: FC<Props> = ({ members, member, roles }) => {
const isPendingInviteAcceptance = member.invited_id
const roleId = member.role_ids?.[0] ?? -1
const canRemoveMember = enablePermissions
? rolesRemovable.includes((member?.role_ids ?? [-1])[0])
: true
const canRemoveMember = rolesRemovable.includes((member?.role_ids ?? [-1])[0])
const canResendInvite = checkPermissions(PermissionAction.CREATE, 'user_invites', {
resource: { role_id: roleId },
})
@@ -54,11 +51,9 @@ const MemberActions: FC<Props> = ({ members, member, roles }) => {
onAsyncConfirm: async () => {
setLoading(true)
const response = enablePermissions
? await delete_(`${API_URL}/organizations/${slug}/members/${member.gotrue_id}`)
: await delete_(`${API_URL}/organizations/${slug}/members/remove`, {
member_id: member.id,
})
const response = await delete_(
`${API_URL}/organizations/${slug}/members/${member.gotrue_id}`
)
if (response.error) {
ui.setNotification({
@@ -67,9 +62,7 @@ const MemberActions: FC<Props> = ({ members, member, roles }) => {
})
setLoading(false)
} else {
const updatedMembers = enablePermissions
? members.filter((m) => m.gotrue_id !== member.gotrue_id)
: members.filter((m) => m.id !== member.id)
const updatedMembers = members.filter((m) => m.gotrue_id !== member.gotrue_id)
mutateOrgMembers(updatedMembers)
ui.setNotification({
@@ -117,7 +110,7 @@ const MemberActions: FC<Props> = ({ members, member, roles }) => {
const response = await post(`${API_URL}/organizations/${slug}/members/invite`, {
invited_email: member.primary_email,
owner_id: member.invited_id,
...(enablePermissions ? { role_id: roleId } : {}),
role_id: roleId,
})
if (response.error) {
@@ -189,7 +182,7 @@ const MemberActions: FC<Props> = ({ members, member, roles }) => {
align="end"
overlay={
<>
{!enablePermissions && !isPendingInviteAcceptance && (
{!isPendingInviteAcceptance && (
<>
<Dropdown.Item onClick={() => setOwnerTransferIsVisible(!ownerTransferIsVisible)}>
<div className="flex flex-col">
@@ -5,7 +5,7 @@ import { observer } from 'mobx-react-lite'
import { Badge, Button, Loading, Listbox, IconUser, Modal } from 'ui'
import { Member, Role } from 'types'
import { useStore, useFlag, useOrganizationDetail } from 'hooks'
import { useStore, useOrganizationDetail } from 'hooks'
import { patch } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import { isInviteExpired, getUserDisplayName } from '../Organization.utils'
@@ -28,17 +28,13 @@ const MembersView = () => {
const { ui } = useStore()
const slug = ui.selectedOrganization?.slug || ''
const isOwner = ui.selectedOrganization?.is_owner
const enablePermissions = useFlag('enablePermissions')
const { mutateOrgMembers } = useOrganizationDetail(slug)
const [loading, setLoading] = useState(false)
const [selectedMember, setSelectedMember] = useState<SelectedMember>()
const [userRoleChangeModalVisible, setUserRoleChangeModalVisible] = useState(false)
const hasAccessToMemberActions = enablePermissions ? true : isOwner
const getRoleNameById = (id: number | undefined) => {
if (!roles) return id
return roles.find((x: any) => x.id === id)?.name
@@ -87,7 +83,7 @@ const MembersView = () => {
<Table.th key="header-status"></Table.th>,
<Table.th key="header-role" className="flex items-center space-x-2">
<span>Role</span>
{enablePermissions && <RolesHelperModal />}
<RolesHelperModal />
</Table.th>,
<Table.th key="header-action"></Table.th>,
]}
@@ -157,71 +153,64 @@ const MembersView = () => {
</Table.td>
<Table.td>
{!role && !enablePermissions && <p>{x.is_owner ? 'Owner' : 'Developer'}</p>}
{!role && <p>{x.is_owner ? 'Owner' : 'Developer'}</p>}
{role && (
<>
{!enablePermissions ? (
<p>{role?.name ?? 'Developer'}</p>
) : (
<Tooltip.Root delayDuration={0}>
<Tooltip.Trigger>
<Listbox
className={disableRoleEdit ? 'pointer-events-none' : ''}
<Tooltip.Root delayDuration={0}>
<Tooltip.Trigger>
<Listbox
className={disableRoleEdit ? 'pointer-events-none' : ''}
disabled={disableRoleEdit}
value={role.id}
onChange={validateSelectedRoleToChange}
>
{roles.map((r: any) => (
<Listbox.Option
key={r.id}
value={r.id}
label={r.name}
disabled={disableRoleEdit}
value={role.id}
onChange={validateSelectedRoleToChange}
>
{roles.map((r: any) => (
<Listbox.Option
key={r.id}
value={r.id}
label={r.name}
disabled={disableRoleEdit}
>
{r.name}
</Listbox.Option>
))}
</Listbox>
</Tooltip.Trigger>
{memberIsPendingInvite ? (
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
'border border-scale-200 ', //border
].join(' ')}
>
<span className="text-xs text-scale-1200">
Role can only be changed after the user has accepted the
invite
</span>
</div>
</Tooltip.Content>
) : !memberIsUser && !canRemoveRole ? (
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
'border border-scale-200 ', //border
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to manage this team member
</span>
</div>
</Tooltip.Content>
) : (
<></>
)}
</Tooltip.Root>
{r.name}
</Listbox.Option>
))}
</Listbox>
</Tooltip.Trigger>
{memberIsPendingInvite ? (
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
'border border-scale-200 ', //border
].join(' ')}
>
<span className="text-xs text-scale-1200">
Role can only be changed after the user has accepted the invite
</span>
</div>
</Tooltip.Content>
) : !memberIsUser && !canRemoveRole ? (
<Tooltip.Content side="bottom">
<Tooltip.Arrow className="radix-tooltip-arrow" />
<div
className={[
'rounded bg-scale-100 py-1 px-2 leading-none shadow', // background
'border border-scale-200 ', //border
].join(' ')}
>
<span className="text-xs text-scale-1200">
You need additional permissions to manage this team member
</span>
</div>
</Tooltip.Content>
) : (
<></>
)}
</>
</Tooltip.Root>
)}
</Table.td>
<Table.td>
{hasAccessToMemberActions && !memberIsUser && (
{!memberIsUser && (
<MemberActions members={PageState.members} member={x} roles={roles} />
)}
</Table.td>
@@ -2,7 +2,7 @@ import { useState, useContext } from 'react'
import { observer } from 'mobx-react-lite'
import { Button, Input, IconSearch } from 'ui'
import { useFlag, useStore } from 'hooks'
import { useStore } from 'hooks'
import { post } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import InviteMemberButton from './InviteMemberButton'
@@ -21,10 +21,8 @@ const TeamSettings = observer(() => {
const slug = ui.selectedOrganization?.slug ?? ''
const isOwner = ui.selectedOrganization?.is_owner
const enablePermissions = useFlag('enablePermissions')
const [isLeaving, setIsLeaving] = useState(false)
const canAddMembers = enablePermissions ? rolesAddable.length > 0 : isOwner
const canAddMembers = rolesAddable.length > 0
function onFilterMemberChange(e: any) {
PageState.membersFilterString = e.target.value
@@ -35,7 +35,7 @@ const CustomDomainConfig = () => {
return (
<section>
<FormHeader title="Custom Domains" description="" />
<FormHeader title="Custom Domains" description="Present a branded experience to your users" />
{data?.status === '0_no_hostname_configured' ? (
<CustomDomainsConfigureHostname projectRef={ref} />
) : (
@@ -83,9 +83,29 @@ const CustomDomainVerify = ({ projectRef, customDomain, settings }: CustomDomain
: 'Please note that it may take up to 24 hours for the DNS records to propagate.'
}
description={
isNotVerifiedYet
? 'Do check again in a bit as it may take up to 24 hours for changes in DNS records to propagate.'
: undefined
isNotVerifiedYet ? (
<div className="space-y-1">
<p>
Do check again in a bit as it may take up to 24 hours for changes in DNS
records to propagate.
</p>
<p>
You may also visit{' '}
<Link href={`https://whatsmydns.net/#TXT/${customDomain.hostname}`}>
<a className="text-brand-900">here</a>
</Link>{' '}
to check if your DNS has been propagated successfully before clicking verify.
</p>
</div>
) : (
<p>
You may also visit{' '}
<Link href={`https://whatsmydns.net/#TXT/${customDomain.hostname}`}>
<a className="text-brand-900">here</a>
</Link>{' '}
to check if your DNS has been propagated successfully before clicking verify.
</p>
)
}
/>
</div>
@@ -88,7 +88,7 @@ const CustomDomainsConfigureHostname = ({ projectRef }: CustomDomainsConfigureHo
</div>
}
>
<FormSection header={<FormSectionLabel>Add a Custom Domain</FormSectionLabel>}>
<FormSection header={<FormSectionLabel>Add a custom domain</FormSectionLabel>}>
<FormSectionContent loading={false}>
<Input
id="domain"
@@ -4,7 +4,7 @@ import { Button, Input } from 'ui'
import * as Tooltip from '@radix-ui/react-tooltip'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useStore, checkPermissions, useFlag } from 'hooks'
import { useStore, checkPermissions } from 'hooks'
import { API_URL, PRICING_TIER_PRODUCT_IDS } from 'lib/constants'
import { delete_, post } from 'lib/common/fetch'
import TextConfirmModal from 'components/ui/Modals/TextConfirmModal'
@@ -17,13 +17,10 @@ interface Props {
const DeleteProjectButton: FC<Props> = ({ type = 'danger' }) => {
const router = useRouter()
const { app, ui } = useStore()
const enablePermissions = useFlag('enablePermissions')
const project = ui.selectedProject
const projectRef = project?.ref
const projectTier = project?.subscription_tier ?? PRICING_TIER_PRODUCT_IDS.FREE
const isOwner = ui.selectedOrganization?.is_owner
const isFree = projectTier === PRICING_TIER_PRODUCT_IDS.FREE
const [isOpen, setIsOpen] = useState(false)
@@ -38,9 +35,7 @@ const DeleteProjectButton: FC<Props> = ({ type = 'danger' }) => {
}
}, [isOpen])
const canDeleteProject = enablePermissions
? checkPermissions(PermissionAction.UPDATE, 'projects')
: isOwner
const canDeleteProject = checkPermissions(PermissionAction.UPDATE, 'projects')
const toggle = () => {
if (loading) return
@@ -2,7 +2,7 @@ import { FC } from 'react'
import { Form, Input } from 'ui'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useStore, checkPermissions, useFlag } from 'hooks'
import { useStore, checkPermissions } from 'hooks'
import { API_URL } from 'lib/constants'
import { post } from 'lib/common/fetch'
import {
@@ -20,16 +20,11 @@ const General: FC<Props> = ({}) => {
const { app, ui } = useStore()
const project = ui.selectedProject
const enablePermissions = useFlag('enablePermissions')
const isOwner = ui.selectedOrganization?.is_owner
const formId = 'project-general-settings'
const initialValues = { name: project?.name ?? '', ref: project?.ref ?? '' }
const canUpdateProject = enablePermissions
? checkPermissions(PermissionAction.UPDATE, 'projects')
: isOwner
const canUpdateProject = checkPermissions(PermissionAction.UPDATE, 'projects')
const onSubmit = async (values: any, { setSubmitting, resetForm }: any) => {
const onSubmit = async (values: any, { resetForm }: any) => {
const response = await post(`${API_URL}/projects/${project?.ref}/update`, { name: values.name })
if (response.error) {
ui.setNotification({
@@ -5,7 +5,7 @@ import * as Tooltip from '@radix-ui/react-tooltip'
import { Button, IconPause } from 'ui'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { checkPermissions, useStore, useFlag } from 'hooks'
import { checkPermissions, useStore } from 'hooks'
import { post } from 'lib/common/fetch'
import { API_URL, PROJECT_STATUS } from 'lib/constants'
import ConfirmModal from 'components/ui/Dialogs/ConfirmDialog'
@@ -21,15 +21,13 @@ const PauseProjectButton: FC<Props> = observer(({ projectRef, projectId }) => {
const [loading, setLoading] = useState(false)
const [isModalOpen, setIsModalOpen] = useState(false)
const enablePermissions = useFlag('enablePermissions')
const isOwner = ui.selectedOrganization?.is_owner
const openModal = () => setIsModalOpen(true)
const closeModal = () => setIsModalOpen(false)
const canPauseProject = enablePermissions
? checkPermissions(PermissionAction.INFRA_EXECUTE, 'queue_jobs.projects.pause')
: isOwner
const canPauseProject = checkPermissions(
PermissionAction.INFRA_EXECUTE,
'queue_jobs.projects.pause'
)
const requestPauseProject = async () => {
if (!canPauseProject) {
@@ -23,7 +23,7 @@ interface Props {
sidePanelKey?: 'row' | 'column' | 'table'
/** Toggles if we're duplicating a table */
isDuplicating: boolean
/** Selected entities if we're editting a row, column or table */
/** Selected entities if we're editing a row, column or table */
selectedRowToEdit?: Dictionary<any>
selectedColumnToEdit?: PostgresColumn
selectedTableToEdit?: PostgresTable
@@ -16,21 +16,19 @@ interface Props {
const ProjectPausedState: FC<Props> = ({ project }) => {
const { ui, app } = useStore()
const isOwner = ui.selectedOrganization?.is_owner
const orgSlug = ui.selectedOrganization?.slug
const kpsEnabled = useFlag('initWithKps')
const enablePermissions = useFlag('enablePermissions')
const { membersExceededLimit } = useFreeProjectLimitCheck(orgSlug)
const hasMembersExceedingFreeTierLimit = (membersExceededLimit || []).length > 0
const [showConfirmRestore, setShowConfirmRestore] = useState(false)
const [showFreeProjectLimitWarning, setShowFreeProjectLimitWarning] = useState(false)
const { membersExceededLimit } = useFreeProjectLimitCheck(orgSlug)
const hasMembersExceedingFreeTierLimit = (membersExceededLimit || []).length > 0
const canResumeProject = enablePermissions
? checkPermissions(PermissionAction.INFRA_EXECUTE, 'queue_jobs.projects.initialize_or_resume')
: isOwner
const canResumeProject = checkPermissions(
PermissionAction.INFRA_EXECUTE,
'queue_jobs.projects.initialize_or_resume'
)
const onSelectRestore = () => {
if (!canResumeProject) {
@@ -43,10 +43,15 @@ function ConfirmModal({
loading={loading}
customFooter={
<div className="flex items-center gap-2">
<Button type="default" onClick={() => onCancelClick()}>
<Button type="default" disabled={loading} onClick={() => onCancelClick()}>
Cancel
</Button>
<Button type="primary" onClick={() => onConfirmClick()} loading={loading}>
<Button
type="primary"
disabled={loading}
onClick={() => onConfirmClick()}
loading={loading}
>
{confirmText}
</Button>
</div>
+1 -1
View File
@@ -26,7 +26,7 @@ const UpgradeToPro: FC<Props> = ({ icon, primaryText, projectRef, secondaryText
>
<div className="flex space-x-3">
{icon && <div className="mt-1">{icon}</div>}
<div className="flex w-full items-center justify-between">
<div className="flex w-full items-center justify-between space-x-32">
<div className="space-y-1">
<p className="text-sm">{primaryText}</p>
<div>
+3 -15
View File
@@ -3,25 +3,13 @@ import useSWR, { mutate } from 'swr'
import { Member } from 'types'
import { API_URL } from 'lib/constants'
import { get } from 'lib/common/fetch'
import { useFlag } from 'hooks'
export function useOrganizationDetail(slug: string) {
const enablePermissions = useFlag('enablePermissions')
// Get org members
const url = enablePermissions
? `${API_URL}/organizations/${slug}/members?member_roles`
: `${API_URL}/props/org/${slug}`
const url = `${API_URL}/organizations/${slug}/members?member_roles`
const { data, error } = useSWR<any>(slug ? url : null, get)
let members = enablePermissions
? data
: (data?.members ?? []).map((m: any) => ({
id: m.id,
is_owner: m.is_owner,
username: m.profile.username,
primary_email: m.profile.primary_email,
})) ?? []
let members = data
// Get pending invite users
const pendingInviteUrl = `${API_URL}/organizations/${slug}/members/invite`
@@ -37,7 +25,7 @@ export function useOrganizationDetail(slug: string) {
username: x.invited_email.slice(0, 1),
primary_email: x.invited_email,
}
return enablePermissions ? { ...member, role_ids: [x.role_id] } : member
return { ...member, role_ids: [x.role_id] }
})
members = [...members, ...invitedMembers]
+1 -4
View File
@@ -1,5 +1,5 @@
import jsonLogic from 'json-logic-js'
import { useFlag, useStore } from 'hooks'
import { useStore } from 'hooks'
import { get } from 'lib/common/fetch'
import { API_URL, IS_PLATFORM } from 'lib/constants'
import useSWR from 'swr'
@@ -40,9 +40,6 @@ export function checkPermissions(
) {
if (!IS_PLATFORM) return true
const enablePermissions = useFlag('enablePermissions')
if (!enablePermissions) return true
const { ui } = useStore()
const orgid = organizationId ?? ui?.selectedOrganization?.id
+1 -7
View File
@@ -45,7 +45,6 @@ const Wizard: NextPageWithLayout = () => {
const { slug } = router.query
const { app, ui } = useStore()
const enablePermissions = useFlag('enablePermissions')
const projectCreationDisabled = useFlag('disableProjectCreationAndUpdate')
const kpsEnabled = useFlag('initWithKps')
const subscriptionStats = useSubscriptionStats()
@@ -66,13 +65,8 @@ const Wizard: NextPageWithLayout = () => {
const currentOrg = organizations.find((o: any) => o.slug === slug)
const stripeCustomerId = currentOrg?.stripe_customer_id
const isOrganizationOwner = ui.selectedOrganization?.is_owner
const availableRegions = getAvailableRegions()
const isAdmin = enablePermissions
? checkPermissions(PermissionAction.CREATE, 'projects')
: isOrganizationOwner
const isAdmin = checkPermissions(PermissionAction.CREATE, 'projects')
const isInvalidSlug = isUndefined(currentOrg)
const isEmptyOrganizations = organizations.length <= 0 && app.organizations.isInitialized
const isEmptyPaymentMethod = paymentMethods ? !paymentMethods.length : false
@@ -42,10 +42,7 @@ const ResetDbPassword: FC<any> = () => {
const { ui } = useStore()
const projectRef = ui.selectedProject?.ref
const enablePermissions = useFlag('enablePermissions')
const canResetDbPassword = enablePermissions
? checkPermissions(PermissionAction.UPDATE, 'projects')
: ui.selectedOrganization?.is_owner
const canResetDbPassword = checkPermissions(PermissionAction.UPDATE, 'projects')
const [showResetDbPass, setShowResetDbPass] = useState<boolean>(false)
const [isUpdatingPassword, setIsUpdatingPassword] = useState<boolean>(false)
@@ -19,6 +19,8 @@ export const CONFIRMATION: FormSchema = {
type: 'code',
description: `
- \`{{ .ConfirmationURL }}\` : URL to confirm the message
- \`{{ .Token }}\` : The 6-digit numeric email OTP
- \`{{ .TokenHash }}\` : The hashed token used in the URL
`,
},
},
@@ -48,6 +50,8 @@ export const INVITE: FormSchema = {
type: 'code',
description: `
- \`{{ .ConfirmationURL }}\` : URL to confirm the message
- \`{{ .Token }}\` : The 6-digit numeric email OTP
- \`{{ .TokenHash }}\` : The hashed token used in the URL
`,
},
},
@@ -77,6 +81,8 @@ export const MAGIC_LINK: FormSchema = {
type: 'code',
description: `
- \`{{ .ConfirmationURL }}\` : URL to confirm the message
- \`{{ .Token }}\` : The 6-digit numeric email OTP
- \`{{ .TokenHash }}\` : The hashed token used in the URL
`,
},
},
@@ -106,6 +112,8 @@ export const EMAIL_CHANGE: FormSchema = {
type: 'code',
description: `
- \`{{ .ConfirmationURL }}\` : URL to confirm the email change
- \`{{ .Token }}\` : The 6-digit numeric email OTP
- \`{{ .TokenHash }}\` : The hashed token used in the URL
`,
},
},
@@ -135,6 +143,8 @@ export const RECOVERY: FormSchema = {
type: 'code',
description: `
- \`{{ .ConfirmationURL }}\` : URL to confirm the password reset
- \`{{ .Token }}\` : The 6-digit numeric email OTP
- \`{{ .TokenHash }}\` : The hashed token used in the URL
`,
},
},
+9 -1
View File
@@ -552,4 +552,12 @@
animation-name: Toastify__slideOutDown;
}
/*# sourceMappingURL=ReactToastify.css.map */
.toast-message {
max-width: 258px;
div[role='status'] {
@apply justify-start w-full break-words;
word-break: break-word;
}
}
/*# sourceMappingURL=ReactToastify.css.map */