mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 18:05:11 +03:00
Merge branch 'master' into chore/upgrade-to-node-18
This commit is contained in:
commit
6cf66fbda4
117 files changed
+2143
-1362
No files matched your search
@@ -472,6 +472,7 @@ export const auth = {
|
||||
items: [
|
||||
{ name: 'Overview', url: '/guides/auth/auth-helpers' },
|
||||
{ name: 'Auth UI', url: '/guides/auth/auth-helpers/auth-ui' },
|
||||
{ name: 'Flutter Auth UI', url: '/guides/auth/auth-helpers/flutter-auth-ui' },
|
||||
{
|
||||
name: 'Next.js',
|
||||
url: '/guides/auth/auth-helpers/nextjs',
|
||||
@@ -526,6 +527,10 @@ export const database: NavMenuConstant = {
|
||||
name: 'Postgres Guides',
|
||||
url: undefined,
|
||||
items: [
|
||||
{
|
||||
name: 'JSON and unstructured data',
|
||||
url: '/guides/database/json',
|
||||
},
|
||||
{ name: 'Implementing Full Text Search', url: '/guides/database/full-text-search' },
|
||||
{ name: 'Implementing Cascade Deletes', url: '/guides/database/postgres/cascade-deletes' },
|
||||
{ name: 'Implementing column encryption', url: '/guides/database/column-encryption' },
|
||||
|
||||
@@ -3,7 +3,7 @@ import fs from 'fs'
|
||||
import { CodeHikeConfig, remarkCodeHike } from '@code-hike/mdx'
|
||||
import matter from 'gray-matter'
|
||||
import { serialize } from 'next-mdx-remote/serialize'
|
||||
import codeHikeTheme from '~/code-hike.theme.json' assert { type: 'json' }
|
||||
import codeHikeTheme from 'config/code-hike.theme.json' assert { type: 'json' }
|
||||
import { ICommonMarkdown } from '~/components/reference/Reference.types'
|
||||
|
||||
async function generateRefMarkdown(sections: ICommonMarkdown[], slug: string) {
|
||||
|
||||
@@ -8,7 +8,7 @@ import withTM from 'next-transpile-modules'
|
||||
import withYaml from 'next-plugin-yaml'
|
||||
import configureBundleAnalyzer from '@next/bundle-analyzer'
|
||||
|
||||
import codeHikeTheme from './code-hike.theme.json' assert { type: 'json' }
|
||||
import codeHikeTheme from 'config/code-hike.theme.json' assert { type: 'json' }
|
||||
|
||||
const withBundleAnalyzer = configureBundleAnalyzer({
|
||||
enabled: process.env.ANALYZE === 'true',
|
||||
@@ -103,7 +103,15 @@ const nextConfig = {
|
||||
|
||||
const configExport = () => {
|
||||
const plugins = [
|
||||
withTM(['ui', 'common', '@supabase/auth-helpers-nextjs', 'mermaid', 'mdx-mermaid', 'dayjs']),
|
||||
withTM([
|
||||
'ui',
|
||||
'common',
|
||||
'@supabase/auth-helpers-nextjs',
|
||||
'mermaid',
|
||||
'mdx-mermaid',
|
||||
'dayjs',
|
||||
'shared-data',
|
||||
]),
|
||||
withMDX,
|
||||
withYaml,
|
||||
withBundleAnalyzer,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import '../../../packages/ui/build/css/themes/light.css'
|
||||
import '../../../packages/ui/build/css/themes/dark.css'
|
||||
|
||||
import '../styles/code-hike.scss'
|
||||
import 'config/code-hike.scss'
|
||||
import '../styles/main.scss?v=1.0.0'
|
||||
import '../styles/new-docs.scss'
|
||||
import '../styles/prism-okaidia.scss'
|
||||
|
||||
@@ -124,6 +124,12 @@ export const integrations = [
|
||||
</div>
|
||||
|
||||
export const customers = [
|
||||
{
|
||||
name: 'Berri AI Boosts Productivity by Migrating from AWS RDS to Supabase with pgvector',
|
||||
description:
|
||||
'Learn how Berri AI overcame challenges with self-hosting their vector database on AWS RDS and successfully migrated to Supabase.',
|
||||
href: 'https://supabase.com/customers/berriai',
|
||||
},
|
||||
{
|
||||
name: 'Mendable switches from Pinecone to Supabase for PostgreSQL vector embeddings',
|
||||
description:
|
||||
|
||||
@@ -284,7 +284,7 @@ With our database set up, we need to process and store all `.mdx` files in the `
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```txt
|
||||
```bash
|
||||
NEXT_PUBLIC_SUPABASE_URL=
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=
|
||||
SUPABASE_SERVICE_ROLE_KEY=
|
||||
|
||||
@@ -5,7 +5,7 @@ import { serialize } from 'next-mdx-remote/serialize'
|
||||
import { relative } from 'path'
|
||||
import rehypeSlug from 'rehype-slug'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import codeHikeTheme from '~/code-hike.theme.json' assert { type: 'json' }
|
||||
import codeHikeTheme from 'config/code-hike.theme.json' assert { type: 'json' }
|
||||
import components from '~/components'
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
import { UrlTransformFunction, linkTransform } from '~/lib/mdx/plugins/rehypeLinkTransform'
|
||||
|
||||
@@ -19,6 +19,14 @@ A collection of framework-specific Auth utilities for working with Supabase.
|
||||
description={'A pre-built React component for authenticating users.'}
|
||||
/>
|
||||
</div>
|
||||
{/* Flutter Auth UI */}
|
||||
<div className="col-span-6">
|
||||
<ButtonCard
|
||||
to={'/guides/auth/auth-helpers/flutter-auth-ui'}
|
||||
title={'Flutter Auth UI'}
|
||||
description={'Pre-built Flutter widgets for authenticating users.'}
|
||||
/>
|
||||
</div>
|
||||
{/* Next.js */}
|
||||
<div className="col-span-6">
|
||||
<ButtonCard
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
|
||||
export const meta = {
|
||||
id: 'flutter-auth-ui',
|
||||
title: 'Flutter Auth UI',
|
||||
description: 'Prebuilt, customizable Flutter widgets for authenticating users.',
|
||||
}
|
||||
|
||||
Flutter Auth UI is a Flutter package containing pre-built widgets for authenticating users.
|
||||
It is unstyled and can match your brand and aesthetic.
|
||||
|
||||
<image src="https://raw.githubusercontent.com/supabase-community/flutter-auth-ui/main/assets/supabase_auth_ui.png" />
|
||||
|
||||
## Add Flutter Auth UI
|
||||
|
||||
Add the latest version of the package [supabase-auth-ui](https://pub.dev/packages/supabase_auth_ui) to pubspec.yaml:
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
supabase_auth_ui: ^0.1.0+2
|
||||
```
|
||||
|
||||
### Initialize the Flutter Auth Package
|
||||
|
||||
```dart
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:supabase_auth_ui/supabase_auth_ui.dart';
|
||||
|
||||
void main() async {
|
||||
await Supabase.initialize(
|
||||
url: dotenv.get('SUPABASE_URL'),
|
||||
anonKey: dotenv.get('SUPABASE_ANON_KEY'),
|
||||
);
|
||||
|
||||
runApp(const MyApp());
|
||||
}
|
||||
```
|
||||
|
||||
### Email Auth
|
||||
|
||||
Use a SupaEmailAuth widget to create an email and password signin and signup form. It also contains a button to toggle to display a forgot password form.
|
||||
|
||||
You can pass metadataFields to add additional fields to the form to pass as metadata to Supabase.
|
||||
|
||||
```dart
|
||||
SupaEmailAuth(
|
||||
redirectTo: kIsWeb ? null : 'io.mydomain.myapp://callback',
|
||||
onSignInComplete: (response) {},
|
||||
onSignUpComplete: (response) {},
|
||||
metadataFields: [
|
||||
MetaDataField(
|
||||
prefixIcon: const Icon(Icons.person),
|
||||
label: 'Username',
|
||||
key: 'username',
|
||||
validator: (val) {
|
||||
if (val == null || val.isEmpty) {
|
||||
return 'Please enter something';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
### Magic Link Auth
|
||||
|
||||
Use SupaMagicAuth widget to create a magic link signIn form.
|
||||
|
||||
```dart
|
||||
SupaMagicAuth(
|
||||
redirectUrl: kIsWeb ? null : 'io.mydomain.myapp://callback',
|
||||
onSuccess: (Session response) {},
|
||||
onError: (error) {},
|
||||
)
|
||||
```
|
||||
|
||||
### Reset password
|
||||
|
||||
Use SupaResetPassword to create a password reset form.
|
||||
|
||||
```dart
|
||||
SupaResetPassword(
|
||||
accessToken: supabase.auth.currentSession?.accessToken,
|
||||
onSuccess: (UserResponse response) {},
|
||||
onError: (error) {},
|
||||
)
|
||||
```
|
||||
|
||||
### Phone Auth
|
||||
|
||||
Use SupaPhoneAuth to create a phone authentication form.
|
||||
|
||||
```dart
|
||||
SupaPhoneAuth(
|
||||
authAction: SupaAuthAction.signUp,
|
||||
onSuccess: (AuthResponse response) {},
|
||||
),
|
||||
```
|
||||
|
||||
### Social Auth
|
||||
|
||||
The package supports login with [official social providers](../../auth#providers).
|
||||
|
||||
Use SupaSocialsAuth to create list of social login buttons.
|
||||
|
||||
```dart
|
||||
SupaSocialsAuth(
|
||||
socialProviders: [
|
||||
SocialProviders.apple,
|
||||
SocialProviders.google,
|
||||
],
|
||||
colored: true,
|
||||
redirectUrl: kIsWeb
|
||||
? null
|
||||
: 'io.mydomain.myapp://callback',
|
||||
onSuccess: (Session response) {},
|
||||
onError: (error) {},
|
||||
)
|
||||
```
|
||||
|
||||
### Theming
|
||||
|
||||
This package uses plain Flutter components allowing you to control the appearance of the components using your own theme.
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
@@ -10,6 +10,15 @@ export const meta = {
|
||||
|
||||
The [Next.js Auth Helpers package](https://github.com/supabase/auth-helpers) configures Supabase Auth to store the user's `session` in a `cookie`, rather than `localStorage`. This makes it available across the client and server of the App Router - [Client Components](/docs/guides/auth/auth-helpers/nextjs#client-components), [Server Components](/docs/guides/auth/auth-helpers/nextjs#server-components), [Server Actions](/docs/guides/auth/auth-helpers/nextjs#server-actions), [Route Handlers](/docs/guides/auth/auth-helpers/nextjs#route-handlers) and [Middleware](/docs/guides/auth/auth-helpers/nextjs#middleware). The `session` is automatically sent along with any requests to Supabase.
|
||||
|
||||
<div className="video-container">
|
||||
<iframe
|
||||
src="https://www.youtube-nocookie.com/embed/MVYhyGZGLuo"
|
||||
frameBorder="1"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
> Note: If you are using the `pages` directory, check out [Auth Helpers in Next.js Pages Directory](/docs/guides/auth/auth-helpers/nextjs-pages).
|
||||
|
||||
## Configuration
|
||||
@@ -170,6 +179,15 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
## Authentication
|
||||
|
||||
<div className="video-container">
|
||||
<iframe
|
||||
src="https://www.youtube-nocookie.com/embed/11zi7sUJFoU"
|
||||
frameBorder="1"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
Authentication can be initiated [client](/docs/guides/auth/auth-helpers/nextjs#client-side) or [server-side](/docs/guides/auth/auth-helpers/nextjs#server-side). All of the [supabase-js authentication strategies](/docs/reference/javascript/auth-api) are supported with the Auth Helpers client.
|
||||
|
||||
> Note: The authentication flow requires the [Code Exchange Route](/docs/guides/auth/auth-helpers/nextjs#code-exchange-route) to exchange a `code` for the user's `session`.
|
||||
@@ -446,6 +464,15 @@ export default async function Login() {
|
||||
|
||||
### Client Component
|
||||
|
||||
<div className="video-container">
|
||||
<iframe
|
||||
src="https://www.youtube-nocookie.com/embed/f4yAnVgcJqI"
|
||||
frameBorder="1"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
[Client Components](https://nextjs.org/docs/getting-started/react-essentials#client-components) allow the use of client-side hooks - such as `useEffect` and `useState`. They can be used to request data from Supabase client-side, and [subscribe to realtime events](https://github.com/supabase/supabase/tree/master/examples/auth/nextjs/app/realtime-posts.tsx).
|
||||
|
||||
<Tabs
|
||||
@@ -531,6 +558,15 @@ const supabase = createClientComponentClient({ isSingleton: false })
|
||||
|
||||
### Server Component
|
||||
|
||||
<div className="video-container">
|
||||
<iframe
|
||||
src="https://www.youtube-nocookie.com/embed/SUS1t6Kq7-8"
|
||||
frameBorder="1"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
[Server Components](https://nextjs.org/docs/getting-started/react-essentials#server-components) allow for asynchronous data to be fetched server-side.
|
||||
|
||||
> Note: In order to use Supabase in Server Components, you need to have implemented the [Middleware](/docs/guides/auth/auth-helpers/nextjs#refresh-session-with-middleware) steps above.
|
||||
@@ -580,6 +616,15 @@ export default async function ServerComponent() {
|
||||
|
||||
### Server Action
|
||||
|
||||
<div className="video-container">
|
||||
<iframe
|
||||
src="https://www.youtube-nocookie.com/embed/B5TUzTKO9CE"
|
||||
frameBorder="1"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
[Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions) allow mutations to be performed server-side.
|
||||
|
||||
> Note: Server Actions are currently in `alpha` so may change without notice.
|
||||
@@ -651,6 +696,15 @@ export default async function NewTodo() {
|
||||
|
||||
### Route Handler
|
||||
|
||||
<div className="video-container">
|
||||
<iframe
|
||||
src="https://www.youtube-nocookie.com/embed/3kK-40z0DHI"
|
||||
frameBorder="1"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
[Route Handlers](https://nextjs.org/docs/app/building-your-application/routing/router-handlers) replace API Routes and allow for logic to be performed server-side. They can respond to `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, and `OPTIONS` requests.
|
||||
|
||||
<Tabs
|
||||
@@ -704,8 +758,9 @@ See [refreshing session example](/docs/guides/auth/auth-helpers/nextjs#refresh-s
|
||||
|
||||
## More examples
|
||||
|
||||
- [Full App Router repo](https://github.com/supabase/supabase/tree/master/examples/auth/nextjs)
|
||||
- [Realtime](https://github.com/supabase/supabase/tree/master/examples/auth/nextjs/app/realtime-posts.tsx)
|
||||
- [Cookie-based Auth and the Next.js 13 App Router (free course)](https://youtube.com/playlist?list=PL5S4mPUpp4OtMhpnp93EFSo42iQ40XjbF)
|
||||
- [Full App Router example](https://github.com/supabase/supabase/tree/master/examples/auth/nextjs)
|
||||
- [Realtime Subscriptions](https://github.com/supabase/supabase/tree/master/examples/auth/nextjs/app/realtime-posts.tsx)
|
||||
- [Protected Routes](https://github.com/supabase/supabase/tree/master/examples/auth/nextjs/app/[id]/page.tsx)
|
||||
- [Conditional Rendering in Client Components with SSR](https://github.com/supabase/supabase/tree/master/examples/auth/nextjs/app/login-form.tsx)
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ async function signInWithGoogle() {
|
||||
queryParams: {
|
||||
access_type: 'offline',
|
||||
prompt: 'consent',
|
||||
hd: 'domain.com //google will also allowo OAuth logins to be restricted to a specified domain using the 'hd' parameter
|
||||
hd: 'domain.com', // google will also allow OAuth logins to be restricted to a specified domain using the 'hd' parameter
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -12,7 +12,7 @@ We can reference the environment variable by using the `env()` function.
|
||||
|
||||
Inside of our `.env` file we add the environment variable as we normally would
|
||||
|
||||
```env
|
||||
```bash
|
||||
GITHUB_CLIENT_ID=""
|
||||
GITHUB_SECRET=""
|
||||
```
|
||||
|
||||
@@ -226,7 +226,7 @@ final data = await supabase.rpc('nearby_restaurants',params: {
|
||||
|
||||

|
||||
|
||||
When you are working on a map-based application where the user scrolls through your map, you might want to load the that lies within the bounding box of the map every time your users scroll. PostGIS can return the rows that are within the bounding box just by supplying the bottom left and the top right coordinates. Let’s look at what the function would look like.
|
||||
When you are working on a map-based application where the user scrolls through your map, you might want to load the data that lies within the bounding box of the map every time your users scroll. PostGIS can return the rows that are within the bounding box just by supplying the bottom left and the top right coordinates. Let’s look at what the function would look like:
|
||||
|
||||
```sql
|
||||
create or replace function restaurants_in_view(min_lat float, min_long float, max_lat float, max_long float)
|
||||
@@ -235,14 +235,14 @@ language sql
|
||||
as $$
|
||||
select id, name, st_astext(location) as location
|
||||
from public.restaurants
|
||||
where location && ST_SetSRID(ST_MakeBox2D(ST_Point(min_long, min_lat), ST_Point(max_long, max_lat)),4326)
|
||||
where location && ST_SetSRID(ST_MakeBox2D(ST_Point(min_long, min_lat), ST_Point(max_long, max_lat)), 4326)
|
||||
$$;
|
||||
```
|
||||
|
||||
[`&&`](https://postgis.net/docs/geometry_overlaps.html) operator used in the `where` statement here returns a boolean of whether the bounding box of the two geometries intersect or not. We basically are creating a bounding box from the two points and finding those points that fall under the bounding box. We are also utilizing a few different PostGIS functions here.
|
||||
The [`&&`](https://postgis.net/docs/geometry_overlaps.html) operator used in the `where` statement here returns a boolean of whether the bounding box of the two geometries intersect or not. We are basically creating a bounding box from the two points and finding those points that fall under the bounding box. We are also utilizing a few different PostGIS functions:
|
||||
|
||||
- [ST_MakeBox2D](https://postgis.net/docs/ST_MakeBox2D.html): Creates a 2-dimensional box from two points.
|
||||
- [ST_SetSRID](https://postgis.net/docs/ST_SetSRID.html): Sets the [SRID](https://postgis.net/docs/manual-dev/using_postgis_dbmanagement.html#spatial_ref_sys), which is an identifier of what coordinate system to use, for the geometry. 4326 the standard longitude and latitude coordinate systems.
|
||||
- [ST_SetSRID](https://postgis.net/docs/ST_SetSRID.html): Sets the [SRID](https://postgis.net/docs/manual-dev/using_postgis_dbmanagement.html#spatial_ref_sys), which is an identifier of what coordinate system to use for the geometry. 4326 is the standard longitude and latitude coordinate system.
|
||||
|
||||
You can call this function from your client using `rpc()` like this:
|
||||
|
||||
|
||||
@@ -2,45 +2,36 @@ import Layout from '~/layouts/DefaultGuideLayout'
|
||||
|
||||
export const meta = {
|
||||
id: 'json',
|
||||
title: 'JSON',
|
||||
description: 'Using the JSON data type in PostgreSQL.',
|
||||
title: 'Managing JSON and unstructured data',
|
||||
description: 'Using the JSON data type in Postgres.',
|
||||
subtitle: 'Using the JSON data type in Postgres.',
|
||||
}
|
||||
|
||||
PostgreSQL supports [JSON functions and operators](https://www.postgresql.org/docs/current/functions-json.html) which gives flexibility when storing data inside a database column.
|
||||
Postgres supports storing and querying unstructured data.
|
||||
|
||||
PostgreSQL supports two types of JSON columns: `JSON` and `JSONB`.
|
||||
The recommended type is `JSONB` for almost all cases.
|
||||
When you use the `JSONB` format, the data is parsed when it's put into the database so it's faster when querying and also it can be indexed.
|
||||
## JSON vs JSONB
|
||||
|
||||
## Create a table with a JSON column
|
||||
Postgres supports two types of JSON columns: `json` (stored as a string) and `jsonb` (stored as a binary). The recommended type is `jsonb` for almost all cases.
|
||||
|
||||
- `json` stores an exact copy of the input text. Database functions must reparse the content on each execution.
|
||||
- `jsonb` stores database in a decomposed binary format. While this makes it slightly slower to input due to added conversion overhead, it is significantly faster to process, since no reparsing is needed.
|
||||
|
||||
## When to use JSON/JSONB
|
||||
|
||||
Generally you should use a `jsonb` column when you have data that is unstructured or has a variable schema. For example, if you wanted to store responses for various webhooks, you might not know the format of the response when creating the table. Instead, you could store the `payload` as a `jsonb` object in a single column.
|
||||
|
||||
Don't go overboard with `json/jsonb` columns. They are a useful tool, but most of the benefits of a relational database come from the ability to query and join structured data, and the referential integrity that brings.
|
||||
|
||||
## Create JSONB columns
|
||||
|
||||
`json/jsonb` is just another "data type" for Postgres columns. You can create a `jsonb` column in the same way you would create a `text` or `int` column:
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="dashboard"
|
||||
defaultActiveId="sql"
|
||||
>
|
||||
<TabPanel id="dashboard" label="Dashboard">
|
||||
|
||||
1. Go to the [Table Editor](https://app.supabase.com/project/_/editor) page in the Dashboard.
|
||||
2. Click **New Table** and create a table called `books`.
|
||||
3. Include a primary key with the following properties:
|
||||
- Name: `id`
|
||||
- Type: `int8`
|
||||
- Default value: `Automatically generate as indentity`
|
||||
4. Click **Save**.
|
||||
5. Click **New Column** and add 3 columns with the following properties:
|
||||
- **title** column
|
||||
- Name: `title`
|
||||
- Type: `text`
|
||||
- **author** column
|
||||
- Name: `author`
|
||||
- Type: `text`
|
||||
- **metadata** column
|
||||
- Name: `metadata`
|
||||
- Type: `jsonb`
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="sql" label="SQL">
|
||||
|
||||
```sql
|
||||
@@ -52,32 +43,39 @@ create table books (
|
||||
);
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="dashboard" label="Dashboard">
|
||||
|
||||
1. Go to the [Table Editor](https://app.supabase.com/project/_/editor) page in the Dashboard.
|
||||
2. Click **New Table** and create a table called `books`.
|
||||
3. Include a primary key with the following properties and click save:
|
||||
|
||||
- Name: `id`
|
||||
- Type: `int8`
|
||||
- Default value: `Automatically generate as indentity`
|
||||
- **title** column
|
||||
- Name: `title`
|
||||
- Type: `text`
|
||||
- **author** column
|
||||
- Name: `author`
|
||||
- Type: `text`
|
||||
- **metadata** column
|
||||
- Name: `metadata`
|
||||
- Type: `jsonb`
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
## Insert data into the table
|
||||
## Inserting JSON data
|
||||
|
||||
You can insert JSON data in the same way that you insert any other data. The data must be valid JSON.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="dashboard"
|
||||
defaultActiveId="sql"
|
||||
>
|
||||
<TabPanel id="dashboard" label="Dashboard">
|
||||
|
||||
1. Go to the [Table Editor](https://app.supabase.com/project/_/editor) page in the Dashboard.
|
||||
2. Select the `books` table in the sidebar.
|
||||
3. Click **+ Insert row** and add 5 rows with the following properties:
|
||||
|
||||
| id | title | author | metadata |
|
||||
| --- | ----------------------------------- | ---------------------- | --------------------------------------------------------------------------------------------------------------------- |
|
||||
| 1 | The Poky Little Puppy | Janette Sebring Lowrey | `json {"ages":[3,6],"price":5.95,"description":"Puppy is slower than other, bigger animals."}` |
|
||||
| 2 | The Tale of Peter Rabbit | Beatrix Potter | `json {"ages":[2,5],"price":4.49,"description":"Rabbit eats some vegetables."}` |
|
||||
| 3 | Tootle | Gertrude Crampton | `json {"ages":[2,5],"price":3.99,"description":"Little toy train has big dreams."}` |
|
||||
| 4 | Green Eggs and Ham | Dr. Seuss | `json {"ages":[4,8],"price":7.49,"description":"Sam has changing food preferences and eats unusually colored food."}` |
|
||||
| 5 | Harry Potter and the Goblet of Fire | J.K. Rowling | `json {"ages":[10,99],"price":24.95,"description":"Fourth year of school starts, big drama ensues."}` |
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="sql" label="SQL">
|
||||
|
||||
```sql
|
||||
@@ -111,6 +109,21 @@ values
|
||||
);
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="dashboard" label="Dashboard">
|
||||
|
||||
1. Go to the [Table Editor](https://app.supabase.com/project/_/editor) page in the Dashboard.
|
||||
2. Select the `books` table in the sidebar.
|
||||
3. Click **+ Insert row** and add 5 rows with the following properties:
|
||||
|
||||
| id | title | author | metadata |
|
||||
| --- | ----------------------------------- | ---------------------- | --------------------------------------------------------------------------------------------------------------------- |
|
||||
| 1 | The Poky Little Puppy | Janette Sebring Lowrey | `json {"ages":[3,6],"price":5.95,"description":"Puppy is slower than other, bigger animals."}` |
|
||||
| 2 | The Tale of Peter Rabbit | Beatrix Potter | `json {"ages":[2,5],"price":4.49,"description":"Rabbit eats some vegetables."}` |
|
||||
| 3 | Tootle | Gertrude Crampton | `json {"ages":[2,5],"price":3.99,"description":"Little toy train has big dreams."}` |
|
||||
| 4 | Green Eggs and Ham | Dr. Seuss | `json {"ages":[4,8],"price":7.49,"description":"Sam has changing food preferences and eats unusually colored food."}` |
|
||||
| 5 | Harry Potter and the Goblet of Fire | J.K. Rowling | `json {"ages":[10,99],"price":24.95,"description":"Fourth year of school starts, big drama ensues."}` |
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
@@ -167,48 +180,11 @@ const { data, error } = await supabase.from('books').insert([
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
## View the data
|
||||
## Query JSON data
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="sql"
|
||||
>
|
||||
<TabPanel id="sql" label="SQL">
|
||||
Querying JSON data is similar to querying other data, with a few other features to access nested values.
|
||||
|
||||
```sql
|
||||
select *
|
||||
from books;
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
```js
|
||||
const { data, error } = await supabase.from('books').select('*')
|
||||
console.log(JSON.stringify(data, null, 2))
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="result" label="Result">
|
||||
|
||||
| id | title | author | metadata |
|
||||
| --- | ----------------------------------- | ---------------------- | --------------------------------------------------------------------------------------------------------------------- |
|
||||
| 1 | The Poky Little Puppy | Janette Sebring Lowrey | `json{"ages":[3,6],"price":5.95,"description":"Puppy is slower than other, bigger animals."}` |
|
||||
| 2 | The Tale of Peter Rabbit | Beatrix Potter | `json{"ages":[2,5],"price":4.49,"description":"Rabbit eats some vegetables."} ` |
|
||||
| 3 | Tootle | Gertrude Crampton | `json{"ages":[2,5],"price":3.99,"description":"Little toy train has big dreams."} ` |
|
||||
| 4 | Green Eggs and Ham | Dr. Seuss | `json{"ages":[4,8],"price":7.49,"description":"Sam has changing food preferences and eats unusually colored food."} ` |
|
||||
| 5 | Harry Potter and the Goblet of Fire | J.K. Rowling | `json{"ages":[10,99],"price":24.95,"description":"Fourth year of school starts, big drama ensues."} ` |
|
||||
|
||||
The data as it appears here has the `JSONB` fields in a different order than when inserted. As mentioned earlier, data is parsed as its inserted when using the JSONB format.
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
## Query the `JSONB` data
|
||||
|
||||
Select the title, description, price, and age range for each book.
|
||||
Postgres support a range of [JSON functions and operators](https://www.postgresql.org/docs/current/functions-json.html). For example, the `->` operator returns values as `jsonb` data. If you want the data returned as `text`, use the `->>` operator.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
@@ -221,7 +197,7 @@ Select the title, description, price, and age range for each book.
|
||||
```sql
|
||||
select
|
||||
title,
|
||||
metadata -> 'description' as description,
|
||||
metadata ->> 'description' as description, -- returned as text
|
||||
metadata -> 'price' as price,
|
||||
metadata -> 'ages' -> 0 as low_age,
|
||||
metadata -> 'ages' -> 1 as high_age
|
||||
@@ -232,12 +208,13 @@ from books;
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
```js
|
||||
const { data, error } = await supabase
|
||||
.from('books')
|
||||
.select(
|
||||
'title,description:metadata->description,price:metadata->price,low_age:metadata->ages->0,high_age:metadata->ages->1'
|
||||
)
|
||||
console.log(JSON.stringify(data, null, 2))
|
||||
const { data, error } = await supabase.from('books').select(`
|
||||
title,
|
||||
description: metadata->description,
|
||||
price: metadata->price,
|
||||
low_age: metadata->ages->0,
|
||||
high_age: metadata->ages->1
|
||||
`)
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
@@ -254,16 +231,42 @@ console.log(JSON.stringify(data, null, 2))
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
Note that the `->` operator returns JSONB data. If you want TEXT/STRING data returned, use the `->>` operator.
|
||||
## Validating JSON data
|
||||
|
||||
- metadata -> 'description' (returns a JSON object)
|
||||
- metadata ->> 'description' (returns STRING/TEXT data)
|
||||
Supabase provides the [`pg_jsonschema` extension](/docs/guides/database/extensions/pg_jsonschema) that adds the ability to validate `json` and `jsonb` data types against [JSON Schema](https://json-schema.org/) documents.
|
||||
|
||||
Once you have enabled the extension, you can add a "check constraint" to your table to validate the JSON data:
|
||||
|
||||
```sql
|
||||
create table customers (
|
||||
id serial primary key,
|
||||
metadata json
|
||||
);
|
||||
|
||||
alter table customers
|
||||
add constriant check_metadata check (
|
||||
json_matches_schema(
|
||||
'{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"maxLength": 16
|
||||
}
|
||||
}
|
||||
}
|
||||
}',
|
||||
metadata
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [Supabase JS Client](https://github.com/supabase/supabase-js)
|
||||
- [PostgreSQL: JSON Functions and Operators](https://www.postgresql.org/docs/12/functions-json.html)
|
||||
- [PostgreSQL JSON types](https://www.postgresql.org/docs/12/datatype-json.html)
|
||||
- [Postgres: JSON Functions and Operators](https://www.postgresql.org/docs/current/functions-json.html)
|
||||
- [Postgres JSON types](https://www.postgresql.org/docs/current/datatype-json.html)
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
|
||||
@@ -233,30 +233,26 @@ To encrypt data, you need a _key id_. You can use the default key id created aut
|
||||
|
||||
Both `vault.create_secret()` and `vault.update_secret()` take an optional fourth `new_key_id` argument. This argument can be used to store a different key id for the secret instead of the default value.
|
||||
|
||||
{/* prettier-ignore */}
|
||||
```sql
|
||||
postgres=> select vault.create_secret('another_s3kre3t_key', 'another_unique_name',
|
||||
'This is another description', (pgsodium.create_key()).id);
|
||||
select vault.create_secret(
|
||||
'another_s3kre3t_key',
|
||||
'another_unique_name',
|
||||
'This is another description',
|
||||
(pgsodium.create_key()).id
|
||||
);
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```sh
|
||||
-[ RECORD 1 ]-+-------------------------------------
|
||||
create_secret | cec9e005-a44d-4b19-86e1-febf3cd40619
|
||||
```
|
||||
|
||||
Which roles should have access to the `vault.secrets` table should be carefully considered. There are two ways to grant access, the first is that the `postgres` user can explicitly grant access to the vault table itself.
|
||||
|
||||
## Turning off Statement Logging
|
||||
|
||||
When you insert secrets into the vault table with an INSERT statement, those statements get logged by default into the Supabase logs. Since this would mean your secrets are stored unencrypted in the logs, you should turn off statement logging while using the Vault.
|
||||
|
||||
While turning off statement logging does hinder you if you're used to looking at the logs to debug your application, it provides a much higher level of security by ensuring that your data does not leak out of the database and into the logs. This is especially critical with encrypted column data, because the statement logs will contain the _unencrypted_ secrets. If you _must_ store that data encrypted, then you _must_ turn off statement logging.
|
||||
|
||||
```sql
|
||||
alter system set statement_log = 'none';
|
||||
```
|
||||
|
||||
And then restart your project from the dashboard to enable that change.
|
||||
|
||||
In the future we are researching various ways to refine the way statement logging interacts with sensitive columns.
|
||||
|
||||
## Resources
|
||||
### Resources
|
||||
|
||||
- Read more about Supabase Vault in the [blog post](https://supabase.com/blog/vault-now-in-beta)
|
||||
- [Supabase Vault on GitHub](https://github.com/supabase/vault)
|
||||
|
||||
@@ -26,7 +26,7 @@ Find the example on [GitHub](https://github.com/supabase/supabase/tree/master/ex
|
||||
|
||||
Get your database connection credentials from your [Supabase Dashboard](https://app.supabase.com/project/_/settings/database) and store them in an `.env` file:
|
||||
|
||||
```txt .env
|
||||
```bash .env
|
||||
DB_HOSTNAME=
|
||||
DB_PASSWORD=
|
||||
DB_SSL_CERT="-----BEGIN CERTIFICATE-----
|
||||
|
||||
@@ -132,7 +132,7 @@ In a given workspace (or workspace folder), sub-paths can be enabled for Deno, w
|
||||
|
||||
For example if you have a project like this:
|
||||
|
||||
```txt
|
||||
```
|
||||
project
|
||||
├── app
|
||||
└── supabase
|
||||
@@ -201,7 +201,7 @@ For use-cases which require low-latency we recommend [Edge Functions](/docs/guid
|
||||
|
||||
We recommend developing “fat functions”. This means that you should develop few large functions, rather than many small functions. One common pattern when developing Functions is that you need to share code between two or more Functions. To do this, you can store any shared code in a folder prefixed with an underscore (`_`). We recommend this folder structure:
|
||||
|
||||
```txt
|
||||
```bash
|
||||
└── supabase
|
||||
├── functions
|
||||
│ ├── import_map.json # A top-level import map to use across functions.
|
||||
|
||||
@@ -62,8 +62,8 @@ NEXT_PUBLIC_SUPABASE_URL=YOUR_SUPABASE_URL
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
|
||||
```
|
||||
|
||||
And one optional step is to update the CSS file `styles/globals.css` to make the app look nice.
|
||||
You can find the full contents of this file [here](https://raw.githubusercontent.com/supabase/supabase/master/examples/user-management/nextjs-ts-user-management/styles/globals.css).
|
||||
And one optional step is to update the CSS file `app/globals.css` to make the app look nice.
|
||||
You can find the full contents of this file [here](https://raw.githubusercontent.com/supabase/supabase/master/examples/user-management/nextjs-user-management/app/globals.css).
|
||||
|
||||
### Supabase Auth Helpers
|
||||
|
||||
@@ -241,6 +241,12 @@ export default function AuthForm() {
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
If you are using TypeScript for this project, see [generating types](/docs/guides/api/rest/generating-types) to automatically generate types from your database tables.
|
||||
|
||||
</Admonition>
|
||||
|
||||
Add the `AuthForm` component to your home page
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ PUBLIC_SUPABASE_URL="YOUR_SUPABASE_URL"
|
||||
PUBLIC_SUPABASE_ANON_KEY="YOUR_SUPABASE_KEY"
|
||||
```
|
||||
|
||||
Optionally, add `src/styles.css` with the [CSS from the example](https://raw.githubusercontent.com/supabase/supabase/master/examples/user-management/svelte-user-management/src/app.css).
|
||||
Optionally, add `src/styles.css` with the [CSS from the example](https://raw.githubusercontent.com/supabase/supabase/master/examples/user-management/sveltekit-user-management/src/styles.css).
|
||||
|
||||
### Supabase Auth Helpers
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ Create a `.env.local` file and enter the following values
|
||||
- `NEXT_PUBLIC_SUPABASE_ANON_KEY` => You can find this project API key under "Settings > API" in your Supabase project
|
||||
- `SUAPBASE_JWT_SECRET`=> You can find this secret under "Settings > API" in your Supabase project
|
||||
|
||||
```txt
|
||||
```bash
|
||||
NEXT_PUBLIC_PICKET_PUBLISHABLE_KEY="YOUR_PICKET_PUBLISHABLE_KEY"
|
||||
PICKET_PROJECT_SECRET_KEY="YOUR_PICKET_PROJECT_SECRET_KEY"
|
||||
NEXT_PUBLIC_SUPABASE_URL="YOUR_SUPABASE_URL"
|
||||
|
||||
@@ -68,8 +68,7 @@ touch .env
|
||||
|
||||
In the `.env` file, add a `DATABASE_URL` variable and add the connection string from **step 1**. The `.env` file should look like:
|
||||
|
||||
```env
|
||||
# .env
|
||||
```bash .env
|
||||
DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:5432/postgres"
|
||||
```
|
||||
|
||||
@@ -147,13 +146,12 @@ Append the `?pgbouncer=true` flag to the `DATABASE_URL` variable.
|
||||
|
||||
Your `.env` file should resemble the following:
|
||||
|
||||
```env
|
||||
# .env
|
||||
```bash .env
|
||||
# PostgreSQL connection string used for migrations
|
||||
DIRECT_URL="postgres://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:5432/postgres"
|
||||
|
||||
# PostgreSQL connection string with pgBouncer config — used by Prisma Client
|
||||
DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:6543/postgres?pgbouncer=true"
|
||||
|
||||
```
|
||||
|
||||
Update your Prisma schema by setting the `directUrl` in the datasource block:
|
||||
|
||||
@@ -32,7 +32,7 @@ body {
|
||||
}
|
||||
|
||||
::selection {
|
||||
background-color: #6ee7b7;
|
||||
background-color: #6ee7b7 !important;
|
||||
color: #333 !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,14 +10,19 @@ date: '2022-08-19'
|
||||
toc_depth: 2
|
||||
---
|
||||
|
||||
Today we're announcing [Supabase Vault](https://github.com/supabase/vault), a Postgres extension for managing secrets and encryption inside your database.
|
||||
Today we're announcing [Supabase Vault](https://supabase.com/docs/guides/database/vault), a Postgres extension for managing secrets and encryption inside your database.
|
||||
Vault is a thin usability-layer on top of [pgsodium](https://github.com/michelp/pgsodium).
|
||||
|
||||
Vault is not available on the Supabase platform yet. We're finalizing usability and security testing.
|
||||
This release is intended to gather user-feedback from our community and customers.
|
||||
<div className="bg-gray-300 rounded-lg px-6 py-2 bold">
|
||||
|
||||
Soon, we'll release a blog post that describes more about the technology behind Vault - [libsodium](https://doc.libsodium.org/) and
|
||||
[pgsodium](https://doc.libsodium.org/). For now, we will go through a quick example of storing a secret, like a service access token, into the Vault.
|
||||
❇️ UPDATE JUNE 2023 ❇️
|
||||
|
||||
Vault is now available on every Supabase project. [Check it out](https://app.supabase.com/project/_/settings/vault/secrets)
|
||||
|
||||
</div>
|
||||
|
||||
[Transparent Column Encryption with Postgres](https://supabase.com/blog/transparent-column-encryption-with-postgres) is a blog post that describes the technology behind Vault - [libsodium](https://doc.libsodium.org/) and
|
||||
[pgsodium](https://doc.libsodium.org/). Now, we will go through a quick example of storing a secret, like a service access token, into the Vault.
|
||||
|
||||
## Background
|
||||
|
||||
@@ -179,4 +184,4 @@ optionally creating new keys, and select them from the view when you want to use
|
||||
The Vault is good for a reasonable amount of secure data, like API keys, access tokens, or environment variables.
|
||||
But if you have a lot more sensitive information, like personally Identifiable Information (PII),
|
||||
you may want to break them out into side-tables using pgsodium's
|
||||
[Transparent Column Encryption](https://github.com/michelp/pgsodium#transparent-column-encryption) which we will describe soon in a follow-up blog post. Stay tuned!
|
||||
[Transparent Column Encryption](https://supabase.com/blog/transparent-column-encryption-with-postgres) which we will describe soon in a follow-up blog post. Stay tuned!
|
||||
@@ -17,9 +17,9 @@ pgsodium's Transparent Column Encryption (TCE) is one of the safest ways, and ca
|
||||
|
||||
To understand how TCE works, let's first do a deep-dive into an important encryption topic: key derivation.
|
||||
|
||||
<div className="bg-gray-300 rounded-lg p-6 italic">
|
||||
<div className="bg-gray-300 rounded-lg px-6 py-2 bold">
|
||||
|
||||
This post is a sneak peek of a big feature we are shipping on LW6. To not miss a thing, get your ticket for [Supabase Launch Week 6!](https://supabase.com/launch-week).
|
||||
👀 Transparent Column Encryption (TCE) is the foundational feature of the Supabase Vault, a safe and easy way to store encrypted secrets and other data in your Postgres database. [Learn more about Supabase Vault](https://supabase.com/docs/guides/database/vault)
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -18,6 +18,14 @@ During our last Launch Week we [announced](/blog/supabase-vault) Supabase Vault
|
||||
|
||||
Vault is a new Postgres extension and accompanying Supabase UI that makes it safe and easy to store encrypted secrets and encrypt other stored data in your database. This foundation opens up a lot of possibilities for Postgres that go beyond what is available in a stock distribution. From a product perspective we're grouping various features under the “Vault banner”. Let's explore a few of these features.
|
||||
|
||||
<div className="bg-gray-300 rounded-lg px-6 py-2 bold">
|
||||
|
||||
❇️ UPDATE JUNE 2023 ❇️
|
||||
|
||||
Supabase Vault is now available on every Supabase project. [Check it out](https://app.supabase.com/project/_/settings/vault/secrets)
|
||||
|
||||
</div>
|
||||
|
||||
## Secrets Management
|
||||
|
||||
Practically speaking, the Vault is a table of Secrets and Encryption Keys that are stored using [Authenticated Encryption](https://en.wikipedia.org/wiki/Authenticated_encryption) on disk, but available in decrypted form through a Postgres view so that the secrets can be used by applications from SQL. Because the secrets are stored encrypted and authenticated, any backups or replication streams also preserve this encryption in a way that can't be forged.
|
||||
@@ -81,7 +89,7 @@ Privacy is becoming one of the most important features in a modern product. Supa
|
||||
|
||||
### Using the Vault
|
||||
|
||||
You'll see the Vault appear in the Dashboard over the next month. If you want to use Vault today, email growth@supabase.com and we'll enable it on your account. Vault is available for all customers (including the Free-plan).
|
||||
To learn how to use Supabase Vault, check out the [docs](https://supabase.com/docs/guides/database/vault). Supabase Vault is now available on all [Supabase projects](https://app.supabase.com/project/_/settings/vault/secrets) (some projects will have to enable via request).
|
||||
|
||||
## More Launch Week 6
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
name: Berri AI
|
||||
title: Berri AI Boosts Productivity by Migrating from AWS RDS to Supabase with pgvector
|
||||
# Use meta_title to add a custom meta title. Otherwise it defaults to '{name} | Supabase Customer Stories':
|
||||
# meta_title:
|
||||
description: Learn how Berri AI overcame challenges with self-hosting their vector database on AWS RDS and successfully migrated to Supabase.
|
||||
# Use meta_description to add a custom meta description. Otherwise it defaults to {description}:
|
||||
meta_description: Learn how Berri AI overcame challenges with self-hosting their vector database on AWS RDS and successfully migrated to Supabase.
|
||||
author: paul_copplestone
|
||||
author_title: Supabase
|
||||
author_url: https://github.com/kiwicopple
|
||||
author_image_url: https://avatars2.githubusercontent.com/u/10214025?s=400&u=c6775be2ae667e2acae3ccd347fed62bb3f5b3e7&v=4
|
||||
authorURL: https://github.com/kiwicopple
|
||||
logo: /images/customers/logos/berriai.png
|
||||
logo_inverse: /images/customers/logos/light/berriai.png
|
||||
og_image: /images/customers/og/berriai.jpg
|
||||
tags:
|
||||
- supabase
|
||||
date: '2023-06-06'
|
||||
company_url: 'https://www.berri.ai/'
|
||||
stats:
|
||||
[
|
||||
{ stat: '00,000', label: Example stat },
|
||||
{ stat: '00,000', label: Example stat },
|
||||
{ stat: '00,000', label: Example stat },
|
||||
]
|
||||
misc: [{ label: 'Backed by', text: 'Y Combinator' }]
|
||||
about: Supercharge your products with the power of LLMs like ChatGPT & go from prototype to production in less than a week.
|
||||
---
|
||||
|
||||
Berri AI is an API for businesses to programmatically create custom ChatGPT instances on their data. Their customers build production-ready ChatGPT Apps in minutes with features such as prebuilt flows, editable options, authentication, and logging.
|
||||
|
||||
## The Challenge
|
||||
|
||||
Initially, Berri AI attempted to develop with AWS RDS to reduce latency and gain more control over their infrastructure. However, they encountered various challenges, including development cycles, difficulties debugging errors, limited accessibility for non-database team members, and the complexity of managing connection pools and rollbacks. These issues hindered their ability to make changes quickly and impacted their overall productivity.
|
||||
|
||||
<Quote img="krrish-d.png" caption="Krrish D. - Co-Founder & CEO, Berri AI">
|
||||
The Python SDK simplified the process for me as a non-database person, empowering me to make
|
||||
changes and bug fixes. This significantly improved our update velocity, which had a huge impact on
|
||||
our productivity.
|
||||
</Quote>
|
||||
|
||||
## Why they chose Supabase:
|
||||
|
||||
Berri AI migrated their vector database from AWS RDS to Supabase for several compelling reasons.
|
||||
|
||||
1. **Community:** After considering alternatives like pgvector, Pinecone, and Chroma, they prioritized a solution with a strong and accessible community, along with managed infrastructure.
|
||||
2. **Fewer services to manage:** Since they were already utilizing Supabase for other purposes, it made sense for Berri AI to consolidate their systems and avoid the complexities of self-hosting. One of the major pain points they faced with their previous setup was managing connection pools and handling errors with the Postgres database hosted on AWS. This involved writing extensive code and dealing with rollbacks, with each database update function requiring 40-50 lines of code. Supabase simplified this process significantly by condensing it to just one line of code, eliminating the burdens of managing dependencies and handling new errors. This significantly improved their update velocity, which had a huge impact on their productivity.
|
||||
3. **Better tooling:** Berri AI experienced frustration with pg_admin. Supabase's user-friendly interface offered a seamless and efficient way to manage and read tables, eliminating the need for pg_admin and providing a simple alternative. This streamlined their workflow and saved valuable time and effort. The accessibility of Supabase's client SDKs and intuitive interface played a crucial role in their decision as well. Team members who were not database experts found it easy to navigate and make changes, reducing reliance on specialized expertise and improving their overall workflow.
|
||||
4. **Simplicity:** Supabase provides the ability to add new columns in a no-code manner, which provided flexibility and agility in responding to evolving requirements. This feature empowered the team to make on-the-fly adjustments and meet their immediate needs effectively.
|
||||
5. **Flexibility:** Berri AI also appreciated Supabase's flexibility in allowing them to choose their preferred embedding method, as it was not tied to a specific embedding model. This ensured compatibility and adaptability for their specific use case.
|
||||
|
||||
The integration of pgvector on Supabase, which facilitated efficient storage and retrieval of vectors, proved to be a significant advantage for Berri AI's operations.
|
||||
|
||||
<Quote img="krrish-d.png" caption="Krrish D. - Co-Founder & CEO, Berri AI">
|
||||
Switching to Supabase was a game-changer for us. The faster view times allowed me to quickly
|
||||
access and debug data, enhancing my ability to analyze individual customer issues. The no-code
|
||||
option to add new columns on the fly was fantastic, enabling us to adapt swiftly.
|
||||
</Quote>
|
||||
|
||||
## What They Built
|
||||
|
||||
Berri AI built a platform that enables developers to create and manage custom ChatGPT instances for each individual user, integrating seamlessly with their unique data.
|
||||
|
||||
With Berri AI, businesses can rapidly scale the power of ChatGPT to cater to the needs of every user. The platform provides an out-of-the-box solution for managing a fleet of ChatGPT instances, offering businesses the ability to deliver personalized responses to user queries. This includes answering specific questions related to order status, monthly revenue, or generating personalized learning experiences in the education technology (EdTech) sector, such as summarizing lecture slides or generating practice questions from textbooks.
|
||||
|
||||
The platform's integration with Supabase and pgvector empowers businesses to unlock the full potential of ChatGPT and deliver customized solutions for a wide range of use cases.
|
||||
|
||||
<Quote img="ishaan-jaffer.png" caption="Ishaan Jaffer - Co-Founder & CTO, Berri AI">
|
||||
With Supabase, my life has become much easier. Previously, managing connection pools and dealing
|
||||
with errors in the Postgres database hosted on AWS was a constant challenge. I had to write 40-50
|
||||
lines of code for each function, handle rollbacks, and struggle with dependencies and errors. But
|
||||
with Supabase, updating the database is now just one line of code. I no longer need to deal with
|
||||
complex dependencies and obscure errors. Additionally, the frustration of using pgadmin is gone.
|
||||
Supabase provides a simple UI that allows me to effortlessly manage and interact with my tables.
|
||||
</Quote>
|
||||
|
||||
## The Results
|
||||
|
||||
Berri AI achieved significant improvements in their operations by choosing Supabase with pgvector. They experienced streamlined database management, increased update velocity, and improved efficiency in accessing and debugging data. The user-friendly interface and flexibility of Supabase empowered non-experts within the team and facilitated agile adaptations to changing requirements. Overall, this decision transformed their workflow, enhancing productivity and positioning Berri AI for continued success and growth.
|
||||
|
||||
## Tech stack
|
||||
|
||||
- Supabase
|
||||
- Zeet
|
||||
- AWS
|
||||
- OpenAI
|
||||
- Hugging Face
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
import { remarkCodeHike } from '@code-hike/mdx'
|
||||
|
||||
declare module '@code-hike/mdx' {
|
||||
export type CodeHikeRemarkPlugin = typeof remarkCodeHike
|
||||
export type CodeHikeConfig = Parameters<CodeHikeRemarkPlugin>[0]
|
||||
}
|
||||
@@ -88,9 +88,9 @@ export default function ComputePricingModal({ showComputeModal, setShowComputeMo
|
||||
<td key={column.key} className="p-3">
|
||||
{column.key === 'dedicated' ? (
|
||||
column.value ? (
|
||||
<IconPricingIncludedCheck tier="Pro plan" />
|
||||
<IconPricingIncludedCheck plan="Pro plan" />
|
||||
) : (
|
||||
<IconPricingMinus tier="Free plan" />
|
||||
<IconPricingMinus plan="Free plan" />
|
||||
)
|
||||
) : (
|
||||
column.value
|
||||
@@ -124,9 +124,9 @@ export default function ComputePricingModal({ showComputeModal, setShowComputeMo
|
||||
>
|
||||
{column.key === 'dedicated' ? (
|
||||
column.value ? (
|
||||
<IconPricingIncludedCheck tier="Pro plan" />
|
||||
<IconPricingIncludedCheck plan="Pro plan" />
|
||||
) : (
|
||||
<IconPricingMinus tier="Free plan" />
|
||||
<IconPricingMinus plan="Free plan" />
|
||||
)
|
||||
) : (
|
||||
column.value
|
||||
|
||||
@@ -12,7 +12,7 @@ export const Check = () => (
|
||||
export const IconPricingIncludedCheck = (props: any) => (
|
||||
<span className="mx-auto">
|
||||
<Check />
|
||||
<span className="sr-only">Included in {props.tier}</span>
|
||||
<span className="sr-only">Included in {props.plan}</span>
|
||||
</span>
|
||||
)
|
||||
|
||||
@@ -32,7 +32,7 @@ export const IconPricingMinus = (props: any) => (
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
<span className="sr-only">Not included in {props.tier}</span>
|
||||
<span className="sr-only">Not included in {props.plan}</span>
|
||||
</>
|
||||
)
|
||||
|
||||
|
||||
@@ -48,35 +48,35 @@ export const PricingTableRowDesktop = (props: any) => {
|
||||
)}
|
||||
</th>
|
||||
|
||||
{Object.entries(feat.tiers).map((entry: any, i) => {
|
||||
const tierName = entry[0]
|
||||
const tierValue = entry[1]
|
||||
{Object.entries(feat.plans).map((entry: any, i) => {
|
||||
const planName = entry[0]
|
||||
const planValue = entry[1]
|
||||
|
||||
return (
|
||||
<td
|
||||
key={i}
|
||||
className={[
|
||||
`px-6 tier-${tierName}`,
|
||||
typeof tierValue === 'boolean' ? 'text-center' : '',
|
||||
`px-6 tier-${planName}`,
|
||||
typeof planValue === 'boolean' ? 'text-center' : '',
|
||||
].join(' ')}
|
||||
>
|
||||
{typeof tierValue === 'boolean' && tierValue === true ? (
|
||||
<IconPricingIncludedCheck tier={tierValue} />
|
||||
) : typeof tierValue === 'boolean' && tierValue === false ? (
|
||||
{typeof planValue === 'boolean' && planValue === true ? (
|
||||
<IconPricingIncludedCheck plan={planValue} />
|
||||
) : typeof planValue === 'boolean' && planValue === false ? (
|
||||
<div className="text-scale-900">
|
||||
<IconPricingMinus tier={tierValue} />
|
||||
<IconPricingMinus plan={planValue} />
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-scale-1200 text-xs dark:text-white flex items-center gap-3">
|
||||
{feat.tooltips?.[tierName] && (
|
||||
{feat.tooltips?.[planName] && (
|
||||
<span
|
||||
className="shrink-0 hover:text-scale-300 cursor-pointer transition-colors"
|
||||
data-tip={feat.tooltips[tierName]}
|
||||
data-tip={feat.tooltips[planName]}
|
||||
>
|
||||
<IconPricingInfo />
|
||||
</span>
|
||||
)}
|
||||
{tierValue}
|
||||
{planValue}
|
||||
</span>
|
||||
)}
|
||||
</td>
|
||||
@@ -96,7 +96,7 @@ export const PricingTableRowDesktop = (props: any) => {
|
||||
|
||||
export const PricingTableRowMobile = (props: any) => {
|
||||
const category = props.category
|
||||
const tier = props.tier
|
||||
const plan = props.plan
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -127,16 +127,16 @@ export const PricingTableRowMobile = (props: any) => {
|
||||
</span>
|
||||
</th>
|
||||
<td className="py-3 pr-4 text-right">
|
||||
{typeof feat.tiers[tier] === 'boolean' && feat.tiers[tier] === true ? (
|
||||
{typeof feat.plans[plan] === 'boolean' && feat.plans[plan] === true ? (
|
||||
<div className="inline-block">
|
||||
<IconPricingIncludedCheck tier={tier} />
|
||||
<IconPricingIncludedCheck plan={plan} />
|
||||
</div>
|
||||
) : typeof feat.tiers[tier] === 'boolean' && feat.tiers[tier] === false ? (
|
||||
) : typeof feat.plans[plan] === 'boolean' && feat.plans[plan] === false ? (
|
||||
<div className="inline-block">
|
||||
<IconPricingMinus tier={tier} />
|
||||
<IconPricingMinus plan={plan} />
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-scale-1200 block text-sm">{feat.tiers[tier]}</span>
|
||||
<span className="text-scale-1200 block text-sm">{feat.plans[plan]}</span>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -41,7 +41,7 @@ export default [
|
||||
img: 'whiteboard.svg',
|
||||
title: 'Shared whiteboard',
|
||||
description:
|
||||
'Allow users to collaorate on a shared whiteboard, sharing positions and states of any kind of object in Realtime.',
|
||||
'Allow users to collaborate on a shared whiteboard, sharing positions and states of any kind of object in Realtime.',
|
||||
},
|
||||
{
|
||||
img: 'location.svg',
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Image from 'next/image'
|
||||
import Avatar from '~/components/Avatar'
|
||||
import CodeBlock from '~/components/CodeBlock/CodeBlock'
|
||||
import { CH } from '@code-hike/mdx/components'
|
||||
import ImageGrid from '~/components/ImageGrid'
|
||||
import Quote from '~/components/Quote'
|
||||
import Chart from '~/components/Charts/PGCharts'
|
||||
@@ -28,6 +29,7 @@ const getCaptionAlign = (align?: 'left' | 'center' | 'right') => {
|
||||
export default function mdxComponents(type?: 'blog' | 'lp' | undefined) {
|
||||
const components = {
|
||||
CodeBlock,
|
||||
CH,
|
||||
Badge,
|
||||
Quote,
|
||||
Avatar,
|
||||
|
||||
@@ -2,10 +2,24 @@ import { serialize } from 'next-mdx-remote/serialize'
|
||||
import rehypeSlug from 'rehype-slug'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
|
||||
import { CodeHikeConfig, remarkCodeHike } from '@code-hike/mdx'
|
||||
import codeHikeTheme from 'config/code-hike.theme.json' assert { type: 'json' }
|
||||
|
||||
export async function mdxSerialize(source: string) {
|
||||
const codeHikeOptions: CodeHikeConfig = {
|
||||
theme: codeHikeTheme,
|
||||
lineNumbers: true,
|
||||
showCopyButton: true,
|
||||
skipLanguages: [],
|
||||
autoImport: false,
|
||||
}
|
||||
|
||||
const mdxSource: any = await serialize(source, {
|
||||
scope: {
|
||||
chCodeConfig: codeHikeOptions,
|
||||
},
|
||||
mdxOptions: {
|
||||
remarkPlugins: [remarkGfm],
|
||||
remarkPlugins: [[remarkCodeHike, codeHikeOptions], remarkGfm],
|
||||
rehypePlugins: [
|
||||
// @ts-ignore
|
||||
rehypeSlug, // add IDs to any h1-h6 tag that doesn't have one, using a slug made from its text
|
||||
|
||||
@@ -1030,6 +1030,11 @@ module.exports = [
|
||||
source: '/blog/2022/02/22/supabase-beta-january-2022',
|
||||
destination: '/blog/supabase-beta-january-2022',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/blog/audit',
|
||||
destination: '/blog/postgres-audit',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/blog/2022/03/08/audit',
|
||||
@@ -1792,6 +1797,26 @@ module.exports = [
|
||||
source: '/docs/reference/dart',
|
||||
destination: '/docs/reference/dart/start',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/reference/python',
|
||||
destination: '/docs/reference/python/start',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/reference/csharp',
|
||||
destination: '/docs/reference/csharp/start',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/reference/swift',
|
||||
destination: '/docs/reference/swift/start',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/reference/kotlin',
|
||||
destination: '/docs/reference/kotlin/start',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/reference/cli',
|
||||
|
||||
@@ -9,10 +9,23 @@ import redirects from './lib/redirects.js'
|
||||
|
||||
import withTM from 'next-transpile-modules'
|
||||
|
||||
import { remarkCodeHike } from '@code-hike/mdx'
|
||||
import codeHikeTheme from 'config/code-hike.theme.json' assert { type: 'json' }
|
||||
|
||||
const withMDX = nextMdx({
|
||||
extension: /\.mdx?$/,
|
||||
options: {
|
||||
remarkPlugins: [remarkGfm],
|
||||
remarkPlugins: [
|
||||
[
|
||||
remarkCodeHike,
|
||||
{
|
||||
theme: codeHikeTheme,
|
||||
lineNumbers: true,
|
||||
showCopyButton: true,
|
||||
},
|
||||
],
|
||||
remarkGfm,
|
||||
],
|
||||
rehypePlugins: [rehypeSlug],
|
||||
// This is required for `MDXProvider` component
|
||||
providerImportSource: '@mdx-js/react',
|
||||
@@ -77,6 +90,6 @@ const nextConfig = {
|
||||
|
||||
// next.config.js.
|
||||
export default () => {
|
||||
const plugins = [withMDX, withBundleAnalyzer, withTM(['ui', 'common'])]
|
||||
const plugins = [withMDX, withBundleAnalyzer, withTM(['ui', 'common', 'shared-data'])]
|
||||
return plugins.reduce((acc, next) => next(acc), nextConfig)
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
"postbuild": "node ./internals/generate-sitemap.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@code-hike/mdx": "^0.8.3",
|
||||
"@heroicons/react": "^1.0.6",
|
||||
"@mdx-js/loader": "^2.1.5",
|
||||
"@mdx-js/react": "^2.1.5",
|
||||
@@ -54,6 +55,7 @@
|
||||
"remark": "^14.0.2",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"remark-html": "^15.0.1",
|
||||
"shared-data": "*",
|
||||
"swiper": "^6.5.0",
|
||||
"swr": "^0.5.4",
|
||||
"typescript": "^5.0.4",
|
||||
|
||||
@@ -13,6 +13,7 @@ import '../styles/index.css'
|
||||
import { post } from '~/lib/fetchWrapper'
|
||||
import { AuthProvider, ThemeProvider, useTelemetryProps } from 'common'
|
||||
import Head from 'next/head'
|
||||
import 'config/code-hike.scss'
|
||||
|
||||
export default function MyApp({ Component, pageProps }: AppProps) {
|
||||
const router = useRouter()
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
Pricing Changelog
|
||||
|
||||
### 18 Jan 2023
|
||||
|
||||
- Clarified over-usage of Realtime messages per month
|
||||
- On the Pro plan, $2.50 per million after included 5 million messages
|
||||
|
||||
### 09 Jan 2023
|
||||
----
|
||||
|
||||
Database Compute Add ons
|
||||
- Clarified iops values to the latest instance types
|
||||
|
||||
### 02 Nov 2022
|
||||
----
|
||||
|
||||
Database
|
||||
- guidance on backups > 1TB in size
|
||||
- Removed "Realtime functionality" as it is redundant with new realtime section
|
||||
Auth
|
||||
- added “advanced security features”
|
||||
- MAUs changed to unlimited for Enterprise
|
||||
Realtime
|
||||
— Added new realtime section
|
||||
- Concurrent Peak Connections
|
||||
- Max Messages Per Month
|
||||
- Max Message Size
|
||||
Edge Functions
|
||||
- Runtime hours removed
|
||||
- Number of functions, usage pricing added after limit on Pro
|
||||
- Invocations, usage pricing added after limit on Pro
|
||||
Support
|
||||
- Security features moved to own section
|
||||
Platform Security and Compliance
|
||||
- New section made
|
||||
- added Access roles
|
||||
- added Custom Domains
|
||||
- added Vanity URLs added
|
||||
Database Compute Add ons
|
||||
- New label “Optimized Compute”
|
||||
|
||||
|
||||
### 16 June 2022
|
||||
----
|
||||
|
||||
Auth
|
||||
- New item “MAUs (Monthly Active Users)”
|
||||
Security & Compliance
|
||||
- New item: “Security questionnaire help” for enterprise
|
||||
- New item - “Metrics Endpoint” for enterprise and pro plan users
|
||||
Database Compute Add ons
|
||||
- New item: “4XL”
|
||||
- New item: “8XL”
|
||||
- New item: “12XL”
|
||||
- New item: “16XL”
|
||||
Database
|
||||
- New item: “Point in time recovery” for enterprise
|
||||
Clarifications
|
||||
- General - Clearer language that we charge on a per project per month
|
||||
- Storage - Uploads for Pro, changed from 3GB → 5GB in title card
|
||||
- Storage - file upload size - clarified that it can be changed in dashboard
|
||||
- All products - “Transfer” renamed to “Egress”
|
||||
- Database compute add-ons - Number of connections more accurate
|
||||
- Storage - Free plan egress per month clarified
|
||||
|
||||
### 31 March 2022
|
||||
----
|
||||
|
||||
Edge Functions pricing announced
|
||||
|
||||
|
||||
### 29 March 2022
|
||||
----
|
||||
|
||||
— Enterprise pricing plan announced
|
||||
|
||||
— Pay as you go plan now the same as the Pro plan
|
||||
|
||||
|
||||
27 August 2021
|
||||
----
|
||||
|
||||
- Changes "Dedicated support" to "Designated support".
|
||||
- Dedicated = one individual solely focused on an account.
|
||||
- Designated = individual who may be helping across various accounts, but still a single point of contact. May be dedicated or non-dedicated.
|
||||
|
||||
|
||||
28 July 2021
|
||||
----
|
||||
|
||||
- Clarified pricing on the "Optimized database instances" to include the starting price of $50/month.
|
||||
|
||||
|
||||
### 29 March 2021
|
||||
----
|
||||
|
||||
Released our Beta pricing.
|
||||
|
||||
|
||||
+231
-206
@@ -8,21 +8,25 @@ import ReactMarkdown from 'react-markdown'
|
||||
import CTABanner from '~/components/CTABanner'
|
||||
import DefaultLayout from '~/components/Layouts/Default'
|
||||
import { PricingTableRowDesktop, PricingTableRowMobile } from '~/components/Pricing/PricingTableRow'
|
||||
import pricing from '~/data/Pricing.json'
|
||||
import pricing from 'shared-data/Pricing.json'
|
||||
import pricingFaq from '~/data/PricingFAQ.json'
|
||||
import { useTheme } from 'common/Providers'
|
||||
import ComputePricingModal from '~/components/Pricing/ComputePricingModal'
|
||||
import { plans } from 'shared-data/plans'
|
||||
|
||||
export default function IndexPage() {
|
||||
const router = useRouter()
|
||||
const { basePath, asPath } = useRouter()
|
||||
const { isDarkMode } = useTheme()
|
||||
const [showComputeModal, setShowComputeModal] = useState(false)
|
||||
const [activeMobileTier, setActiveMobileTier] = useState('Free')
|
||||
const [activeMobilePlan, setActiveMobilePlan] = useState('Free')
|
||||
|
||||
const meta_title = 'Pricing & fees | Supabase'
|
||||
const meta_description =
|
||||
'Explore Supabase fees and pricing information. Find our competitive pricing tiers, with no hidden pricing. We have a generous free plan for those getting started, and Pay As You Go for those scaling up.'
|
||||
'Explore Supabase fees and pricing information. Find our competitive pricing plans, with no hidden pricing. We have a generous free plan for those getting started, and Pay As You Go for those scaling up.'
|
||||
|
||||
const plansExceptEnterprise = plans.filter((it) => it.name !== 'Enterprise')
|
||||
const planEnterprise = plans.find((it) => it.name === 'Enterprise')!
|
||||
|
||||
// Ability to scroll into pricing sections like storage
|
||||
useEffect(() => {
|
||||
@@ -45,93 +49,6 @@ export default function IndexPage() {
|
||||
}
|
||||
}, [asPath])
|
||||
|
||||
/**
|
||||
* @mildtomato same plan metadata is also in /studio dashboard
|
||||
* would be good if this constant was shared across apps
|
||||
*
|
||||
* https://github.com/supabase/supabase/blob/master/www/pages/pricing/index.tsx
|
||||
*/
|
||||
const tiers = [
|
||||
{
|
||||
name: 'Free',
|
||||
nameBadge: '',
|
||||
costUnit: 'per month per project',
|
||||
href: 'https://supabase.com/dashboard/new/new-project',
|
||||
priceLabel: 'Starting from',
|
||||
priceMonthly: 0,
|
||||
warning: 'Limit of 2 free projects',
|
||||
description: 'Perfect for passion projects & simple websites.',
|
||||
preface: 'Get started with:',
|
||||
features: [
|
||||
'Social OAuth providers',
|
||||
'Up to 500MB database & 1GB file storage',
|
||||
'Up to 2GB bandwidth',
|
||||
'Up to 50MB file uploads',
|
||||
'50,000 monthly active users',
|
||||
'Up to 500K Edge Function invocations',
|
||||
'200 concurrent Realtime connections',
|
||||
'2 million Realtime messages',
|
||||
'1-day log retention',
|
||||
'Community support',
|
||||
],
|
||||
scale: 'Free projects are paused after 1 week of inactivity.',
|
||||
|
||||
cta: 'Get Started',
|
||||
},
|
||||
{
|
||||
name: 'Pro',
|
||||
nameBadge: '',
|
||||
costUnit: 'per month per project',
|
||||
href: 'https://supabase.com/dashboard/new/new-project',
|
||||
from: true,
|
||||
priceLabel: 'Starting from',
|
||||
warning: '+ usage',
|
||||
priceMonthly: 25,
|
||||
description: 'For production applications with the option to scale.',
|
||||
features: [
|
||||
'No project pausing',
|
||||
'8GB database & 100GB file storage',
|
||||
'50GB bandwidth',
|
||||
'5GB file uploads',
|
||||
'100,000 monthly active users',
|
||||
'2M Edge Function invocations',
|
||||
'500 concurrent Realtime connections',
|
||||
'5 million Realtime messages',
|
||||
'7-day log retention',
|
||||
'Email support',
|
||||
'Daily backups',
|
||||
],
|
||||
scale: 'Additional fees apply for usage and storage beyond the limits above.',
|
||||
shutdown: '',
|
||||
preface: 'Everything in the Free plan, plus:',
|
||||
additional: '',
|
||||
cta: 'Get Started',
|
||||
},
|
||||
{
|
||||
name: 'Enterprise',
|
||||
href: 'https://forms.supabase.com/enterprise',
|
||||
description: 'For large-scale applications managing serious workloads.',
|
||||
features: [
|
||||
`Designated Support manager & SLAs`,
|
||||
`Enterprise OAuth providers`,
|
||||
`SSO/ SAML`,
|
||||
`SOC2`,
|
||||
`Custom contracts & invoicing`,
|
||||
`On-premise support`,
|
||||
`24×7×365 premium enterprise support`,
|
||||
`Custom Security questionnaires`,
|
||||
`Private Slack channel`,
|
||||
`Uptime SLA`,
|
||||
],
|
||||
priceLabel: '',
|
||||
priceMonthly: 'Contact us',
|
||||
preface: 'These apply to all projects within the organization:',
|
||||
scale: '',
|
||||
shutdown: '',
|
||||
cta: 'Contact Us',
|
||||
},
|
||||
]
|
||||
|
||||
const addons = [
|
||||
{
|
||||
name: 'Optimized Compute',
|
||||
@@ -174,21 +91,21 @@ export default function IndexPage() {
|
||||
description,
|
||||
priceDescription,
|
||||
price,
|
||||
tier,
|
||||
plan,
|
||||
showDollarSign = true,
|
||||
from = false,
|
||||
}: {
|
||||
description: string
|
||||
priceDescription: string
|
||||
price: string
|
||||
tier: string
|
||||
plan: string
|
||||
showDollarSign?: boolean
|
||||
from?: boolean
|
||||
}) => {
|
||||
return (
|
||||
<div className="mt-8 px-4 mobile-header">
|
||||
<h2 className="gradient-text-brand-500 dark:gradient-text-brand-100 text-3xl font-medium uppercase font-mono">
|
||||
{tier}
|
||||
{plan}
|
||||
</h2>
|
||||
<div className="flex items-baseline gap-2">
|
||||
{from && <span className="text-scale-1200 text-base">From</span>}
|
||||
@@ -242,7 +159,7 @@ export default function IndexPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mx-auto flex max-w-7xl flex-col">
|
||||
<div className="mx-auto max-w-7xl flex flex-col">
|
||||
{/* <div className="absolute inset-0 shadow-sm bg-scale-200 h-3/5" /> */}
|
||||
|
||||
<div
|
||||
@@ -252,28 +169,28 @@ export default function IndexPage() {
|
||||
"
|
||||
>
|
||||
<div className="mx-auto max-w-md grid lg:max-w-6xl lg:grid-cols-3 gap-24 lg:gap-5">
|
||||
{tiers.map((tier) => (
|
||||
{plansExceptEnterprise.map((plan) => (
|
||||
<div
|
||||
key={`row-${tier.name}`}
|
||||
key={`row-${plan.name}`}
|
||||
className={[
|
||||
tier.name === 'Pro'
|
||||
plan.name === 'Pro'
|
||||
? 'bg-brand-1100 dark:bg-brand-900 border px-0.5 -mt-8 rounded-[6px]'
|
||||
: '',
|
||||
].join(' ')}
|
||||
>
|
||||
{tier.name === 'Pro' && (
|
||||
{plan.name === 'Pro' && (
|
||||
<p className="text-xs text-center py-2 text-white">Most Popular</p>
|
||||
)}
|
||||
<div
|
||||
key={tier.name}
|
||||
key={plan.name}
|
||||
className={[
|
||||
'flex flex-col overflow-hidden',
|
||||
tier.name === 'Pro' ? '' : 'border h-full rounded-[4px]',
|
||||
plan.name === 'Pro' ? '' : 'border h-full rounded-[4px]',
|
||||
].join(' ')}
|
||||
>
|
||||
<div
|
||||
className={`dark:bg-scale-300 bg-white px-8 pt-6 rounded-tr-[4px] rounded-tl-[4px] ${
|
||||
tier.name === 'Pro' ? 'rounded-tr-[4px] rounded-tl-[4px]' : ''
|
||||
plan.name === 'Pro' ? 'rounded-tr-[4px] rounded-tl-[4px]' : ''
|
||||
}`}
|
||||
>
|
||||
<div className="mb-2 flex items-center gap-2">
|
||||
@@ -282,55 +199,56 @@ export default function IndexPage() {
|
||||
className="gradient-text-brand-500 dark:gradient-text-brand-100 text-2xl font-normal
|
||||
uppercase flex items-center gap-4 font-mono"
|
||||
>
|
||||
{tier.name}
|
||||
{plan.name}
|
||||
</h3>
|
||||
{tier.nameBadge && (
|
||||
<span className="bg-scale-300 text-scale-900 dark:bg-scale-400 dark:text-scale-1100 rounded-md bg-opacity-10 py-0.5 px-2 text-xs [background-image: none]">
|
||||
{tier.nameBadge}
|
||||
{plan.nameBadge && (
|
||||
<span className="bg-brand-500 text-brand-1100 rounded-md bg-opacity-30 py-0.5 px-2 text-xs">
|
||||
{plan.nameBadge}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-scale-1100 my-4 h-[55px] text-sm border-b dark:border-scale-500 pb-4 lg:pr-20">
|
||||
{tier.description}
|
||||
<p className="text-scale-1100 my-4 text-sm border-b dark:border-scale-500 pb-4 2xl:pr-4">
|
||||
{plan.description}
|
||||
</p>
|
||||
|
||||
<div
|
||||
className="
|
||||
text-scale-1200 flex items-baseline
|
||||
text-5xl
|
||||
font-normal
|
||||
lg:text-4xl
|
||||
xl:text-4xl
|
||||
border-b
|
||||
dark:border-scale-500
|
||||
pt-4
|
||||
pb-8
|
||||
min-h-[175px]
|
||||
"
|
||||
className={`
|
||||
text-scale-1200 flex items-baseline
|
||||
text-5xl
|
||||
font-normal
|
||||
lg:text-4xl
|
||||
xl:text-4xl
|
||||
border-b
|
||||
dark:border-scale-500
|
||||
min-h-[175px] ${plan.priceLabel ? 'pt-6' : 'pt-10'}`}
|
||||
>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-end gap-2">
|
||||
<div>
|
||||
<p className="text-scale-900 ml-1 text-xs font-normal">
|
||||
{tier.priceLabel}
|
||||
</p>
|
||||
<p
|
||||
className={`mt-2 gradient-text-scale-500 dark:gradient-text-scale-100 pb-1 ${
|
||||
tier.name !== 'Enterprise' ? 'text-5xl' : 'text-4xl'
|
||||
}`}
|
||||
>
|
||||
{tier.name !== 'Enterprise' && '$'}
|
||||
{tier.priceMonthly}
|
||||
</p>
|
||||
{tier.costUnit && (
|
||||
<p className="text-scale-900 mt-0.5 text-xs">{tier.costUnit}</p>
|
||||
{plan.priceLabel && (
|
||||
<p className="text-scale-900 ml-1 text-xs font-normal">
|
||||
{plan.priceLabel}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{tier.warning && (
|
||||
<div className="flex items-end">
|
||||
<p
|
||||
className={`mt-2 gradient-text-scale-500 dark:gradient-text-scale-100 pb-1 ${
|
||||
plan.name !== 'Enterprise' ? 'text-5xl' : 'text-4xl'
|
||||
}`}
|
||||
>
|
||||
${plan.priceMonthly}
|
||||
</p>
|
||||
<p className="text-scale-900 mb-1.5 ml-1 text-xs">
|
||||
{plan.costUnit}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{plan.warning && (
|
||||
<p className="-mt-2">
|
||||
<span className="bg-brand-500 text-brand-1100 rounded-md bg-opacity-30 py-0.5 px-2 text-xs ">
|
||||
{tier.warning}
|
||||
<span className="bg-scale-200 text-brand-1100 border shadow-sm rounded-md bg-opacity-30 py-0.5 px-2 text-xs">
|
||||
{plan.warning}
|
||||
</span>
|
||||
</p>
|
||||
)}
|
||||
@@ -342,18 +260,18 @@ export default function IndexPage() {
|
||||
<div
|
||||
className={[
|
||||
`dark:border-scale-400 dark:bg-scale-300 flex h-full rounded-bl-[4px] rounded-br-[4px] flex-1 flex-col bg-white px-8 py-6`,
|
||||
tier.name === 'Pro' ? 'mb-0.5 rounded-bl-[4px] rounded-br-[4px]' : '',
|
||||
plan.name === 'Pro' ? 'mb-0.5 rounded-bl-[4px] rounded-br-[4px]' : '',
|
||||
].join(' ')}
|
||||
>
|
||||
{tier.preface && (
|
||||
<p className="text-scale-1100 text-xs mt-2 mb-4">{tier.preface}</p>
|
||||
{plan.preface && (
|
||||
<p className="text-scale-1100 text-xs mt-2 mb-4">{plan.preface}</p>
|
||||
)}
|
||||
{/* <p className="text-scale-900 text-sm">Included with plan:</p> */}
|
||||
<ul role="list" className="text-xs text-scale-1000">
|
||||
{tier.features.map((feature) => (
|
||||
{plan.features.map((feature) => (
|
||||
<li key={feature} className="flex items-center py-2 first:mt-0">
|
||||
<IconCheck
|
||||
className="text-brand-900 h-4 w-4 "
|
||||
className="text-brand-900 h-4 w-4"
|
||||
aria-hidden="true"
|
||||
strokeWidth={3}
|
||||
/>
|
||||
@@ -365,15 +283,13 @@ export default function IndexPage() {
|
||||
|
||||
<div className="flex flex-col gap-6 mt-auto prose">
|
||||
<div className="space-y-2 mt-12">
|
||||
{tier.additional && <p className="text-sm">{tier.additional}</p>}
|
||||
{tier.scale && <p className="text-xs">{tier.scale}</p>}
|
||||
{tier.shutdown && (
|
||||
<p className="text-scale-1000 text-xs">{tier.shutdown}</p>
|
||||
{plan.footer && (
|
||||
<p className="text-xs whitespace-pre-wrap">{plan.footer}</p>
|
||||
)}
|
||||
</div>
|
||||
<a href={tier.href}>
|
||||
<a href={plan.href}>
|
||||
<Button block size="small">
|
||||
{tier.cta}
|
||||
{plan.cta}
|
||||
</Button>
|
||||
</a>
|
||||
</div>
|
||||
@@ -384,6 +300,51 @@ export default function IndexPage() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-md lg:max-w-6xl grid grid-cols-1 lg:grid-cols-3 mx-auto border bg-scale-100 rounded-md p-8 lg:px-0 mt-8 gap-x-5">
|
||||
<div className="flex flex-col justify-center lg:px-8">
|
||||
<div>
|
||||
<h3
|
||||
className="gradient-text-brand-500 dark:gradient-text-brand-100 text-2xl font-normal
|
||||
uppercase flex items-center gap-4 font-mono"
|
||||
>
|
||||
{planEnterprise.name}
|
||||
</h3>
|
||||
<p className="text-scale-1100 my-4 text-sm pb-2 2xl:pr-4">
|
||||
{planEnterprise.description}
|
||||
</p>
|
||||
|
||||
<a href={planEnterprise.href} className="hidden lg:block">
|
||||
<Button type="default" size="small">
|
||||
{planEnterprise.cta}
|
||||
</Button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col justify-center lg:col-span-2">
|
||||
<ul role="list" className="text-xs text-scale-1000 lg:grid lg:grid-cols-2 lg:gap-x-5">
|
||||
{planEnterprise.features.map((feature) => (
|
||||
<li key={feature} className="flex items-center py-2 first:mt-0">
|
||||
<IconCheck
|
||||
className="text-brand-900 h-4 w-4 "
|
||||
aria-hidden="true"
|
||||
strokeWidth={3}
|
||||
/>
|
||||
<span className="dark:text-scale-1200 mb-0 ml-3 ">{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<a href={planEnterprise.href} className="visible lg:hidden mt-8">
|
||||
<Button block type="default" size="small">
|
||||
{planEnterprise.cta}
|
||||
</Button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-center mt-24">
|
||||
<a href="#compare-plans">
|
||||
<Button size="tiny" type="default">
|
||||
@@ -486,12 +447,10 @@ export default function IndexPage() {
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<div className="">
|
||||
<img
|
||||
className="w-full"
|
||||
src={`${basePath}/images/pricing/spend-cap${isDarkMode ? '' : '-light'}.png`}
|
||||
/>
|
||||
</div>
|
||||
<img
|
||||
className="w-full"
|
||||
src={`${basePath}/images/pricing/spend-cap${isDarkMode ? '' : '-light'}.png`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -519,79 +478,80 @@ export default function IndexPage() {
|
||||
id="change-plan"
|
||||
name="Change plan"
|
||||
layout="vertical"
|
||||
value={activeMobileTier}
|
||||
value={activeMobilePlan}
|
||||
className="min-w-[120px] bg-slate-400 text-red-500"
|
||||
onChange={(e) => setActiveMobileTier(e.target.value)}
|
||||
onChange={(e) => setActiveMobilePlan(e.target.value)}
|
||||
>
|
||||
<Select.Option value="Free">Free</Select.Option>
|
||||
<Select.Option value="Pro">Pro</Select.Option>
|
||||
<Select.Option value="Team">Team</Select.Option>
|
||||
<Select.Option value="Enterprise">Enterprise</Select.Option>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
{activeMobileTier === 'Free' && (
|
||||
{activeMobilePlan === 'Free' && (
|
||||
<>
|
||||
<MobileHeader
|
||||
tier="Free"
|
||||
plan="Free"
|
||||
price={'0'}
|
||||
priceDescription={'/mo'}
|
||||
description={'Perfect for hobby projects and experiments'}
|
||||
/>
|
||||
<PricingTableRowMobile
|
||||
category={pricing.database}
|
||||
tier={'free'}
|
||||
plan={'free'}
|
||||
icon={Solutions['database'].icon}
|
||||
sectionId="database"
|
||||
/>
|
||||
<PricingTableRowMobile
|
||||
category={pricing.auth}
|
||||
tier={'free'}
|
||||
plan={'free'}
|
||||
icon={Solutions['authentication'].icon}
|
||||
sectionId="auth"
|
||||
/>
|
||||
<PricingTableRowMobile
|
||||
category={pricing.storage}
|
||||
tier={'free'}
|
||||
plan={'free'}
|
||||
icon={Solutions['storage'].icon}
|
||||
sectionId="storage"
|
||||
/>
|
||||
<PricingTableRowMobile
|
||||
category={pricing.realtime}
|
||||
tier={'free'}
|
||||
plan={'free'}
|
||||
icon={Solutions['realtime'].icon}
|
||||
sectionId="realtime"
|
||||
/>
|
||||
<PricingTableRowMobile
|
||||
category={pricing['edge-functions']}
|
||||
tier={'free'}
|
||||
plan={'free'}
|
||||
icon={Solutions['edge-functions'].icon}
|
||||
sectionId="edge-functions"
|
||||
/>
|
||||
<PricingTableRowMobile
|
||||
category={pricing.dashboard}
|
||||
tier={'free'}
|
||||
plan={'free'}
|
||||
icon={pricing.dashboard.icon}
|
||||
sectionId="dashboard"
|
||||
/>
|
||||
<PricingTableRowMobile
|
||||
category={pricing.security}
|
||||
tier={'free'}
|
||||
plan={'free'}
|
||||
icon={pricing.security.icon}
|
||||
sectionId="security"
|
||||
/>
|
||||
<PricingTableRowMobile
|
||||
category={pricing.support}
|
||||
tier={'free'}
|
||||
plan={'free'}
|
||||
icon={pricing.support.icon}
|
||||
sectionId="support"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{activeMobileTier === 'Pro' && (
|
||||
{activeMobilePlan === 'Pro' && (
|
||||
<>
|
||||
<MobileHeader
|
||||
tier="Pro"
|
||||
plan="Pro"
|
||||
from={false}
|
||||
price={'25'}
|
||||
priceDescription={'/mo + additional use'}
|
||||
@@ -599,51 +559,103 @@ export default function IndexPage() {
|
||||
/>
|
||||
<PricingTableRowMobile
|
||||
category={pricing.database}
|
||||
tier={'pro'}
|
||||
plan={'pro'}
|
||||
icon={Solutions['database'].icon}
|
||||
/>
|
||||
<PricingTableRowMobile
|
||||
category={pricing.auth}
|
||||
tier={'pro'}
|
||||
plan={'pro'}
|
||||
icon={Solutions['authentication'].icon}
|
||||
/>
|
||||
<PricingTableRowMobile
|
||||
category={pricing.storage}
|
||||
tier={'pro'}
|
||||
plan={'pro'}
|
||||
icon={Solutions['storage'].icon}
|
||||
/>
|
||||
<PricingTableRowMobile
|
||||
category={pricing.realtime}
|
||||
tier={'pro'}
|
||||
plan={'pro'}
|
||||
icon={Solutions['realtime'].icon}
|
||||
/>
|
||||
<PricingTableRowMobile
|
||||
category={pricing['edge-functions']}
|
||||
tier={'pro'}
|
||||
plan={'pro'}
|
||||
icon={Solutions['edge-functions'].icon}
|
||||
/>
|
||||
<PricingTableRowMobile
|
||||
category={pricing.dashboard}
|
||||
tier={'pro'}
|
||||
plan={'pro'}
|
||||
icon={pricing.dashboard.icon}
|
||||
/>
|
||||
<PricingTableRowMobile
|
||||
category={pricing.security}
|
||||
tier={'pro'}
|
||||
plan={'pro'}
|
||||
icon={pricing.security.icon}
|
||||
/>
|
||||
<PricingTableRowMobile
|
||||
category={pricing.support}
|
||||
tier={'pro'}
|
||||
plan={'pro'}
|
||||
icon={pricing.support.icon}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{activeMobileTier === 'Enterprise' && (
|
||||
{activeMobilePlan === 'Team' && (
|
||||
<>
|
||||
<MobileHeader
|
||||
tier="Enterprise"
|
||||
plan="Team"
|
||||
from={false}
|
||||
price={'599'}
|
||||
priceDescription={'/mo + additional use'}
|
||||
description={'Collaborate with different permissions and access patterns'}
|
||||
/>
|
||||
<PricingTableRowMobile
|
||||
category={pricing.database}
|
||||
plan={'team'}
|
||||
icon={Solutions['database'].icon}
|
||||
/>
|
||||
<PricingTableRowMobile
|
||||
category={pricing.auth}
|
||||
plan={'team'}
|
||||
icon={Solutions['authentication'].icon}
|
||||
/>
|
||||
<PricingTableRowMobile
|
||||
category={pricing.storage}
|
||||
plan={'team'}
|
||||
icon={Solutions['storage'].icon}
|
||||
/>
|
||||
<PricingTableRowMobile
|
||||
category={pricing.realtime}
|
||||
plan={'team'}
|
||||
icon={Solutions['realtime'].icon}
|
||||
/>
|
||||
<PricingTableRowMobile
|
||||
category={pricing['edge-functions']}
|
||||
plan={'team'}
|
||||
icon={Solutions['edge-functions'].icon}
|
||||
/>
|
||||
<PricingTableRowMobile
|
||||
category={pricing.dashboard}
|
||||
plan={'team'}
|
||||
icon={pricing.dashboard.icon}
|
||||
/>
|
||||
<PricingTableRowMobile
|
||||
category={pricing.security}
|
||||
plan={'team'}
|
||||
icon={pricing.security.icon}
|
||||
/>
|
||||
<PricingTableRowMobile
|
||||
category={pricing.support}
|
||||
plan={'team'}
|
||||
icon={pricing.support.icon}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{activeMobilePlan === 'Enterprise' && (
|
||||
<>
|
||||
<MobileHeader
|
||||
plan="Enterprise"
|
||||
price={'Contact us for a quote'}
|
||||
priceDescription={''}
|
||||
description={
|
||||
@@ -653,42 +665,42 @@ export default function IndexPage() {
|
||||
/>
|
||||
<PricingTableRowMobile
|
||||
category={pricing.database}
|
||||
tier={'enterprise'}
|
||||
plan={'enterprise'}
|
||||
icon={Solutions['database'].icon}
|
||||
/>
|
||||
<PricingTableRowMobile
|
||||
category={pricing.auth}
|
||||
tier={'enterprise'}
|
||||
plan={'enterprise'}
|
||||
icon={Solutions['authentication'].icon}
|
||||
/>
|
||||
<PricingTableRowMobile
|
||||
category={pricing.storage}
|
||||
tier={'enterprise'}
|
||||
plan={'enterprise'}
|
||||
icon={Solutions['storage'].icon}
|
||||
/>
|
||||
<PricingTableRowMobile
|
||||
category={pricing.realtime}
|
||||
tier={'enterprise'}
|
||||
plan={'enterprise'}
|
||||
icon={Solutions['realtime'].icon}
|
||||
/>
|
||||
<PricingTableRowMobile
|
||||
category={pricing['edge-functions']}
|
||||
tier={'enterprise'}
|
||||
plan={'enterprise'}
|
||||
icon={Solutions['edge-functions'].icon}
|
||||
/>
|
||||
<PricingTableRowMobile
|
||||
category={pricing.dashboard}
|
||||
tier={'enterprise'}
|
||||
plan={'enterprise'}
|
||||
icon={pricing.dashboard.icon}
|
||||
/>
|
||||
<PricingTableRowMobile
|
||||
category={pricing.security}
|
||||
tier={'enterprise'}
|
||||
plan={'enterprise'}
|
||||
icon={pricing.security.icon}
|
||||
/>
|
||||
<PricingTableRowMobile
|
||||
category={pricing.support}
|
||||
tier={'enterprise'}
|
||||
plan={'enterprise'}
|
||||
icon={pricing.support.icon}
|
||||
/>
|
||||
</>
|
||||
@@ -712,14 +724,14 @@ export default function IndexPage() {
|
||||
></div>
|
||||
</th>
|
||||
|
||||
{tiers.map((tier) => (
|
||||
{plans.map((plan) => (
|
||||
<th
|
||||
className="text-scale-1200 w-1/4 px-6 pr-2 pt-2 pb-2 text-left text-sm font-normal"
|
||||
scope="col"
|
||||
key={tier.name}
|
||||
key={plan.name}
|
||||
>
|
||||
<h3 className="gradient-text-brand-500 dark:gradient-text-brand-100 text-2xl font-mono font-normal uppercase flex items-center gap-4">
|
||||
{tier.name}
|
||||
{plan.name}
|
||||
</h3>
|
||||
<div
|
||||
className="h-0.25 absolute bottom-0 left-0 w-full"
|
||||
@@ -735,13 +747,13 @@ export default function IndexPage() {
|
||||
scope="col"
|
||||
></th>
|
||||
|
||||
{tiers.map((tier) => (
|
||||
{plans.map((plan) => (
|
||||
<th
|
||||
className="text-scale-1200 w-1/4 px-6 pt-2 pb-2 text-left text-sm font-normal"
|
||||
scope="col"
|
||||
key={`th-${tier.name}`}
|
||||
key={`th-${plan.name}`}
|
||||
>
|
||||
<p className="p text-sm border-b border-scale-700 pb-4">{tier.description}</p>
|
||||
<p className="p text-sm border-b border-scale-700 pb-4">{plan.description}</p>
|
||||
<div
|
||||
className="h-0.25 absolute bottom-0 left-0 w-full"
|
||||
style={{ height: '1px' }}
|
||||
@@ -750,47 +762,50 @@ export default function IndexPage() {
|
||||
))}
|
||||
</tr>
|
||||
<tbody className="border-scale-700 dark:border-scale-400 divide-scale-700 dark:divide-scale-400 divide-y">
|
||||
<tr className="">
|
||||
<tr>
|
||||
<th
|
||||
className="text-scale-900 px-6 py-8 text-left align-top text-sm font-medium dark:text-white"
|
||||
scope="row"
|
||||
></th>
|
||||
|
||||
{tiers.map((tier) => (
|
||||
<td className="h-full px-6 py-2 align-top" key={`price-${tier.name}`}>
|
||||
{plans.map((plan) => (
|
||||
<td className="h-full px-6 py-2 align-top" key={`price-${plan.name}`}>
|
||||
<div className="relative table h-full w-full">
|
||||
<div className="flex flex-col justify-between h-full">
|
||||
<>
|
||||
<span
|
||||
className={`text-scale-1200 ${
|
||||
tier.name !== 'Enterprise' ? 'text-5xl' : 'text-4xl'
|
||||
plan.name !== 'Enterprise' ? 'text-5xl' : 'text-4xl'
|
||||
}`}
|
||||
>
|
||||
{tier.name !== 'Enterprise' && '$'}
|
||||
{tier.priceMonthly}
|
||||
{plan.name !== 'Enterprise' && '$'}
|
||||
{plan.priceMonthly}
|
||||
</span>
|
||||
{tier.name !== 'Enterprise' && (
|
||||
<p className="p text-xs mt-1">per project per month</p>
|
||||
{['Pro', 'Free'].includes(plan.name) && (
|
||||
<p className="p text-xs mt-1">per month</p>
|
||||
)}
|
||||
{['Team'].includes(plan.name) && (
|
||||
<p className="p text-xs mt-1">per month</p>
|
||||
)}
|
||||
</>
|
||||
|
||||
{tier.warning && (
|
||||
{plan.warning && (
|
||||
<p className="-mt-2">
|
||||
<span className="bg-brand-500 text-brand-1100 rounded-md bg-opacity-30 py-0.5 px-2 text-xs ">
|
||||
{tier.warning}
|
||||
<span className="bg-scale-100 text-brand-1100 border shadow-sm rounded-md bg-opacity-30 py-0.5 px-2 text-xs">
|
||||
{plan.warning}
|
||||
</span>
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className={tier.name === 'Enterprise' ? 'mt-auto' : 'mt-8'}>
|
||||
<Link href={tier.href} as={tier.href}>
|
||||
<div className={plan.name === 'Enterprise' ? 'mt-auto' : 'mt-8'}>
|
||||
<Link href={plan.href} as={plan.href}>
|
||||
<a>
|
||||
<Button
|
||||
size="tiny"
|
||||
type={tier.name === 'Enterprise' ? 'default' : 'primary'}
|
||||
type={plan.name === 'Enterprise' ? 'default' : 'primary'}
|
||||
block
|
||||
>
|
||||
{tier.cta}
|
||||
{plan.cta}
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
@@ -875,7 +890,7 @@ export default function IndexPage() {
|
||||
</td>
|
||||
|
||||
<td className="px-6 pt-5">
|
||||
<Link href="https://forms.supabase.com/enterprise">
|
||||
<Link href="https://forms.supabase.com/team">
|
||||
<a>
|
||||
<Button size="tiny" type="primary" block>
|
||||
Contact us
|
||||
@@ -883,6 +898,16 @@ export default function IndexPage() {
|
||||
</a>
|
||||
</Link>
|
||||
</td>
|
||||
|
||||
<td className="px-6 pt-5">
|
||||
<Link href="https://forms.supabase.com/enterprise">
|
||||
<a>
|
||||
<Button size="tiny" type="default" block>
|
||||
Contact us
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 297 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 149 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 262 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 328 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
@@ -148,7 +148,7 @@ h6:not(.overwrite) {
|
||||
}
|
||||
|
||||
::selection {
|
||||
background-color: #6ee7b7;
|
||||
background-color: #6ee7b7 !important;
|
||||
color: #333 !important;
|
||||
}
|
||||
|
||||
|
||||
+250
-207
@@ -21,13 +21,29 @@
|
||||
"tailwindcss": "^3.1.8",
|
||||
"tslib": "^2.4.0",
|
||||
"typescript": "^4.7.4",
|
||||
"vite": "^3.0.0"
|
||||
"vite": "^3.2.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz",
|
||||
"integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.14.53.tgz",
|
||||
"integrity": "sha512-W2dAL6Bnyn4xa/QRSU3ilIK4EzD5wgYXKXJiS1HDF5vU3675qc2bvFyLwbUcdmssDveyndy7FbitrCoiV/eMLg==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz",
|
||||
"integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
@@ -660,9 +676,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.53.tgz",
|
||||
"integrity": "sha512-ohO33pUBQ64q6mmheX1mZ8mIXj8ivQY/L4oVuAshr+aJI+zLl+amrp3EodrUNDNYVrKJXGPfIHFGhO8slGRjuw==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz",
|
||||
"integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
@@ -672,33 +688,34 @@
|
||||
"node": ">=12"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/linux-loong64": "0.14.53",
|
||||
"esbuild-android-64": "0.14.53",
|
||||
"esbuild-android-arm64": "0.14.53",
|
||||
"esbuild-darwin-64": "0.14.53",
|
||||
"esbuild-darwin-arm64": "0.14.53",
|
||||
"esbuild-freebsd-64": "0.14.53",
|
||||
"esbuild-freebsd-arm64": "0.14.53",
|
||||
"esbuild-linux-32": "0.14.53",
|
||||
"esbuild-linux-64": "0.14.53",
|
||||
"esbuild-linux-arm": "0.14.53",
|
||||
"esbuild-linux-arm64": "0.14.53",
|
||||
"esbuild-linux-mips64le": "0.14.53",
|
||||
"esbuild-linux-ppc64le": "0.14.53",
|
||||
"esbuild-linux-riscv64": "0.14.53",
|
||||
"esbuild-linux-s390x": "0.14.53",
|
||||
"esbuild-netbsd-64": "0.14.53",
|
||||
"esbuild-openbsd-64": "0.14.53",
|
||||
"esbuild-sunos-64": "0.14.53",
|
||||
"esbuild-windows-32": "0.14.53",
|
||||
"esbuild-windows-64": "0.14.53",
|
||||
"esbuild-windows-arm64": "0.14.53"
|
||||
"@esbuild/android-arm": "0.15.18",
|
||||
"@esbuild/linux-loong64": "0.15.18",
|
||||
"esbuild-android-64": "0.15.18",
|
||||
"esbuild-android-arm64": "0.15.18",
|
||||
"esbuild-darwin-64": "0.15.18",
|
||||
"esbuild-darwin-arm64": "0.15.18",
|
||||
"esbuild-freebsd-64": "0.15.18",
|
||||
"esbuild-freebsd-arm64": "0.15.18",
|
||||
"esbuild-linux-32": "0.15.18",
|
||||
"esbuild-linux-64": "0.15.18",
|
||||
"esbuild-linux-arm": "0.15.18",
|
||||
"esbuild-linux-arm64": "0.15.18",
|
||||
"esbuild-linux-mips64le": "0.15.18",
|
||||
"esbuild-linux-ppc64le": "0.15.18",
|
||||
"esbuild-linux-riscv64": "0.15.18",
|
||||
"esbuild-linux-s390x": "0.15.18",
|
||||
"esbuild-netbsd-64": "0.15.18",
|
||||
"esbuild-openbsd-64": "0.15.18",
|
||||
"esbuild-sunos-64": "0.15.18",
|
||||
"esbuild-windows-32": "0.15.18",
|
||||
"esbuild-windows-64": "0.15.18",
|
||||
"esbuild-windows-arm64": "0.15.18"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-android-64": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.53.tgz",
|
||||
"integrity": "sha512-fIL93sOTnEU+NrTAVMIKiAw0YH22HWCAgg4N4Z6zov2t0kY9RAJ50zY9ZMCQ+RT6bnOfDt8gCTnt/RaSNA2yRA==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz",
|
||||
"integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -712,9 +729,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-android-arm64": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.53.tgz",
|
||||
"integrity": "sha512-PC7KaF1v0h/nWpvlU1UMN7dzB54cBH8qSsm7S9mkwFA1BXpaEOufCg8hdoEI1jep0KeO/rjZVWrsH8+q28T77A==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz",
|
||||
"integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -728,9 +745,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-darwin-64": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.53.tgz",
|
||||
"integrity": "sha512-gE7P5wlnkX4d4PKvLBUgmhZXvL7lzGRLri17/+CmmCzfncIgq8lOBvxGMiQ4xazplhxq+72TEohyFMZLFxuWvg==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz",
|
||||
"integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -744,9 +761,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-darwin-arm64": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.53.tgz",
|
||||
"integrity": "sha512-otJwDU3hnI15Q98PX4MJbknSZ/WSR1I45il7gcxcECXzfN4Mrpft5hBDHXNRnCh+5858uPXBXA1Vaz2jVWLaIA==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz",
|
||||
"integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -760,9 +777,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-freebsd-64": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.53.tgz",
|
||||
"integrity": "sha512-WkdJa8iyrGHyKiPF4lk0MiOF87Q2SkE+i+8D4Cazq3/iqmGPJ6u49je300MFi5I2eUsQCkaOWhpCVQMTKGww2w==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz",
|
||||
"integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -776,9 +793,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-freebsd-arm64": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.53.tgz",
|
||||
"integrity": "sha512-9T7WwCuV30NAx0SyQpw8edbKvbKELnnm1FHg7gbSYaatH+c8WJW10g/OdM7JYnv7qkimw2ZTtSA+NokOLd2ydQ==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz",
|
||||
"integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -792,9 +809,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-32": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.53.tgz",
|
||||
"integrity": "sha512-VGanLBg5en2LfGDgLEUxQko2lqsOS7MTEWUi8x91YmsHNyzJVT/WApbFFx3MQGhkf+XdimVhpyo5/G0PBY91zg==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz",
|
||||
"integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -808,9 +825,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-64": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.53.tgz",
|
||||
"integrity": "sha512-pP/FA55j/fzAV7N9DF31meAyjOH6Bjuo3aSKPh26+RW85ZEtbJv9nhoxmGTd9FOqjx59Tc1ZbrJabuiXlMwuZQ==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz",
|
||||
"integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -824,9 +841,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-arm": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.53.tgz",
|
||||
"integrity": "sha512-/u81NGAVZMopbmzd21Nu/wvnKQK3pT4CrvQ8BTje1STXcQAGnfyKgQlj3m0j2BzYbvQxSy+TMck4TNV2onvoPA==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz",
|
||||
"integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -840,9 +857,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-arm64": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.53.tgz",
|
||||
"integrity": "sha512-GDmWITT+PMsjCA6/lByYk7NyFssW4Q6in32iPkpjZ/ytSyH+xeEx8q7HG3AhWH6heemEYEWpTll/eui3jwlSnw==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz",
|
||||
"integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -856,9 +873,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-mips64le": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.53.tgz",
|
||||
"integrity": "sha512-d6/XHIQW714gSSp6tOOX2UscedVobELvQlPMkInhx1NPz4ThZI9uNLQ4qQJHGBGKGfu+rtJsxM4NVHLhnNRdWQ==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz",
|
||||
"integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
@@ -872,9 +889,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-ppc64le": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.53.tgz",
|
||||
"integrity": "sha512-ndnJmniKPCB52m+r6BtHHLAOXw+xBCWIxNnedbIpuREOcbSU/AlyM/2dA3BmUQhsHdb4w3amD5U2s91TJ3MzzA==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz",
|
||||
"integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -888,9 +905,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-riscv64": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.53.tgz",
|
||||
"integrity": "sha512-yG2sVH+QSix6ct4lIzJj329iJF3MhloLE6/vKMQAAd26UVPVkhMFqFopY+9kCgYsdeWvXdPgmyOuKa48Y7+/EQ==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz",
|
||||
"integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -904,9 +921,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-linux-s390x": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.53.tgz",
|
||||
"integrity": "sha512-OCJlgdkB+XPYndHmw6uZT7jcYgzmx9K+28PVdOa/eLjdoYkeAFvH5hTwX4AXGLZLH09tpl4bVsEtvuyUldaNCg==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz",
|
||||
"integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -920,9 +937,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-netbsd-64": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.53.tgz",
|
||||
"integrity": "sha512-gp2SB+Efc7MhMdWV2+pmIs/Ja/Mi5rjw+wlDmmbIn68VGXBleNgiEZG+eV2SRS0kJEUyHNedDtwRIMzaohWedQ==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz",
|
||||
"integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -936,9 +953,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-openbsd-64": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.53.tgz",
|
||||
"integrity": "sha512-eKQ30ZWe+WTZmteDYg8S+YjHV5s4iTxeSGhJKJajFfQx9TLZJvsJX0/paqwP51GicOUruFpSUAs2NCc0a4ivQQ==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz",
|
||||
"integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -952,9 +969,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-sunos-64": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.53.tgz",
|
||||
"integrity": "sha512-OWLpS7a2FrIRukQqcgQqR1XKn0jSJoOdT+RlhAxUoEQM/IpytS3FXzCJM6xjUYtpO5GMY0EdZJp+ur2pYdm39g==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz",
|
||||
"integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -968,9 +985,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-windows-32": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.53.tgz",
|
||||
"integrity": "sha512-m14XyWQP5rwGW0tbEfp95U6A0wY0DYPInWBB7D69FAXUpBpBObRoGTKRv36lf2RWOdE4YO3TNvj37zhXjVL5xg==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz",
|
||||
"integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -984,9 +1001,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-windows-64": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.53.tgz",
|
||||
"integrity": "sha512-s9skQFF0I7zqnQ2K8S1xdLSfZFsPLuOGmSx57h2btSEswv0N0YodYvqLcJMrNMXh6EynOmWD7rz+0rWWbFpIHQ==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz",
|
||||
"integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1000,9 +1017,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-windows-arm64": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.53.tgz",
|
||||
"integrity": "sha512-E+5Gvb+ZWts+00T9II6wp2L3KG2r3iGxByqd/a1RmLmYWVsSVUjkvIxZuJ3hYTIbhLkH5PRwpldGTKYqVz0nzQ==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz",
|
||||
"integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1399,10 +1416,16 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.4",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
|
||||
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
|
||||
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
@@ -1526,9 +1549,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.14",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz",
|
||||
"integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==",
|
||||
"version": "8.4.24",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz",
|
||||
"integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -1538,10 +1561,14 @@
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/postcss"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.4",
|
||||
"nanoid": "^3.3.6",
|
||||
"picocolors": "^1.0.0",
|
||||
"source-map-js": "^1.0.2"
|
||||
},
|
||||
@@ -1763,9 +1790,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "2.77.2",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.77.2.tgz",
|
||||
"integrity": "sha512-m/4YzYgLcpMQbxX3NmAqDvwLATZzxt8bIegO78FZLl+lAgKJBd1DRAOeEiZcKOIOPjxE6ewHWHNgGEalFXuz1g==",
|
||||
"version": "2.79.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz",
|
||||
"integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
@@ -2181,15 +2208,15 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-3.0.4.tgz",
|
||||
"integrity": "sha512-NU304nqnBeOx2MkQnskBQxVsa0pRAH5FphokTGmyy8M3oxbvw7qAXts2GORxs+h/2vKsD+osMhZ7An6yK6F1dA==",
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.7.tgz",
|
||||
"integrity": "sha512-29pdXjk49xAP0QBr0xXqu2s5jiQIXNvE/xwd0vUizYT2Hzqe4BksNNoWllFVXJf4eLZ+UlVQmXfB4lWrc+t18g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.14.47",
|
||||
"postcss": "^8.4.14",
|
||||
"esbuild": "^0.15.9",
|
||||
"postcss": "^8.4.18",
|
||||
"resolve": "^1.22.1",
|
||||
"rollup": "^2.75.6"
|
||||
"rollup": "^2.79.1"
|
||||
},
|
||||
"bin": {
|
||||
"vite": "bin/vite.js"
|
||||
@@ -2201,12 +2228,17 @@
|
||||
"fsevents": "~2.3.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/node": ">= 14",
|
||||
"less": "*",
|
||||
"sass": "*",
|
||||
"stylus": "*",
|
||||
"sugarss": "*",
|
||||
"terser": "^5.4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
},
|
||||
"less": {
|
||||
"optional": true
|
||||
},
|
||||
@@ -2216,6 +2248,9 @@
|
||||
"stylus": {
|
||||
"optional": true
|
||||
},
|
||||
"sugarss": {
|
||||
"optional": true
|
||||
},
|
||||
"terser": {
|
||||
"optional": true
|
||||
}
|
||||
@@ -2351,10 +2386,17 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@esbuild/android-arm": {
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz",
|
||||
"integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"@esbuild/linux-loong64": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.14.53.tgz",
|
||||
"integrity": "sha512-W2dAL6Bnyn4xa/QRSU3ilIK4EzD5wgYXKXJiS1HDF5vU3675qc2bvFyLwbUcdmssDveyndy7FbitrCoiV/eMLg==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz",
|
||||
"integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
@@ -2845,171 +2887,172 @@
|
||||
}
|
||||
},
|
||||
"esbuild": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.53.tgz",
|
||||
"integrity": "sha512-ohO33pUBQ64q6mmheX1mZ8mIXj8ivQY/L4oVuAshr+aJI+zLl+amrp3EodrUNDNYVrKJXGPfIHFGhO8slGRjuw==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz",
|
||||
"integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@esbuild/linux-loong64": "0.14.53",
|
||||
"esbuild-android-64": "0.14.53",
|
||||
"esbuild-android-arm64": "0.14.53",
|
||||
"esbuild-darwin-64": "0.14.53",
|
||||
"esbuild-darwin-arm64": "0.14.53",
|
||||
"esbuild-freebsd-64": "0.14.53",
|
||||
"esbuild-freebsd-arm64": "0.14.53",
|
||||
"esbuild-linux-32": "0.14.53",
|
||||
"esbuild-linux-64": "0.14.53",
|
||||
"esbuild-linux-arm": "0.14.53",
|
||||
"esbuild-linux-arm64": "0.14.53",
|
||||
"esbuild-linux-mips64le": "0.14.53",
|
||||
"esbuild-linux-ppc64le": "0.14.53",
|
||||
"esbuild-linux-riscv64": "0.14.53",
|
||||
"esbuild-linux-s390x": "0.14.53",
|
||||
"esbuild-netbsd-64": "0.14.53",
|
||||
"esbuild-openbsd-64": "0.14.53",
|
||||
"esbuild-sunos-64": "0.14.53",
|
||||
"esbuild-windows-32": "0.14.53",
|
||||
"esbuild-windows-64": "0.14.53",
|
||||
"esbuild-windows-arm64": "0.14.53"
|
||||
"@esbuild/android-arm": "0.15.18",
|
||||
"@esbuild/linux-loong64": "0.15.18",
|
||||
"esbuild-android-64": "0.15.18",
|
||||
"esbuild-android-arm64": "0.15.18",
|
||||
"esbuild-darwin-64": "0.15.18",
|
||||
"esbuild-darwin-arm64": "0.15.18",
|
||||
"esbuild-freebsd-64": "0.15.18",
|
||||
"esbuild-freebsd-arm64": "0.15.18",
|
||||
"esbuild-linux-32": "0.15.18",
|
||||
"esbuild-linux-64": "0.15.18",
|
||||
"esbuild-linux-arm": "0.15.18",
|
||||
"esbuild-linux-arm64": "0.15.18",
|
||||
"esbuild-linux-mips64le": "0.15.18",
|
||||
"esbuild-linux-ppc64le": "0.15.18",
|
||||
"esbuild-linux-riscv64": "0.15.18",
|
||||
"esbuild-linux-s390x": "0.15.18",
|
||||
"esbuild-netbsd-64": "0.15.18",
|
||||
"esbuild-openbsd-64": "0.15.18",
|
||||
"esbuild-sunos-64": "0.15.18",
|
||||
"esbuild-windows-32": "0.15.18",
|
||||
"esbuild-windows-64": "0.15.18",
|
||||
"esbuild-windows-arm64": "0.15.18"
|
||||
}
|
||||
},
|
||||
"esbuild-android-64": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.53.tgz",
|
||||
"integrity": "sha512-fIL93sOTnEU+NrTAVMIKiAw0YH22HWCAgg4N4Z6zov2t0kY9RAJ50zY9ZMCQ+RT6bnOfDt8gCTnt/RaSNA2yRA==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz",
|
||||
"integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-android-arm64": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.53.tgz",
|
||||
"integrity": "sha512-PC7KaF1v0h/nWpvlU1UMN7dzB54cBH8qSsm7S9mkwFA1BXpaEOufCg8hdoEI1jep0KeO/rjZVWrsH8+q28T77A==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz",
|
||||
"integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-darwin-64": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.53.tgz",
|
||||
"integrity": "sha512-gE7P5wlnkX4d4PKvLBUgmhZXvL7lzGRLri17/+CmmCzfncIgq8lOBvxGMiQ4xazplhxq+72TEohyFMZLFxuWvg==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz",
|
||||
"integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-darwin-arm64": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.53.tgz",
|
||||
"integrity": "sha512-otJwDU3hnI15Q98PX4MJbknSZ/WSR1I45il7gcxcECXzfN4Mrpft5hBDHXNRnCh+5858uPXBXA1Vaz2jVWLaIA==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz",
|
||||
"integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-freebsd-64": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.53.tgz",
|
||||
"integrity": "sha512-WkdJa8iyrGHyKiPF4lk0MiOF87Q2SkE+i+8D4Cazq3/iqmGPJ6u49je300MFi5I2eUsQCkaOWhpCVQMTKGww2w==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz",
|
||||
"integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-freebsd-arm64": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.53.tgz",
|
||||
"integrity": "sha512-9T7WwCuV30NAx0SyQpw8edbKvbKELnnm1FHg7gbSYaatH+c8WJW10g/OdM7JYnv7qkimw2ZTtSA+NokOLd2ydQ==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz",
|
||||
"integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-32": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.53.tgz",
|
||||
"integrity": "sha512-VGanLBg5en2LfGDgLEUxQko2lqsOS7MTEWUi8x91YmsHNyzJVT/WApbFFx3MQGhkf+XdimVhpyo5/G0PBY91zg==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz",
|
||||
"integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-64": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.53.tgz",
|
||||
"integrity": "sha512-pP/FA55j/fzAV7N9DF31meAyjOH6Bjuo3aSKPh26+RW85ZEtbJv9nhoxmGTd9FOqjx59Tc1ZbrJabuiXlMwuZQ==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz",
|
||||
"integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-arm": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.53.tgz",
|
||||
"integrity": "sha512-/u81NGAVZMopbmzd21Nu/wvnKQK3pT4CrvQ8BTje1STXcQAGnfyKgQlj3m0j2BzYbvQxSy+TMck4TNV2onvoPA==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz",
|
||||
"integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-arm64": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.53.tgz",
|
||||
"integrity": "sha512-GDmWITT+PMsjCA6/lByYk7NyFssW4Q6in32iPkpjZ/ytSyH+xeEx8q7HG3AhWH6heemEYEWpTll/eui3jwlSnw==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz",
|
||||
"integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-mips64le": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.53.tgz",
|
||||
"integrity": "sha512-d6/XHIQW714gSSp6tOOX2UscedVobELvQlPMkInhx1NPz4ThZI9uNLQ4qQJHGBGKGfu+rtJsxM4NVHLhnNRdWQ==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz",
|
||||
"integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-ppc64le": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.53.tgz",
|
||||
"integrity": "sha512-ndnJmniKPCB52m+r6BtHHLAOXw+xBCWIxNnedbIpuREOcbSU/AlyM/2dA3BmUQhsHdb4w3amD5U2s91TJ3MzzA==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz",
|
||||
"integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-riscv64": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.53.tgz",
|
||||
"integrity": "sha512-yG2sVH+QSix6ct4lIzJj329iJF3MhloLE6/vKMQAAd26UVPVkhMFqFopY+9kCgYsdeWvXdPgmyOuKa48Y7+/EQ==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz",
|
||||
"integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-linux-s390x": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.53.tgz",
|
||||
"integrity": "sha512-OCJlgdkB+XPYndHmw6uZT7jcYgzmx9K+28PVdOa/eLjdoYkeAFvH5hTwX4AXGLZLH09tpl4bVsEtvuyUldaNCg==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz",
|
||||
"integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-netbsd-64": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.53.tgz",
|
||||
"integrity": "sha512-gp2SB+Efc7MhMdWV2+pmIs/Ja/Mi5rjw+wlDmmbIn68VGXBleNgiEZG+eV2SRS0kJEUyHNedDtwRIMzaohWedQ==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz",
|
||||
"integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-openbsd-64": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.53.tgz",
|
||||
"integrity": "sha512-eKQ30ZWe+WTZmteDYg8S+YjHV5s4iTxeSGhJKJajFfQx9TLZJvsJX0/paqwP51GicOUruFpSUAs2NCc0a4ivQQ==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz",
|
||||
"integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-sunos-64": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.53.tgz",
|
||||
"integrity": "sha512-OWLpS7a2FrIRukQqcgQqR1XKn0jSJoOdT+RlhAxUoEQM/IpytS3FXzCJM6xjUYtpO5GMY0EdZJp+ur2pYdm39g==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz",
|
||||
"integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-windows-32": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.53.tgz",
|
||||
"integrity": "sha512-m14XyWQP5rwGW0tbEfp95U6A0wY0DYPInWBB7D69FAXUpBpBObRoGTKRv36lf2RWOdE4YO3TNvj37zhXjVL5xg==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz",
|
||||
"integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-windows-64": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.53.tgz",
|
||||
"integrity": "sha512-s9skQFF0I7zqnQ2K8S1xdLSfZFsPLuOGmSx57h2btSEswv0N0YodYvqLcJMrNMXh6EynOmWD7rz+0rWWbFpIHQ==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz",
|
||||
"integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"esbuild-windows-arm64": {
|
||||
"version": "0.14.53",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.53.tgz",
|
||||
"integrity": "sha512-E+5Gvb+ZWts+00T9II6wp2L3KG2r3iGxByqd/a1RmLmYWVsSVUjkvIxZuJ3hYTIbhLkH5PRwpldGTKYqVz0nzQ==",
|
||||
"version": "0.15.18",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz",
|
||||
"integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
@@ -3313,9 +3356,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"nanoid": {
|
||||
"version": "3.3.4",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
|
||||
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
|
||||
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
|
||||
"dev": true
|
||||
},
|
||||
"next-tick": {
|
||||
@@ -3397,12 +3440,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"postcss": {
|
||||
"version": "8.4.14",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz",
|
||||
"integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==",
|
||||
"version": "8.4.24",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz",
|
||||
"integrity": "sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"nanoid": "^3.3.4",
|
||||
"nanoid": "^3.3.6",
|
||||
"picocolors": "^1.0.0",
|
||||
"source-map-js": "^1.0.2"
|
||||
}
|
||||
@@ -3531,9 +3574,9 @@
|
||||
}
|
||||
},
|
||||
"rollup": {
|
||||
"version": "2.77.2",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.77.2.tgz",
|
||||
"integrity": "sha512-m/4YzYgLcpMQbxX3NmAqDvwLATZzxt8bIegO78FZLl+lAgKJBd1DRAOeEiZcKOIOPjxE6ewHWHNgGEalFXuz1g==",
|
||||
"version": "2.79.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz",
|
||||
"integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fsevents": "~2.3.2"
|
||||
@@ -3808,16 +3851,16 @@
|
||||
"dev": true
|
||||
},
|
||||
"vite": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-3.0.4.tgz",
|
||||
"integrity": "sha512-NU304nqnBeOx2MkQnskBQxVsa0pRAH5FphokTGmyy8M3oxbvw7qAXts2GORxs+h/2vKsD+osMhZ7An6yK6F1dA==",
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.7.tgz",
|
||||
"integrity": "sha512-29pdXjk49xAP0QBr0xXqu2s5jiQIXNvE/xwd0vUizYT2Hzqe4BksNNoWllFVXJf4eLZ+UlVQmXfB4lWrc+t18g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"esbuild": "^0.14.47",
|
||||
"esbuild": "^0.15.9",
|
||||
"fsevents": "~2.3.2",
|
||||
"postcss": "^8.4.14",
|
||||
"postcss": "^8.4.18",
|
||||
"resolve": "^1.22.1",
|
||||
"rollup": "^2.75.6"
|
||||
"rollup": "^2.79.1"
|
||||
}
|
||||
},
|
||||
"webidl-conversions": {
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"tailwindcss": "^3.1.8",
|
||||
"tslib": "^2.4.0",
|
||||
"typescript": "^4.7.4",
|
||||
"vite": "^3.0.0"
|
||||
"vite": "^3.2.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"@supabase/supabase-js": "^2.0.4"
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
"svelte-preprocess": "^5.0.3",
|
||||
"tslib": "^2.5.0",
|
||||
"typescript": "^5.0.4",
|
||||
"vite": "^4.3.5"
|
||||
"vite": "^4.3.9"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
@@ -1782,9 +1782,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "4.3.5",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.3.5.tgz",
|
||||
"integrity": "sha512-0gEnL9wiRFxgz40o/i/eTBwm+NEbpUeTWhzKrZDSdKm6nplj+z4lKz8ANDgildxHm47Vg8EUia0aicKbawUVVA==",
|
||||
"version": "4.3.9",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.3.9.tgz",
|
||||
"integrity": "sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==",
|
||||
"dependencies": {
|
||||
"esbuild": "^0.17.5",
|
||||
"postcss": "^8.4.23",
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"svelte-preprocess": "^5.0.3",
|
||||
"tslib": "^2.5.0",
|
||||
"typescript": "^5.0.4",
|
||||
"vite": "^4.3.5"
|
||||
"vite": "^4.3.9"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
+25
-17
@@ -13,7 +13,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^3.1.0",
|
||||
"vite": "^3.1.0"
|
||||
"vite": "^3.2.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
@@ -842,9 +842,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "2.78.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.78.1.tgz",
|
||||
"integrity": "sha512-VeeCgtGi4P+o9hIg+xz4qQpRl6R401LWEXBmxYKOV4zlF82lyhgh2hTZnheFUbANE8l2A41F458iwj2vEYaXJg==",
|
||||
"version": "2.79.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz",
|
||||
"integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
@@ -920,15 +920,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "3.1.8",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-3.1.8.tgz",
|
||||
"integrity": "sha512-m7jJe3nufUbuOfotkntGFupinL/fmuTNuQmiVE7cH2IZMuf4UbfbGYMUT3jVWgGYuRVLY9j8NnrRqgw5rr5QTg==",
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.7.tgz",
|
||||
"integrity": "sha512-29pdXjk49xAP0QBr0xXqu2s5jiQIXNvE/xwd0vUizYT2Hzqe4BksNNoWllFVXJf4eLZ+UlVQmXfB4lWrc+t18g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.15.9",
|
||||
"postcss": "^8.4.16",
|
||||
"postcss": "^8.4.18",
|
||||
"resolve": "^1.22.1",
|
||||
"rollup": "~2.78.0"
|
||||
"rollup": "^2.79.1"
|
||||
},
|
||||
"bin": {
|
||||
"vite": "bin/vite.js"
|
||||
@@ -940,12 +940,17 @@
|
||||
"fsevents": "~2.3.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/node": ">= 14",
|
||||
"less": "*",
|
||||
"sass": "*",
|
||||
"stylus": "*",
|
||||
"sugarss": "*",
|
||||
"terser": "^5.4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
},
|
||||
"less": {
|
||||
"optional": true
|
||||
},
|
||||
@@ -955,6 +960,9 @@
|
||||
"stylus": {
|
||||
"optional": true
|
||||
},
|
||||
"sugarss": {
|
||||
"optional": true
|
||||
},
|
||||
"terser": {
|
||||
"optional": true
|
||||
}
|
||||
@@ -1558,9 +1566,9 @@
|
||||
}
|
||||
},
|
||||
"rollup": {
|
||||
"version": "2.78.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.78.1.tgz",
|
||||
"integrity": "sha512-VeeCgtGi4P+o9hIg+xz4qQpRl6R401LWEXBmxYKOV4zlF82lyhgh2hTZnheFUbANE8l2A41F458iwj2vEYaXJg==",
|
||||
"version": "2.79.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz",
|
||||
"integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fsevents": "~2.3.2"
|
||||
@@ -1614,16 +1622,16 @@
|
||||
}
|
||||
},
|
||||
"vite": {
|
||||
"version": "3.1.8",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-3.1.8.tgz",
|
||||
"integrity": "sha512-m7jJe3nufUbuOfotkntGFupinL/fmuTNuQmiVE7cH2IZMuf4UbfbGYMUT3jVWgGYuRVLY9j8NnrRqgw5rr5QTg==",
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-3.2.7.tgz",
|
||||
"integrity": "sha512-29pdXjk49xAP0QBr0xXqu2s5jiQIXNvE/xwd0vUizYT2Hzqe4BksNNoWllFVXJf4eLZ+UlVQmXfB4lWrc+t18g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"esbuild": "^0.15.9",
|
||||
"fsevents": "~2.3.2",
|
||||
"postcss": "^8.4.16",
|
||||
"postcss": "^8.4.18",
|
||||
"resolve": "^1.22.1",
|
||||
"rollup": "~2.78.0"
|
||||
"rollup": "^2.79.1"
|
||||
}
|
||||
},
|
||||
"vue": {
|
||||
|
||||
@@ -14,6 +14,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^3.1.0",
|
||||
"vite": "^3.1.0"
|
||||
"vite": "^3.2.7"
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -35,7 +35,7 @@
|
||||
## الحالة
|
||||
|
||||
- [x] طور الألفا: نحن نختبر Supabase مع مجموعة مغلقة من العملاء
|
||||
- [x] طور الألفا العام: يمكن لأي شخص الاشتراك في [app.supabase.com] (https://app.supabase.com). لكن تحلى بالصبر ، فهناك بعض العقد الصغيرة.
|
||||
- [x] طور الألفا العام: يمكن لأي شخص الاشتراك في [app.supabase.com](https://app.supabase.com). لكن تحلى بالصبر ، فهناك بعض العقد الصغيرة.
|
||||
- [x] طور البيتا العام: مستقر بدرجة كافية لمعظم حالات الاستخدام غير المتعلقة بالمؤسسات
|
||||
- [ ] الطور العام: جاهز للإنتاج
|
||||
|
||||
|
||||
+4
-4
@@ -7,7 +7,7 @@
|
||||
|
||||
# Supabase
|
||||
|
||||
[Supabase] (https://supabase.com) er et Open Source Firebase-alternativ. Vi opbygger Firebase-funktionerne ved hjælp af open source-værktøjer i virksomhedskvalitet.
|
||||
[Supabase](https://supabase.com) er et Open Source Firebase-alternativ. Vi opbygger Firebase-funktionerne ved hjælp af open source-værktøjer i virksomhedskvalitet.
|
||||
|
||||
- [x] Hosted Postgres Database. [Docs](https://supabase.com/docs/guides/database)
|
||||
- [x] Autentifikation og autorisering. [Docs](https://supabase.com/docs/guides/auth)
|
||||
@@ -39,7 +39,7 @@ For at se, hvordan man bidrager, besøg [Getting Started](./DEVELOPERS.md)
|
||||
## Status
|
||||
|
||||
- [x] Alpha: Vi tester Supabase med et lukket sæt af kunder
|
||||
- [x] Offentlig Alpha: Alle kan tilmelde sig på [app.supabase.com] (https://app.supabase.com). Men vær forsigtig med os, der er et par knuder
|
||||
- [x] Offentlig Alpha: Alle kan tilmelde sig på [app.supabase.com](https://app.supabase.com). Men vær forsigtig med os, der er et par knuder
|
||||
- [x] Public Beta: Stabil nok til de fleste ikke-virksomhedsrelaterede brugssager
|
||||
- [ ] Public: Public: Generel tilgængelighed [[status](https://supabase.com/docs/guides/getting-started/features#feature-status)]
|
||||
|
||||
@@ -55,14 +55,14 @@ Supabase er en kombination af open source-værktøjer. Vi opbygger funktionerne
|
||||
|
||||
**Arkitektur**
|
||||
|
||||
Supabase er en [hosted platform] (https://app.supabase.com). Du kan tilmelde dig og begynde at bruge Supabase uden at installere noget.
|
||||
Supabase er en [hosted platform](https://app.supabase.com). Du kan tilmelde dig og begynde at bruge Supabase uden at installere noget.
|
||||
Du kan også [selv hoste](https://supabase.com/docs/guides/hosting/overview) og [udvikle lokalt](https://supabase.com/docs/guides/local-development).
|
||||
|
||||

|
||||
|
||||
- [PostgreSQL](https://www.postgresql.org/) er et objektrelationelt databasesystem med over 30 års aktiv udvikling, der har givet det et godt ry for pålidelighed, robusthed og ydeevne.
|
||||
- [Realtime](https://github.com/supabase/realtime) er en Elixir-server, der giver dig mulighed for at lytte til PostgreSQL-indsættelser, -opdateringer og -slettelser ved hjælp af websockets. Realtime spørger Postgres' indbyggede replikationsfunktionalitet efter databaseændringer, konverterer ændringer til JSON og sender derefter JSON'en over websockets til autoriserede klienter.
|
||||
- [PostgREST] (http://postgrest.org/) er en webserver, der forvandler din PostgreSQL-database direkte til et RESTful API
|
||||
- [PostgREST](http://postgrest.org/) er en webserver, der forvandler din PostgreSQL-database direkte til et RESTful API
|
||||
- [pg_graphql](http://github.com/supabase/pg_graphql/) er en PostgreSQL-udvidelse, der eksponerer et GraphQL API
|
||||
- [Storage](https://github.com/supabase/storage-api) giver en RESTful grænseflade til administration af filer gemt i S3, der bruger Postgres til at administrere tilladelser.
|
||||
- [postgres-meta](https://github.com/supabase/postgres-meta) er et RESTful API til administration af din Postgres, så du kan hente tabeller, tilføje roller og køre forespørgsler osv.
|
||||
|
||||
+2
-2
@@ -55,8 +55,8 @@ Supabase ist eine Kombination aus Open-Source-Tools. Wir entwickeln die Funktion
|
||||
|
||||
**Architektur**
|
||||
|
||||
Supabase ist eine [gehostete Plattform] (https://app.supabase.com). Sie können sich anmelden und Supabase verwenden, ohne etwas zu installieren.
|
||||
Sie können auch [selbst hosten] (https://supabase.com/docs/guides/hosting/overview) und [lokal entwickeln] (https://supabase.com/docs/guides/local-development).
|
||||
Supabase ist eine [gehostete Plattform](https://app.supabase.com). Sie können sich anmelden und Supabase verwenden, ohne etwas zu installieren.
|
||||
Sie können auch [selbst hosten](https://supabase.com/docs/guides/hosting/overview) und [lokal entwickeln](https://supabase.com/docs/guides/local-development).
|
||||
|
||||

|
||||
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ Actualmente estamos en Beta Pública. Esté atento a "releases" de este reposito
|
||||
|
||||
Supabase es una combinación de herramientas de código abierto. Estamos construyendo las características de Firebase utilizando productos de código abierto de nivel empresarial. Si las herramientas y las comunidades existen, con una licencia abierta MIT, Apache 2 o equivalente, utilizaremos y daremos soporte a esa herramienta. Si la herramienta no existe, la construimos y la desarrollamos nosotros mismos. Supabase no es un mapeo 1 a 1 de Firebase. Nuestro objetivo es ofrecer a los desarrolladores una experiencia similar a la de Firebase utilizando herramientas de código abierto.
|
||||
|
||||
\*\*Arquitectura
|
||||
**Arquitectura**
|
||||
|
||||
Supabase es una [plataforma alojada](https://app.supabase.com). Puedes registrarte y empezar a usar Supabase sin instalar nada.
|
||||
También puede [autoalojarse](https://supabase.com/docs/guides/hosting/overview) y [desarrollar localmente](https://supabase.com/docs/guides/local-development).
|
||||
|
||||
+3
-3
@@ -39,7 +39,7 @@ Pour savoir comment contribuer, visitez [Getting Started](./DEVELOPERS.md)
|
||||
## Statut
|
||||
|
||||
- [x] Alpha : Nous testons Supabase avec un groupe fermé de clients
|
||||
- [x] Alpha publique : Tout le monde peut s'inscrire sur [app.supabase.com] (https://app.supabase.com). Mais allez-y doucement, il y a quelques problèmes
|
||||
- [x] Alpha publique : Tout le monde peut s'inscrire sur [app.supabase.com](https://app.supabase.com). Mais allez-y doucement, il y a quelques problèmes
|
||||
- [x] Bêta publique : Suffisamment stable pour la plupart des cas d'utilisation hors entreprise
|
||||
- [ ] Public : Disponibilité générale [[status](https://supabase.com/docs/guides/getting-started/features#feature-status)]
|
||||
|
||||
@@ -55,14 +55,14 @@ Supabase est une combinaison d'outils open source. Nous construisons les fonctio
|
||||
|
||||
**Architecture**
|
||||
|
||||
Supabase est une [plateforme hébergée] (https://app.supabase.com). Vous pouvez vous inscrire et commencer à utiliser Supabase sans rien installer.
|
||||
Supabase est une [plateforme hébergée](https://app.supabase.com). Vous pouvez vous inscrire et commencer à utiliser Supabase sans rien installer.
|
||||
Vous pouvez également [auto-héberger](https://supabase.com/docs/guides/hosting/overview) et [développer localement](https://supabase.com/docs/guides/local-development).
|
||||
|
||||

|
||||
|
||||
- [PostgreSQL](https://www.postgresql.org/) est un système de base de données objet-relationnel avec plus de 30 ans de développement actif qui lui a valu une solide réputation de fiabilité, de robustesse et de performance.
|
||||
- [Realtime](https://github.com/supabase/realtime) est un serveur Elixir qui vous permet d'écouter les insertions, les mises à jour et les suppressions de PostgreSQL en utilisant des websockets. Realtime interroge la fonctionnalité de réplication intégrée de Postgres pour les changements de base de données, convertit les changements en JSON, puis diffuse le JSON via des websockets aux clients autorisés.
|
||||
- [PostgREST] (http://postgrest.org/) est un serveur web qui transforme votre base de données PostgreSQL en une API RESTful
|
||||
- [PostgREST](http://postgrest.org/) est un serveur web qui transforme votre base de données PostgreSQL en une API RESTful
|
||||
- [pg_graphql](http://github.com/supabase/pg_graphql/) est une extension de PostgreSQL qui expose une API GraphQL
|
||||
- [Storage](https://github.com/supabase/storage-api) fournit une interface RESTful pour gérer les fichiers stockés dans S3, en utilisant Postgres pour gérer les permissions.
|
||||
- [postgres-meta](https://github.com/supabase/postgres-meta) est une API RESTful pour gérer votre Postgres, vous permettant de récupérer des tables, d'ajouter des rôles, et d'exécuter des requêtes, etc.
|
||||
|
||||
+8
-8
@@ -7,7 +7,7 @@
|
||||
|
||||
# Supabase
|
||||
|
||||
[Supabase] (https://supabase.com) adalah alternatif Firebase open source. Kami membangun fitur-fitur Firebase menggunakan alat bantu open source tingkat perusahaan.
|
||||
[Supabase](https://supabase.com) adalah alternatif Firebase open source. Kami membangun fitur-fitur Firebase menggunakan alat bantu open source tingkat perusahaan.
|
||||
|
||||
- [x] Basis Data Postgres yang dihosting. [Dokumen](https://supabase.com/docs/guides/database)
|
||||
- [x] Otentikasi dan Otorisasi. [Dokumen](https://supabase.com/docs/guides/auth)
|
||||
@@ -55,19 +55,19 @@ Supabase adalah kombinasi dari alat sumber terbuka. Kami membangun fitur-fitur F
|
||||
|
||||
**Arsitektur**
|
||||
|
||||
Supabase adalah [platform yang di-host] (https://app.supabase.com). Anda bisa mendaftar dan mulai menggunakan Supabase tanpa menginstal apa pun.
|
||||
Supabase adalah [platform yang di-host](https://app.supabase.com). Anda bisa mendaftar dan mulai menggunakan Supabase tanpa menginstal apa pun.
|
||||
Anda juga bisa [menghosting sendiri](https://supabase.com/docs/guides/hosting/overview) dan [mengembangkan secara lokal](https://supabase.com/docs/guides/local-development).
|
||||
|
||||

|
||||
|
||||
- [PostgreSQL](https://www.postgresql.org/) adalah sistem basis data relasional objek dengan lebih dari 30 tahun pengembangan aktif yang telah membuatnya memiliki reputasi yang kuat dalam hal keandalan, ketangguhan fitur, dan kinerja.
|
||||
- [Realtime] (https://github.com/supabase/realtime) adalah server Elixir yang memungkinkan Anda untuk mendengarkan penyisipan, pembaruan, dan penghapusan PostgreSQL menggunakan websocket. Realtime melakukan polling terhadap fungsionalitas replikasi bawaan Postgres untuk perubahan database, mengkonversi perubahan ke JSON, kemudian menyiarkan JSON melalui websockets ke klien yang berwenang.
|
||||
- [PostgREST] (http://postgrest.org/) adalah server web yang mengubah basis data PostgreSQL Anda secara langsung menjadi RESTful API
|
||||
- [pg_graphql] (http://github.com/supabase/pg_graphql/) ekstensi PostgreSQL yang mengekspos API GraphQL
|
||||
- [Realtime](https://github.com/supabase/realtime) adalah server Elixir yang memungkinkan Anda untuk mendengarkan penyisipan, pembaruan, dan penghapusan PostgreSQL menggunakan websocket. Realtime melakukan polling terhadap fungsionalitas replikasi bawaan Postgres untuk perubahan database, mengkonversi perubahan ke JSON, kemudian menyiarkan JSON melalui websockets ke klien yang berwenang.
|
||||
- [PostgREST](http://postgrest.org/) adalah server web yang mengubah basis data PostgreSQL Anda secara langsung menjadi RESTful API
|
||||
- [pg_graphql](http://github.com/supabase/pg_graphql/) ekstensi PostgreSQL yang mengekspos API GraphQL
|
||||
- [Storage](https://github.com/supabase/storage-api) menyediakan antarmuka RESTful untuk mengelola File yang disimpan di S3, menggunakan Postgres untuk mengelola izin.
|
||||
- [postgres-meta](https://github.com/supabase/postgres-meta) adalah API RESTful untuk mengelola Postgres Anda, yang memungkinkan Anda untuk mengambil tabel, menambahkan peran, dan menjalankan kueri, dll.
|
||||
- [GoTrue] (https://github.com/netlify/gotrue) adalah API berbasis SWT untuk mengelola pengguna dan menerbitkan token SWT.
|
||||
- [Kong] (https://github.com/Kong/kong) adalah gateway API asli cloud.
|
||||
- [GoTrue](https://github.com/netlify/gotrue) adalah API berbasis SWT untuk mengelola pengguna dan menerbitkan token SWT.
|
||||
- [Kong](https://github.com/Kong/kong) adalah gateway API asli cloud.
|
||||
|
||||
#### Perpustakaan klien
|
||||
|
||||
@@ -239,7 +239,7 @@ Pendekatan kami untuk pustaka klien bersifat modular. Setiap sub-pustaka adalah
|
||||
- [Bahasa Polandia / Polski](/i18n/README.pl.md)
|
||||
- [Bahasa Portugis / Português](/i18n/README.pt.md)
|
||||
- [Portugis (Brasil) / Português Brasileiro](/i18n/README.pt-br.md)
|
||||
- [Bahasa Rumania / Rumania] (/i18n/README.ro.md)
|
||||
- [Bahasa Rumania / Rumania](/i18n/README.ro.md)
|
||||
- [Bahasa Rusia / Pусский](/i18n/README.ru.md)
|
||||
- [Bahasa Serbia / Srpski](/i18n/README.sr.md)
|
||||
- [Sinhala / සිංහල](/i18n/README.si.md)
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@ Supabase è una combinazione di strumenti open source. Stiamo costruendo le funz
|
||||
|
||||
**Architettura**
|
||||
|
||||
Supabase è una [piattaforma ospitata] (https://app.supabase.com). È possibile registrarsi e iniziare a usare Supabase senza installare nulla.
|
||||
Supabase è una [piattaforma ospitata](https://app.supabase.com). È possibile registrarsi e iniziare a usare Supabase senza installare nulla.
|
||||
È anche possibile [auto-ospitare](https://supabase.com/docs/guides/hosting/overview) e [sviluppare localmente](https://supabase.com/docs/guides/local-development).
|
||||
|
||||

|
||||
|
||||
+2
-2
@@ -32,14 +32,14 @@ Norėdami sužinoti, kaip prisidėti, apsilankykite [Getting Started](./DEVELOPE
|
||||
## Bendruomenė ir palaikymas
|
||||
|
||||
- [Bendrijos forumas](https://github.com/supabase/supabase/discussions). Geriausiai tinka: pagalbai kuriant, diskusijoms apie geriausią duomenų bazių praktiką.
|
||||
- [GitHub Issues] (https://github.com/supabase/supabase/issues). Geriausia: klaidos ir klaidos, su kuriomis susiduriate naudodami "Supabase".
|
||||
- [GitHub Issues](https://github.com/supabase/supabase/issues). Geriausia: klaidos ir klaidos, su kuriomis susiduriate naudodami "Supabase".
|
||||
- [Pagalba el. paštu](https://supabase.com/docs/support#business-support). Geriausiai tinka: problemoms, susijusioms su jūsų duomenų baze ar infrastruktūra, spręsti.
|
||||
- [Discord](https://discord.supabase.com). Geriausiai tinka: dalytis savo programomis ir bendrauti su bendruomene.
|
||||
|
||||
## Statusas
|
||||
|
||||
- [x] Alfa: Bandome "Supabase" su uždaru klientų rinkiniu
|
||||
- [x] Viešoji Alfa versija: [app.supabase.com] (https://app.supabase.com). Tačiau neskubėkite su mumis, yra keletas trūkumų
|
||||
- [x] Viešoji Alfa versija: [app.supabase.com](https://app.supabase.com). Tačiau neskubėkite su mumis, yra keletas trūkumų
|
||||
- [x] Viešoji beta versija: Pakankamai stabili daugeliui ne įmonių naudojimo atvejų
|
||||
- [ ] Vieša: Bendras prieinamumas [[statusas](https://supabase.com/docs/guides/getting-started/features#feature-status)]
|
||||
|
||||
|
||||
+2
-2
@@ -55,14 +55,14 @@ Supabase er en kombinasjon av verktøy med åpen kildekode. Vi bygger funksjonen
|
||||
|
||||
\*\*Arkitektur
|
||||
|
||||
Supabase er en [vertsplattform] (https://app.supabase.com). Du kan registrere deg og begynne å bruke Supabase uten å installere noe.
|
||||
Supabase er en [vertsplattform](https://app.supabase.com). Du kan registrere deg og begynne å bruke Supabase uten å installere noe.
|
||||
Du kan også [selv være vert](https://supabase.com/docs/guides/hosting/overview) og [utvikle lokalt](https://supabase.com/docs/guides/local-development).
|
||||
|
||||
arkitektur](https://github.com/supabase/supabase/blob/master/apps/docs/public/img/supabase-architecture.png)
|
||||
|
||||
- [PostgreSQL](https://www.postgresql.org/) er et objektrelasjonsdatabasesystem med over 30 års aktiv utvikling som har gitt det et sterkt rykte for pålitelighet, robusthet og ytelse.
|
||||
- [Realtime](https://github.com/supabase/realtime) er en Elixir-server som lar deg lytte til PostgreSQL-innlegg, oppdateringer og slettinger ved hjelp av websockets. Realtime polls Postgres' innebygde replikeringsfunksjonalitet for databaseendringer, konverterer endringer til JSON og sender deretter JSON over websockets til autoriserte klienter.
|
||||
- [PostgREST] (http://postgrest.org/) er en webserver som gjør PostgreSQL-databasen din direkte om til et RESTful API
|
||||
- [PostgREST](http://postgrest.org/) er en webserver som gjør PostgreSQL-databasen din direkte om til et RESTful API
|
||||
- [pg_graphql](http://github.com/supabase/pg_graphql/) en PostgreSQL-utvidelse som eksponerer et GraphQL API
|
||||
- [Storage](https://github.com/supabase/storage-api) gir et RESTful-grensesnitt for å administrere filer som er lagret i S3, ved hjelp av Postgres for å administrere tillatelser.
|
||||
- [postgres-meta](https://github.com/supabase/postgres-meta) er et RESTful API for å administrere Postgres, slik at du kan hente tabeller, legge til roller og kjøre spørringer osv.
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@ Supabase is een combinatie van open source tools. We bouwen de functies van Fire
|
||||
|
||||
**Architectuur**
|
||||
|
||||
Supabase is een [gehost platform] (https://app.supabase.com). U kunt zich aanmelden en Supabase gebruiken zonder iets te installeren.
|
||||
Supabase is een [gehost platform](https://app.supabase.com). U kunt zich aanmelden en Supabase gebruiken zonder iets te installeren.
|
||||
U kunt ook [zelf hosten](https://supabase.com/docs/guides/hosting/overview) en [lokaal ontwikkelen](https://supabase.com/docs/guides/local-development).
|
||||
|
||||
architectuur](https://github.com/supabase/supabase/blob/master/apps/docs/public/img/supabase-architecture.png)
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@ Supabase to połączenie narzędzi open source. Tworzymy funkcje Firebase przy u
|
||||
|
||||
**Architektura**
|
||||
|
||||
Supabase jest [platformą hostowaną] (https://app.supabase.com). Możesz zarejestrować się i zacząć korzystać z Supabase bez instalowania czegokolwiek.
|
||||
Supabase jest [platformą hostowaną](https://app.supabase.com). Możesz zarejestrować się i zacząć korzystać z Supabase bez instalowania czegokolwiek.
|
||||
Możesz także [self-host](https://supabase.com/docs/guides/hosting/overview) i [rozwijać lokalnie](https://supabase.com/docs/guides/local-development).
|
||||
|
||||

|
||||
|
||||
@@ -39,7 +39,7 @@ Para ver como contribuir, visite [Getting Started](./DEVELOPERS.md)
|
||||
## Status
|
||||
|
||||
- [x] Alfa: Estamos a testar o Supabase com um conjunto fechado de clientes
|
||||
- [x] Alfa Público: Qualquer pessoa pode inscrever-se em [app.supabase.com] (https://app.supabase.com). Mas vá com calma, ainda há alguns problemas
|
||||
- [x] Alfa Público: Qualquer pessoa pode inscrever-se em [app.supabase.com](https://app.supabase.com). Mas vá com calma, ainda há alguns problemas
|
||||
- [x] Beta Público: Estável o suficiente para a maioria dos casos de uso não empresarial
|
||||
- [ ] Público: Disponibilidade geral [[status](https://supabase.com/docs/guides/getting-started/features#feature-status)]
|
||||
|
||||
@@ -55,7 +55,7 @@ O Supabase é uma combinação de ferramentas de código aberto. Estamos a const
|
||||
|
||||
**Arquitectura**
|
||||
|
||||
Supabase é uma [plataforma alojada] (https://app.supabase.com). Pode registar-se e começar a usar o Supabase sem instalar nada.
|
||||
Supabase é uma [plataforma alojada](https://app.supabase.com). Pode registar-se e começar a usar o Supabase sem instalar nada.
|
||||
Pode também [auto-hospedar](https://supabase.com/docs/guides/hosting/overview) e [desenvolver localmente](https://supabase.com/docs/guides/local-development).
|
||||
|
||||

|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
# Supabase
|
||||
|
||||
[Supabase] (https://supabase.com) é uma alternativa de código aberto ao Firebase. Estamos desenvolvendo os recursos do Firebase usando ferramentas de código aberto de nível empresarial.
|
||||
[Supabase](https://supabase.com) é uma alternativa de código aberto ao Firebase. Estamos desenvolvendo os recursos do Firebase usando ferramentas de código aberto de nível empresarial.
|
||||
|
||||
- [x] Banco de dados Postgres hospedado. [Docs](https://supabase.com/docs/guides/database)
|
||||
- [x] Autenticação e autorização. [Docs](https://supabase.com/docs/guides/auth)
|
||||
@@ -39,7 +39,7 @@ Para ver como contribuir, visite [Getting Started](./DEVELOPERS.md)
|
||||
## Status
|
||||
|
||||
- [x] Alfa: Estamos testando o Supabase com um conjunto fechado de clientes
|
||||
- [x] Alfa público: Qualquer pessoa pode se registrar em [app.supabase.com] (https://app.supabase.com). Mas vá com calma, pois há alguns problemas
|
||||
- [x] Alfa público: Qualquer pessoa pode se registrar em [app.supabase.com](https://app.supabase.com). Mas vá com calma, pois há alguns problemas
|
||||
- [x] Beta público: Estável o suficiente para a maioria dos casos de uso não corporativos
|
||||
- [ ] Público: Disponibilidade geral [[status](https://supabase.com/docs/guides/getting-started/features#feature-status)]
|
||||
|
||||
@@ -55,8 +55,8 @@ Supabase é uma combinação de ferramentas de código aberto. Estamos desenvolv
|
||||
|
||||
**Arquitetura**
|
||||
|
||||
Supabase é uma [plataforma hospedada] (https://app.supabase.com). Você pode se registrar e começar a usar o Supabase sem instalar nada.
|
||||
Você também pode [auto-hospedar] (https://supabase.com/docs/guides/hosting/overview) e [desenvolver localmente] (https://supabase.com/docs/guides/local-development).
|
||||
Supabase é uma [plataforma hospedada](https://app.supabase.com). Você pode se registrar e começar a usar o Supabase sem instalar nada.
|
||||
Você também pode [auto-hospedar](https://supabase.com/docs/guides/hosting/overview) e [desenvolver localmente](https://supabase.com/docs/guides/local-development).
|
||||
|
||||

|
||||
|
||||
|
||||
+2
-2
@@ -32,14 +32,14 @@ Za celotno dokumentacijo obiščite [supabase.com/docs](https://supabase.com/doc
|
||||
## Skupnost in podpora
|
||||
|
||||
- [Forum skupnosti](https://github.com/supabase/supabase/discussions). Najprimernejši za: pomoč pri gradnji, razpravo o najboljših praksah zbirke podatkov.
|
||||
- [GitHub Issues] (https://github.com/supabase/supabase/issues). Najprimernejši za: hrošče in napake, na katere naletite pri uporabi zbirke Supabase.
|
||||
- [GitHub Issues](https://github.com/supabase/supabase/issues). Najprimernejši za: hrošče in napake, na katere naletite pri uporabi zbirke Supabase.
|
||||
- [E-poštna podpora](https://supabase.com/docs/support#business-support). Najboljše za: težave s podatkovno zbirko ali infrastrukturo.
|
||||
- [Discord](https://discord.supabase.com). Najboljši za: izmenjavo aplikacij in druženje s skupnostjo.
|
||||
|
||||
## Status
|
||||
|
||||
- [x] Alfa: Testiramo bazo Supabase z zaprtim naborom strank
|
||||
- [x] Javna alfa: [app.supabase.com] (https://app.supabase.com). Vendar nas ne obremenjujte, saj je še nekaj pomanjkljivosti
|
||||
- [x] Javna alfa: [app.supabase.com](https://app.supabase.com). Vendar nas ne obremenjujte, saj je še nekaj pomanjkljivosti
|
||||
- [x] Javna beta različica: Dovolj stabilna za večino primerov uporabe, ki niso povezani s podjetji
|
||||
- [ ] Javna: Splošna razpoložljivost [[status](https://supabase.com/docs/guides/getting-started/features#feature-status)]
|
||||
|
||||
|
||||
+8
-8
@@ -7,16 +7,16 @@
|
||||
|
||||
# Supabase
|
||||
|
||||
[Supabase] (https://supabase.com) är ett alternativ till Firebase med öppen källkod. Vi bygger upp Firebase-funktionerna med hjälp av verktyg för öppen källkod i företagsklass.
|
||||
[Supabase](https://supabase.com) är ett alternativ till Firebase med öppen källkod. Vi bygger upp Firebase-funktionerna med hjälp av verktyg för öppen källkod i företagsklass.
|
||||
|
||||
- [x] Hosted Postgres-databas. [Docs](https://supabase.com/docs/guides/database)
|
||||
- [x] Autentisering och auktorisering. [Docs](https://supabase.com/docs/guides/auth)
|
||||
- [x] Automatiskt genererade API:er.
|
||||
- [x] REST. [Docs](https://supabase.com/docs/guides/database/api#rest-api)
|
||||
- [x] GraphQL. [Docs](https://supabase.com/docs/guides/database/api#graphql-api)
|
||||
- [x] Prenumerationer i realtid. [Dokument] (https://supabase.com/docs/guides/database/api#realtime-api)
|
||||
- [x] Prenumerationer i realtid. [Dokument](https://supabase.com/docs/guides/database/api#realtime-api)
|
||||
- [x] Funktioner.
|
||||
- [x] Databasfunktioner. [Dokument] (https://supabase.com/docs/guides/database/functions)
|
||||
- [x] Databasfunktioner. [Dokument](https://supabase.com/docs/guides/database/functions)
|
||||
- [x] Edge-funktioner [Docs](https://supabase.com/docs/guides/functions)
|
||||
- [x] Lagring av filer. [Docs](https://supabase.com/docs/guides/storage)
|
||||
- [x] Kontrollpanel
|
||||
@@ -25,13 +25,13 @@
|
||||
|
||||
## Dokumentation
|
||||
|
||||
Fullständig dokumentation finns på [supabase.com/docs] (https://supabase.com/docs)
|
||||
Fullständig dokumentation finns på [supabase.com/docs](https://supabase.com/docs)
|
||||
|
||||
För att se hur man bidrar, besök [Getting Started](./DEVELOPERS.md)
|
||||
|
||||
## Gemenskap och stöd
|
||||
|
||||
- [Community Forum] (https://github.com/supabase/supabase/discussions). Bäst för: hjälp med att bygga, diskussion om bästa praxis för databaser.
|
||||
- [Community Forum](https://github.com/supabase/supabase/discussions). Bäst för: hjälp med att bygga, diskussion om bästa praxis för databaser.
|
||||
- [GitHub Issues](https://github.com/supabase/supabase/issues). Bäst för: buggar och fel som du stöter på när du använder Supabase.
|
||||
- [E-postsupport](https://supabase.com/docs/support#business-support). Bäst för: problem med din databas eller infrastruktur.
|
||||
- [Discord](https://discord.supabase.com). Bäst för: att dela med dig av dina applikationer och umgås med gemenskapen.
|
||||
@@ -39,7 +39,7 @@ För att se hur man bidrar, besök [Getting Started](./DEVELOPERS.md)
|
||||
## Status
|
||||
|
||||
- [Alpha: Vi testar Supabase med en sluten grupp av kunder
|
||||
- [x] Public Alpha: Vem som helst kan registrera sig på [app.supabase.com] (https://app.supabase.com). Men var försiktig med oss, det finns några problem
|
||||
- [x] Public Alpha: Vem som helst kan registrera sig på [app.supabase.com](https://app.supabase.com). Men var försiktig med oss, det finns några problem
|
||||
- [x] Public Beta: Stabil nog för de flesta användningsområden som inte är företag
|
||||
- [ ] Public: Allmän tillgänglighet [[status](https://supabase.com/docs/guides/getting-started/features#feature-status)]]
|
||||
|
||||
@@ -55,14 +55,14 @@ Supabase är en kombination av verktyg med öppen källkod. Vi bygger funktioner
|
||||
|
||||
**Arkitektur**
|
||||
|
||||
Supabase är en [värdplattform] (https://app.supabase.com). Du kan registrera dig och börja använda Supabase utan att installera något.
|
||||
Supabase är en [värdplattform](https://app.supabase.com). Du kan registrera dig och börja använda Supabase utan att installera något.
|
||||
Du kan också [självhosta](https://supabase.com/docs/guides/hosting/overview) och [utveckla lokalt](https://supabase.com/docs/guides/local-development).
|
||||
|
||||

|
||||
|
||||
- [PostgreSQL](https://www.postgresql.org/) är ett objektrelationellt databassystem med över 30 års aktiv utveckling som har gett det ett gott rykte när det gäller tillförlitlighet, robusthet och prestanda.
|
||||
- [Realtime](https://github.com/supabase/realtime) är en Elixir-server som låter dig lyssna på PostgreSQL-insättningar, uppdateringar och borttagningar med hjälp av websockets. Realtime söker Postgres inbyggda replikeringsfunktionalitet efter databasändringar, omvandlar ändringarna till JSON och sänder sedan JSON via websockets till auktoriserade klienter.
|
||||
- [PostgREST] (http://postgrest.org/) är en webbserver som omvandlar din PostgreSQL-databas direkt till ett RESTful API
|
||||
- [PostgREST](http://postgrest.org/) är en webbserver som omvandlar din PostgreSQL-databas direkt till ett RESTful API
|
||||
- [pg_graphql](http://github.com/supabase/pg_graphql/) är ett PostgreSQL-tillägg som exponerar ett GraphQL API
|
||||
- [Storage](https://github.com/supabase/storage-api) tillhandahåller ett RESTful-gränssnitt för hantering av filer som lagras i S3, där Postgres används för att hantera behörigheter.
|
||||
- [postgres-meta](https://github.com/supabase/postgres-meta) är ett RESTful API för hantering av Postgres, så att du kan hämta tabeller, lägga till roller, köra frågor osv.
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
|
||||
# Supabase
|
||||
|
||||
[Supabase] (https://supabase.com) açık kaynaklı bir Firebase alternatifidir. Kurumsal düzeyde açık kaynak araçları kullanarak Firebase'in özelliklerini geliştiriyoruz.
|
||||
[Supabase](https://supabase.com) açık kaynaklı bir Firebase alternatifidir. Kurumsal düzeyde açık kaynak araçları kullanarak Firebase'in özelliklerini geliştiriyoruz.
|
||||
|
||||
- [x] Barındırılan Postgres Veritabanı. [Dokümanlar](https://supabase.com/docs/guides/database)
|
||||
- [x] Kimlik Doğrulama ve Yetkilendirme. [Dokümanlar](https://supabase.com/docs/guides/auth)
|
||||
|
||||
+6
-6
@@ -32,14 +32,14 @@
|
||||
## Спільнота та підтримка
|
||||
|
||||
- [Форум спільноти](https://github.com/supabase/supabase/discussions). Найкраще для: допомоги у створенні, обговорення найкращих практик роботи з базами даних.
|
||||
- [Проблеми GitHub] (https://github.com/supabase/supabase/issues). Найкраще для: помилок і помилок, з якими ви зіткнулися при використанні Supabase.
|
||||
- [Підтримка електронною поштою] (https://supabase.com/docs/support#business-support). Найкраще для: проблем з вашою базою даних або інфраструктурою.
|
||||
- [Discord] (https://discord.supabase.com). Найкраще для: обміну вашими додатками та спілкування зі спільнотою.
|
||||
- [Проблеми GitHub](https://github.com/supabase/supabase/issues). Найкраще для: помилок і помилок, з якими ви зіткнулися при використанні Supabase.
|
||||
- [Підтримка електронною поштою](https://supabase.com/docs/support#business-support). Найкраще для: проблем з вашою базою даних або інфраструктурою.
|
||||
- [Discord](https://discord.supabase.com). Найкраще для: обміну вашими додатками та спілкування зі спільнотою.
|
||||
|
||||
## Статус
|
||||
|
||||
- [x] Альфа: Ми тестуємо Supabase із закритим набором клієнтів
|
||||
- [x] Публічна альфа-версія: Будь-хто може зареєструватися на [app.supabase.com] (https://app.supabase.com). Але будьте обережні з нами, є кілька нюансів
|
||||
- [x] Публічна альфа-версія: Будь-хто може зареєструватися на [app.supabase.com](https://app.supabase.com). Але будьте обережні з нами, є кілька нюансів
|
||||
- [x] Публічна бета-версія: Достатньо стабільна для більшості непідприємницьких випадків використання
|
||||
- [ ] Загальнодоступна: Загальна доступність [[статус](https://supabase.com/docs/guides/getting-started/features#feature-status)]
|
||||
|
||||
@@ -55,7 +55,7 @@ Supabase - це комбінація інструментів з відкрит
|
||||
|
||||
**Архітектура**
|
||||
|
||||
Supabase - це [хостингова платформа] (https://app.supabase.com). Ви можете зареєструватися і почати використовувати Supabase без встановлення.
|
||||
Supabase - це [хостингова платформа](https://app.supabase.com). Ви можете зареєструватися і почати використовувати Supabase без встановлення.
|
||||
Ви також можете [самостійно хостити](https://supabase.com/docs/guides/hosting/overview) і [розробляти локально](https://supabase.com/docs/guides/local-development).
|
||||
|
||||

|
||||
@@ -67,7 +67,7 @@ Supabase - це [хостингова платформа] (https://app.supabase.
|
||||
- [Storage](https://github.com/supabase/storage-api) надає RESTful інтерфейс для керування файлами, що зберігаються у S3, використовуючи Postgres для керування дозволами.
|
||||
- [postgres-meta](https://github.com/supabase/postgres-meta) - RESTful API для керування Postgres, що дозволяє отримувати таблиці, додавати ролі, виконувати запити тощо.
|
||||
- [GoTrue](https://github.com/netlify/gotrue) - API на основі SWT для керування користувачами та випуску токенів SWT.
|
||||
- [Kong] (https://github.com/Kong/kong) - хмарний API-шлюз.
|
||||
- [Kong](https://github.com/Kong/kong) - хмарний API-шлюз.
|
||||
|
||||
#### Клієнтські бібліотеки
|
||||
|
||||
|
||||
+11
-11
@@ -7,7 +7,7 @@
|
||||
|
||||
# Supabase
|
||||
|
||||
[Supabase](https://supabase.com)是一个开源的Firebase替代品。我们正在使用企业级的开源工具构建Firebase的功能。
|
||||
[Supabase](https://supabase.com)是一个开源的 Firebase 替代品。我们正在使用企业级的开源工具构建 Firebase 的功能。
|
||||
|
||||
- [x] 托管的 Postgres 数据库。[文档](https://supabase.com/docs/guides/database)
|
||||
- [x] 认证和授权。[文档](https://supabase.com/docs/guides/auth)
|
||||
@@ -21,7 +21,7 @@
|
||||
- [x] 文件存储。[文件](https://supabase.com/docs/guides/storage)
|
||||
- [x] 仪表板
|
||||
|
||||
! [Supabase Dashboard](https://raw.githubusercontent.com/supabase/supabase/master/apps/www/public/images/github/supabase-dashboard.png)
|
||||

|
||||
|
||||
## 文档
|
||||
|
||||
@@ -39,9 +39,9 @@
|
||||
## 状态
|
||||
|
||||
- [x] 阿尔法:我们正在与一组封闭的客户测试 Supabase。
|
||||
- [x] 公开阿尔法:任何人都可以在[app.supabase.com](https://app.supabase.com)上注册。但请对我们宽容一些,有一些小问题。
|
||||
- [x] 公开阿尔法:任何人都可以在[app.supabase.com](https://app.supabase.com)上注册。但请对我们宽容一些,有一些小问题。
|
||||
- [x] 公开测试版:足够稳定,适合大多数非企业使用的情况。
|
||||
- [] 公开:普遍可用 [[状态](https://supabase.com/docs/guides/getting-started/features#feature-status)]
|
||||
- [] 公开:普遍可用 [状态](https://supabase.com/docs/guides/getting-started/features#feature-status)
|
||||
|
||||
我们目前正处于公开测试阶段。请关注本软件库的 "发布",以获得重大更新的通知。
|
||||
|
||||
@@ -55,10 +55,10 @@ Supabase 是一个开源工具的组合。我们正在使用企业级的开源
|
||||
|
||||
**架构**
|
||||
|
||||
Supabase 是一个[托管平台](https://app.supabase.com)。你可以注册并开始使用Supabase,无需安装任何东西。
|
||||
Supabase 是一个[托管平台](https://app.supabase.com)。你可以注册并开始使用 Supabase,无需安装任何东西。
|
||||
你也可以[自我托管](https://supabase.com/docs/guides/hosting/overview)和[本地开发](https://supabase.com/docs/guides/local-development)。
|
||||
|
||||
! [架构](https://github.com/supabase/supabase/blob/master/apps/docs/public/img/supabase-architecture.png)
|
||||

|
||||
|
||||
- [PostgreSQL](https://www.postgresql.org/)是一个对象关系型数据库系统,经过 30 多年的积极开发,它在可靠性、功能稳健性和性能方面赢得了良好的声誉。
|
||||
- [Realtime](https://github.com/supabase/realtime)是一个 Elixir 服务器,允许你使用 websockets 监听 PostgreSQL 的插入、更新和删除。Realtime 对 Postgres 内置的复制功能进行投票,以了解数据库的变化,将变化转换为 JSON,然后通过 websockets 将 JSON 广播给授权客户。
|
||||
@@ -77,7 +77,7 @@ Supabase 是一个[托管平台](https://app.supabase.com)。你可以注册
|
||||
<tr>
|
||||
<th>语言</th>
|
||||
<th>客户端</th>
|
||||
<th colspan="5">特征-客户端(捆绑在Supabase客户端中)</th>
|
||||
<th colspan="5">特征-客户端(捆绑在Supabase客户端中)</th>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
@@ -238,10 +238,10 @@ Supabase 是一个[托管平台](https://app.supabase.com)。你可以注册
|
||||
- [Persian / فارسی](/i18n/README.fa.md)
|
||||
- [Polish / Polski](/i18n/README.pl.md)
|
||||
- [葡萄牙语 / Português](/i18n/README.pt.md)
|
||||
- [葡萄牙语(巴西)/Português Brasileiro](/i18n/README.pt-br.md)
|
||||
- [Romanian / Română] (/i18n/README.ro.md)
|
||||
- [葡萄牙语(巴西)/Português Brasileiro](/i18n/README.pt-br.md)
|
||||
- [Romanian / Română](/i18n/README.ro.md)
|
||||
- [俄语 / Pусский](/i18n/README.ru.md)
|
||||
- [塞尔维亚语 / Srpski] (/i18n/README.sr.md)
|
||||
- [塞尔维亚语 / Srpski](/i18n/README.sr.md)
|
||||
- [Sinhala / සිංහල](/i18n/README.si.md)
|
||||
- [西班牙语 / Español](/i18n/README.es.md)
|
||||
- [Simplified Chinese / 简体中文](/i18n/README.zh-cn.md)
|
||||
@@ -257,4 +257,4 @@ Supabase 是一个[托管平台](https://app.supabase.com)。你可以注册
|
||||
|
||||
## 赞助商
|
||||
|
||||
[](https://github.com/sponsors/supabase)
|
||||
[](https://github.com/sponsors/supabase)
|
||||
Generated
+2
@@ -314,6 +314,7 @@
|
||||
"version": "0.0.3",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@code-hike/mdx": "^0.8.3",
|
||||
"@heroicons/react": "^1.0.6",
|
||||
"@mdx-js/loader": "^2.1.5",
|
||||
"@mdx-js/react": "^2.1.5",
|
||||
@@ -355,6 +356,7 @@
|
||||
"remark": "^14.0.2",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"remark-html": "^15.0.1",
|
||||
"shared-data": "*",
|
||||
"swiper": "^6.5.0",
|
||||
"swr": "^0.5.4",
|
||||
"typescript": "^5.0.4",
|
||||
|
||||
File renamed without changes.
File renamed without changes.
@@ -8,17 +8,19 @@
|
||||
"tooltips": {
|
||||
"main": "A Postgres database with no restrictions? You get it. No pseudo limited users, you are the postgres root user. No caveats."
|
||||
},
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": true,
|
||||
"pro": true,
|
||||
"team": true,
|
||||
"enterprise": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Unlimited API requests",
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": true,
|
||||
"pro": true,
|
||||
"team": true,
|
||||
"enterprise": true
|
||||
}
|
||||
},
|
||||
@@ -27,9 +29,10 @@
|
||||
"tooltips": {
|
||||
"main": "Billing is based on the average database size in GB throughout the billing period."
|
||||
},
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": "500 MB",
|
||||
"pro": "8 GB, then $0.125 per GB",
|
||||
"team": "8 GB, then $0.125 per GB",
|
||||
"enterprise": "Unlimited"
|
||||
}
|
||||
},
|
||||
@@ -37,11 +40,13 @@
|
||||
"title": "Automatic backups",
|
||||
"tooltips": {
|
||||
"main": "Backups are entire copies of your database that can be restored in the future.",
|
||||
"pro": "7 days of backup (if > 1TB, contact for Enterprise pricing)"
|
||||
"pro": "7 days of backup (if > 1TB, contact for Enterprise pricing)",
|
||||
"team": "14 days of backup (if > 1TB, contact for Enterprise pricing)"
|
||||
},
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": false,
|
||||
"pro": "7 days",
|
||||
"team": "14 days",
|
||||
"enterprise": "Custom"
|
||||
}
|
||||
},
|
||||
@@ -49,11 +54,13 @@
|
||||
"title": "Point in time recovery",
|
||||
"tooltips": {
|
||||
"main": "PITR cannot be applied retroactively, projects can only be rolled back to the point from which PITR has been applied.",
|
||||
"pro": "$100 per 7 days. If > 28 day rollback period, contact enterprise"
|
||||
"pro": "$100 per 7 days. If > 28 day rollback period, contact enterprise",
|
||||
"team": "$100 per 7 days. If > 28 day rollback period, contact enterprise"
|
||||
},
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": false,
|
||||
"pro": "$100 per 7 days",
|
||||
"team": "$100 per 7 days",
|
||||
"enterprise": true
|
||||
}
|
||||
},
|
||||
@@ -62,9 +69,10 @@
|
||||
"tooltips": {
|
||||
"main": "Projects that have no activity or API requests will be paused. They can be reactivated via the dashboard"
|
||||
},
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": "After 1 inactive week",
|
||||
"pro": "Never",
|
||||
"team": "Never",
|
||||
"enterprise": "Never"
|
||||
}
|
||||
},
|
||||
@@ -73,9 +81,10 @@
|
||||
"tooltips": {
|
||||
"main": "Billing is based on the total sum of outgoing traffic of your database in GB throughout your billing period. Only download operations are counted towards the limit"
|
||||
},
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": "2GB",
|
||||
"pro": "50 GB, then $0.09 per GB",
|
||||
"team": "50 GB, then $0.09 per GB",
|
||||
"enterprise": "Unlimited"
|
||||
}
|
||||
}
|
||||
@@ -88,9 +97,10 @@
|
||||
{
|
||||
"title": "Total Users",
|
||||
"tooltips": { "main": "The maximum number of users your project can have" },
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": "Unlimited",
|
||||
"pro": "Unlimited",
|
||||
"team": "Unlimited",
|
||||
"enterprise": "Unlimited"
|
||||
}
|
||||
},
|
||||
@@ -99,74 +109,83 @@
|
||||
"tooltips": {
|
||||
"main": "Distinct users requesting your API throughout the billing period."
|
||||
},
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": "50,000",
|
||||
"pro": "100,000, then $0.00325 per MAU",
|
||||
"team": "100,000, then $0.00325 per MAU",
|
||||
"enterprise": "Unlimited"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Social OAuth providers",
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": true,
|
||||
"pro": true,
|
||||
"team": true,
|
||||
"enterprise": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Custom SMTP server",
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": true,
|
||||
"pro": true,
|
||||
"team": true,
|
||||
"enterprise": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Remove Supabase branding from emails",
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": false,
|
||||
"pro": true,
|
||||
"team": true,
|
||||
"enterprise": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Enterprise OAuth providers",
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": false,
|
||||
"pro": false,
|
||||
"team": false,
|
||||
"enterprise": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Audit trails",
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": "1 hour",
|
||||
"pro": "7 days",
|
||||
"team": "28 days",
|
||||
"enterprise": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Supabase Auth emails",
|
||||
"tooltips": { "main": "Rate limits do not apply to Custom SMTP" },
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": "30 / hour",
|
||||
"pro": "100 / hour",
|
||||
"team": "100 / hour",
|
||||
"enterprise": "Contact Us"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Single Sign-On (SAML 2.0)",
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": false,
|
||||
"pro": "50 free, then $0.015 per MAU",
|
||||
"team": "50 free, then $0.015 per MAU",
|
||||
"enterprise": "Contact Us"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Advanced security features",
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": false,
|
||||
"pro": false,
|
||||
"team": false,
|
||||
"enterprise": "Contact Us"
|
||||
}
|
||||
}
|
||||
@@ -182,9 +201,10 @@
|
||||
"tooltips": {
|
||||
"main": "The sum of all objects' size in your storage buckets."
|
||||
},
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": "1 GB",
|
||||
"pro": "100 GB, then $0.021 per GB",
|
||||
"team": "100 GB, then $0.021 per GB",
|
||||
"enterprise": "Unlimited"
|
||||
}
|
||||
},
|
||||
@@ -193,26 +213,29 @@
|
||||
"tooltips": {
|
||||
"main": "Total sum of outgoing traffic (egress) from your storage buckets, only download operations are counted."
|
||||
},
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": "2 GB",
|
||||
"pro": "200 GB, then $0.09 per GB",
|
||||
"team": "200 GB, then $0.09 per GB",
|
||||
"enterprise": "Unlimited"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Custom access controls",
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": true,
|
||||
"pro": true,
|
||||
"team": true,
|
||||
"enterprise": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Max file upload size",
|
||||
"tooltips": { "main": "You can change the upload size in the dashboard" },
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": "50MB",
|
||||
"pro": "5GB",
|
||||
"team": "5GB",
|
||||
"enterprise": "Unlimited"
|
||||
}
|
||||
},
|
||||
@@ -221,9 +244,11 @@
|
||||
"tooltips": {
|
||||
"main": "Distinct count of all images that were transformed in the billing period, ignoring any transformations."
|
||||
},
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": false,
|
||||
"pro": "100 origin images, then $5 per 1000 origin images"
|
||||
"pro": "100 origin images, then $5 per 1000 origin images",
|
||||
"team": "100 origin images, then $5 per 1000 origin images",
|
||||
"enterprise": "Unlimited"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -238,17 +263,19 @@
|
||||
"main": "Billing is based on the sum of all invocations, independent of response status, throughout your billing period."
|
||||
},
|
||||
"main": "Billing is based on the sum of all invocations, independent of response status, throughout your billing period.",
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": "500K/month",
|
||||
"pro": "2 Million, then $2 per 1 Million",
|
||||
"team": "2 Million, then $2 per 1 Million",
|
||||
"enterprise": "Unlimited"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Script size",
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": "2 MB",
|
||||
"pro": "10 MB",
|
||||
"team": "10 MB",
|
||||
"enterprise": "Unlimited"
|
||||
}
|
||||
},
|
||||
@@ -257,9 +284,10 @@
|
||||
"tooltips": {
|
||||
"main": "Billing is based on the maximum amount of functions at any point in time throughout your billing period."
|
||||
},
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": "10",
|
||||
"pro": "100, then $10 per additional 100",
|
||||
"team": "100, then $10 per additional 100",
|
||||
"enterprise": "Unlimited"
|
||||
}
|
||||
}
|
||||
@@ -271,9 +299,10 @@
|
||||
"features": [
|
||||
{
|
||||
"title": "Postgres Changes",
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": true,
|
||||
"pro": true,
|
||||
"team": true,
|
||||
"enterprise": true
|
||||
}
|
||||
},
|
||||
@@ -282,9 +311,10 @@
|
||||
"tooltips": {
|
||||
"main": "We only charge for successful connections - not connection attempts!"
|
||||
},
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": "200",
|
||||
"pro": "500, then $10 per 1000",
|
||||
"team": "500, then $10 per 1000",
|
||||
"enterprise": "Unlimited concurrent connections and volume discount"
|
||||
}
|
||||
},
|
||||
@@ -293,17 +323,19 @@
|
||||
"tooltips": {
|
||||
"main": "Billing is based on the total sum of Realtime messages throughout your billing period."
|
||||
},
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": "2 Million",
|
||||
"pro": "5 Million, then $2.50 per Million",
|
||||
"team": "5 Million, then $2.50 per Million",
|
||||
"enterprise": "Volume discounts on messages"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Max Message Size",
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": "250 KB",
|
||||
"pro": "3 MB",
|
||||
"team": "3 MB",
|
||||
"enterprise": "Custom"
|
||||
}
|
||||
}
|
||||
@@ -315,25 +347,28 @@
|
||||
"features": [
|
||||
{
|
||||
"title": "Team members",
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": "Unlimited",
|
||||
"pro": "Unlimited",
|
||||
"team": "Unlimited",
|
||||
"enterprise": "Unlimited"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Access controls",
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": "Coming soon",
|
||||
"pro": "Coming soon",
|
||||
"team": true,
|
||||
"enterprise": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Audit trails",
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": false,
|
||||
"pro": false,
|
||||
"team": true,
|
||||
"enterprise": true
|
||||
}
|
||||
}
|
||||
@@ -345,73 +380,82 @@
|
||||
"features": [
|
||||
{
|
||||
"title": "On Premises / BYO cloud",
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": false,
|
||||
"pro": false,
|
||||
"team": false,
|
||||
"enterprise": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Log retention (API & Database)",
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": "1 day",
|
||||
"pro": "7 days",
|
||||
"team": "28 days",
|
||||
"enterprise": "90 days"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Log drain",
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": false,
|
||||
"pro": false,
|
||||
"team": "Coming soon",
|
||||
"enterprise": "Coming soon"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Metrics endpoint",
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": false,
|
||||
"pro": true,
|
||||
"team": true,
|
||||
"enterprise": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "SOC2",
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": false,
|
||||
"pro": false,
|
||||
"team": true,
|
||||
"enterprise": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "SSO",
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": false,
|
||||
"pro": false,
|
||||
"team": "Contact Us",
|
||||
"enterprise": "Contact Us"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "99.9% SLA",
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": false,
|
||||
"pro": false,
|
||||
"team": false,
|
||||
"enterprise": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Access Roles",
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": "Owner, Developer",
|
||||
"pro": "Owner, Developer",
|
||||
"team": "Additional owner(s), admin, read-only, billing admin, custom",
|
||||
"enterprise": "Additional owner(s), admin, read-only, billing admin, custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Vanity URLs",
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": false,
|
||||
"pro": true,
|
||||
"team": true,
|
||||
"enterprise": true
|
||||
}
|
||||
},
|
||||
@@ -420,9 +464,10 @@
|
||||
"tooltips": {
|
||||
"enterprise": "Volume discounts available."
|
||||
},
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": false,
|
||||
"pro": "$10 per domain per month per project add on",
|
||||
"team": "$10 per domain per month per project add on",
|
||||
"enterprise": "1, additional $10/domain/month"
|
||||
}
|
||||
}
|
||||
@@ -434,57 +479,64 @@
|
||||
"features": [
|
||||
{
|
||||
"title": "Community support",
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": true,
|
||||
"pro": true,
|
||||
"team": true,
|
||||
"enterprise": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Email support",
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": false,
|
||||
"pro": true,
|
||||
"team": true,
|
||||
"enterprise": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Email support SLA",
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": false,
|
||||
"pro": false,
|
||||
"team": true,
|
||||
"enterprise": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Designated support",
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": false,
|
||||
"pro": false,
|
||||
"team": false,
|
||||
"enterprise": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "On Boarding Support",
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": false,
|
||||
"pro": false,
|
||||
"team": false,
|
||||
"enterprise": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Designated customer success engineer ",
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": false,
|
||||
"pro": false,
|
||||
"team": false,
|
||||
"enterprise": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"title": "Security Questionnaire Help",
|
||||
"tiers": {
|
||||
"plans": {
|
||||
"free": false,
|
||||
"pro": false,
|
||||
"team": true,
|
||||
"enterprise": true
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
import tweets from './tweets'
|
||||
import logConstants from './logConstants'
|
||||
import { plans, PricingInformation } from './plans'
|
||||
|
||||
export { tweets, logConstants }
|
||||
export { tweets, logConstants, plans }
|
||||
export type { PricingInformation }
|
||||
@@ -3,6 +3,7 @@
|
||||
"version": "0.0.0",
|
||||
"description": "Data shared between Supabase apps",
|
||||
"main": "./index.ts",
|
||||
"types": "./index.ts",
|
||||
"scripts": {},
|
||||
"author": "",
|
||||
"license": "MIT"
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
export interface PricingInformation {
|
||||
id: string
|
||||
name: string
|
||||
nameBadge?: string
|
||||
costUnit?: string
|
||||
href: string
|
||||
priceLabel?: string
|
||||
priceMonthly: number | string
|
||||
warning?: string
|
||||
description: string
|
||||
preface: string
|
||||
features: string[]
|
||||
footer?: string
|
||||
cta: string
|
||||
}
|
||||
|
||||
export const plans: PricingInformation[] = [
|
||||
{
|
||||
id: 'tier_free',
|
||||
name: 'Free',
|
||||
nameBadge: '',
|
||||
costUnit: '/ month',
|
||||
href: 'https://supabase.com/dashboard/new/new-project',
|
||||
priceLabel: '',
|
||||
priceMonthly: 0,
|
||||
warning: 'Limit of 2 free projects',
|
||||
description: 'Perfect for passion projects & simple websites.',
|
||||
preface: 'Get started with:',
|
||||
features: [
|
||||
'Unlimited API requests',
|
||||
'Social OAuth providers',
|
||||
'Up to 500MB database space',
|
||||
'Up to 1GB file storage',
|
||||
'Up to 2GB bandwidth',
|
||||
'Up to 50MB file uploads',
|
||||
'Up to 50,000 monthly active users',
|
||||
'Up to 500K Edge Function invocations',
|
||||
'Up to 200 concurrent Realtime connections',
|
||||
'Up to 2 million Realtime messages',
|
||||
'1-day log retention',
|
||||
'Community support',
|
||||
],
|
||||
footer: 'Free projects are paused after 1 week of inactivity.',
|
||||
cta: 'Get Started',
|
||||
},
|
||||
{
|
||||
id: 'tier_pro',
|
||||
name: 'Pro',
|
||||
nameBadge: '',
|
||||
costUnit: '/ month',
|
||||
href: 'https://supabase.com/dashboard/new/new-project',
|
||||
priceLabel: 'From',
|
||||
warning: 'Usage-based plan',
|
||||
priceMonthly: 25,
|
||||
description: 'For production applications with the option to scale.',
|
||||
features: [
|
||||
'No project pausing',
|
||||
'Daily backups stored for 7 days',
|
||||
'8GB database space included',
|
||||
'100GB file storage included',
|
||||
'50GB bandwidth included',
|
||||
'5GB file uploads included',
|
||||
'100,000 monthly active users included',
|
||||
'2M Edge Function invocations included',
|
||||
'500 concurrent Realtime connections included',
|
||||
'5 million Realtime messages included',
|
||||
'7-day log retention',
|
||||
'Email support',
|
||||
],
|
||||
footer:
|
||||
'Your cost control settings determine if you allow over-usage.',
|
||||
preface: 'Everything in the Free plan, plus:',
|
||||
cta: 'Get Started',
|
||||
},
|
||||
{
|
||||
id: 'tier_team',
|
||||
name: 'Team',
|
||||
nameBadge: 'New',
|
||||
costUnit: '/ month',
|
||||
href: 'https://forms.supabase.com/team',
|
||||
priceLabel: 'From',
|
||||
warning: 'Usage-based plan',
|
||||
priceMonthly: 599,
|
||||
description: 'Collaborate with different permissions and access patterns.',
|
||||
features: [
|
||||
'Additional Organization member roles',
|
||||
'Daily backups stored for 14 days',
|
||||
'Standardised Security Questionnaire',
|
||||
'SOC2',
|
||||
'SSO for Supabase Dashboard',
|
||||
'Priority email support & SLAs',
|
||||
'28-day log retention',
|
||||
],
|
||||
footer: 'Additional fees apply for usage beyond included usage.',
|
||||
preface: 'Everything in the Pro plan, plus:',
|
||||
cta: 'Contact Us',
|
||||
},
|
||||
{
|
||||
id: 'tier_enterprise',
|
||||
name: 'Enterprise',
|
||||
href: 'https://forms.supabase.com/enterprise',
|
||||
description: 'For large-scale applications managing serious workloads.',
|
||||
features: [
|
||||
`Designated Support manager & SLAs`,
|
||||
`Enterprise OAuth providers`,
|
||||
`SSO/SAML`,
|
||||
`On-premise support`,
|
||||
`24×7×365 premium enterprise support`,
|
||||
`Private Slack channel`,
|
||||
],
|
||||
priceLabel: '',
|
||||
priceMonthly: 'Contact us',
|
||||
preface: '',
|
||||
footer: '',
|
||||
cta: 'Contact Us',
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"extends": "tsconfig/react-library.json",
|
||||
"include": ["."],
|
||||
"exclude": ["dist", "build", "node_modules"]
|
||||
}
|
||||
@@ -7,24 +7,22 @@ interface AiIconProps {
|
||||
export const AiIcon = ({ className = 'text-brand-900' }: AiIconProps) => (
|
||||
<svg
|
||||
className={`w-6 h-6 ${className}`}
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth="1.5"
|
||||
stroke="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M9.7003 13.379L8.33022 14.9739C7.99629 15.3626 7.35899 15.1265 7.35899 14.614V10.2188L2.13597 10.2188C1.20712 10.2189 0.693015 9.14211 1.277 8.4198L7.24456 1.03867C7.5719 0.633801 8.22633 0.865262 8.22633 1.3859V5.71008H13.7967C14.7348 5.71008 15.2458 6.80561 14.6431 7.52442L14.1628 8.09707"
|
||||
stroke="#4CC38A"
|
||||
strokeMiterlimit="10"
|
||||
d="m10.618 11.832-2.283 2.657a.5.5 0 0 1-.88-.325v-3.98H2.728a1 1 0 0 1-.777-1.628l5.402-6.682a.5.5 0 0 1 .889.314v3.915h5.043a1 1 0 0 1 .766 1.642l-1.373 1.638"
|
||||
stroke-miterlimit="10"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
stroke="#4CC38A"
|
||||
strokeLinejoin="bevel"
|
||||
strokeMiterlimit="10"
|
||||
d="m12.232 14.15.418-1.254a2.383 2.383 0 0 1 1.507-1.507l1.253-.417-1.253-.418a2.383 2.383 0 0 1-1.508-1.507l-.417-1.253-.417 1.253a2.383 2.383 0 0 1-1.507 1.507l-1.253.418 1.253.417a2.383 2.383 0 0 1 1.507 1.507l.417 1.253Z"
|
||||
d="M12.2534 14.3848L12.5765 13.4148C12.7202 12.9836 12.9624 12.5917 13.2839 12.2702C13.6053 11.9488 13.9972 11.7066 14.4285 11.5628L15.3984 11.2398C15.5808 11.179 15.5808 10.921 15.3984 10.8603L14.4285 10.5372C13.9972 10.3935 13.6053 10.1513 13.2839 9.8298C12.9624 9.50834 12.7202 9.11646 12.5765 8.68518L12.2534 7.71525C12.1927 7.53285 11.9347 7.53285 11.8739 7.71525L11.5509 8.68518C11.4071 9.11646 11.1649 9.50834 10.8435 9.8298C10.522 10.1513 10.1301 10.3935 9.69885 10.5372L8.72892 10.8603C8.54652 10.921 8.54652 11.179 8.72892 11.2398L9.69885 11.5628C10.1301 11.7066 10.522 11.9488 10.8435 12.2702C11.1649 12.5917 11.4071 12.9836 11.5509 13.4148L11.8739 14.3848C11.9347 14.5672 12.1927 14.5672 12.2534 14.3848Z"
|
||||
fill="#4CC38A"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
@@ -1,22 +1,5 @@
|
||||
import { IconArchive, IconCode, IconDatabase, IconKey, IconZap } from 'ui'
|
||||
|
||||
export const CANCELLATION_REASONS = [
|
||||
'Pricing',
|
||||
"My project isn't getting traction",
|
||||
'Poor customer service',
|
||||
'Missing feature',
|
||||
"I didn't see the value",
|
||||
"Supabase didn't meet my needs",
|
||||
'Dashboard is too complicated',
|
||||
'Postgres is too complicated',
|
||||
'Problem not solved',
|
||||
'Bug issues',
|
||||
'I decided to use something else',
|
||||
'My work has finished/discontinued',
|
||||
'I’m migrating to/starting a new project',
|
||||
'None of the above',
|
||||
]
|
||||
|
||||
export const USAGE_BASED_PRODUCTS = [
|
||||
{
|
||||
title: 'Database',
|
||||
|
||||
@@ -8,11 +8,11 @@ import { Button, Form, Input, Modal, IconArrowLeft, Alert } from 'ui'
|
||||
import { useFlag, useStore } from 'hooks'
|
||||
import { post, patch } from 'lib/common/fetch'
|
||||
import { API_URL, PROJECT_STATUS } from 'lib/constants'
|
||||
import { CANCELLATION_REASONS } from '../Billing.constants'
|
||||
import { UpdateSuccess } from '../'
|
||||
import { SubscriptionPreview } from '../Billing.types'
|
||||
import { StripeSubscription } from 'components/interfaces/Billing'
|
||||
import HCaptcha from '@hcaptcha/react-hcaptcha'
|
||||
import { CANCELLATION_REASONS } from 'components/interfaces/BillingV2/Billing.constants'
|
||||
|
||||
interface ExitSurveyProps {
|
||||
freeTier: any
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Transition } from '@headlessui/react'
|
||||
import PlanCard from './Plans/PlanCard'
|
||||
import { formatTierOptions } from './PlanSelection.utils'
|
||||
import { Button, IconExternalLink } from 'ui'
|
||||
import PlanCardEnterprise from './Plans/PlanCardEnterprise'
|
||||
|
||||
interface Props {
|
||||
visible: boolean
|
||||
@@ -16,11 +17,7 @@ interface Props {
|
||||
const PlanSelection: FC<Props> = ({ visible, tiers, currentPlan, onSelectPlan }) => {
|
||||
const formattedTiers = formatTierOptions(tiers)
|
||||
|
||||
// [Kevin] TODO Remove after team plan is generally available
|
||||
const gridCols =
|
||||
formattedTiers.length === 3
|
||||
? 'gap-8 lg:grid-cols-3'
|
||||
: 'gap-4 lg:grid-cols-3 xl:grid-cols-billingWithTeam'
|
||||
const enterprisePlan = formattedTiers.find((it) => it.name === 'Enterprise')!
|
||||
|
||||
return (
|
||||
<Transition
|
||||
@@ -31,17 +28,26 @@ const PlanSelection: FC<Props> = ({ visible, tiers, currentPlan, onSelectPlan })
|
||||
>
|
||||
<div>
|
||||
<h4 className="text-lg">Change your project's subscription</h4>
|
||||
<div className={`grid py-8 grid-cols-2 ${gridCols}`}>
|
||||
{formattedTiers.map((plan) => {
|
||||
return (
|
||||
<PlanCard
|
||||
key={plan.id}
|
||||
plan={plan}
|
||||
currentPlan={currentPlan}
|
||||
onSelectPlan={() => onSelectPlan(plan)}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
<div className={`grid py-8 grid-cols-1 gap-8 md:grid-cols-3`}>
|
||||
{formattedTiers
|
||||
.filter((it) => it.name !== 'Enterprise')
|
||||
.map((plan) => {
|
||||
return (
|
||||
<PlanCard
|
||||
key={plan.id}
|
||||
plan={plan}
|
||||
currentPlan={currentPlan}
|
||||
onSelectPlan={() => onSelectPlan(plan)}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<div>
|
||||
<PlanCardEnterprise
|
||||
plan={enterprisePlan}
|
||||
currentPlan={currentPlan}
|
||||
onSelectPlan={() => onSelectPlan(enterprisePlan)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-center items-center mt-4">
|
||||
<Link href="https://supabase.com/pricing">
|
||||
|
||||
@@ -20,13 +20,23 @@ const PlanCTAButton = ({ plan, currentPlan, onSelectPlan }: PlanCTAButtonProps)
|
||||
const isTeamTier = currentPlan?.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.TEAM
|
||||
|
||||
const getButtonType = (plan: any, currentPlan: any) => {
|
||||
if (
|
||||
currentPlan.supabase_prod_id !== PRICING_TIER_PRODUCT_IDS.ENTERPRISE &&
|
||||
plan.name === 'Enterprise'
|
||||
) {
|
||||
return 'default'
|
||||
}
|
||||
|
||||
if (['Free tier', 'Free plan'].includes(plan.name)) {
|
||||
// Free is always default
|
||||
return 'default'
|
||||
} else if (currentPlan.prod_id === STRIPE_PRODUCT_IDS.FREE) {
|
||||
// If the current plan is free, other plans are primary
|
||||
return 'primary'
|
||||
} else if (currentPlan.prod_id === STRIPE_PRODUCT_IDS.TEAM && !['Team tier', 'Team plan'].includes(plan.name)) {
|
||||
} else if (
|
||||
currentPlan.prod_id === STRIPE_PRODUCT_IDS.TEAM &&
|
||||
!['Team tier', 'Team plan'].includes(plan.name)
|
||||
) {
|
||||
// Non-free plans are default (pro), when team plan is selected
|
||||
return 'default'
|
||||
}
|
||||
@@ -60,18 +70,10 @@ const PlanCTAButton = ({ plan, currentPlan, onSelectPlan }: PlanCTAButtonProps)
|
||||
}
|
||||
}
|
||||
|
||||
if (plan.id === STRIPE_PRODUCT_IDS.TEAM) {
|
||||
if (currentPlan.prod_id === STRIPE_PRODUCT_IDS.TEAM) {
|
||||
return 'Edit plan configuration'
|
||||
} else if (
|
||||
[STRIPE_PRODUCT_IDS.FREE, STRIPE_PRODUCT_IDS.PRO, STRIPE_PRODUCT_IDS.PAYG].includes(
|
||||
currentPlan.prod_id
|
||||
)
|
||||
) {
|
||||
return 'Upgrade to Team'
|
||||
} else {
|
||||
return 'Contact sales'
|
||||
}
|
||||
if (plan.id === STRIPE_PRODUCT_IDS.TEAM && currentPlan.prod_id === STRIPE_PRODUCT_IDS.TEAM) {
|
||||
return 'Edit plan configuration'
|
||||
} else {
|
||||
return 'Contact sales'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +105,18 @@ const PlanCTAButton = ({ plan, currentPlan, onSelectPlan }: PlanCTAButtonProps)
|
||||
)
|
||||
}
|
||||
|
||||
if (!isTeamTier && plan.name === 'Team plan') {
|
||||
return (
|
||||
<Link href="https://forms.supabase.com/team">
|
||||
<a>
|
||||
<Button disabled={disabled} type={type} block>
|
||||
{ctaText}
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center space-y-2">
|
||||
<Tooltip.Root delayDuration={0}>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { PRICING_TIER_PRODUCT_IDS, STRIPE_PRODUCT_IDS } from 'lib/constants'
|
||||
import { STRIPE_PRODUCT_IDS } from 'lib/constants'
|
||||
import { FC } from 'react'
|
||||
import { IconCheck } from 'ui'
|
||||
|
||||
import { useFlag } from 'hooks'
|
||||
import PlanCTAButton from './PlanCTAButton'
|
||||
import { PRICING_META } from './Plans.Constants'
|
||||
|
||||
@@ -13,12 +12,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const PlanCard: FC<Props> = ({ plan, currentPlan, onSelectPlan }) => {
|
||||
// Team tier is enabled when the flag is turned on OR the user is already on the team tier (manually assigned by us)
|
||||
const teamTierEnabled =
|
||||
useFlag('teamTier') || currentPlan?.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.TEAM
|
||||
|
||||
const planMeta = PRICING_META[plan.id]
|
||||
const isEnterprise = plan.id === 'Enterprise'
|
||||
const isCurrentPlan =
|
||||
plan.id === currentPlan?.prod_id ||
|
||||
(plan.id === STRIPE_PRODUCT_IDS.PRO && currentPlan?.prod_id === STRIPE_PRODUCT_IDS.PRO) ||
|
||||
@@ -26,35 +20,12 @@ const PlanCard: FC<Props> = ({ plan, currentPlan, onSelectPlan }) => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[
|
||||
'px-6 py-6 border rounded flex flex-col justify-between',
|
||||
`${isEnterprise ? 'bg-scale-200' : 'bg-scale-300'}`,
|
||||
`${isEnterprise && teamTierEnabled ? 'lg:col-span-3 xl:col-auto' : ''}`,
|
||||
].join(' ')}
|
||||
className={['px-6 py-6 border rounded flex flex-col justify-between bg-scale-300'].join(' ')}
|
||||
>
|
||||
<div
|
||||
className={[
|
||||
'space-y-8',
|
||||
`${isEnterprise ? 'flex flex-col xl:space-x-0 xl:flex-col' : ''}`,
|
||||
`${isEnterprise && teamTierEnabled ? 'lg:flex-row lg:space-x-24' : ''}`,
|
||||
].join(' ')}
|
||||
>
|
||||
<div
|
||||
className={[
|
||||
'space-y-4',
|
||||
`${isEnterprise ? 'xl:w-auto' : ''}`,
|
||||
`${isEnterprise && teamTierEnabled ? 'xl:w-auto' : ''}`,
|
||||
].join(' ')}
|
||||
>
|
||||
<div className={['space-y-8'].join(' ')}>
|
||||
<div className={['space-y-4'].join(' ')}>
|
||||
<div className="flex items-center space-x-2">
|
||||
<p
|
||||
className={[
|
||||
'text-brand-900 text-sm',
|
||||
`${isEnterprise ? 'lg:text-base xl:text-sm font-bold font-mono uppercase' : ''}`,
|
||||
].join(' ')}
|
||||
>
|
||||
{planMeta.name}
|
||||
</p>
|
||||
<p className={['text-brand-900 text-sm'].join(' ')}>{planMeta.name}</p>
|
||||
{isCurrentPlan ? (
|
||||
<div className="text-xs bg-scale-500 text-scale-1000 rounded px-2 py-0.5">
|
||||
Current plan
|
||||
@@ -66,16 +37,7 @@ const PlanCard: FC<Props> = ({ plan, currentPlan, onSelectPlan }) => {
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-scale-1100">{planMeta.description}</p>
|
||||
<div
|
||||
className={[
|
||||
'space-y-2',
|
||||
`${
|
||||
isEnterprise
|
||||
? 'pb-6 border-b lg:pt-6 lg:border-b-0 lg:border-t xl:pt-0 xl:pb-6 xl:border-t-0 xl:border-b'
|
||||
: 'border-b border-t py-8'
|
||||
}`,
|
||||
].join(' ')}
|
||||
>
|
||||
<div className={['space-y-2', 'border-b border-t py-8'].join(' ')}>
|
||||
{planMeta.priceMonthly !== undefined ? (
|
||||
<>
|
||||
<p className="text-sm text-scale-1000">Starting from</p>
|
||||
@@ -92,15 +54,9 @@ const PlanCard: FC<Props> = ({ plan, currentPlan, onSelectPlan }) => {
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={`${isEnterprise ? 'lg:space-y-0 lg:!mt-0 xl:space-y-4' : 'space-y-4'}`}>
|
||||
<div className="space-y-4">
|
||||
<p className="text-xs text-scale-1000">{planMeta.preface}</p>
|
||||
<ul
|
||||
role="list"
|
||||
className={[
|
||||
`${isEnterprise ? 'xl:gap-0 xl:columns-1' : ''}`,
|
||||
`${isEnterprise && teamTierEnabled ? 'lg:gap-16 lg:columns-2' : ''}`,
|
||||
].join(' ')}
|
||||
>
|
||||
<ul role="list">
|
||||
{planMeta.features.map((feature) => (
|
||||
<li key={feature} className="flex py-2">
|
||||
<div className="w-[12px]">
|
||||
@@ -116,35 +72,34 @@ const PlanCard: FC<Props> = ({ plan, currentPlan, onSelectPlan }) => {
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{!isEnterprise && (
|
||||
<div className="space-y-4 !mt-20">
|
||||
<div className="space-y-2">
|
||||
<p className="text-xs text-scale-1000">{planMeta.scale}</p>
|
||||
|
||||
{planMeta.name === 'Pro' && currentPlan?.prod_id === STRIPE_PRODUCT_IDS.PRO && (
|
||||
<div>
|
||||
<p className="text-sm text-scale-1100">
|
||||
Need more? Turn off your spend cap to Pay As You Grow
|
||||
</p>
|
||||
<p className="text-xs text-scale-1000">
|
||||
Additional fees apply for spend beyond the limits above
|
||||
</p>{' '}
|
||||
</div>
|
||||
)}
|
||||
<div className="space-y-4 !mt-20">
|
||||
<div className="space-y-2">
|
||||
<p className="text-xs text-scale-1000">{planMeta.scale}</p>
|
||||
|
||||
{planMeta.name === 'Pro' && currentPlan?.prod_id === STRIPE_PRODUCT_IDS.PAYG && (
|
||||
<div>
|
||||
<p className="text-sm text-scale-1100">Spend cap is currently disabled</p>
|
||||
<p className="text-xs text-scale-1000">
|
||||
You will be charged for usage beyond the plan limits
|
||||
</p>{' '}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{planMeta.name === 'Pro' && currentPlan?.prod_id === STRIPE_PRODUCT_IDS.PRO && (
|
||||
<div>
|
||||
<p className="text-sm text-scale-1100">
|
||||
Need more? Turn off your spend cap to Pay As You Grow
|
||||
</p>
|
||||
<p className="text-xs text-scale-1000">
|
||||
Additional fees apply for spend beyond the limits above
|
||||
</p>{' '}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<PlanCTAButton plan={plan} currentPlan={currentPlan} onSelectPlan={onSelectPlan} />
|
||||
{planMeta.name === 'Pro' && currentPlan?.prod_id === STRIPE_PRODUCT_IDS.PAYG && (
|
||||
<div>
|
||||
<p className="text-sm text-scale-1100">Spend cap is currently disabled</p>
|
||||
<p className="text-xs text-scale-1000">
|
||||
You will be charged for usage beyond the plan limits
|
||||
</p>{' '}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<PlanCTAButton plan={plan} currentPlan={currentPlan} onSelectPlan={onSelectPlan} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import { STRIPE_PRODUCT_IDS } from 'lib/constants'
|
||||
import { FC } from 'react'
|
||||
import { IconCheck } from 'ui'
|
||||
|
||||
import PlanCTAButton from './PlanCTAButton'
|
||||
import { PRICING_META } from './Plans.Constants'
|
||||
|
||||
interface Props {
|
||||
plan: any
|
||||
currentPlan: any
|
||||
onSelectPlan: () => void
|
||||
}
|
||||
|
||||
const PlanCardEnterprise: FC<Props> = ({ plan, currentPlan, onSelectPlan }) => {
|
||||
const planMeta = PRICING_META[plan.id]
|
||||
const isCurrentPlan =
|
||||
plan.id === currentPlan?.prod_id ||
|
||||
(plan.id === STRIPE_PRODUCT_IDS.PRO && currentPlan?.prod_id === STRIPE_PRODUCT_IDS.PRO) ||
|
||||
(plan.id === STRIPE_PRODUCT_IDS.PRO && currentPlan?.prod_id === STRIPE_PRODUCT_IDS.PAYG)
|
||||
|
||||
return (
|
||||
<div className="px-6 py-6 border rounded flex-col bg-scale-200 flex">
|
||||
<div className="space-y-8">
|
||||
<div className=" grid grid-cols-3 space-x-8">
|
||||
<div className="space-y-4 pr-14">
|
||||
<div className="flex items-center ">
|
||||
<p className="text-brand-900 text-sm">{planMeta.name}</p>
|
||||
{isCurrentPlan ? (
|
||||
<div className="text-xs bg-scale-500 text-scale-1000 rounded px-2 py-0.5">
|
||||
Current plan
|
||||
</div>
|
||||
) : planMeta.new ? (
|
||||
<div className="text-xs bg-brand-400 text-brand-900 rounded px-2 py-0.5">New</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-scale-1100">{planMeta.description}</p>
|
||||
<div className="space-y-2 border-t pb-6 lg:pt-6 xl:pt-0 xl:pb-6">
|
||||
<PlanCTAButton plan={plan} currentPlan={currentPlan} onSelectPlan={onSelectPlan} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-4 lg:col-span-2">
|
||||
<p className="text-xs text-scale-1000">{planMeta.preface}</p>
|
||||
<ul role="list" className="text-xs text-scale-1000 md:grid md:grid-cols-2 md:gap-x-10">
|
||||
{planMeta.features.map((feature) => (
|
||||
<li key={feature} className="flex items-center py-2 first:mt-0">
|
||||
<IconCheck
|
||||
className="text-brand-900 h-4 w-4"
|
||||
aria-hidden="true"
|
||||
strokeWidth={3}
|
||||
/>
|
||||
<span className="dark:text-scale-1200 mb-0 ml-3 ">{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PlanCardEnterprise
|
||||
@@ -1,11 +1,7 @@
|
||||
import { STRIPE_PRODUCT_IDS } from 'lib/constants'
|
||||
|
||||
/**
|
||||
* @mildtomato same plan metadata is also in /www website
|
||||
* would be good if this constant was shared across apps
|
||||
*
|
||||
* https://github.com/supabase/supabase/blob/master/studio/components/interfaces/Billing/PlanSelection/Plans/Plans.Constants.ts
|
||||
*/
|
||||
// This file will get deleted soon and superseded by components in BillingV2/*
|
||||
|
||||
export const PRICING_META = {
|
||||
[STRIPE_PRODUCT_IDS.FREE]: {
|
||||
id: 'tier_free',
|
||||
@@ -13,18 +9,20 @@ export const PRICING_META = {
|
||||
name: 'Free',
|
||||
description: 'Perfect for passion projects & simple websites.',
|
||||
priceMonthly: 0,
|
||||
priceUnit: 'per month per project',
|
||||
priceUnit: '/ month',
|
||||
warning: 'Limit of 2 free projects',
|
||||
preface: 'Get started with:',
|
||||
features: [
|
||||
'Unlimited API requests',
|
||||
'Social OAuth providers',
|
||||
'Up to 500MB database & 1GB file storage',
|
||||
'Up to 500MB database space',
|
||||
'Up to 1GB file storage',
|
||||
'Up to 2GB bandwidth',
|
||||
'Up to 50MB file uploads',
|
||||
'50,000 monthly active users',
|
||||
'Up to 50,000 monthly active users',
|
||||
'Up to 500K Edge Function invocations',
|
||||
'200 concurrent Realtime connections',
|
||||
'2 million Realtime messages',
|
||||
'Up to 200 concurrent Realtime connections',
|
||||
'Up to 2 million Realtime messages',
|
||||
'1-day log retention',
|
||||
'Community support',
|
||||
],
|
||||
@@ -36,21 +34,22 @@ export const PRICING_META = {
|
||||
name: 'Pro',
|
||||
description: 'For production applications with the option to scale.',
|
||||
priceMonthly: 25,
|
||||
priceUnit: 'per month per project',
|
||||
warning: '+ any additional usage',
|
||||
priceUnit: '/ month',
|
||||
warning: 'Usage-based plan',
|
||||
preface: 'Everything in the Free plan, plus:',
|
||||
features: [
|
||||
'No project pausing',
|
||||
'8GB database & 100GB file storage',
|
||||
'50GB bandwidth',
|
||||
'5GB file uploads',
|
||||
'100,000 monthly active users',
|
||||
'2M Edge Function invocations',
|
||||
'500 concurrent Realtime connections',
|
||||
'5 million Realtime messages',
|
||||
'Daily backups stored for 7 days',
|
||||
'8GB database space included',
|
||||
'100GB file storage included',
|
||||
'50GB bandwidth included',
|
||||
'5GB file uploads included',
|
||||
'100,000 monthly active users included',
|
||||
'2M Edge Function invocations included',
|
||||
'500 concurrent Realtime connections included',
|
||||
'5 million Realtime messages included',
|
||||
'7-day log retention',
|
||||
'Email support',
|
||||
'Daily backups',
|
||||
],
|
||||
},
|
||||
[STRIPE_PRODUCT_IDS.TEAM]: {
|
||||
@@ -59,20 +58,17 @@ export const PRICING_META = {
|
||||
name: 'Team',
|
||||
description: 'Collaborate with different permissions and access patterns.',
|
||||
priceMonthly: 599,
|
||||
priceUnit: 'per month per organization',
|
||||
warning: '+ usage',
|
||||
preface: 'The following features will apply to all projects within the organization:',
|
||||
priceUnit: '/ month',
|
||||
warning: 'Usage-based plan',
|
||||
preface: 'Everything in the Pro plan, plus:',
|
||||
features: [
|
||||
'Usage-based pricing',
|
||||
'100,000 monthly active users included',
|
||||
'Organization member roles (ABAC)',
|
||||
'Additional Organization member roles',
|
||||
'Daily backups stored for 14 days',
|
||||
'Standardised Security Questionnaire',
|
||||
'SOC2',
|
||||
'SSO for Supabase Dashboard',
|
||||
'Priority email support & SLAs',
|
||||
'1 XS compute instance',
|
||||
'14 day backups',
|
||||
'28 day log retention',
|
||||
'28-day log retention',
|
||||
],
|
||||
},
|
||||
Enterprise: {
|
||||
@@ -85,14 +81,10 @@ export const PRICING_META = {
|
||||
features: [
|
||||
`Designated Support manager & SLAs`,
|
||||
`Enterprise OAuth providers`,
|
||||
`SSO/ SAML`,
|
||||
`SOC2`,
|
||||
`Custom contracts & invoicing`,
|
||||
`SSO/SAML`,
|
||||
`On-premise support`,
|
||||
`24×7×365 premium enterprise support`,
|
||||
`Custom Security questionnaires`,
|
||||
`Private Slack channel`,
|
||||
`Uptime SLA`,
|
||||
],
|
||||
},
|
||||
}
|
||||
@@ -48,10 +48,8 @@ const TeamUpgrade = ({
|
||||
const { app, ui } = useStore()
|
||||
const router = useRouter()
|
||||
|
||||
// Team tier is enabled when the flag is turned on OR the user is already on the team tier (manually assigned by us)
|
||||
const userIsOnTeamTier =
|
||||
currentSubscription?.tier?.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.TEAM
|
||||
const teamTierEnabled = useFlag('teamTier') || userIsOnTeamTier
|
||||
// Team tier is enabled when the user is already on the team tier (manually assigned by us)
|
||||
const teamTierEnabled = currentSubscription?.tier?.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.TEAM
|
||||
|
||||
const [captchaToken, setCaptchaToken] = useState<string | null>(null)
|
||||
const captchaRef = useRef<HCaptcha>(null)
|
||||
|
||||
@@ -1 +1,18 @@
|
||||
export const USAGE_APPROACHING_THRESHOLD = 0.8
|
||||
|
||||
export const CANCELLATION_REASONS = [
|
||||
'Pricing',
|
||||
"My project isn't getting traction",
|
||||
'Poor customer service',
|
||||
'Missing feature',
|
||||
"I didn't see the value",
|
||||
"Supabase didn't meet my needs",
|
||||
'Dashboard is too complicated',
|
||||
'Postgres is too complicated',
|
||||
'Problem not solved',
|
||||
'Bug issues',
|
||||
'I decided to use something else',
|
||||
'My work has finished/discontinued',
|
||||
'I’m migrating to/starting a new project',
|
||||
'None of the above',
|
||||
]
|
||||
@@ -42,7 +42,7 @@ const BillingBreakdown = ({}: BillingBreakdownProps) => {
|
||||
const currentPlan = subscription?.plan
|
||||
const isUsageBillingEnabled = subscription?.usage_billing_enabled
|
||||
const [usageFees, fixedFees] = partition(upcomingInvoice?.lines ?? [], (item) => item.usage_based)
|
||||
const totalUsageFees = usageFees.reduce((a, b) => a + b.amount, 0)
|
||||
const totalUsageFees = Number(usageFees.reduce((a, b) => a + b.amount, 0).toFixed(2))
|
||||
|
||||
const hasExceededAnyLimits =
|
||||
isUsageBillingEnabled === false &&
|
||||
@@ -77,13 +77,14 @@ const BillingBreakdown = ({}: BillingBreakdownProps) => {
|
||||
{isUsageBillingEnabled ? (
|
||||
<p className="text-sm text-scale-1000">
|
||||
Your plan includes a limited amount of included usage. If the usage on your project
|
||||
exceeds these quotas, your subscription will be charged for the extra usage.
|
||||
exceeds these quotas, your subscription will be charged for the overage. It may take
|
||||
up to 24 hours for usage stats to update.
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-sm text-scale-1000">
|
||||
Your plan includes a limited amount of included usage. If the usage on your project
|
||||
exceeds these quotas, you may experience restrictions, as you are currently not billed
|
||||
for overusage.
|
||||
for overage. It may take up to 24 hours for usage stats to update.
|
||||
</p>
|
||||
)}
|
||||
|
||||
@@ -96,7 +97,7 @@ const BillingBreakdown = ({}: BillingBreakdownProps) => {
|
||||
<Button
|
||||
key="upgrade-button"
|
||||
type="default"
|
||||
className="ml-4"
|
||||
className="ml-8"
|
||||
onClick={() =>
|
||||
snap.setPanelKey(
|
||||
currentPlan?.id === 'free' ? 'subscriptionPlan' : 'costControl'
|
||||
@@ -110,7 +111,7 @@ const BillingBreakdown = ({}: BillingBreakdownProps) => {
|
||||
Your project can become unresponsive or enter read only mode.{' '}
|
||||
{currentPlan?.id === 'free'
|
||||
? 'Please upgrade to the Pro plan to ensure that your project remains available.'
|
||||
: 'Please disable spend caps to ensure that your project remains available.'}
|
||||
: 'Please disable spend cap to ensure that your project remains available.'}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
@@ -149,8 +150,8 @@ const BillingBreakdown = ({}: BillingBreakdownProps) => {
|
||||
<div
|
||||
key={metric.key}
|
||||
className={clsx(
|
||||
'col-span-6 space-y-4 py-4 border-scale-400',
|
||||
i % 2 === 0 ? 'border-r pr-4' : 'pl-4',
|
||||
'col-span-12 md:col-span-6 space-y-4 py-4 border-scale-400',
|
||||
i % 2 === 0 ? 'md:border-r md:pr-4' : 'md:pl-4',
|
||||
i < BILLING_BREAKDOWN_METRICS.length - 2 && 'border-b'
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -1,66 +1,60 @@
|
||||
import clsx from 'clsx'
|
||||
import { Button, IconCheck } from 'ui'
|
||||
import { PricingInformation } from './Tier.constants'
|
||||
import Link from 'next/link'
|
||||
import { PricingInformation } from 'shared-data'
|
||||
|
||||
export interface EnterpriseCardProps {
|
||||
plan: PricingInformation
|
||||
isCurrentPlan: boolean
|
||||
isTeamTierEnabled: boolean
|
||||
}
|
||||
|
||||
const EnterpriseCard = ({ plan, isCurrentPlan, isTeamTierEnabled }: EnterpriseCardProps) => {
|
||||
const EnterpriseCard = ({ plan, isCurrentPlan }: EnterpriseCardProps) => {
|
||||
return (
|
||||
<div
|
||||
key={plan.id}
|
||||
className={clsx(
|
||||
'flex border rounded-md bg-scale-200',
|
||||
isTeamTierEnabled
|
||||
? 'px-12 py-8 col-span-12 justify-between'
|
||||
: 'px-6 py-6 col-span-4 flex-col'
|
||||
'grid grid-cols-1 md:grid-cols-3 border rounded-md bg-scale-200',
|
||||
'py-4 col-span-12 justify-between gap-x-8'
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
<div className="flex flex-col justify-center px-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<p className={clsx('text-brand-900 text-sm uppercase')}>{plan.name}</p>
|
||||
{isCurrentPlan ? (
|
||||
<div className="text-xs bg-scale-500 text-scale-1000 rounded px-2 py-0.5">
|
||||
Current plan
|
||||
</div>
|
||||
) : plan.new ? (
|
||||
<div className="text-xs bg-brand-400 text-brand-900 rounded px-2 py-0.5">New</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
) : plan.nameBadge ? (
|
||||
<div className="text-xs bg-brand-400 text-brand-900 rounded px-2 py-0.5">
|
||||
{plan.nameBadge}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<p className="text-sm mt-2 mb-4">{plan.preface}</p>
|
||||
<p className="text-sm mt-2 mb-4">{plan.description}</p>
|
||||
|
||||
<Link href="https://supabase.com/contact/enterprise">
|
||||
<a target="blank" rel="noreferrer">
|
||||
<Button block={!isTeamTierEnabled} type="default">
|
||||
Contact Sales Team
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
<a href={plan.href} className="hidden md:block" target="_blank">
|
||||
<Button block type="default" size="tiny">
|
||||
{plan.cta}
|
||||
</Button>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{!isTeamTierEnabled && <div className="border-t my-6" />}
|
||||
<div className="flex flex-col justify-center col-span-2 px-4 md:px-0">
|
||||
<ul role="list" className="text-xs text-scale-1000 md:grid md:grid-cols-2 md:gap-x-10">
|
||||
{plan.features.map((feature) => (
|
||||
<li key={feature} className="flex items-center py-2 first:mt-0">
|
||||
<IconCheck className="text-brand-900 h-4 w-4" aria-hidden="true" strokeWidth={3} />
|
||||
<span className="dark:text-scale-1200 mb-0 ml-3 ">{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
<ul role="list" className={clsx(isTeamTierEnabled && 'pr-12')}>
|
||||
{plan.features.map((feature) => (
|
||||
<li key={feature} className="flex py-2">
|
||||
<div className="w-[12px]">
|
||||
<IconCheck
|
||||
className="h-3 w-3 text-brand-900 translate-y-[2.5px]"
|
||||
aria-hidden="true"
|
||||
strokeWidth={3}
|
||||
/>
|
||||
</div>
|
||||
<p className="ml-3 text-xs text-scale-1100">{feature}</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<a href={plan.href} className="visible md:hidden mt-8" target="_blank">
|
||||
<Button block type="default" size="tiny">
|
||||
{plan.cta}
|
||||
</Button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ import { includes, without } from 'lodash'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useReducer, useRef, useState } from 'react'
|
||||
import { Button, Input, Modal } from 'ui'
|
||||
import { CANCELLATION_REASONS } from './Tier.constants'
|
||||
import ProjectUpdateDisabledTooltip from '../../ProjectUpdateDisabledTooltip'
|
||||
import { CANCELLATION_REASONS } from '../../Billing.constants'
|
||||
|
||||
export interface ExitSurveyModalProps {
|
||||
visible: boolean
|
||||
|
||||
@@ -39,12 +39,12 @@ const SubscriptionTier = ({}: SubscriptionTierProps) => {
|
||||
<div className="sticky space-y-6 top-16">
|
||||
<p className="text-base">Subscription plan</p>
|
||||
<div className="text-sm text-scale-1000">
|
||||
To manage your billing address, emails or tax IDs, head to your{' '}
|
||||
To manage your billing address, emails or Tax ID, head to your{' '}
|
||||
<Link href={`/org/${orgSlug}/billing`}>
|
||||
<a rel="noreferrer">
|
||||
<p className="text-sm text-green-900 transition hover:text-green-1000">
|
||||
<a>
|
||||
<span className="text-sm text-green-900 transition hover:text-green-1000">
|
||||
organization settings
|
||||
</p>
|
||||
</span>.
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
export interface PricingInformation {
|
||||
id: string
|
||||
new: boolean
|
||||
name: string
|
||||
preface?: string
|
||||
features: string[]
|
||||
footer?: string
|
||||
}
|
||||
|
||||
// This only contains the meta information of plans like description and features, prices are from the API
|
||||
// Also ideally shift this to common folder (e.g packages/data)
|
||||
export const SUBSCRIPTION_PLANS: PricingInformation[] = [
|
||||
{
|
||||
id: 'tier_free',
|
||||
new: false,
|
||||
name: 'Free',
|
||||
preface: undefined,
|
||||
features: [
|
||||
'Social OAuth providers',
|
||||
'Up to 500MB database & 1GB file storage',
|
||||
'Up to 2GB bandwidth',
|
||||
'Up to 50MB file uploads',
|
||||
'50,000 monthly active users',
|
||||
'Up to 500K Edge Function invocations',
|
||||
'200 concurrent Realtime connections',
|
||||
'2 million Realtime messages',
|
||||
'1-day log retention',
|
||||
'Community support',
|
||||
],
|
||||
footer: undefined,
|
||||
},
|
||||
{
|
||||
id: 'tier_pro',
|
||||
new: false,
|
||||
name: 'Pro',
|
||||
preface: undefined,
|
||||
features: [
|
||||
'No project pausing',
|
||||
'8GB database & 100GB file storage',
|
||||
'50GB bandwidth',
|
||||
'5GB file uploads',
|
||||
'100,000 monthly active users',
|
||||
'2M Edge Function invocations',
|
||||
'500 concurrent Realtime connections',
|
||||
'5 million Realtime messages',
|
||||
'7-day log retention',
|
||||
'Email support',
|
||||
'Daily backups',
|
||||
],
|
||||
footer:
|
||||
'Your cost control settings determine if you wish to pay for anything more than the above.',
|
||||
},
|
||||
{
|
||||
id: 'tier_team',
|
||||
new: true,
|
||||
name: 'Team',
|
||||
preface: 'Everything in Pro, plus:',
|
||||
features: [
|
||||
'Organization member roles (ABAC)',
|
||||
'Standardised Security Questionnaire',
|
||||
'SOC2',
|
||||
'SSO for Supabase Dashboard',
|
||||
'Priority email support & SLAs',
|
||||
'14 day backups',
|
||||
'28 day log retention',
|
||||
],
|
||||
footer: undefined,
|
||||
},
|
||||
{
|
||||
id: 'tier_enterprise',
|
||||
new: false,
|
||||
name: 'Enterprise',
|
||||
preface: 'For large-scale applications managing serious workloads',
|
||||
features: [`Designated Support manager & SLAs`, `SSO/ SAML`, `Custom contracts & invoicing`],
|
||||
footer: undefined,
|
||||
},
|
||||
]
|
||||
|
||||
export const CANCELLATION_REASONS = [
|
||||
'Pricing',
|
||||
"My project isn't getting traction",
|
||||
'Poor customer service',
|
||||
'Missing feature',
|
||||
"I didn't see the value",
|
||||
"Supabase didn't meet my needs",
|
||||
'Dashboard is too complicated',
|
||||
'Postgres is too complicated',
|
||||
'Problem not solved',
|
||||
'Bug issues',
|
||||
'I decided to use something else',
|
||||
'My work has finished/discontinued',
|
||||
'I’m migrating to/starting a new project',
|
||||
'None of the above',
|
||||
]
|
||||
@@ -5,9 +5,7 @@ import { useFreeProjectLimitCheckQuery } from 'data/organizations/free-project-l
|
||||
import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query'
|
||||
import { useProjectPlansQuery } from 'data/subscriptions/project-plans-query'
|
||||
import { useProjectSubscriptionUpdateMutation } from 'data/subscriptions/project-subscription-update-mutation'
|
||||
import { useProjectSubscriptionV2Query } from 'data/subscriptions/project-subscription-v2-query'
|
||||
import { useFlag, useStore } from 'hooks'
|
||||
import { PRICING_TIER_PRODUCT_IDS } from 'lib/constants'
|
||||
import { useStore } from 'hooks'
|
||||
import Link from 'next/link'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useSubscriptionPageStateSnapshot } from 'state/subscription-page'
|
||||
@@ -16,7 +14,9 @@ import EnterpriseCard from './EnterpriseCard'
|
||||
import ExitSurveyModal from './ExitSurveyModal'
|
||||
import MembersExceedLimitModal from './MembersExceedLimitModal'
|
||||
import PaymentMethodSelection from './PaymentMethodSelection'
|
||||
import { SUBSCRIPTION_PLANS } from './Tier.constants'
|
||||
import { plans as subscriptionsPlans } from 'shared-data/plans'
|
||||
import { useProjectSubscriptionV2Query } from 'data/subscriptions/project-subscription-v2-query'
|
||||
import { PRICING_TIER_PRODUCT_IDS } from 'lib/constants'
|
||||
|
||||
const TierUpdateSidePanel = () => {
|
||||
const { ui } = useStore()
|
||||
@@ -34,18 +34,16 @@ const TierUpdateSidePanel = () => {
|
||||
const onClose = () => snap.setPanelKey(undefined)
|
||||
|
||||
const { data: plans, isLoading: isLoadingPlans } = useProjectPlansQuery({ projectRef })
|
||||
const { data: subscription } = useProjectSubscriptionV2Query({ projectRef })
|
||||
const { data: addons } = useProjectAddonsQuery({ projectRef })
|
||||
const { data: membersExceededLimit } = useFreeProjectLimitCheckQuery({ slug })
|
||||
const { data: subscription, isLoading } = useProjectSubscriptionV2Query({ projectRef })
|
||||
const { mutateAsync: updateSubscriptionTier } = useProjectSubscriptionUpdateMutation()
|
||||
|
||||
const availablePlans = plans ?? []
|
||||
const subscriptionAddons = addons?.selected_addons ?? []
|
||||
const userIsOnTeamTier = subscription?.plan?.id === 'team'
|
||||
|
||||
const teamTierEnabled = useFlag('teamTier') || userIsOnTeamTier
|
||||
const hasMembersExceedingFreeTierLimit = (membersExceededLimit || []).length > 0
|
||||
const selectedTierMeta = SUBSCRIPTION_PLANS.find((tier) => tier.id === selectedTier)
|
||||
const selectedTierMeta = subscriptionsPlans.find((tier) => tier.id === selectedTier)
|
||||
const selectedPlanMeta = availablePlans.find(
|
||||
(plan) => plan.id === selectedTier?.split('tier_')[1]
|
||||
)
|
||||
@@ -118,22 +116,15 @@ const TierUpdateSidePanel = () => {
|
||||
>
|
||||
<SidePanel.Content>
|
||||
<div className="py-6 grid grid-cols-12 gap-3">
|
||||
{SUBSCRIPTION_PLANS.map((plan) => {
|
||||
{subscriptionsPlans.map((plan) => {
|
||||
const planMeta = availablePlans.find((p) => p.id === plan.id.split('tier_')[1])
|
||||
const tierMeta = subscriptionsPlans.find((it) => it.id === plan.id)
|
||||
const price = planMeta?.price ?? 0
|
||||
const isDowngradeOption = planMeta?.change_type === 'downgrade'
|
||||
const isCurrentPlan = planMeta?.is_current ?? false
|
||||
|
||||
if (plan.id === 'tier_team' && !teamTierEnabled) return null
|
||||
if (plan.id === 'tier_enterprise') {
|
||||
return (
|
||||
<EnterpriseCard
|
||||
key={plan.id}
|
||||
plan={plan}
|
||||
isCurrentPlan={isCurrentPlan}
|
||||
isTeamTierEnabled={teamTierEnabled}
|
||||
/>
|
||||
)
|
||||
return <EnterpriseCard key={plan.id} plan={plan} isCurrentPlan={isCurrentPlan} />
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -141,7 +132,7 @@ const TierUpdateSidePanel = () => {
|
||||
key={plan.id}
|
||||
className={clsx(
|
||||
'border rounded-md px-4 py-4 flex flex-col items-start justify-between',
|
||||
teamTierEnabled && plan.id === 'tier_enterprise' ? 'col-span-12' : 'col-span-4',
|
||||
plan.id === 'tier_enterprise' ? 'col-span-12' : 'col-span-12 md:col-span-4',
|
||||
plan.id === 'tier_enterprise' ? 'bg-scale-200' : 'bg-scale-300'
|
||||
)}
|
||||
>
|
||||
@@ -152,9 +143,9 @@ const TierUpdateSidePanel = () => {
|
||||
<div className="text-xs bg-scale-500 text-scale-1000 rounded px-2 py-0.5">
|
||||
Current plan
|
||||
</div>
|
||||
) : plan.new ? (
|
||||
) : plan.nameBadge ? (
|
||||
<div className="text-xs bg-brand-400 text-brand-900 rounded px-2 py-0.5">
|
||||
New
|
||||
{plan.nameBadge}
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
@@ -169,32 +160,38 @@ const TierUpdateSidePanel = () => {
|
||||
) : (
|
||||
<p className="text-scale-1200 text-lg">${price}</p>
|
||||
)}
|
||||
<p className="text-scale-1000 text-sm">per month</p>
|
||||
<p className="text-scale-1000 text-sm">{tierMeta?.costUnit}</p>
|
||||
</div>
|
||||
<div
|
||||
className={clsx(
|
||||
'flex mt-1 mb-4',
|
||||
plan.id !== PRICING_TIER_PRODUCT_IDS.TEAM && 'opacity-0'
|
||||
)}
|
||||
>
|
||||
<div className="text-xs bg-brand-400 text-brand-900 rounded px-2 py-0.5">
|
||||
Usage based plan
|
||||
<div className={clsx('flex mt-1 mb-4', !tierMeta?.warning && 'opacity-0')}>
|
||||
<div className="bg-scale-200 text-brand-1100 border shadow-sm rounded-md bg-opacity-30 py-0.5 px-2 text-xs">
|
||||
{tierMeta?.warning}
|
||||
</div>
|
||||
</div>
|
||||
{isCurrentPlan ? (
|
||||
<Button block disabled type="default">
|
||||
Current plan
|
||||
</Button>
|
||||
) : (
|
||||
) : plan.id !== PRICING_TIER_PRODUCT_IDS.TEAM ? (
|
||||
<Button
|
||||
block
|
||||
// no self-serve downgrades from team plan right now
|
||||
disabled={
|
||||
plan.id !== PRICING_TIER_PRODUCT_IDS.TEAM &&
|
||||
['team', 'enterprise'].includes(subscription?.plan?.id || '')
|
||||
}
|
||||
type={isDowngradeOption ? 'default' : 'primary'}
|
||||
loading={isLoading}
|
||||
disabled={isLoading}
|
||||
onClick={() => setSelectedTier(plan.id as any)}
|
||||
>
|
||||
{isDowngradeOption ? 'Downgrade' : 'Upgrade'} to {plan.name}
|
||||
</Button>
|
||||
) : (
|
||||
<Link href={plan.href} passHref className="hidden md:block">
|
||||
<a target="_blank">
|
||||
<Button block type="primary">
|
||||
Contact Us
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
<div className="border-t my-6" />
|
||||
|
||||
@@ -8,9 +8,16 @@ export interface ActivityProps {
|
||||
startDate: string | undefined
|
||||
endDate: string | undefined
|
||||
subscription: ProjectSubscriptionResponse | undefined
|
||||
currentBillingCycleSelected: boolean
|
||||
}
|
||||
|
||||
const Activity = ({ projectRef, subscription, startDate, endDate }: ActivityProps) => {
|
||||
const Activity = ({
|
||||
projectRef,
|
||||
subscription,
|
||||
startDate,
|
||||
endDate,
|
||||
currentBillingCycleSelected,
|
||||
}: ActivityProps) => {
|
||||
const { data: mauData, isLoading: isLoadingMauData } = useDailyStatsQuery({
|
||||
projectRef,
|
||||
attribute: 'total_auth_billing_period_mau',
|
||||
@@ -108,9 +115,10 @@ const Activity = ({ projectRef, subscription, startDate, endDate }: ActivityProp
|
||||
return (
|
||||
<UsageSection
|
||||
projectRef={projectRef}
|
||||
categoryKey='activity'
|
||||
categoryKey="activity"
|
||||
chartMeta={chartMeta}
|
||||
subscription={subscription}
|
||||
currentBillingCycleSelected={currentBillingCycleSelected}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -8,9 +8,16 @@ export interface BandwidthProps {
|
||||
startDate: string | undefined
|
||||
endDate: string | undefined
|
||||
subscription: ProjectSubscriptionResponse | undefined
|
||||
currentBillingCycleSelected: boolean
|
||||
}
|
||||
|
||||
const Bandwidth = ({ projectRef, subscription, startDate, endDate }: BandwidthProps) => {
|
||||
const Bandwidth = ({
|
||||
projectRef,
|
||||
subscription,
|
||||
startDate,
|
||||
endDate,
|
||||
currentBillingCycleSelected,
|
||||
}: BandwidthProps) => {
|
||||
const { data: dbEgressData, isLoading: isLoadingDbEgressData } = useDailyStatsQuery({
|
||||
projectRef,
|
||||
attribute: 'total_egress_modified',
|
||||
@@ -47,9 +54,10 @@ const Bandwidth = ({ projectRef, subscription, startDate, endDate }: BandwidthPr
|
||||
return (
|
||||
<UsageSection
|
||||
projectRef={projectRef}
|
||||
categoryKey='bandwidth'
|
||||
categoryKey="bandwidth"
|
||||
chartMeta={chartMeta}
|
||||
subscription={subscription}
|
||||
currentBillingCycleSelected={currentBillingCycleSelected}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { DataPoint } from 'data/analytics/constants'
|
||||
import { useInfraMonitoringQuery } from 'data/analytics/infra-monitoring-query'
|
||||
import dayjs from 'dayjs'
|
||||
import Link from 'next/link'
|
||||
import { Alert, Button } from 'ui'
|
||||
import { Alert, Button, IconBarChart2 } from 'ui'
|
||||
import SectionContent from './SectionContent'
|
||||
import SectionHeader from './SectionHeader'
|
||||
import { COMPUTE_INSTANCE_SPECS, USAGE_CATEGORIES } from './Usage.constants'
|
||||
@@ -11,35 +11,27 @@ import { getUpgradeUrlFromV2Subscription } from './Usage.utils'
|
||||
import UsageBarChart from './UsageBarChart'
|
||||
import Panel from 'components/ui/Panel'
|
||||
import { useProjectSubscriptionV2Query } from 'data/subscriptions/project-subscription-v2-query'
|
||||
import { useMemo } from 'react'
|
||||
import { useFlag } from 'hooks'
|
||||
|
||||
export interface InfrastructureProps {
|
||||
projectRef: string
|
||||
startDate?: string
|
||||
endDate?: string
|
||||
currentBillingCycleSelected: boolean
|
||||
}
|
||||
|
||||
// [Joshen] Need to update the IO budget chart to show burst mbps and duration next time
|
||||
|
||||
const Infrastructure = ({ projectRef }: InfrastructureProps) => {
|
||||
const Infrastructure = ({
|
||||
projectRef,
|
||||
startDate,
|
||||
endDate,
|
||||
currentBillingCycleSelected,
|
||||
}: InfrastructureProps) => {
|
||||
const enableSubscriptionV2 = useFlag('subscriptionV2')
|
||||
const { data: subscription } = useProjectSubscriptionV2Query({ projectRef })
|
||||
const { current_period_start, current_period_end } = subscription ?? {}
|
||||
const startDate = useMemo(() => {
|
||||
return current_period_start ? new Date(current_period_start * 1000).toISOString() : undefined
|
||||
}, [current_period_start])
|
||||
|
||||
const endDate = useMemo(() => {
|
||||
const periodEndDate = current_period_end ? new Date(current_period_end * 1000) : undefined
|
||||
// If end date is in future, set end date to now
|
||||
if (periodEndDate && dayjs(periodEndDate).isAfter(dayjs())) {
|
||||
// LF seems to have an issue with the milliseconds, causes infinite loading sometimes
|
||||
return new Date().toISOString().slice(0, -5) + 'Z'
|
||||
} else if (periodEndDate) {
|
||||
return periodEndDate.toISOString()
|
||||
}
|
||||
}, [current_period_end])
|
||||
|
||||
const categoryMeta = USAGE_CATEGORIES.find((category) => category.key === 'infra')
|
||||
|
||||
const upgradeUrl = getUpgradeUrlFromV2Subscription(projectRef, subscription)
|
||||
const upgradeUrl = getUpgradeUrlFromV2Subscription(projectRef, subscription, enableSubscriptionV2)
|
||||
const isFreeTier = subscription?.plan?.id === 'free'
|
||||
const currentComputeInstance = subscription?.addons.find((addon) =>
|
||||
addon.supabase_prod_id.includes('_instance_')
|
||||
@@ -55,7 +47,7 @@ const Infrastructure = ({ projectRef }: InfrastructureProps) => {
|
||||
|
||||
if (diffInHours <= 48) {
|
||||
interval = '1h'
|
||||
dateFormat = 'HH a'
|
||||
dateFormat = 'h a'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +111,7 @@ const Infrastructure = ({ projectRef }: InfrastructureProps) => {
|
||||
<SectionContent section={attribute}>
|
||||
{attribute.key === 'disk_io_consumption' && (
|
||||
<>
|
||||
{highestIoBudgetConsumption >= 100 ? (
|
||||
{currentBillingCycleSelected && highestIoBudgetConsumption >= 100 ? (
|
||||
<Alert withIcon variant="danger" title="IO Budget for today has been used up">
|
||||
<p className="mb-4">
|
||||
Your workload has used up all the burst IO throughput minutes and ran at the
|
||||
@@ -134,7 +126,7 @@ const Infrastructure = ({ projectRef }: InfrastructureProps) => {
|
||||
</a>
|
||||
</Link>
|
||||
</Alert>
|
||||
) : highestIoBudgetConsumption >= 80 ? (
|
||||
) : currentBillingCycleSelected && highestIoBudgetConsumption >= 80 ? (
|
||||
<Alert withIcon variant="warning" title="IO Budget for today is running out">
|
||||
<p className="mb-4">
|
||||
Your workload is about to use up all the burst IO throughput minutes during
|
||||
@@ -154,10 +146,6 @@ const Infrastructure = ({ projectRef }: InfrastructureProps) => {
|
||||
<div className="space-y-1">
|
||||
<p>Disk IO Bandwidth</p>
|
||||
|
||||
<p className="text-sm text-scale-1000">
|
||||
The disk performance of your workload is determined by the Disk IO bandwidth.
|
||||
</p>
|
||||
|
||||
{currentComputeInstanceSpecs.maxBandwidth ===
|
||||
currentComputeInstanceSpecs.baseBandwidth ? (
|
||||
<p className="text-sm text-scale-1000">
|
||||
@@ -191,17 +179,20 @@ const Infrastructure = ({ projectRef }: InfrastructureProps) => {
|
||||
{currentComputeInstanceSpecs.baseBandwidth.toLocaleString()} Mbps
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center justify-between py-1">
|
||||
<p className="text-xs text-scale-1000">Daily burst time limit</p>
|
||||
<p className="text-xs">30 mins</p>
|
||||
</div>
|
||||
{currentComputeInstanceSpecs.maxBandwidth !==
|
||||
currentComputeInstanceSpecs.baseBandwidth && (
|
||||
<div className="flex items-center justify-between py-1">
|
||||
<p className="text-xs text-scale-1000">Daily burst time limit</p>
|
||||
<p className="text-xs">30 mins</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="space-y-1">
|
||||
<div className="flex flex-row justify-between">
|
||||
{attribute.key === 'disk_io_consumption' ? (
|
||||
<p>IO consumed per {interval === '1d' ? 'day' : 'hour'}</p>
|
||||
<p>Disk IO consumed per {interval === '1d' ? 'day' : 'hour'}</p>
|
||||
) : (
|
||||
<p>
|
||||
Max{' '}
|
||||
@@ -250,8 +241,9 @@ const Infrastructure = ({ projectRef }: InfrastructureProps) => {
|
||||
<Panel>
|
||||
<Panel.Content>
|
||||
<div className="flex flex-col items-center justify-center space-y-2">
|
||||
<p>No data</p>
|
||||
<p className="text-sm text-scale-1000">There is no data in period</p>
|
||||
<IconBarChart2 className="text-scale-1100 mb-2" />
|
||||
<p className="text-sm">No data in period</p>
|
||||
<p className="text-sm text-scale-1000">May take a few minutes to show</p>
|
||||
</div>
|
||||
</Panel.Content>
|
||||
</Panel>
|
||||
|
||||
@@ -8,9 +8,16 @@ export interface SizeAndCountsProps {
|
||||
startDate: string | undefined
|
||||
endDate: string | undefined
|
||||
subscription: ProjectSubscriptionResponse | undefined
|
||||
currentBillingCycleSelected: boolean
|
||||
}
|
||||
|
||||
const SizeAndCounts = ({ projectRef, startDate, endDate, subscription }: SizeAndCountsProps) => {
|
||||
const SizeAndCounts = ({
|
||||
projectRef,
|
||||
startDate,
|
||||
endDate,
|
||||
subscription,
|
||||
currentBillingCycleSelected,
|
||||
}: SizeAndCountsProps) => {
|
||||
const { data: dbSizeData, isLoading: isLoadingDbSizeData } = useDailyStatsQuery({
|
||||
projectRef,
|
||||
attribute: 'total_db_size_bytes',
|
||||
@@ -61,9 +68,10 @@ const SizeAndCounts = ({ projectRef, startDate, endDate, subscription }: SizeAnd
|
||||
return (
|
||||
<UsageSection
|
||||
projectRef={projectRef}
|
||||
categoryKey='sizeCount'
|
||||
categoryKey="sizeCount"
|
||||
chartMeta={chartMeta}
|
||||
subscription={subscription}
|
||||
currentBillingCycleSelected={currentBillingCycleSelected}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ export const USAGE_CATEGORIES: CategoryMeta[] = [
|
||||
anchor: 'disk_io',
|
||||
key: 'disk_io_consumption',
|
||||
attribute: 'disk_io_consumption',
|
||||
name: 'Disk IO bandwidth',
|
||||
name: 'Disk IO Bandwidth',
|
||||
unit: 'percentage',
|
||||
links: [
|
||||
{
|
||||
@@ -89,7 +89,7 @@ export const USAGE_CATEGORIES: CategoryMeta[] = [
|
||||
},
|
||||
],
|
||||
description:
|
||||
'Smaller compute instances (below 4XL) can burst up to their largest throughput and IOPS for 30 minutes in a day. Beyond that, the performance reverts to the baseline. Your disk budget gets replenished throughout the day.',
|
||||
'The disk performance of your workload is determined by the Disk IO bandwidth.\nSmaller compute instances (below 4XL) can burst up to their largest throughput and IOPS for 30 minutes in a day. Beyond that, the performance reverts to the baseline. Your disk budget gets replenished throughout the day.',
|
||||
chartDescription: '',
|
||||
},
|
||||
],
|
||||
@@ -103,7 +103,7 @@ export const USAGE_CATEGORIES: CategoryMeta[] = [
|
||||
anchor: 'dbEgress',
|
||||
key: 'db_egress',
|
||||
attribute: 'total_egress_modified',
|
||||
name: 'Database egress',
|
||||
name: 'Database Egress',
|
||||
unit: 'bytes',
|
||||
description:
|
||||
'Contains any outgoing traffic (egress) from your database.\nBilling is based on the total sum of egress in GB throughout your billing period.',
|
||||
@@ -113,7 +113,7 @@ export const USAGE_CATEGORIES: CategoryMeta[] = [
|
||||
anchor: 'storageEgress',
|
||||
key: 'storage_egress',
|
||||
attribute: 'total_storage_egress',
|
||||
name: 'Storage egress',
|
||||
name: 'Storage Egress',
|
||||
unit: 'bytes',
|
||||
description:
|
||||
'All requests to view and download your storage items go through our CDN. We sum up all outgoing traffic (egress) for storage related requests through our CDN. We do not differentiate between cache and no cache hits.\nBilling is based on the total amount of egress in GB throughout your billing period.',
|
||||
@@ -166,7 +166,7 @@ export const USAGE_CATEGORIES: CategoryMeta[] = [
|
||||
anchor: 'storageSize',
|
||||
key: 'storage_size',
|
||||
attribute: 'total_storage_size_bytes',
|
||||
name: 'Storage size',
|
||||
name: 'Storage Size',
|
||||
chartPrefix: 'Max ',
|
||||
unit: 'bytes',
|
||||
description:
|
||||
@@ -177,7 +177,7 @@ export const USAGE_CATEGORIES: CategoryMeta[] = [
|
||||
anchor: 'funcCount',
|
||||
key: 'func_count',
|
||||
attribute: 'total_func_count',
|
||||
name: 'Edge function count',
|
||||
name: 'Edge Function Count',
|
||||
chartPrefix: 'Max ',
|
||||
unit: 'absolute',
|
||||
description:
|
||||
@@ -217,7 +217,7 @@ export const USAGE_CATEGORIES: CategoryMeta[] = [
|
||||
anchor: 'storageImageTransformations',
|
||||
key: 'storage_image_render_count',
|
||||
attribute: 'total_storage_image_render_count',
|
||||
name: 'Storage image transformations',
|
||||
name: 'Storage Image Transformations',
|
||||
unit: 'absolute',
|
||||
description:
|
||||
'We count all images that were transformed in the billing period, ignoring any transformations.\nUsage example: You transform one image with four different size transformations and another image with just a single transformations. It counts as two, as only two images were transformed.\nBilling is based on the count of (origin) images that used transformations throughout the billing period. Resets every billing cycle.',
|
||||
@@ -228,7 +228,7 @@ export const USAGE_CATEGORIES: CategoryMeta[] = [
|
||||
anchor: 'functionInvocations',
|
||||
key: 'func_invocations',
|
||||
attribute: 'total_func_invocations',
|
||||
name: 'Edge function invocations',
|
||||
name: 'Edge Function Invocations',
|
||||
unit: 'absolute',
|
||||
description:
|
||||
'Every serverless function invocation independent of response status is counted.\nBilling is based on the sum of all invocations throughout your billing period.',
|
||||
@@ -238,7 +238,7 @@ export const USAGE_CATEGORIES: CategoryMeta[] = [
|
||||
anchor: 'realtimeMessageCount',
|
||||
key: 'realtime_message_count',
|
||||
attribute: 'total_realtime_message_count',
|
||||
name: 'Realtime message count',
|
||||
name: 'Realtime Message Count',
|
||||
unit: 'absolute',
|
||||
description:
|
||||
"Count of messages going through Realtime.\nUsage example: If you do a database change and 5 clients listen to that change via Realtime, that's 5 messages. If you broadcast a message and 4 clients listen to that, that's 5 messages (1 message sent, 4 received).\nBilling is based on the total amount of messages throughout your billing period.",
|
||||
@@ -248,11 +248,11 @@ export const USAGE_CATEGORIES: CategoryMeta[] = [
|
||||
anchor: 'realtimePeakConnection',
|
||||
key: 'realtime_peak_connection',
|
||||
attribute: 'total_realtime_peak_connection',
|
||||
name: 'Realtime peak connections',
|
||||
name: 'Realtime Peak Connections',
|
||||
chartPrefix: 'Max ',
|
||||
unit: 'absolute',
|
||||
description:
|
||||
'Total number of successful connections (not connection attempts).\nBilling is based on the maximum amount of concurrent peak connections throughout your billing period.',
|
||||
'Total number of successful connections. Connections attempts are not counted towards usage.\nBilling is based on the maximum amount of concurrent peak connections throughout your billing period.',
|
||||
chartDescription: 'The data refreshes every 24 hours.',
|
||||
},
|
||||
],
|
||||
|
||||
@@ -4,10 +4,9 @@ import { useInfraMonitoringQuery } from 'data/analytics/infra-monitoring-query'
|
||||
import { useProjectSubscriptionV2Query } from 'data/subscriptions/project-subscription-v2-query'
|
||||
import { useProjectUsageQuery } from 'data/usage/project-usage-query'
|
||||
import dayjs from 'dayjs'
|
||||
import Link from 'next/link'
|
||||
import { useMemo, useRef, useState } from 'react'
|
||||
import { InView } from 'react-intersection-observer'
|
||||
import { Button, IconAlertCircle, IconLoader, Listbox } from 'ui'
|
||||
import { IconAlertCircle, IconLoader } from 'ui'
|
||||
import { cn } from 'ui/src/utils/cn'
|
||||
import Activity from './Activity'
|
||||
import Bandwidth from './Bandwidth'
|
||||
@@ -15,6 +14,8 @@ import Infrastructure from './Infrastructure'
|
||||
import SizeAndCounts from './SizeAndCounts'
|
||||
import { USAGE_CATEGORIES, USAGE_STATUS } from './Usage.constants'
|
||||
import { getUsageStatus } from './Usage.utils'
|
||||
import DateRangePicker from 'components/to-be-cleaned/DateRangePicker'
|
||||
import { TIME_PERIODS_BILLING, TIME_PERIODS_REPORTS } from 'lib/constants'
|
||||
|
||||
export type usageSectionIds = 'infra' | 'bandwidth' | 'sizeCount' | 'activity'
|
||||
|
||||
@@ -32,36 +33,54 @@ const Usage = () => {
|
||||
|
||||
const [activeTab, setActiveTab] = useState<usageSectionIds>('infra')
|
||||
|
||||
const [dateRange, setDateRange] = useState<any>()
|
||||
|
||||
const { data: usage } = useProjectUsageQuery({ projectRef: ref })
|
||||
const { data: subscription, isLoading: isLoadingSubscription } = useProjectSubscriptionV2Query({
|
||||
projectRef: selectedProjectRef,
|
||||
})
|
||||
|
||||
const { current_period_start, current_period_end } = subscription ?? {}
|
||||
const currentBillingCycleSelected = useMemo(() => {
|
||||
// Selected by default
|
||||
if (!dateRange?.period_start || !dateRange?.period_end || !subscription) return true
|
||||
|
||||
const { current_period_start, current_period_end } = subscription
|
||||
|
||||
return (
|
||||
dayjs(dateRange.period_start.date).isSame(new Date(current_period_start * 1000)) &&
|
||||
dayjs(dateRange.period_end.date).isSame(new Date(current_period_end * 1000))
|
||||
)
|
||||
}, [dateRange, subscription])
|
||||
|
||||
const startDate = useMemo(() => {
|
||||
return current_period_start ? new Date(current_period_start * 1000).toISOString() : undefined
|
||||
}, [current_period_start])
|
||||
|
||||
const endDate =
|
||||
current_period_end !== undefined ? new Date(current_period_end * 1000).toISOString() : undefined
|
||||
|
||||
const dailyStatsEndDate = useMemo(() => {
|
||||
// If end date is in future, set end date to now
|
||||
if (endDate && dayjs(endDate).isAfter(dayjs())) {
|
||||
if (!dateRange?.period_start?.date) {
|
||||
return undefined
|
||||
} else {
|
||||
// LF seems to have an issue with the milliseconds, causes infinite loading sometimes
|
||||
return new Date().toISOString().slice(0, -5) + 'Z'
|
||||
} else if (endDate) {
|
||||
return endDate
|
||||
return new Date(dateRange?.period_start?.date).toISOString().slice(0, -5) + 'Z'
|
||||
}
|
||||
}, [endDate])
|
||||
}, [dateRange, subscription])
|
||||
|
||||
const endDate = useMemo(() => {
|
||||
// If end date is in future, set end date to end of current day
|
||||
if (dateRange?.period_end?.date && dayjs(dateRange.period_end.date).isAfter(dayjs())) {
|
||||
// LF seems to have an issue with the milliseconds, causes infinite loading sometimes
|
||||
// In order to have full days from Prometheus metrics when using 1d interval,
|
||||
// the time needs to be greater or equal than the time of the start date
|
||||
return dayjs().endOf('day').toISOString().slice(0, -5) + 'Z'
|
||||
} else if (dateRange?.period_end?.date) {
|
||||
// LF seems to have an issue with the milliseconds, causes infinite loading sometimes
|
||||
return new Date(dateRange.period_end.date).toISOString().slice(0, -5) + 'Z'
|
||||
}
|
||||
}, [dateRange, subscription])
|
||||
|
||||
const { data: ioBudgetData } = useInfraMonitoringQuery({
|
||||
projectRef: selectedProjectRef,
|
||||
attribute: 'disk_io_budget',
|
||||
interval: '1d',
|
||||
startDate,
|
||||
endDate,
|
||||
startDate: dateRange?.period_start?.date,
|
||||
endDate: dateRange?.period_end?.date,
|
||||
})
|
||||
const currentDayIoBudget = Number(
|
||||
ioBudgetData?.data.find((x) => x.periodStartFormatted === dayjs().format('DD MMM'))?.[
|
||||
@@ -117,7 +136,7 @@ const Usage = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="">
|
||||
<div>
|
||||
<div className="1xl:px-28 mx-auto flex flex-col px-5 lg:px-16 2xl:px-32 pt-6 space-y-4">
|
||||
<h3 className="text-scale-1200 text-xl">Usage</h3>
|
||||
</div>
|
||||
@@ -127,22 +146,19 @@ const Usage = () => {
|
||||
<div className="1xl:px-28 mx-auto px-5 lg:px-16 2xl:px-32 flex flex-col gap-2">
|
||||
<div className="flex items-center mt-4 justify-between">
|
||||
<div className="flex items-center space-x-4">
|
||||
<Listbox
|
||||
disabled
|
||||
size="small"
|
||||
id="billingCycle"
|
||||
name="billingCycle"
|
||||
value={'current'}
|
||||
className="!w-[200px]"
|
||||
onChange={() => {}}
|
||||
>
|
||||
<Listbox.Option label="Current billing cycle" value="current">
|
||||
Current billing cycle
|
||||
</Listbox.Option>
|
||||
<Listbox.Option label="Previous billing cycle" value="previous">
|
||||
Previous billing cycle
|
||||
</Listbox.Option>
|
||||
</Listbox>
|
||||
{!isLoadingSubscription && (
|
||||
<DateRangePicker
|
||||
id="billingCycle"
|
||||
name="billingCycle"
|
||||
onChange={setDateRange}
|
||||
value={TIME_PERIODS_BILLING[0].key}
|
||||
options={[...TIME_PERIODS_BILLING, ...TIME_PERIODS_REPORTS]}
|
||||
loading={isLoadingSubscription}
|
||||
currentBillingPeriodStart={subscription?.current_period_start}
|
||||
className="!w-[200px]"
|
||||
/>
|
||||
)}
|
||||
|
||||
{isLoadingSubscription ? (
|
||||
<IconLoader className="animate-spin" size={14} />
|
||||
) : subscription !== undefined ? (
|
||||
@@ -150,7 +166,7 @@ const Usage = () => {
|
||||
<p
|
||||
className={clsx('text-sm transition', isLoadingSubscription && 'opacity-50')}
|
||||
>
|
||||
Project is on {subscription.plan.name}
|
||||
Project is on {subscription.plan.name} plan
|
||||
</p>
|
||||
<p className="text-sm text-scale-1000">
|
||||
{billingCycleStart.format('DD MMM YYYY')} -{' '}
|
||||
@@ -159,18 +175,6 @@ const Usage = () => {
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="items-center space-x-2 hidden lg:flex">
|
||||
<Link href={`/project/${selectedProjectRef}/settings/billing/invoices`}>
|
||||
<a>
|
||||
<Button type="default">View invoices</Button>
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/project/${selectedProjectRef}/settings/billing/subscription`}>
|
||||
<a>
|
||||
<Button type="default">View billing</Button>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-6">
|
||||
{USAGE_CATEGORIES.map((category) => {
|
||||
@@ -197,9 +201,13 @@ const Usage = () => {
|
||||
: 'opacity-50'
|
||||
)}
|
||||
>
|
||||
{usageBillingEnabled === false && status === USAGE_STATUS.APPROACHING ? (
|
||||
{currentBillingCycleSelected &&
|
||||
usageBillingEnabled === false &&
|
||||
status === USAGE_STATUS.APPROACHING ? (
|
||||
<IconAlertCircle size={15} strokeWidth={2} className="text-amber-900" />
|
||||
) : usageBillingEnabled === false && status === USAGE_STATUS.EXCEEDED ? (
|
||||
) : currentBillingCycleSelected &&
|
||||
usageBillingEnabled === false &&
|
||||
status === USAGE_STATUS.EXCEEDED ? (
|
||||
<IconAlertCircle size={15} strokeWidth={2} className="text-red-900" />
|
||||
) : null}
|
||||
<p className="text-sm">{category.name}</p>
|
||||
@@ -212,7 +220,12 @@ const Usage = () => {
|
||||
|
||||
<InView as="div" threshold={0.2} onChange={(inView) => inView && setActiveTab('infra')}>
|
||||
<div id="infrastructure" ref={infrastructureRef} style={{ scrollMarginTop: '100px' }}>
|
||||
<Infrastructure projectRef={selectedProjectRef} />
|
||||
<Infrastructure
|
||||
projectRef={selectedProjectRef}
|
||||
startDate={startDate}
|
||||
endDate={endDate}
|
||||
currentBillingCycleSelected={currentBillingCycleSelected}
|
||||
/>
|
||||
</div>
|
||||
</InView>
|
||||
<InView
|
||||
@@ -225,7 +238,8 @@ const Usage = () => {
|
||||
projectRef={selectedProjectRef}
|
||||
subscription={subscription}
|
||||
startDate={startDate}
|
||||
endDate={dailyStatsEndDate}
|
||||
endDate={endDate}
|
||||
currentBillingCycleSelected={currentBillingCycleSelected}
|
||||
/>
|
||||
</div>
|
||||
</InView>
|
||||
@@ -239,7 +253,8 @@ const Usage = () => {
|
||||
projectRef={selectedProjectRef}
|
||||
subscription={subscription}
|
||||
startDate={startDate}
|
||||
endDate={dailyStatsEndDate}
|
||||
endDate={endDate}
|
||||
currentBillingCycleSelected={currentBillingCycleSelected}
|
||||
/>
|
||||
</div>
|
||||
</InView>
|
||||
@@ -253,7 +268,8 @@ const Usage = () => {
|
||||
projectRef={selectedProjectRef}
|
||||
subscription={subscription}
|
||||
startDate={startDate}
|
||||
endDate={dailyStatsEndDate}
|
||||
endDate={endDate}
|
||||
currentBillingCycleSelected={currentBillingCycleSelected}
|
||||
/>
|
||||
</div>
|
||||
</InView>
|
||||
|
||||
@@ -51,17 +51,28 @@ export const getUpgradeUrl = (projectRef: string, subscription?: StripeSubscript
|
||||
|
||||
export const getUpgradeUrlFromV2Subscription = (
|
||||
projectRef: string,
|
||||
subscription?: ProjectSubscriptionResponse
|
||||
subscription?: ProjectSubscriptionResponse,
|
||||
newSubscriptionPage?: boolean
|
||||
) => {
|
||||
if (!subscription) return `/project/${projectRef}/settings/billing/update`
|
||||
if (!subscription) {
|
||||
return !newSubscriptionPage
|
||||
? `/project/${projectRef}/settings/billing/update`
|
||||
: `/project/${projectRef}/settings/billing/subscription`
|
||||
}
|
||||
|
||||
return subscription?.plan.id === 'enterprise'
|
||||
? `/project/${projectRef}/settings/billing/update/enterprise`
|
||||
: subscription?.plan.id === 'team'
|
||||
? `/project/${projectRef}/settings/billing/update/team`
|
||||
: subscription?.plan.id === 'free'
|
||||
? `/project/${projectRef}/settings/billing/update`
|
||||
: `/project/${projectRef}/settings/billing/update/pro`
|
||||
if (!newSubscriptionPage) {
|
||||
return subscription?.plan.id === 'enterprise'
|
||||
? `/project/${projectRef}/settings/billing/update/enterprise`
|
||||
: subscription?.plan.id === 'team'
|
||||
? `/project/${projectRef}/settings/billing/update/team`
|
||||
: subscription?.plan.id === 'free'
|
||||
? `/project/${projectRef}/settings/billing/update`
|
||||
: `/project/${projectRef}/settings/billing/update/pro`
|
||||
} else {
|
||||
return subscription?.plan?.id === 'pro' && subscription?.usage_billing_enabled === false
|
||||
? `/project/${projectRef}/settings/billing/subscription#cost-control`
|
||||
: `/project/${projectRef}/settings/billing/subscription?panel=subscriptionPlan`
|
||||
}
|
||||
}
|
||||
|
||||
const compactNumberFormatter = new Intl.NumberFormat('en-US', {
|
||||
|
||||
Loaded 100 of 117 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user