Merge branch 'master' into chore/storage-download-multiple-files-as-zip
No files matched your search
@@ -142,7 +142,7 @@ Our approach for client libraries is modular. Each sub-library is a standalone i
|
||||
<td>-</td>
|
||||
<td><a href="https://github.com/supabase-community/gotrue-java" target="_blank" rel="noopener noreferrer">gotrue-java</a></td>
|
||||
<td>-</td>
|
||||
<td><a href="https://github.com/supabase-community/storage-java" target="_blanke" rel="noopener noreferrer">storage-java</a></td>
|
||||
<td><a href="https://github.com/supabase-community/storage-java" target="_blank" rel="noopener noreferrer">storage-java</a></td>
|
||||
<td>-</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
||||
@@ -204,6 +204,11 @@ export const menuItems: NavMenu = {
|
||||
url: '/guides/database/extensions/plv8',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
name: 'PostGIS: Geo queries',
|
||||
url: '/guides/database/extensions/postgis',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
name: 'uuid-ossp: Unique Identifiers',
|
||||
url: '/guides/database/extensions/uuid-ossp',
|
||||
|
||||
@@ -42,13 +42,14 @@ export const gettingstarted = {
|
||||
items: [
|
||||
{ name: 'Features', url: '/guides/getting-started/features', items: [] },
|
||||
{ name: 'Architecture', url: '/guides/getting-started/architecture', items: [] },
|
||||
// {
|
||||
// name: 'Quickstarts',
|
||||
// items: [
|
||||
// { name: 'NextJS', url: '/guides/getting-started/quickstarts/nextjs', items: [] },
|
||||
// { name: 'ReactJS', url: '/guides/getting-started/quickstarts/reactjs', items: [] },
|
||||
// ],
|
||||
// },
|
||||
{
|
||||
name: 'Framework Quickstarts',
|
||||
items: [
|
||||
{ name: 'ReactJS', url: '/guides/getting-started/quickstarts/reactjs', items: [] },
|
||||
{ name: 'NextJS', url: '/guides/getting-started/quickstarts/nextjs', items: [] },
|
||||
{ name: 'Flutter', url: '/guides/getting-started/quickstarts/flutter', items: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Web app tutorials',
|
||||
items: [
|
||||
@@ -389,6 +390,11 @@ export const database = {
|
||||
url: '/guides/database/extensions/pgnet',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
name: 'PostGIS: Geo queries',
|
||||
url: '/guides/database/extensions/postgis',
|
||||
items: [],
|
||||
},
|
||||
{ name: 'pgTAP: Unit Testing', url: '/guides/database/extensions/pgtap', items: [] },
|
||||
{
|
||||
name: 'uuid-ossp: Unique Identifiers',
|
||||
|
||||
@@ -7,10 +7,6 @@ import { IconChevronLeft } from '~/../../packages/ui'
|
||||
import * as NavItems from './NavigationMenu.constants'
|
||||
import * as Accordion from '@radix-ui/react-accordion'
|
||||
|
||||
interface ImPureProps {
|
||||
// users: Array<User>
|
||||
}
|
||||
|
||||
const HeaderImage = React.memo(function HeaderImage(props: any) {
|
||||
const router = useRouter()
|
||||
const { isDarkMode } = useTheme()
|
||||
@@ -45,12 +41,6 @@ const ContentAccordionLink = React.memo(function ContentAccordionLink(props: any
|
||||
const router = useRouter()
|
||||
const { isDarkMode } = useTheme()
|
||||
|
||||
let subItemMenuOpen = false
|
||||
|
||||
if (router.asPath.includes(props.subItem.url)) {
|
||||
subItemMenuOpen = true
|
||||
}
|
||||
|
||||
const LinkContainer = (props) => {
|
||||
return (
|
||||
<Link href={props.url} passHref>
|
||||
@@ -188,7 +178,7 @@ const Content = (props) => {
|
||||
return (
|
||||
<div key={x.name}>
|
||||
{x.items && x.items.length > 0 ? (
|
||||
<div>
|
||||
<div className="flex flex-col gap-1">
|
||||
{x.items.map((subItem, subItemIndex) => {
|
||||
return (
|
||||
<ContentAccordionLink
|
||||
|
||||
@@ -148,7 +148,7 @@ const RenderLink = React.memo(function RenderLink(props: any) {
|
||||
|
||||
const SideMenuTitle = ({ title }: { title: string }) => {
|
||||
return (
|
||||
<span className="font-mono text-xs uppercase text-scale-1200 font-medium tracking-wider mb-3">
|
||||
<span className="font-mono text-xs uppercase text-scale-1200 font-medium tracking-wider">
|
||||
{title}
|
||||
</span>
|
||||
)
|
||||
@@ -245,7 +245,7 @@ const Content: React.FC<INavigationMenuRefList> = ({ id, lib, commonSections, sp
|
||||
<RevVersionDropdown />
|
||||
</div>
|
||||
|
||||
<ul className="function-link-list">
|
||||
<ul className="function-link-list flex flex-col gap-1">
|
||||
{sections.map((fn: any, fnIndex) => {
|
||||
// run allow check
|
||||
if (isFuncNotInLibraryOrVersion(fn.id, fn.type, allowedKeys)) {
|
||||
|
||||
@@ -34,6 +34,10 @@ Make sure to specify the `on delete cascade` clause when referencing `auth.users
|
||||
|
||||
</Admonition>
|
||||
|
||||
## Deleting Users
|
||||
|
||||
You may delete users directly or via the management console at Authentication > Users. Note that deleting a user from the `auth.users` table does not automatically sign out a user. As Supabase makes use of JSON Web Tokens (JWT), a user's JWT will remain "valid" until it has expired. Should you wish to immediately revoke access for a user, do considering making use of a Row Level Security policy as described below.
|
||||
|
||||
## Public access
|
||||
|
||||
Since Row Level Security is enabled, this table is accessible via the API but no data will be returned unless we set up some Policies.
|
||||
|
||||
@@ -6,7 +6,7 @@ export const meta = {
|
||||
description: 'Add Twitter OAuth to your Supabase project',
|
||||
}
|
||||
|
||||
To enable Twitter Auth for your project, you need to set up a Twitter OAuth application with [elevated access](https://developer.twitter.com/en/portal/products/elevated)and add the application credentials in the Supabase Dashboard.
|
||||
To enable Twitter Auth for your project, you need to set up a Twitter OAuth application with [elevated access](https://developer.twitter.com/en/portal/products/elevated) and add the application credentials in the Supabase Dashboard.
|
||||
|
||||
## Overview
|
||||
|
||||
|
||||
@@ -0,0 +1,302 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
|
||||
export const meta = {
|
||||
id: 'postgis',
|
||||
title: 'PostGIS: Geo queries',
|
||||
description: 'Working with geo-spatial data in Postgres',
|
||||
}
|
||||
|
||||
[PostGIS](https://postgis.net/) is a Postgres extension that allows you to interact with Geo data within Postgres. You can sort your data by geographic location, get data within certain geographic boundaries, and do much more with it.
|
||||
|
||||
## Overview
|
||||
|
||||
While you may be able to store simple lat/long geographic coordinates as a set of decimals, it does not scale very well when you try to query through a large data set. PostGIS comes with special data types that are efficient, and indexable for high scalability.
|
||||
|
||||
The additional data types that PostGIS provides include [Point](https://postgis.net/docs/using_postgis_dbmanagement.html#Point), [Polygon](https://postgis.net/docs/using_postgis_dbmanagement.html#Polygon), [Linestring](https://postgis.net/docs/using_postgis_dbmanagement.html#LineString), and many more to represent different types of geographical data. In this guide, we will mainly focus on how to interact with `Point` type, which represents a single set of latitude and longitude. If you are interested in digging deeper, you can learn more about different data types on the [data management section of PostGIS docs](https://postgis.net/docs/using_postgis_dbmanagement.html).
|
||||
|
||||
## Usage
|
||||
|
||||
### Enable the extension
|
||||
|
||||
You can get started with PostGIS by enabling the PostGIS extension in your Supabase dashboard.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="dashboard"
|
||||
>
|
||||
<TabPanel id="dashboard" label="Dashboard">
|
||||
|
||||
1. Go to the [Database](https://app.supabase.com/project/_/database/tables) page in the Dashboard.
|
||||
2. Click on **Extensions** in the sidebar.
|
||||
3. Search for "postgis" and enable the extension.
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="sql" label="SQL">
|
||||
|
||||
```sql
|
||||
-- Example: enable the "postgis" extension
|
||||
create extension postgis with schema extensions;
|
||||
|
||||
-- Example: disable the "postgis" extension
|
||||
drop extension if exists postgis;
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
## Examples
|
||||
|
||||
Now that we are ready to get started with PostGIS, let’s create a table and see how we can utilize PostGIS for some typical use cases. Let’s imagine we are creating a simple restaurant-searching app.
|
||||
|
||||
Let’s create our table. Each row represents a restaurant with its location stored in `location` column as a `Point` type.
|
||||
|
||||
```sql
|
||||
create table if not exists public.restaurants (
|
||||
id int generated by default as identity primary key,
|
||||
name text not null,
|
||||
location geography(POINT) not null
|
||||
);
|
||||
```
|
||||
|
||||
We can then set a [spatial index](https://postgis.net/docs/using_postgis_dbmanagement.html#build-indexes) on the `location` column of this table.
|
||||
|
||||
```sql
|
||||
create index restaurants_geo_index
|
||||
on public.restaurants
|
||||
using GIST (location);
|
||||
```
|
||||
|
||||
### Inserting data
|
||||
|
||||
You can insert geographical data through SQL or through our API.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="data"
|
||||
>
|
||||
<TabPanel id="data" label="Data">
|
||||
|
||||
<h4>Restaurants</h4>
|
||||
|
||||
| id | name | location |
|
||||
| --- | ----------- | -------------------------------- |
|
||||
| 1 | Supa Burger | lat: 40.807416, long: -73.946823 |
|
||||
| 2 | Supa Pizza | lat: 40.807475, long: -73.94581 |
|
||||
| 3 | Supa Taco | lat: 40.80629, long: -73.945826 |
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="sql" label="SQL">
|
||||
|
||||
```sql
|
||||
insert into public.restaurants
|
||||
(name, location)
|
||||
values
|
||||
('Supa Burger', st_point(-73.946823 40.807416)),
|
||||
('Supa Pizza', st_point(-73.94581 40.807475)),
|
||||
('Supa Taco', st_point(-73.945826 40.80629));
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
```js
|
||||
const { error } = await supabase.from('restaurants').insert([
|
||||
{
|
||||
name: 'Supa Burger',
|
||||
location: 'POINT(-73.946823 40.807416)',
|
||||
},
|
||||
{
|
||||
name: 'Supa Pizza',
|
||||
location: 'POINT(-73.94581 40.807475)',
|
||||
},
|
||||
{
|
||||
name: 'Supa Taco',
|
||||
location: 'POINT(-73.945826 40.80629)',
|
||||
},
|
||||
])
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="dart" label="Dart">
|
||||
|
||||
```dart
|
||||
await supabase.from('restaurants').insert([
|
||||
{
|
||||
'name': 'Supa Burger',
|
||||
'location': 'POINT(-73.946823 40.807416)',
|
||||
},
|
||||
{
|
||||
'name': 'Supa Pizza',
|
||||
'location': 'POINT(-73.94581 40.807475)',
|
||||
},
|
||||
{
|
||||
'name': 'Supa Taco',
|
||||
'location': 'POINT(-73.945826 40.80629)',
|
||||
},
|
||||
]);
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
Notice the order in which you pass the latitude and longitude. Longitude comes first, and is because longitude represents the x-axis of the location. Another thing to watch for is that there is no comma between the two values, just a single space.
|
||||
|
||||
At this point, if you go into your Supabase dashboard and look at the data, you will notice that the value of the `location` column looks something like this.
|
||||
|
||||
```
|
||||
0101000020E6100000A4DFBE0E9C91614044FAEDEBC0494240
|
||||
```
|
||||
|
||||
We can query the `restaurants` table directly, but it will return the `location` column in the format you see above. We will create [database functions](https://supabase.com/docs/guides/database/functions) so that we can use the [st_astext()](https://postgis.net/docs/ST_AsText.html) function to convert it back to a human-readable format like `POINT(-73.946713 40.807313)`.
|
||||
|
||||
### Order by distance
|
||||
|
||||
Sorting datasets from closest to farthest, sometimes called nearest-neighbor sort, is a very common use case in Geo-queries. PostGIS can handle it very easily with the use of the [`<->`](https://postgis.net/docs/geometry_distance_knn.html) operator. `<->` operator returns the two-dimensional distance between two geometries and will utilize the spatial index when used within `order by` clause. You can create the following database function to sort the restaurants from closest to farthest by passing the current locations as parameters.
|
||||
|
||||
```sql
|
||||
create or replace function nearby_restaurants(lat float, long float)
|
||||
returns setof record
|
||||
language sql
|
||||
as $$
|
||||
select id, name, st_astext(location) as location, st_distance(location, st_point(long, lat)::geography) as dist_meters
|
||||
from public.restaurants
|
||||
order by location <-> st_point(long, lat)::geography;
|
||||
$$;
|
||||
```
|
||||
|
||||
You can call this function from your client using `rpc()` like this:
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="js"
|
||||
>
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
```js
|
||||
const { data, error } = await supabase.rpc('nearby_restaurants', {
|
||||
lat: 40.807313,
|
||||
long: -73.946713,
|
||||
})
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="dart" label="Dart">
|
||||
|
||||
```dart
|
||||
final data = await supabase.rpc('nearby_restaurants',params: {
|
||||
'lat': 40.807313,
|
||||
'long': -73.946713,
|
||||
});
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="result" label="Result">
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Supa Burger",
|
||||
"location": "POINT(-73.946823 40.807416)",
|
||||
"dist_meters": 14.73033739
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Supa Pizza",
|
||||
"location": "POINT(-73.94581 40.807475)",
|
||||
"dist_meters": 78.28980007
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Supa Taco",
|
||||
"location": "POINT(-73.945826 40.80629)",
|
||||
"dist_meters": 136.04329002
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### Finding all data points within a bounding box
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||
```sql
|
||||
create or replace function restaurants_in_view(min_lat float, min_long float, max_lat float, max_long float)
|
||||
returns setof record
|
||||
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)
|
||||
$$;
|
||||
```
|
||||
|
||||
[`&&`](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.
|
||||
|
||||
- [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.
|
||||
|
||||
You can call this function from your client using `rpc()` like this:
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="js"
|
||||
>
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
```js
|
||||
const { data, error } = await supabase.rpc('restaurants_in_view', {
|
||||
min_lat: 40.807,
|
||||
min_long: -73.946,
|
||||
max_lat: 40.808,
|
||||
max_long: -73.945,
|
||||
})
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="dart" label="Dart">
|
||||
|
||||
```dart
|
||||
final data = await supabase.rpc('restaurants_in_view', params: {
|
||||
'min_lat': 40.807,
|
||||
'min_long': -73.946,
|
||||
'max_lat': 40.808,
|
||||
'max_long': -73.945,
|
||||
});
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="result" label="Result">
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Supa Pizza",
|
||||
"location": "POINT(-73.94581 40.807475)"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
## Resources
|
||||
|
||||
- [Official PostGIS documentation](https://postgis.net/documentation/)
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
@@ -0,0 +1,196 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
import StepHikeCompact from '~/components/StepHikeCompact'
|
||||
|
||||
export const meta = {
|
||||
title: 'Use Supabase with Flutter',
|
||||
subtitle:
|
||||
'Learn how to create a Supabase project, add some sample data to your database, and query the data from a Flutter app.',
|
||||
breadcrumb: 'Framework Quickstarts',
|
||||
}
|
||||
|
||||
<StepHikeCompact>
|
||||
|
||||
<StepHikeCompact.Step step={1}>
|
||||
<StepHikeCompact.Details title="Set up a Supabase project with sample data">
|
||||
|
||||
[Create a new project](https://app.supabase.com) in the Supabase Dashboard.
|
||||
|
||||
After your project is ready, create a table in your Supabase database using the [SQL Editor](https://app.supabase.com/project/_/sql) in the Dashboard. Use the following SQL statement to create a `countries` table with some sample data.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```sql SQL_EDITOR
|
||||
-- Create the table
|
||||
CREATE TABLE countries (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL
|
||||
);
|
||||
-- Insert some sample data into the table
|
||||
INSERT INTO countries (name) VALUES ('United States');
|
||||
INSERT INTO countries (name) VALUES ('Canada');
|
||||
INSERT INTO countries (name) VALUES ('Mexico');
|
||||
````
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={2}>
|
||||
|
||||
<StepHikeCompact.Details title="Create a Flutter app">
|
||||
|
||||
Create a Flutter app using the `flutter create` command. You can skip this step if you already have a working app.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```bash Terminal
|
||||
flutter create my_app
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={3}>
|
||||
|
||||
<StepHikeCompact.Details title="Install the Supabase client library">
|
||||
|
||||
The fastest way to get started is to use the [`supabase_flutter`](https://pub.dev/packages/supabase_flutter) client library which provides a convenient interface for working with Supabase from a Flutter app.
|
||||
|
||||
Open the `pubspec.yaml` file inside your Flutter app and add `supabase_flutter` as a dependency.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```yaml pubspec.yaml
|
||||
supabase_flutter: ^1.2.2
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={4}>
|
||||
|
||||
<StepHikeCompact.Details title="Initialize the Supabase client">
|
||||
|
||||
Open `lib/main.dart` and edit the main function to initialize Supabase using your [project URL and public API (anon) key](https://app.supabase.com/project/_/settings/api).
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```dart lib/main.dart
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
await Supabase.initialize(
|
||||
url: 'YOUR_SUPABASE_URL',
|
||||
anonKey: 'YOUR_SUPABASE_ANON_KEY',
|
||||
);
|
||||
runApp(MyApp());
|
||||
}
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={5}>
|
||||
|
||||
<StepHikeCompact.Details title="Query data from the app">
|
||||
|
||||
Use a `FutureBuilder` to fetch the data when the home page loads and display the query result in a `ListView`.
|
||||
|
||||
Replace the default `MyApp` and `MyHomePage` classes with the following code.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```dart lib/main.dart
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const MaterialApp(
|
||||
title: 'Countries',
|
||||
home: HomePage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class HomePage extends StatefulWidget {
|
||||
const HomePage({super.key});
|
||||
|
||||
@override
|
||||
State<HomePage> createState() => _HomePageState();
|
||||
}
|
||||
|
||||
class _HomePageState extends State<HomePage> {
|
||||
final _future = Supabase.instance.client
|
||||
.from('countries')
|
||||
.select<List<Map<String, dynamic>>>();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: FutureBuilder<List<Map<String, dynamic>>>(
|
||||
future: _future,
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
final countries = snapshot.data!;
|
||||
return ListView.builder(
|
||||
itemCount: countries.length,
|
||||
itemBuilder: ((context, index) {
|
||||
final country = countries[index];
|
||||
return ListTile(
|
||||
title: Text(country['name']),
|
||||
);
|
||||
}),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={6}>
|
||||
<StepHikeCompact.Details title="Start the app">
|
||||
|
||||
Run your app on a platform of your choosing! By default an app should launch in your web browser.
|
||||
|
||||
Note that `supabase_flutter` is compatible with web, iOS, Android, macOS, and Windows apps.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```bash Terminal
|
||||
flutter run
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
</StepHikeCompact>
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} hideToc={true} />
|
||||
|
||||
export default Page
|
||||
@@ -3,7 +3,8 @@ import StepHikeCompact from '~/components/StepHikeCompact'
|
||||
|
||||
export const meta = {
|
||||
title: 'Use Supabase with NextJS',
|
||||
subtitle: 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a NextJS app.',
|
||||
subtitle:
|
||||
'Learn how to create a Supabase project, add some sample data to your database, and query the data from a NextJS app.',
|
||||
breadcrumb: 'Framework Quickstarts',
|
||||
}
|
||||
|
||||
@@ -58,7 +59,7 @@ export const meta = {
|
||||
<StepHikeCompact.Details title="Install the Supabase client library">
|
||||
|
||||
The fastest way to get started is to use the `supabase-js` client library which provides a convenient interface for working with Supabase from a NextJS app.
|
||||
|
||||
|
||||
Navigate to the NextJS app and install `supabase-js`.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
@@ -95,8 +96,8 @@ export const meta = {
|
||||
<StepHikeCompact.Step step={5}>
|
||||
<StepHikeCompact.Details title="Query data from the app">
|
||||
|
||||
Use a `getInitialProps` method to fetch the data when the page loads and display the query result as a simple list.
|
||||
|
||||
Use the `getServerSideProps` method to fetch the data server-side and display the query result as a simple list.
|
||||
|
||||
Replace the existing function in your index file in the `pages` directory with the following code.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
@@ -116,11 +117,13 @@ export const meta = {
|
||||
);
|
||||
}
|
||||
|
||||
Page.getInitialProps = async () => {
|
||||
const { data, error } = await supabase.from('countries').select()
|
||||
export async function getServerSideProps() {
|
||||
let { data } = await supabase.from('countries').select()
|
||||
|
||||
return {
|
||||
countries: data
|
||||
props: {
|
||||
countries: data
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@ import StepHikeCompact from '~/components/StepHikeCompact'
|
||||
|
||||
export const meta = {
|
||||
title: 'Use Supabase with React',
|
||||
subtitle: 'Learn how to create a Supabase project, add some sample data to your database, and query the data from a React app.',
|
||||
subtitle:
|
||||
'Learn how to create a Supabase project, add some sample data to your database, and query the data from a React app.',
|
||||
breadcrumb: 'Framework Quickstarts',
|
||||
}
|
||||
|
||||
@@ -58,7 +59,7 @@ export const meta = {
|
||||
<StepHikeCompact.Details title="Install the Supabase client library">
|
||||
|
||||
The fastest way to get started is to use the `supabase-js` client library which provides a convenient interface for working with Supabase from a React app.
|
||||
|
||||
|
||||
Navigate to the React app and install `supabase-js`.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
@@ -73,11 +74,11 @@ export const meta = {
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={5}>
|
||||
<StepHikeCompact.Step step={4}>
|
||||
<StepHikeCompact.Details title="Query data from the app">
|
||||
|
||||
In `index.js`, create a Supabase client using your [Project URL and public API (anon) key](https://app.supabase.com/project/_/settings/api).
|
||||
|
||||
|
||||
Add a `getCountries` function to fetch the data and log the query result to the browser console.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
@@ -90,7 +91,7 @@ export const meta = {
|
||||
const supabase = createClient('https://<project>.supabase.co', '<your-anon-key>')
|
||||
|
||||
async function getCountries() {
|
||||
const countries = await supabase.from('countries').select('*')
|
||||
const countries = await supabase.from('countries').select()
|
||||
console.log(countries)
|
||||
}
|
||||
|
||||
@@ -101,7 +102,7 @@ export const meta = {
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={6}>
|
||||
<StepHikeCompact.Step step={5}>
|
||||
<StepHikeCompact.Details title="Start the app">
|
||||
|
||||
Start the app, go to http://localhost:3000 in a browser, and open the browser console and you should see the list of countries.
|
||||
|
||||
@@ -129,6 +129,7 @@ Wrap your `pages/_app.tsx` component with the `SessionContextProvider` component
|
||||
import { useState } from 'react'
|
||||
import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
|
||||
import { SessionContextProvider, Session } from '@supabase/auth-helpers-react'
|
||||
import { AppProps } from 'next/app'
|
||||
|
||||
function MyApp({
|
||||
Component,
|
||||
|
||||
@@ -13,7 +13,7 @@ In this guide we will build a simple expense tracker web application using Stytc
|
||||
|
||||
We will leverage Supabase to store and authorize access to user data. Supabase makes it simple to set up Row Level Security (RLS) policies which ensure users can only read and write data that they are authorized to do so. If you do not already have a Supabase account, you will need to create one.
|
||||
|
||||
This guide will use [Next.js](https://nextjs.org/) which is a web application framework built on top of React. Stytch provides a [Node.js library](https://github.com/stytchauth/stytch-node) and a [React library](https://github.com/stytchauth/stytch-react) which makes building Next.js apps super easy.
|
||||
This guide will use [Next.js](https://nextjs.org/) which is a web application framework built on top of React. Stytch provides a [Node.js library](https://github.com/stytchauth/stytch-node) and a [React library](https://stytch.com/docs/sdks/javascript-sdk) which makes building Next.js apps super easy.
|
||||
|
||||
> Note: You can find a completed version of this project on [Github](https://github.com/stytchauth/stytch-nextjs-supabase).
|
||||
|
||||
|
||||
@@ -8,18 +8,18 @@ export const meta = {
|
||||
|
||||
Every project on the Supabase Platform comes with its own dedicated Postgres instance running inside a virtual machine (VM). The following table describes the base instance with additional compute add-ons available if you need extra performance when scaling up Supabase.
|
||||
|
||||
| Plan | Pricing | CPU | Memory | Disk IO Bandwidth | Connections: Direct | Connections: Pooler |
|
||||
| --------------- | ------- | ----------------------- | ------ | ----------------- | ------------------- | -------------------- |
|
||||
| Free (Included) | $0 | 2-core ARM (shared) | 1 GB | Up to 2,606 Mbps | 10 (recommended) | 50 (recommended) |
|
||||
| Small | $5 | 2-core ARM (shared) | 2 GB | Up to 2,606 Mbps | 30 (recommended) | 75 (recommended) |
|
||||
| Medium | $50 | 2-core ARM (shared) | 4 GB | Up to 2,606 Mbps | 50 (recommended) | 150 (recommended) |
|
||||
| Large | $100 | 2-core ARM (dedicated) | 8 GB | Up to 4,750 Mbps | 100 (recommended) | 300 (recommended) |
|
||||
| XL | $200 | 4-core ARM (dedicated) | 16 GB | Up to 4,750 Mbps | 200 (recommended) | 600 (recommended) |
|
||||
| 2XL | $400 | 8-core ARM (dedicated) | 32 GB | Up to 4,750 Mbps | 350 (recommended) | 1200 (recommended) |
|
||||
| 4XL | $950 | 16-core ARM (dedicated) | 64 GB | Up to 4,750 Mbps | 420 (recommended) | 2800 (recommended) |
|
||||
| 8XL | $1,860 | 32-core ARM (dedicated) | 128 GB | Up to 9,000 Mbps | 450 (recommended) | 5600 (recommended) |
|
||||
| 12XL | $2,790 | 48-core ARM (dedicated) | 192 GB | Up to 13,500 Mbps | 480 (recommended) | 8600 (recommended) |
|
||||
| 16XL | $3,720 | 64-core ARM (dedicated) | 256 GB | Up to 19,000 Mbps | 500 (recommended) | 11,600 (recommended) |
|
||||
| Plan | Pricing | CPU | Memory | Maximum Disk IO Bandwidth | Baseline Disk IO Bandwidth | Connections: Direct (recommended) | Connections: Pooler (recommended) |
|
||||
| --------------- | ------- | ----------------------- | ------ | ------------------------- | -------------------------- | --------------------------------- | --------------------------------- |
|
||||
| Free (Included) | $0 | 2-core ARM (shared) | 1 GB | 2,606 Mbps | 87 Mbps | 10 | 50 |
|
||||
| Small | $5 | 2-core ARM (shared) | 2 GB | 2,606 Mbps | 174 Mbps | 30 | 75 |
|
||||
| Medium | $50 | 2-core ARM (shared) | 4 GB | 2,606 Mbps | 347 Mbps | 50 | 150 |
|
||||
| Large | $100 | 2-core ARM (dedicated) | 8 GB | 4,750 Mbps | 630 Mbps | 100 | 300 |
|
||||
| XL | $200 | 4-core ARM (dedicated) | 16 GB | 4,750 Mbps | 1,188 Mbps | 200 | 600 |
|
||||
| 2XL | $400 | 8-core ARM (dedicated) | 32 GB | 4,750 Mbps | 2,375 Mbps | 350 | 1200 |
|
||||
| 4XL | $950 | 16-core ARM (dedicated) | 64 GB | 4,750 Mbps | 4,750 Mbps | 420 | 2800 |
|
||||
| 8XL | $1,860 | 32-core ARM (dedicated) | 128 GB | 9,500 Mbps | 9,500 Mbps | 450 | 5600 |
|
||||
| 12XL | $2,790 | 48-core ARM (dedicated) | 192 GB | 14,250 Mbps | 14,250 Mbps | 480 | 8600 |
|
||||
| 16XL | $3,720 | 64-core ARM (dedicated) | 256 GB | 19,000 Mbps | 19,000 Mbps | 500 | 11,600 |
|
||||
|
||||
[Contact us](https://supabase.com/contact/enterprise) if you require a custom plan.
|
||||
|
||||
@@ -33,7 +33,9 @@ When considering compute upgrades, assess whether your bottlenecks are hardware-
|
||||
|
||||
## Disk IO bandwidth
|
||||
|
||||
Disk IO bandwidth can be measured in IOPS, a unit of measurement representing the number of input/output operations per second. These operations are calculated in Megabits per second. Depending on the read/write load of your application, you may need to upgrade to a larger instance capable of a higher number of IOPS. If you're unsure of how many IOPS your application requires, you can load test your project and inspect these [metrics in the Dashboard](https://app.supabase.com/project/_/reports). Alternatively, you can start on a smaller instance size and upgrade later as needed.
|
||||
SSD Disks are attached to your servers and the disk performance of your workload is determined by the Disk IO bandwidth of this connection. Smaller compute instances can burst up to the maximum disk IO bandwidth for 30 minutes in a day. Beyond that, the performance reverts to the baseline disk IO bandwidth. For example, the free tier can burst up to 2,606 Mbps for 30 minutes a day and reverts to the baseline performance of 87 Mbps. If you need consistent disk performance, choose the 4XL or larger compute add-on which has the same baseline and maximum disk IO bandwidth.
|
||||
|
||||
If you're unsure of how many IOPS your application requires, you can load test your project and inspect these [metrics in the Dashboard](https://app.supabase.com/project/_/reports). If the `Daily Disk IO Budget % Remaining` stat is less than 100%, it indicates that your workload has burst beyond the baseline IO throughput during the day. If this metric drops to zero, the workload has used up all the burst IO throughput minutes during the day and is running at the baseline performance. These projects are good candidates for upgrading to a larger compute add with higher baseline throughput.
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
|
||||
@@ -11,16 +11,15 @@ export const meta = {
|
||||
|
||||
<h2 className="text-base max-w-xl m-0">
|
||||
Supabase is an open source Firebase alternative providing all the backend features you need to
|
||||
build a product. [Learn more](/docs/guides/getting-started/architecture) about Supabase, start
|
||||
with the [tutorials](/docs/guides/getting-started#tutorials), or dive straight into the different
|
||||
products and APIs.
|
||||
build a product. [Learn more](/docs/guides/getting-started/architecture) about Supabase, follow a
|
||||
quickstart for an overview, or dive straight into the different products and APIs.
|
||||
</h2>
|
||||
|
||||
{/* start container */}
|
||||
|
||||
<div className="flex flex-col gap-12 my-12">
|
||||
|
||||
<div className="space-y-8 hidden">
|
||||
<div className="space-y-8">
|
||||
<div className="
|
||||
border border-scale-400
|
||||
relative overflow-hidden
|
||||
@@ -63,16 +62,21 @@ export const meta = {
|
||||
<Button size="small">Get started</Button>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-3 md:gap-6">
|
||||
<Link href={`/getting-started/quickstarts/reactjs`} passHref>
|
||||
<Link href={`/guides/getting-started/quickstarts/reactjs`} passHref>
|
||||
<a className="no-underline">
|
||||
<IconPanel title="ReactJS" icon="/docs/img/icons/react-icon" />
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/getting-started/quickstarts/nextjs`} passHref>
|
||||
<Link href={`/guides/getting-started/quickstarts/nextjs`} passHref>
|
||||
<a className="no-underline">
|
||||
<IconPanel title="NextJS" icon="/docs/img/icons/nextjs-icon" />
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/guides/getting-started/quickstarts/flutter`} passHref>
|
||||
<a className="no-underline">
|
||||
<IconPanel title="Flutter" icon="/docs/img/icons/flutter-icon" />
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -106,7 +110,7 @@ export const meta = {
|
||||
md:items-center md:gap-3
|
||||
">
|
||||
<h2 className="text-sm lg:text-base text-scale-1200 m-0">Ready to make a fully working app?</h2>
|
||||
<p className="text-sm lg:text-base text-scale-1100 m-0">Follow the step-by-step <Link href="/guides/tutorials"><a className="m-0 text-scale-1200 underline">tutorials</a></Link>.</p>
|
||||
<p className="text-sm lg:text-base text-scale-1100 m-0">Follow the step-by-step <Link href="/guides/getting-started#tutorials"><a className="m-0 text-scale-1200 underline">tutorials</a></Link>.</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
After Width: | Height: | Size: 4.3 MiB |
@@ -39,7 +39,7 @@ Supa SQL App turns human language into SQL queries. Simply tell it what you want
|
||||
|
||||
### Winner
|
||||
|
||||
[HTTP Sense](https://github.com/http-sense/http-sense) - by [@tchyut_p](https://twitter.com/tchyut_p).
|
||||
[HTTP Sense](https://github.com/http-sense/http-sense) - by [@tchyut_p](https://twitter.com/tchyut_p) and [@nkitsaini](https://github.com/nkitsaini).
|
||||
|
||||
HTTP Sense is a reverse proxy and network monitoring tool that provides comprehensive network monitoring for your backend servers and microservices. It provides a secure and high-performance gateway that can be used to monitor incoming traffic.
|
||||
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
---
|
||||
title: Supabase Beta December 2022
|
||||
description: This month the Beta Update is a Launch Week 6 Special, where we review the cascade of announcements.
|
||||
author: ant_wilson
|
||||
image: 2023-01-05-december-beta-update/monthly-update-december2022.jpg
|
||||
thumb: 2023-01-05-december-beta-update/monthly-update-december2022.jpg
|
||||
tags:
|
||||
- release-notes
|
||||
date: '2023-01-05'
|
||||
toc_depth: 3
|
||||
---
|
||||
|
||||
Happy New Year! Hope you are off to a great start. We closed 2022 on a high note with [Launch Week 6](https://supabase.com/launch-week) and a cascade of announcements. Here is everything we shipped so you can try them all 💻
|
||||
|
||||
## Day 1: New Docs
|
||||
|
||||

|
||||
|
||||
Documentation is super important to us, it's part of the product, and the Supabase docs needed a refresh. That's why we worked on a new redesign, better navigation, better reference docs, and we rebuilt the site using Next.js.
|
||||
|
||||
- [Blog Post](https://supabase.com/blog/new-supabase-docs-built-with-nextjs)
|
||||
- [Video announcement](https://www.youtube.com/watch?v=Q1Amk6iDlF8)
|
||||
|
||||
## Day 2: Storage v2 - Image resizing and Smart CDN
|
||||
|
||||

|
||||
|
||||
We introduced three new features for Supabase Storage: Image resizing, webhooks, and a Smart CDN. These features are designed to work together to deliver a next-gen image resizing system.
|
||||
|
||||
- [Blog Post](https://supabase.com/blog/storage-image-resizing-smart-cdn)
|
||||
- [Video announcement](https://www.youtube.com/watch?v=NpEl20iuOtg)
|
||||
|
||||
## Day 3 - Multi-factor Authentication via Row Level Security Enforcement
|
||||
|
||||

|
||||
|
||||
We released Multi-factor Authentication (MFA) for everyone, including the option to enforce it via RLS 🔐. Plus, in preparation for releasing SAML, we're "dogfooding" the feature with the introduction of Single Sign On (SSO) on our dashboard.
|
||||
|
||||
- [Blog post](https://supabase.com/blog/mfa-auth-via-rls)
|
||||
- [Video announcement](https://www.youtube.com/watch?v=He7LI2mv9v0)
|
||||
|
||||
## Day 4 - Supabase Wrappers, a Postgres FDW framework
|
||||
|
||||

|
||||
|
||||
Foreign Data Wrappers are a core feature of PostgreSQL. With Supabase Wrappers, we've extended this feature to query other databases or any other external system (including third-party APIs), using SQL.
|
||||
|
||||
- [Blog post](https://supabase.com/blog/postgres-foreign-data-wrappers-rust)
|
||||
- [Video announcement](https://www.youtube.com/watch?v=QA2qC5F-4OU)
|
||||
|
||||
## Day 5 - The Vault is now in Beta
|
||||
|
||||

|
||||
|
||||
The Vault is a new Postgres extension that makes it safe and easy to store encrypted secrets and encrypt other stored data in your database.
|
||||
|
||||
- [Blog post](https://supabase.com/blog/vault-now-in-beta)
|
||||
- [Video announcement](https://www.youtube.com/watch?v=QHLPNDrdN2w)
|
||||
|
||||
## Community Day
|
||||
|
||||

|
||||
|
||||
It's starting to feel like Supabase is really just a "community of communities" 🤗. Far too much has happened in the last 3 months for us to possibly feature it all here, so here's a collection of the highlights!
|
||||
|
||||
[Blog post](https://supabase.com/blog/launch-week-6-community-day)
|
||||
|
||||
## Postgres Ecosystem
|
||||
|
||||
We are all in on Postgres. That is why contributing to the ecosystem is always a priority. Launch Week 6 was no exception, with several announcements:
|
||||
|
||||

|
||||
|
||||
- [pg_graphql v1.0](https://supabase.com/blog/pg-graphql-v1)
|
||||
- [PostgREST 11 pre-release](https://supabase.com/blog/postgrest-11-prerelease)
|
||||
- [What's new in Postgres 15\?](https://supabase.com/blog/new-in-postgres-15)
|
||||
- [pg_crdt - an experimental CRDT extension for Postgres](https://supabase.com/blog/postgres-crdt)
|
||||
|
||||
## Custom Domains and Point-in-Time Recovery
|
||||
|
||||

|
||||
|
||||
Two key features to level up your projects are now available: Custom domains allow you to use your own domain for your Supabase project, while PITR provides the ability for a database to be restored at any specified point in time.
|
||||
|
||||
- [Custom Domains](https://supabase.com/blog/custom-domain-names)
|
||||
- [Point in Time Recovery](https://supabase.com/blog/postgres-point-in-time-recovery)
|
||||
|
||||
## Hackathon Winners
|
||||
|
||||

|
||||
|
||||
It was great to see so many high-quality projects. The decision wasn't easy, but in the end, the panel of judges chose Depulso as the winner of the Best Overall Project.
|
||||
|
||||
Congratulations 👏 to [@_iamkarthikeyan](https://twitter.com/_iamkarthikeyan) and [@SindhuMansi](https://twitter.com/SindhuMansi) who get each a Supabase Darkmode Keyboard and the $1500 Github sponsorship!
|
||||
|
||||
- [Full list of Winners](https://supabase.com/blog/launch-week-6-hackathon-winners)
|
||||
- [See all the submissions](https://www.madewithsupabase.com/launch-week-6)
|
||||
|
||||
## Extended Community Highlights
|
||||
|
||||

|
||||
|
||||
- FlutterFlow and Supabase integration. [Video](https://www.youtube.com/watch?v=hw9Q-NjASbU)
|
||||
- ClickHouse and PostgreSQL - a match made in data heaven. [Article](https://clickhouse.com/blog/migrating-data-between-clickhouse-postgres)
|
||||
- Build any Front-end like Admin Panel or CRM for your Supabase. [Article](https://blog.jetadmin.io/how-to-build-any-front-end-such-as-admin-panel-or-crm-for-your-supabase-in-minutes/)
|
||||
- Everything Svelte: a course with all you need to know to build a modern web application. [Course](https://www.everythingsvelte.com/)
|
||||
- Edgy Edge Functions: a look at a new Edge Function per week. [Youtube Playlist](https://www.youtube.com/playlist?list=PL5S4mPUpp4OulD3olUW8Eq1IYKpUbk5Ob)
|
||||
- Pixels, an app that provides canvas to create pixel art. [App](https://pixels.refine.dev/) | [Github Repo](https://github.com/refinedev/refine/tree/next/examples/pixels)
|
||||
- Supabase Angular authentication with RxJS Observables. [Tutorial](https://dev.to/kylerummens/supabase-angular-authentication-with-rxjs-observables-3d0f)
|
||||
- Create Calendar Events in React With Google Calendar API and Supabase. [Video Tutorial](https://www.youtube.com/watch?v=tgcCl52EN84)
|
||||
- Going Serverless with Supabase. [Part 1](https://dev.to/davepar/going-serverless-with-supabase-103h)
|
||||
- Supabase a Complete Backend Solution. [Video](https://www.youtube.com/watch?v=Ve0qE55fw0Y)
|
||||
- Supabase OAuth with SvelteKit (Discord, Google, GitHub). [Video](https://www.youtube.com/watch?v=KfezTtt2GsA)
|
||||
|
||||
## Meme Zone
|
||||
|
||||
As always, one of our favorite memes from last month. [Follow us on Twitter](https://twitter.com/supabase) for more.
|
||||
|
||||

|
||||
|
||||
See you next month!
|
||||
@@ -24,8 +24,7 @@ const Favicons = () => {
|
||||
/>
|
||||
<link rel="manifest" href={`${basePath}/favicon/site.webmanifest`} />
|
||||
{/* <link rel="mask-icon" href={`${basePath}/favicon/safari-pinned-tab.svg`} color="#333333" /> */}
|
||||
{/* <link rel="shortcut icon" href={`${basePath}/favicon/favicon.ico`} /> */}
|
||||
<link rel="shortcut icon" href={`${basePath}/favicon/favicon-lw.svg`} />
|
||||
<link rel="shortcut icon" href={`${basePath}/favicon/favicon.ico`} />
|
||||
<meta name="msapplication-TileColor" content="#1E1E1E" />
|
||||
<meta name="msapplication-config" content={`${basePath}/favicon/browserconfig.xml`} />
|
||||
<meta name="theme-color" content="#1E1E1E" />
|
||||
|
||||
@@ -14,16 +14,21 @@
|
||||
{ "key": "pricing", "title": "Pricing", "value": "$0" },
|
||||
{ "key": "cpu", "title": "CPU", "value": "2-core ARM (shared)" },
|
||||
{ "key": "memory", "title": "Memory", "value": "1 GB" },
|
||||
{ "key": "diskIO", "title": "Disk IO Bandwidth", "value": "Up to 2,606 Mbps" },
|
||||
{ "key": "maxDiskIO", "title": "Maximum Disk IO Bandwidth", "value": "2,606 Mbps" },
|
||||
{
|
||||
"key": "baseDiskIO",
|
||||
"title": "Baseline Disk IO Bandwidth",
|
||||
"value": "87 Mbps"
|
||||
},
|
||||
{
|
||||
"key": "directConnections",
|
||||
"title": "Connections: Direct",
|
||||
"value": "10 (recommended)"
|
||||
"title": "Connections: Direct (recommended)",
|
||||
"value": "10"
|
||||
},
|
||||
{
|
||||
"key": "poolerConnections",
|
||||
"title": "Connections: Pooler",
|
||||
"value": "50 (recommended)"
|
||||
"title": "Connections: Pooler (recommended)",
|
||||
"value": "50"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -33,16 +38,21 @@
|
||||
{ "key": "pricing", "title": "Pricing", "value": "$5" },
|
||||
{ "key": "cpu", "title": "CPU", "value": "2-core ARM (shared)" },
|
||||
{ "key": "memory", "title": "Memory", "value": "2 GB" },
|
||||
{ "key": "diskIO", "title": "Disk IO Bandwidth", "value": "Up to 2,606 Mbps" },
|
||||
{ "key": "maxDiskIO", "title": "Maximum Disk IO Bandwidth", "value": "2,606 Mbps" },
|
||||
{
|
||||
"key": "baseDiskIO",
|
||||
"title": "Baseline Disk IO Bandwidth",
|
||||
"value": "174 Mbps"
|
||||
},
|
||||
{
|
||||
"key": "directConnections",
|
||||
"title": "Connections: Direct",
|
||||
"value": "30 (recommended)"
|
||||
"title": "Connections: Direct (recommended)",
|
||||
"value": "30"
|
||||
},
|
||||
{
|
||||
"key": "poolerConnections",
|
||||
"title": "Connections: Pooler",
|
||||
"value": "75 (recommended)"
|
||||
"title": "Connections: Pooler (recommended)",
|
||||
"value": "75"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -52,16 +62,21 @@
|
||||
{ "key": "pricing", "title": "Pricing", "value": "$50" },
|
||||
{ "key": "cpu", "title": "CPU", "value": "2-core ARM (shared)" },
|
||||
{ "key": "memory", "title": "Memory", "value": "4 GB" },
|
||||
{ "key": "diskIO", "title": "Disk IO Bandwidth", "value": "Up to 2,606 Mbps" },
|
||||
{ "key": "maxDiskIO", "title": "Maximum Disk IO Bandwidth", "value": "2,606 Mbps" },
|
||||
{
|
||||
"key": "baseDiskIO",
|
||||
"title": "Baseline Disk IO Bandwidth",
|
||||
"value": "347 Mbps"
|
||||
},
|
||||
{
|
||||
"key": "directConnections",
|
||||
"title": "Connections: Direct",
|
||||
"value": "50 (recommended)"
|
||||
"title": "Connections: Direct (recommended)",
|
||||
"value": "50"
|
||||
},
|
||||
{
|
||||
"key": "poolerConnections",
|
||||
"title": "Connections: Pooler",
|
||||
"value": "150 (recommended)"
|
||||
"title": "Connections: Pooler (recommended)",
|
||||
"value": "150"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -71,16 +86,21 @@
|
||||
{ "key": "pricing", "title": "Pricing", "value": "$100" },
|
||||
{ "key": "cpu", "title": "CPU", "value": "2-core ARM (dedicated)" },
|
||||
{ "key": "memory", "title": "Memory", "value": "8 GB" },
|
||||
{ "key": "diskIO", "title": "Disk IO Bandwidth", "value": "Up to 4,750 Mbps" },
|
||||
{ "key": "maxDiskIO", "title": "Maximum Disk IO Bandwidth", "value": "4,750 Mbps" },
|
||||
{
|
||||
"key": "baseDiskIO",
|
||||
"title": "Baseline Disk IO Bandwidth",
|
||||
"value": "630 Mbps"
|
||||
},
|
||||
{
|
||||
"key": "directConnections",
|
||||
"title": "Connections: Direct",
|
||||
"value": "100 (recommended)"
|
||||
"title": "Connections: Direct (recommended)",
|
||||
"value": "100"
|
||||
},
|
||||
{
|
||||
"key": "poolerConnections",
|
||||
"title": "Connections: Pooler",
|
||||
"value": "300 (recommended)"
|
||||
"title": "Connections: Pooler (recommended)",
|
||||
"value": "300"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -90,16 +110,21 @@
|
||||
{ "key": "pricing", "title": "Pricing", "value": "$200" },
|
||||
{ "key": "cpu", "title": "CPU", "value": "4-core ARM (dedicated)" },
|
||||
{ "key": "memory", "title": "Memory", "value": "16 GB" },
|
||||
{ "key": "diskIO", "title": "Disk IO Bandwidth", "value": "Up to 4,750 Mbps" },
|
||||
{ "key": "maxDiskIO", "title": "Maximum Disk IO Bandwidth", "value": "4,750 Mbps" },
|
||||
{
|
||||
"key": "baseDiskIO",
|
||||
"title": "Baseline Disk IO Bandwidth",
|
||||
"value": "1,188 Mbps"
|
||||
},
|
||||
{
|
||||
"key": "directConnections",
|
||||
"title": "Connections: Direct",
|
||||
"value": "200 (recommended)"
|
||||
"title": "Connections: Direct (recommended)",
|
||||
"value": "200"
|
||||
},
|
||||
{
|
||||
"key": "poolerConnections",
|
||||
"title": "Connections: Pooler",
|
||||
"value": "600 (recommended)"
|
||||
"title": "Connections: Pooler (recommended)",
|
||||
"value": "600"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -109,16 +134,21 @@
|
||||
{ "key": "pricing", "title": "Pricing", "value": "$400" },
|
||||
{ "key": "cpu", "title": "CPU", "value": "8-core ARM (dedicated)" },
|
||||
{ "key": "memory", "title": "Memory", "value": "32 GB" },
|
||||
{ "key": "diskIO", "title": "Disk IO Bandwidth", "value": "Up to 4,750 Mbps" },
|
||||
{ "key": "maxDiskIO", "title": "Maximum Disk IO Bandwidth", "value": "4,750 Mbps" },
|
||||
{
|
||||
"key": "baseDiskIO",
|
||||
"title": "Baseline Disk IO Bandwidth",
|
||||
"value": "2,375 Mbps"
|
||||
},
|
||||
{
|
||||
"key": "directConnections",
|
||||
"title": "Connections: Direct",
|
||||
"value": "350 (recommended)"
|
||||
"title": "Connections: Direct (recommended)",
|
||||
"value": "350"
|
||||
},
|
||||
{
|
||||
"key": "poolerConnections",
|
||||
"title": "Connections: Pooler",
|
||||
"value": "1200 (recommended)"
|
||||
"title": "Connections: Pooler (recommended)",
|
||||
"value": "1200"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -128,16 +158,21 @@
|
||||
{ "key": "pricing", "title": "Pricing", "value": "$950" },
|
||||
{ "key": "cpu", "title": "CPU", "value": "16-core ARM (dedicated)" },
|
||||
{ "key": "memory", "title": "Memory", "value": "64 GB" },
|
||||
{ "key": "diskIO", "title": "Disk IO Bandwidth", "value": "Up to 4,750 Mbps" },
|
||||
{ "key": "maxDiskIO", "title": "Maximum Disk IO Bandwidth", "value": "4,750 Mbps" },
|
||||
{
|
||||
"key": "baseDiskIO",
|
||||
"title": "Baseline Disk IO Bandwidth",
|
||||
"value": "4,750 Mbps"
|
||||
},
|
||||
{
|
||||
"key": "directConnections",
|
||||
"title": "Connections: Direct",
|
||||
"value": "420 (recommended)"
|
||||
"title": "Connections: Direct (recommended)",
|
||||
"value": "420"
|
||||
},
|
||||
{
|
||||
"key": "poolerConnections",
|
||||
"title": "Connections: Pooler",
|
||||
"value": "2800 (recommended)"
|
||||
"title": "Connections: Pooler (recommended)",
|
||||
"value": "2800"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -147,16 +182,21 @@
|
||||
{ "key": "pricing", "title": "Pricing", "value": "$1,860" },
|
||||
{ "key": "cpu", "title": "CPU", "value": "32-core ARM (dedicated)" },
|
||||
{ "key": "memory", "title": "Memory", "value": "128 GB" },
|
||||
{ "key": "diskIO", "title": "Disk IO Bandwidth", "value": "Up to 9,000 Mbps" },
|
||||
{ "key": "maxDiskIO", "title": "Maximum Disk IO Bandwidth", "value": "9,500 Mbps" },
|
||||
{
|
||||
"key": "baseDiskIO",
|
||||
"title": "Baseline Disk IO Bandwidth",
|
||||
"value": "9,500 Mbps"
|
||||
},
|
||||
{
|
||||
"key": "directConnections",
|
||||
"title": "Connections: Direct",
|
||||
"value": "450 (recommended)"
|
||||
"title": "Connections: Direct (recommended)",
|
||||
"value": "450"
|
||||
},
|
||||
{
|
||||
"key": "poolerConnections",
|
||||
"title": "Connections: Pooler",
|
||||
"value": "5600 (recommended)"
|
||||
"title": "Connections: Pooler (recommended)",
|
||||
"value": "5600"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -166,16 +206,25 @@
|
||||
{ "key": "pricing", "title": "Pricing", "value": "$2,790" },
|
||||
{ "key": "cpu", "title": "CPU", "value": "48-core ARM (dedicated)" },
|
||||
{ "key": "memory", "title": "Memory", "value": "192 GB" },
|
||||
{ "key": "diskIO", "title": "Disk IO Bandwidth", "value": "Up to 13,500 Mbps" },
|
||||
{
|
||||
"key": "maxDiskIO",
|
||||
"title": "Maximum Disk IO Bandwidth",
|
||||
"value": "14,250 Mbps"
|
||||
},
|
||||
{
|
||||
"key": "baseDiskIO",
|
||||
"title": "Baseline Disk IO Bandwidth",
|
||||
"value": "14,250 Mbps"
|
||||
},
|
||||
{
|
||||
"key": "directConnections",
|
||||
"title": "Connections: Direct",
|
||||
"value": "480 (recommended)"
|
||||
"title": "Connections: Direct (recommended)",
|
||||
"value": "480"
|
||||
},
|
||||
{
|
||||
"key": "poolerConnections",
|
||||
"title": "Connections: Pooler",
|
||||
"value": "8600 (recommended)"
|
||||
"title": "Connections: Pooler (recommended)",
|
||||
"value": "8600"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -185,16 +234,25 @@
|
||||
{ "key": "pricing", "title": "Pricing", "value": "$3,720" },
|
||||
{ "key": "cpu", "title": "CPU", "value": "64-core ARM (dedicated)" },
|
||||
{ "key": "memory", "title": "Memory", "value": "256 GB" },
|
||||
{ "key": "diskIO", "title": "Disk IO Bandwidth", "value": "Up to 19,000 Mbps" },
|
||||
{
|
||||
"key": "maxDiskIO",
|
||||
"title": "Maximum Disk IO Bandwidth",
|
||||
"value": "19,000 Mbps"
|
||||
},
|
||||
{
|
||||
"key": "baseDiskIO",
|
||||
"title": "Baseline Disk IO Bandwidth",
|
||||
"value": "19,000 Mbps"
|
||||
},
|
||||
{
|
||||
"key": "directConnections",
|
||||
"title": "Connections: Direct",
|
||||
"value": "500 (recommended)"
|
||||
"title": "Connections: Direct (recommended)",
|
||||
"value": "500"
|
||||
},
|
||||
{
|
||||
"key": "poolerConnections",
|
||||
"title": "Connections: Pooler",
|
||||
"value": "11,600 (recommended)"
|
||||
"title": "Connections: Pooler (recommended)",
|
||||
"value": "11,600"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -204,15 +262,20 @@
|
||||
{ "key": "pricing", "title": "Pricing", "value": "Contact Us" },
|
||||
{ "key": "cpu", "title": "CPU", "value": "Contact Us" },
|
||||
{ "key": "memory", "title": "Memory", "value": "Contact Us" },
|
||||
{ "key": "diskIO", "title": "Disk IO Bandwidth", "value": "Contact Us" },
|
||||
{ "key": "maxDiskIO", "title": "Maximum Disk IO Bandwidth", "value": "Contact Us" },
|
||||
{
|
||||
"key": "baseDiskIO",
|
||||
"title": "Baseline Disk IO Bandwidth",
|
||||
"value": "Contact Us"
|
||||
},
|
||||
{
|
||||
"key": "directConnections",
|
||||
"title": "Connections: Direct",
|
||||
"title": "Connections: Direct (recommended)",
|
||||
"value": "Contact Us"
|
||||
},
|
||||
{
|
||||
"key": "poolerConnections",
|
||||
"title": "Connections: Pooler",
|
||||
"title": "Connections: Pooler (recommended)",
|
||||
"value": "Contact Us"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1217,26 +1217,11 @@ module.exports = [
|
||||
source: '/docs/reference/cli/supabase-db-commit',
|
||||
destination: '/docs/reference/cli/usage',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/reference/cli/supabase-db-reset',
|
||||
destination: '/docs/reference/cli/usage',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/reference/cli/supabase-db-remote-set',
|
||||
destination: '/docs/reference/cli/usage',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/reference/cli/supabase-db-remote-commit',
|
||||
destination: '/docs/reference/cli/usage',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/reference/cli/supabase-db-push',
|
||||
destination: '/docs/reference/cli/usage',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/reference/cli/supabase-functions-delete',
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
Pricing Changelog
|
||||
|
||||
### 09 Jan 2023
|
||||
----
|
||||
|
||||
Database Compute Add ons
|
||||
- Clarified iops values to the latest instance types
|
||||
|
||||
### 02 Nov 2022
|
||||
----
|
||||
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="1.48403" y="1.48403" width="29.0319" height="29.0319" rx="4.33084" fill="url(#paint0_linear_920_70794)"/>
|
||||
<rect x="1.48403" y="1.48403" width="29.0319" height="29.0319" rx="4.33084" stroke="url(#paint1_linear_920_70794)" stroke-width="0.577445"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.9125 7.63694C15.9013 6.89111 14.9598 6.57102 14.4954 7.1552L7.48827 15.9706C6.6612 17.011 7.40299 18.5455 8.73305 18.5455H15.9793L16.0645 24.2263C16.0757 24.9721 17.0172 25.2922 17.4815 24.7081L24.4887 15.8927C25.3157 14.8522 24.5739 13.3177 23.2439 13.3177H15.9498L15.9125 7.63694Z" fill="url(#paint2_linear_920_70794)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_920_70794" x1="10.1877" y1="9.76698" x2="25.4827" y2="42.2286" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#14292C"/>
|
||||
<stop offset="1" stop-color="#121212"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_920_70794" x1="19.9762" y1="1.25667" x2="28.1178" y2="12.0371" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#76A891" stop-opacity="0.11"/>
|
||||
<stop offset="1" stop-color="#132525" stop-opacity="0.35"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_920_70794" x1="3.62028" y1="6.85546" x2="32.7486" y2="6.85546" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#F4FFFA"/>
|
||||
<stop offset="1" stop-color="#D8FFEC" stop-opacity="0.6"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 205 KiB |
|
After Width: | Height: | Size: 321 KiB |
|
After Width: | Height: | Size: 143 KiB |
|
After Width: | Height: | Size: 171 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 203 KiB |
|
After Width: | Height: | Size: 175 KiB |
|
After Width: | Height: | Size: 163 KiB |
|
After Width: | Height: | Size: 265 KiB |
|
After Width: | Height: | Size: 122 KiB |
@@ -18,6 +18,8 @@ services:
|
||||
db:
|
||||
restart: 'no'
|
||||
volumes:
|
||||
# Always use a fresh database when developing
|
||||
- /var/lib/postgresql/data
|
||||
# Seed data should be inserted last (alphabetical order)
|
||||
- ./dev/data.sql:/docker-entrypoint-initdb.d/seed.sql
|
||||
storage:
|
||||
|
||||
@@ -47,7 +47,7 @@ services:
|
||||
|
||||
auth:
|
||||
container_name: supabase-auth
|
||||
image: supabase/gotrue:v2.31.0
|
||||
image: supabase/gotrue:v2.40.1
|
||||
depends_on:
|
||||
db: # Disable this if you are using an external Postgres database
|
||||
condition: service_healthy
|
||||
@@ -104,29 +104,30 @@ services:
|
||||
PGRST_DB_USE_LEGACY_GUCS: "false"
|
||||
|
||||
realtime:
|
||||
container_name: supabase-realtime
|
||||
image: supabase/realtime:v0.25.1
|
||||
container_name: realtime-dev.supabase-realtime
|
||||
image: supabase/realtime:v2.0.2
|
||||
depends_on:
|
||||
db: # Disable this if you are using an external Postgres database
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
PORT: 4000
|
||||
DB_HOST: ${POSTGRES_HOST}
|
||||
DB_PORT: ${POSTGRES_PORT}
|
||||
DB_NAME: ${POSTGRES_DB}
|
||||
DB_USER: supabase_admin
|
||||
DB_USER: ${POSTGRES_USER}
|
||||
DB_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
DB_SSL: "false"
|
||||
PORT: 4000
|
||||
JWT_SECRET: ${JWT_SECRET}
|
||||
REPLICATION_MODE: RLS
|
||||
REPLICATION_POLL_INTERVAL: 100
|
||||
SECURE_CHANNELS: "true"
|
||||
SLOT_NAME: supabase_realtime_rls
|
||||
TEMPORARY_SLOT: "true"
|
||||
DB_NAME: ${POSTGRES_DB}
|
||||
DB_AFTER_CONNECT_QUERY: 'SET search_path TO _realtime'
|
||||
DB_ENC_KEY: supabaserealtime
|
||||
API_JWT_SECRET: ${JWT_SECRET}
|
||||
FLY_ALLOC_ID: fly123
|
||||
FLY_APP_NAME: realtime
|
||||
SECRET_KEY_BASE: UpNVntn3cDxHJpq99YMc1T1AQgQpc8kfYTuRgBiYa15BLrx8etQoXz3gZv1/u2oq
|
||||
ERL_AFLAGS: -proto_dist inet_tcp
|
||||
ENABLE_TAILSCALE: "false"
|
||||
DNS_NODES: "''"
|
||||
command: >
|
||||
bash -c "./prod/rel/realtime/bin/realtime eval Realtime.Release.migrate
|
||||
&& ./prod/rel/realtime/bin/realtime start"
|
||||
sh -c "/app/bin/migrate && /app/bin/realtime eval 'Realtime.Release.seeds(Realtime.Repo)' && /app/bin/server"
|
||||
|
||||
storage:
|
||||
container_name: supabase-storage
|
||||
@@ -208,5 +209,6 @@ services:
|
||||
PGDATABASE: ${POSTGRES_DB}
|
||||
POSTGRES_DB: ${POSTGRES_DB}
|
||||
volumes:
|
||||
- ./volumes/db/realtime.sql:/docker-entrypoint-initdb.d/realtime.sql
|
||||
- ./volumes/db/roles.sql:/docker-entrypoint-initdb.d/roles.sql
|
||||
- ./volumes/db/data:/var/lib/postgresql/data
|
||||
@@ -124,7 +124,7 @@ services:
|
||||
## Secure Realtime routes
|
||||
- name: realtime-v1
|
||||
_comment: "Realtime: /realtime/v1/* -> ws://realtime:4000/socket/*"
|
||||
url: http://realtime:4000/socket/
|
||||
url: http://realtime-dev.supabase-realtime:4000/socket/
|
||||
routes:
|
||||
- name: realtime-v1-all
|
||||
strip_path: true
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
\set pguser `echo "$POSTGRES_USER"`
|
||||
|
||||
create schema if not exists _realtime;
|
||||
alter schema _realtime owner to :pguser;
|
||||
@@ -2180,9 +2180,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.0"
|
||||
@@ -4922,9 +4922,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"minimist": "^1.2.0"
|
||||
|
||||
@@ -7093,9 +7093,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/json5": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
|
||||
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"json5": "lib/cli.js"
|
||||
@@ -17212,9 +17212,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"json5": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
|
||||
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
||||
"dev": true
|
||||
},
|
||||
"jsonc-parser": {
|
||||
|
||||
@@ -2364,9 +2364,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.0"
|
||||
@@ -5219,9 +5219,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"minimist": "^1.2.0"
|
||||
|
||||
@@ -4385,9 +4385,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/json5": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
|
||||
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"json5": "lib/cli.js"
|
||||
@@ -10873,9 +10873,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"json5": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
|
||||
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
||||
"dev": true
|
||||
},
|
||||
"jsonc-parser": {
|
||||
|
||||
@@ -4140,9 +4140,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/cache-loader/node_modules/json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.0"
|
||||
},
|
||||
@@ -6823,9 +6823,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/html-webpack-plugin/node_modules/json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.0"
|
||||
},
|
||||
@@ -7642,9 +7642,9 @@
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
|
||||
},
|
||||
"node_modules/json5": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
|
||||
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
||||
"bin": {
|
||||
"json5": "lib/cli.js"
|
||||
},
|
||||
@@ -9446,9 +9446,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-loader/node_modules/json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.0"
|
||||
},
|
||||
@@ -12440,9 +12440,9 @@
|
||||
"integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA=="
|
||||
},
|
||||
"node_modules/vue-loader/node_modules/json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.0"
|
||||
},
|
||||
@@ -12591,9 +12591,9 @@
|
||||
"integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA=="
|
||||
},
|
||||
"node_modules/vue-style-loader/node_modules/json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.0"
|
||||
},
|
||||
@@ -13407,9 +13407,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/webpack/node_modules/json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.0"
|
||||
},
|
||||
@@ -17088,9 +17088,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"requires": {
|
||||
"minimist": "^1.2.0"
|
||||
}
|
||||
@@ -19177,9 +19177,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"requires": {
|
||||
"minimist": "^1.2.0"
|
||||
}
|
||||
@@ -19762,9 +19762,9 @@
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
|
||||
},
|
||||
"json5": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
|
||||
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA=="
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "6.1.0",
|
||||
@@ -21177,9 +21177,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"requires": {
|
||||
"minimist": "^1.2.0"
|
||||
}
|
||||
@@ -23561,9 +23561,9 @@
|
||||
"integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA=="
|
||||
},
|
||||
"json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"requires": {
|
||||
"minimist": "^1.2.0"
|
||||
}
|
||||
@@ -23686,9 +23686,9 @@
|
||||
"integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA=="
|
||||
},
|
||||
"json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"requires": {
|
||||
"minimist": "^1.2.0"
|
||||
}
|
||||
@@ -24190,9 +24190,9 @@
|
||||
}
|
||||
},
|
||||
"json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"requires": {
|
||||
"minimist": "^1.2.0"
|
||||
}
|
||||
|
||||
@@ -4273,9 +4273,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/babel-loader/node_modules/json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.0"
|
||||
},
|
||||
@@ -9521,9 +9521,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/html-webpack-plugin/node_modules/json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.0"
|
||||
},
|
||||
@@ -12595,12 +12595,9 @@
|
||||
"integrity": "sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA=="
|
||||
},
|
||||
"node_modules/json5": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz",
|
||||
"integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==",
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.5"
|
||||
},
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
||||
"bin": {
|
||||
"json5": "lib/cli.js"
|
||||
},
|
||||
@@ -13133,9 +13130,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/mini-css-extract-plugin/node_modules/json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.0"
|
||||
},
|
||||
@@ -14942,9 +14939,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-loader/node_modules/json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.0"
|
||||
},
|
||||
@@ -16818,9 +16815,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/resolve-url-loader/node_modules/json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.0"
|
||||
},
|
||||
@@ -19194,9 +19191,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tsconfig-paths/node_modules/json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.0"
|
||||
},
|
||||
@@ -20999,9 +20996,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/webpack/node_modules/json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.0"
|
||||
},
|
||||
@@ -24968,9 +24965,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"requires": {
|
||||
"minimist": "^1.2.0"
|
||||
}
|
||||
@@ -29143,9 +29140,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"requires": {
|
||||
"minimist": "^1.2.0"
|
||||
}
|
||||
@@ -31411,12 +31408,9 @@
|
||||
"integrity": "sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA=="
|
||||
},
|
||||
"json5": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz",
|
||||
"integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==",
|
||||
"requires": {
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "6.1.0",
|
||||
@@ -31836,9 +31830,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"requires": {
|
||||
"minimist": "^1.2.0"
|
||||
}
|
||||
@@ -33259,9 +33253,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"requires": {
|
||||
"minimist": "^1.2.0"
|
||||
}
|
||||
@@ -34764,9 +34758,9 @@
|
||||
"integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k="
|
||||
},
|
||||
"json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"requires": {
|
||||
"minimist": "^1.2.0"
|
||||
}
|
||||
@@ -36653,9 +36647,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"requires": {
|
||||
"minimist": "^1.2.0"
|
||||
}
|
||||
@@ -37523,9 +37517,9 @@
|
||||
"integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0="
|
||||
},
|
||||
"json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"requires": {
|
||||
"minimist": "^1.2.0"
|
||||
}
|
||||
|
||||
@@ -1320,9 +1320,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/json5": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
|
||||
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"json5": "lib/cli.js"
|
||||
@@ -2631,9 +2631,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"json5": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
|
||||
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
||||
"dev": true
|
||||
},
|
||||
"merge-anything": {
|
||||
|
||||
@@ -14540,9 +14540,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/css-loader/node_modules/json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.0"
|
||||
@@ -19585,9 +19585,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/html-webpack-plugin/node_modules/json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.0"
|
||||
@@ -22588,9 +22588,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/js-yaml-loader/node_modules/json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.0"
|
||||
},
|
||||
@@ -22734,9 +22734,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/json5": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
|
||||
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
||||
"bin": {
|
||||
"json5": "lib/cli.js"
|
||||
},
|
||||
@@ -34683,9 +34683,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tsconfig-paths/node_modules/json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.0"
|
||||
},
|
||||
@@ -36622,9 +36622,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/webpack/node_modules/json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.0"
|
||||
},
|
||||
@@ -48428,9 +48428,9 @@
|
||||
}
|
||||
},
|
||||
"json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"minimist": "^1.2.0"
|
||||
@@ -52827,9 +52827,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"minimist": "^1.2.0"
|
||||
@@ -55190,9 +55190,9 @@
|
||||
}
|
||||
},
|
||||
"json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"requires": {
|
||||
"minimist": "^1.2.0"
|
||||
}
|
||||
@@ -55297,9 +55297,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"json5": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
|
||||
"integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA=="
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="
|
||||
},
|
||||
"jsonc-parser": {
|
||||
"version": "3.2.0",
|
||||
@@ -64542,9 +64542,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"requires": {
|
||||
"minimist": "^1.2.0"
|
||||
}
|
||||
@@ -66059,9 +66059,9 @@
|
||||
"integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw=="
|
||||
},
|
||||
"json5": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
|
||||
"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
|
||||
"integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
|
||||
"requires": {
|
||||
"minimist": "^1.2.0"
|
||||
}
|
||||
|
||||
@@ -338,165 +338,6 @@
|
||||
"security": [{ "apiKeyHeader": [], "apiKeyParam": [] }, { "bearer": [] }]
|
||||
}
|
||||
},
|
||||
"/v1/projects/{ref}/secrets": {
|
||||
"get": {
|
||||
"operationId": "getSecrets",
|
||||
"summary": "List all secrets",
|
||||
"description": "Returns all secrets you've previously added to the specified project.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "ref",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"description": "Project ref",
|
||||
"schema": { "minLength": 20, "maxLength": 20, "type": "string" }
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/components/schemas/SecretResponse" }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": { "description": "" },
|
||||
"500": { "description": "Failed to retrieve project's secrets" }
|
||||
},
|
||||
"tags": ["secrets"],
|
||||
"security": [{ "apiKeyHeader": [], "apiKeyParam": [] }, { "bearer": [] }]
|
||||
},
|
||||
"post": {
|
||||
"operationId": "createSecrets",
|
||||
"summary": "Bulk create secrets",
|
||||
"description": "Creates multiple secrets and adds them to the specified project.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "ref",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"description": "Project ref",
|
||||
"schema": { "minLength": 20, "maxLength": 20, "type": "string" }
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/components/schemas/CreateSecretBody" }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": { "description": "" },
|
||||
"403": { "description": "" },
|
||||
"500": { "description": "Failed to create project's secrets" }
|
||||
},
|
||||
"tags": ["secrets"],
|
||||
"security": [{ "apiKeyHeader": [], "apiKeyParam": [] }, { "bearer": [] }]
|
||||
},
|
||||
"delete": {
|
||||
"operationId": "deleteSecrets",
|
||||
"summary": "Bulk delete secrets",
|
||||
"description": "Deletes all secrets with the given names from the specified project",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "ref",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"description": "Project ref",
|
||||
"schema": { "minLength": 20, "maxLength": 20, "type": "string" }
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": { "schema": { "type": "array", "items": { "type": "string" } } }
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": { "application/json": { "schema": { "type": "object" } } }
|
||||
},
|
||||
"403": { "description": "" },
|
||||
"500": { "description": "Failed to delete secrets with given names" }
|
||||
},
|
||||
"tags": ["secrets"],
|
||||
"security": [{ "apiKeyHeader": [], "apiKeyParam": [] }, { "bearer": [] }]
|
||||
}
|
||||
},
|
||||
"/v1/projects/{ref}/postgrest": {
|
||||
"get": {
|
||||
"operationId": "getPostgRESTConfig",
|
||||
"summary": "Gets project's postgrest config",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "ref",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"description": "Project ref",
|
||||
"schema": { "minLength": 20, "maxLength": 20, "type": "string" }
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/PostgrestConfigResponse" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": { "description": "" },
|
||||
"500": { "description": "Failed to retrieve project's postgrest config" }
|
||||
},
|
||||
"tags": ["services"],
|
||||
"security": [{ "bearer": [] }]
|
||||
},
|
||||
"patch": {
|
||||
"operationId": "updatePostgRESTConfig",
|
||||
"summary": "Updates project's postgrest config",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "ref",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"description": "Project ref",
|
||||
"schema": { "minLength": 20, "maxLength": 20, "type": "string" }
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/UpdatePostgrestConfigBody" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/PostgrestConfigResponse" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": { "description": "" },
|
||||
"500": { "description": "Failed to update project's postgrest config" }
|
||||
},
|
||||
"tags": ["services"],
|
||||
"security": [{ "bearer": [] }]
|
||||
}
|
||||
},
|
||||
"/v1/projects/{ref}/custom-hostname": {
|
||||
"get": {
|
||||
"operationId": "getCustomHostnameConfig",
|
||||
@@ -641,106 +482,6 @@
|
||||
"security": [{ "bearer": [] }]
|
||||
}
|
||||
},
|
||||
"/v1/projects/{ref}/pgsodium": {
|
||||
"get": {
|
||||
"operationId": "getConfig",
|
||||
"summary": "Gets project's pgsodium config",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "ref",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"description": "Project ref",
|
||||
"schema": { "minLength": 20, "maxLength": 20, "type": "string" }
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/PgsodiumConfigResponse" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": { "description": "" },
|
||||
"500": { "description": "Failed to retrieve project's pgsodium config" }
|
||||
},
|
||||
"tags": ["pgsodium (beta)"],
|
||||
"security": [{ "bearer": [] }]
|
||||
},
|
||||
"put": {
|
||||
"operationId": "updateConfig",
|
||||
"summary": "Updates project's pgsodium config. Updating the root_key can cause all data encrypted with the older key to become inaccessible.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "ref",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"description": "Project ref",
|
||||
"schema": { "minLength": 20, "maxLength": 20, "type": "string" }
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/UpdatePgsodiumConfigBody" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/PgsodiumConfigResponse" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": { "description": "" },
|
||||
"500": { "description": "Failed to update project's pgsodium config" }
|
||||
},
|
||||
"tags": ["pgsodium (beta)"],
|
||||
"security": [{ "bearer": [] }]
|
||||
}
|
||||
},
|
||||
"/v1/projects/{ref}/types/typescript": {
|
||||
"get": {
|
||||
"operationId": "getTypescriptTypes",
|
||||
"summary": "Generate TypeScript types",
|
||||
"description": "Returns the TypeScript types of your schema for use with supabase-js.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "included_schemas",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"schema": { "default": "public", "type": "string" }
|
||||
},
|
||||
{
|
||||
"name": "ref",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"description": "Project ref",
|
||||
"schema": { "minLength": 20, "maxLength": 20, "type": "string" }
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/TypescriptResponse" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": { "description": "" },
|
||||
"500": { "description": "Failed to generate TypeScript types" }
|
||||
},
|
||||
"tags": ["projects"],
|
||||
"security": [{ "apiKeyHeader": [], "apiKeyParam": [] }, { "bearer": [] }]
|
||||
}
|
||||
},
|
||||
"/v1/projects/{ref}/network-bans/retrieve": {
|
||||
"post": {
|
||||
"operationId": "getNetworkBans",
|
||||
@@ -866,6 +607,329 @@
|
||||
"security": [{ "bearer": [] }]
|
||||
}
|
||||
},
|
||||
"/v1/projects/{ref}/pgsodium": {
|
||||
"get": {
|
||||
"operationId": "getConfig",
|
||||
"summary": "Gets project's pgsodium config",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "ref",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"description": "Project ref",
|
||||
"schema": { "minLength": 20, "maxLength": 20, "type": "string" }
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/PgsodiumConfigResponse" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": { "description": "" },
|
||||
"500": { "description": "Failed to retrieve project's pgsodium config" }
|
||||
},
|
||||
"tags": ["pgsodium (beta)"],
|
||||
"security": [{ "bearer": [] }]
|
||||
},
|
||||
"put": {
|
||||
"operationId": "updateConfig",
|
||||
"summary": "Updates project's pgsodium config. Updating the root_key can cause all data encrypted with the older key to become inaccessible.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "ref",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"description": "Project ref",
|
||||
"schema": { "minLength": 20, "maxLength": 20, "type": "string" }
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/UpdatePgsodiumConfigBody" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/PgsodiumConfigResponse" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": { "description": "" },
|
||||
"500": { "description": "Failed to update project's pgsodium config" }
|
||||
},
|
||||
"tags": ["pgsodium (beta)"],
|
||||
"security": [{ "bearer": [] }]
|
||||
}
|
||||
},
|
||||
"/v1/projects/{ref}/postgrest": {
|
||||
"get": {
|
||||
"operationId": "getPostgRESTConfig",
|
||||
"summary": "Gets project's postgrest config",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "ref",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"description": "Project ref",
|
||||
"schema": { "minLength": 20, "maxLength": 20, "type": "string" }
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/PostgrestConfigResponse" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": { "description": "" },
|
||||
"500": { "description": "Failed to retrieve project's postgrest config" }
|
||||
},
|
||||
"tags": ["services"],
|
||||
"security": [{ "bearer": [] }]
|
||||
},
|
||||
"patch": {
|
||||
"operationId": "updatePostgRESTConfig",
|
||||
"summary": "Updates project's postgrest config",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "ref",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"description": "Project ref",
|
||||
"schema": { "minLength": 20, "maxLength": 20, "type": "string" }
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/UpdatePostgrestConfigBody" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/PostgrestConfigResponse" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": { "description": "" },
|
||||
"500": { "description": "Failed to update project's postgrest config" }
|
||||
},
|
||||
"tags": ["services"],
|
||||
"security": [{ "bearer": [] }]
|
||||
}
|
||||
},
|
||||
"/v1/projects/{ref}/secrets": {
|
||||
"get": {
|
||||
"operationId": "getSecrets",
|
||||
"summary": "List all secrets",
|
||||
"description": "Returns all secrets you've previously added to the specified project.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "ref",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"description": "Project ref",
|
||||
"schema": { "minLength": 20, "maxLength": 20, "type": "string" }
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/components/schemas/SecretResponse" }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": { "description": "" },
|
||||
"500": { "description": "Failed to retrieve project's secrets" }
|
||||
},
|
||||
"tags": ["secrets"],
|
||||
"security": [{ "apiKeyHeader": [], "apiKeyParam": [] }, { "bearer": [] }]
|
||||
},
|
||||
"post": {
|
||||
"operationId": "createSecrets",
|
||||
"summary": "Bulk create secrets",
|
||||
"description": "Creates multiple secrets and adds them to the specified project.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "ref",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"description": "Project ref",
|
||||
"schema": { "minLength": 20, "maxLength": 20, "type": "string" }
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/components/schemas/CreateSecretBody" }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": { "description": "" },
|
||||
"403": { "description": "" },
|
||||
"500": { "description": "Failed to create project's secrets" }
|
||||
},
|
||||
"tags": ["secrets"],
|
||||
"security": [{ "apiKeyHeader": [], "apiKeyParam": [] }, { "bearer": [] }]
|
||||
},
|
||||
"delete": {
|
||||
"operationId": "deleteSecrets",
|
||||
"summary": "Bulk delete secrets",
|
||||
"description": "Deletes all secrets with the given names from the specified project",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "ref",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"description": "Project ref",
|
||||
"schema": { "minLength": 20, "maxLength": 20, "type": "string" }
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": { "schema": { "type": "array", "items": { "type": "string" } } }
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": { "application/json": { "schema": { "type": "object" } } }
|
||||
},
|
||||
"403": { "description": "" },
|
||||
"500": { "description": "Failed to delete secrets with given names" }
|
||||
},
|
||||
"tags": ["secrets"],
|
||||
"security": [{ "apiKeyHeader": [], "apiKeyParam": [] }, { "bearer": [] }]
|
||||
}
|
||||
},
|
||||
"/v1/projects/{ref}/ssl-enforcement": {
|
||||
"get": {
|
||||
"operationId": "getSslEnforcementConfig",
|
||||
"summary": "Get project's SSL enforcement configuration.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "ref",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"description": "Project ref",
|
||||
"schema": { "minLength": 20, "maxLength": 20, "type": "string" }
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/SslEnforcementResponse" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": { "description": "" },
|
||||
"500": { "description": "Failed to retrieve project's SSL enforcement config" }
|
||||
},
|
||||
"tags": ["ssl enforcement (beta)"],
|
||||
"security": [{ "bearer": [] }]
|
||||
},
|
||||
"put": {
|
||||
"operationId": "updateSslEnforcementConfig",
|
||||
"summary": "Update project's SSL enforcement configuration.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "ref",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"description": "Project ref",
|
||||
"schema": { "minLength": 20, "maxLength": 20, "type": "string" }
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/SslEnforcementRequest" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/SslEnforcementResponse" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": { "description": "" },
|
||||
"500": { "description": "Failed to update project's SSL enforcement configuration." }
|
||||
},
|
||||
"tags": ["ssl enforcement (beta)"],
|
||||
"security": [{ "bearer": [] }]
|
||||
}
|
||||
},
|
||||
"/v1/projects/{ref}/types/typescript": {
|
||||
"get": {
|
||||
"operationId": "getTypescriptTypes",
|
||||
"summary": "Generate TypeScript types",
|
||||
"description": "Returns the TypeScript types of your schema for use with supabase-js.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "included_schemas",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"schema": { "default": "public", "type": "string" }
|
||||
},
|
||||
{
|
||||
"name": "ref",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"description": "Project ref",
|
||||
"schema": { "minLength": 20, "maxLength": 20, "type": "string" }
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/TypescriptResponse" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": { "description": "" },
|
||||
"500": { "description": "Failed to generate TypeScript types" }
|
||||
},
|
||||
"tags": ["projects"],
|
||||
"security": [{ "apiKeyHeader": [], "apiKeyParam": [] }, { "bearer": [] }]
|
||||
}
|
||||
},
|
||||
"/v1/projects/{ref}/vanity-subdomain": {
|
||||
"get": {
|
||||
"operationId": "getVanitySubdomainConfig",
|
||||
@@ -1100,36 +1164,6 @@
|
||||
"verify_jwt": { "type": "boolean" }
|
||||
}
|
||||
},
|
||||
"SecretResponse": {
|
||||
"type": "object",
|
||||
"properties": { "name": { "type": "string" }, "value": { "type": "string" } },
|
||||
"required": ["name", "value"]
|
||||
},
|
||||
"CreateSecretBody": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"value": { "type": "string", "pattern": "/^(?!SUPABASE_).*/" }
|
||||
},
|
||||
"required": ["name", "value"]
|
||||
},
|
||||
"PostgrestConfigResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"max_rows": { "type": "integer" },
|
||||
"db_schema": { "type": "string" },
|
||||
"db_extra_search_path": { "type": "string" }
|
||||
},
|
||||
"required": ["max_rows", "db_schema", "db_extra_search_path"]
|
||||
},
|
||||
"UpdatePostgrestConfigBody": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"max_rows": { "type": "integer", "minimum": 0, "maximum": 1000000 },
|
||||
"db_extra_search_path": { "type": "string" },
|
||||
"db_schema": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"UpdateCustomHostnameResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -1153,21 +1187,6 @@
|
||||
"properties": { "custom_hostname": { "type": "string" } },
|
||||
"required": ["custom_hostname"]
|
||||
},
|
||||
"PgsodiumConfigResponse": {
|
||||
"type": "object",
|
||||
"properties": { "root_key": { "type": "string" } },
|
||||
"required": ["root_key"]
|
||||
},
|
||||
"UpdatePgsodiumConfigBody": {
|
||||
"type": "object",
|
||||
"properties": { "root_key": { "type": "string" } },
|
||||
"required": ["root_key"]
|
||||
},
|
||||
"TypescriptResponse": {
|
||||
"type": "object",
|
||||
"properties": { "types": { "type": "string" } },
|
||||
"required": ["types"]
|
||||
},
|
||||
"NetworkBanResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -1194,6 +1213,69 @@
|
||||
},
|
||||
"required": ["entitlement", "config", "status"]
|
||||
},
|
||||
"PgsodiumConfigResponse": {
|
||||
"type": "object",
|
||||
"properties": { "root_key": { "type": "string" } },
|
||||
"required": ["root_key"]
|
||||
},
|
||||
"UpdatePgsodiumConfigBody": {
|
||||
"type": "object",
|
||||
"properties": { "root_key": { "type": "string" } },
|
||||
"required": ["root_key"]
|
||||
},
|
||||
"PostgrestConfigResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"max_rows": { "type": "integer" },
|
||||
"db_schema": { "type": "string" },
|
||||
"db_extra_search_path": { "type": "string" }
|
||||
},
|
||||
"required": ["max_rows", "db_schema", "db_extra_search_path"]
|
||||
},
|
||||
"UpdatePostgrestConfigBody": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"max_rows": { "type": "integer", "minimum": 0, "maximum": 1000000 },
|
||||
"db_extra_search_path": { "type": "string" },
|
||||
"db_schema": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"SecretResponse": {
|
||||
"type": "object",
|
||||
"properties": { "name": { "type": "string" }, "value": { "type": "string" } },
|
||||
"required": ["name", "value"]
|
||||
},
|
||||
"CreateSecretBody": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"value": { "type": "string", "pattern": "/^(?!SUPABASE_).*/" }
|
||||
},
|
||||
"required": ["name", "value"]
|
||||
},
|
||||
"SslEnforcements": {
|
||||
"type": "object",
|
||||
"properties": { "database": { "type": "boolean" } },
|
||||
"required": ["database"]
|
||||
},
|
||||
"SslEnforcementResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"currentConfig": { "$ref": "#/components/schemas/SslEnforcements" },
|
||||
"appliedSuccessfully": { "type": "boolean" }
|
||||
},
|
||||
"required": ["currentConfig", "appliedSuccessfully"]
|
||||
},
|
||||
"SslEnforcementRequest": {
|
||||
"type": "object",
|
||||
"properties": { "requestedConfig": { "$ref": "#/components/schemas/SslEnforcements" } },
|
||||
"required": ["requestedConfig"]
|
||||
},
|
||||
"TypescriptResponse": {
|
||||
"type": "object",
|
||||
"properties": { "types": { "type": "string" } },
|
||||
"required": ["types"]
|
||||
},
|
||||
"VanitySubdomainConfigResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -809,7 +809,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "RealtimeChannel.ts",
|
||||
"line": 315,
|
||||
"line": 327,
|
||||
"character": 2
|
||||
}
|
||||
],
|
||||
@@ -821,7 +821,7 @@
|
||||
"kindString": "Call signature",
|
||||
"flags": {},
|
||||
"comment": {
|
||||
"shortText": "Listen to messages."
|
||||
"shortText": "Creates an event handler that listens to changes"
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
@@ -830,6 +830,9 @@
|
||||
"kind": 32768,
|
||||
"kindString": "Parameter",
|
||||
"flags": {},
|
||||
"comment": {
|
||||
"shortText": "Realtime feature (broadcast, presence, or postgres_changes)"
|
||||
},
|
||||
"type": {
|
||||
"type": "literal",
|
||||
"value": "broadcast"
|
||||
@@ -841,6 +844,9 @@
|
||||
"kind": 32768,
|
||||
"kindString": "Parameter",
|
||||
"flags": {},
|
||||
"comment": {
|
||||
"shortText": "Pre-defined and custom values specific to the Realtime feature"
|
||||
},
|
||||
"type": {
|
||||
"type": "reflection",
|
||||
"declaration": {
|
||||
@@ -856,10 +862,13 @@
|
||||
"kind": 1024,
|
||||
"kindString": "Property",
|
||||
"flags": {},
|
||||
"comment": {
|
||||
"shortText": "Event to listen to. If listening to postgres_changes type\nthen this value is one of INSERT, UPDATE, DELETE, *"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "RealtimeChannel.ts",
|
||||
"line": 317,
|
||||
"line": 329,
|
||||
"character": 14
|
||||
}
|
||||
],
|
||||
@@ -885,6 +894,9 @@
|
||||
"kind": 32768,
|
||||
"kindString": "Parameter",
|
||||
"flags": {},
|
||||
"comment": {
|
||||
"shortText": "Function to be invoked when event handler is triggered\n"
|
||||
},
|
||||
"type": {
|
||||
"type": "reflection",
|
||||
"declaration": {
|
||||
@@ -925,7 +937,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "RealtimeChannel.ts",
|
||||
"line": 320,
|
||||
"line": 332,
|
||||
"character": 6
|
||||
}
|
||||
],
|
||||
@@ -943,7 +955,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "RealtimeChannel.ts",
|
||||
"line": 319,
|
||||
"line": 331,
|
||||
"character": 6
|
||||
}
|
||||
],
|
||||
@@ -1045,7 +1057,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "RealtimeChannel.ts",
|
||||
"line": 326,
|
||||
"line": 338,
|
||||
"character": 14
|
||||
}
|
||||
],
|
||||
@@ -1144,7 +1156,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "RealtimeChannel.ts",
|
||||
"line": 331,
|
||||
"line": 343,
|
||||
"character": 14
|
||||
}
|
||||
],
|
||||
@@ -1257,7 +1269,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "RealtimeChannel.ts",
|
||||
"line": 336,
|
||||
"line": 348,
|
||||
"character": 14
|
||||
}
|
||||
],
|
||||
@@ -1911,7 +1923,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "RealtimeChannel.ts",
|
||||
"line": 367,
|
||||
"line": 379,
|
||||
"character": 2
|
||||
}
|
||||
],
|
||||
@@ -1947,7 +1959,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "RealtimeChannel.ts",
|
||||
"line": 368,
|
||||
"line": 380,
|
||||
"character": 15
|
||||
}
|
||||
],
|
||||
@@ -2298,7 +2310,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "RealtimeChannel.ts",
|
||||
"line": 407,
|
||||
"line": 419,
|
||||
"character": 2
|
||||
}
|
||||
],
|
||||
@@ -2443,7 +2455,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "RealtimeChannel.ts",
|
||||
"line": 394,
|
||||
"line": 406,
|
||||
"character": 2
|
||||
}
|
||||
],
|
||||
@@ -3246,7 +3258,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "RealtimeClient.ts",
|
||||
"line": 218,
|
||||
"line": 228,
|
||||
"character": 2
|
||||
}
|
||||
],
|
||||
@@ -3330,7 +3342,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "RealtimeClient.ts",
|
||||
"line": 198,
|
||||
"line": 208,
|
||||
"character": 2
|
||||
}
|
||||
],
|
||||
@@ -3424,7 +3436,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "RealtimeClient.ts",
|
||||
"line": 162,
|
||||
"line": 165,
|
||||
"character": 2
|
||||
}
|
||||
],
|
||||
@@ -3435,6 +3447,9 @@
|
||||
"kind": 4096,
|
||||
"kindString": "Call signature",
|
||||
"flags": {},
|
||||
"comment": {
|
||||
"shortText": "Returns all created channels"
|
||||
},
|
||||
"type": {
|
||||
"type": "array",
|
||||
"elementType": {
|
||||
@@ -3455,7 +3470,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "RealtimeClient.ts",
|
||||
"line": 214,
|
||||
"line": 224,
|
||||
"character": 2
|
||||
}
|
||||
],
|
||||
@@ -3485,7 +3500,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "RealtimeClient.ts",
|
||||
"line": 191,
|
||||
"line": 201,
|
||||
"character": 2
|
||||
}
|
||||
],
|
||||
@@ -3553,7 +3568,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "RealtimeClient.ts",
|
||||
"line": 236,
|
||||
"line": 246,
|
||||
"character": 2
|
||||
}
|
||||
],
|
||||
@@ -3607,7 +3622,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "RealtimeClient.ts",
|
||||
"line": 177,
|
||||
"line": 187,
|
||||
"character": 2
|
||||
}
|
||||
],
|
||||
@@ -3618,6 +3633,9 @@
|
||||
"kind": 4096,
|
||||
"kindString": "Call signature",
|
||||
"flags": {},
|
||||
"comment": {
|
||||
"shortText": "Unsubscribes and removes all channels"
|
||||
},
|
||||
"type": {
|
||||
"type": "reference",
|
||||
"typeArguments": [
|
||||
@@ -3646,7 +3664,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "RealtimeClient.ts",
|
||||
"line": 166,
|
||||
"line": 173,
|
||||
"character": 2
|
||||
}
|
||||
],
|
||||
@@ -3657,6 +3675,9 @@
|
||||
"kind": 4096,
|
||||
"kindString": "Call signature",
|
||||
"flags": {},
|
||||
"comment": {
|
||||
"shortText": "Unsubscribes and removes a single channel"
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"id": 354,
|
||||
@@ -3664,6 +3685,9 @@
|
||||
"kind": 32768,
|
||||
"kindString": "Parameter",
|
||||
"flags": {},
|
||||
"comment": {
|
||||
"shortText": "A RealtimeChannel instance\n"
|
||||
},
|
||||
"type": {
|
||||
"type": "reference",
|
||||
"id": 55,
|
||||
@@ -3696,7 +3720,7 @@
|
||||
"sources": [
|
||||
{
|
||||
"fileName": "RealtimeClient.ts",
|
||||
"line": 263,
|
||||
"line": 273,
|
||||
"character": 2
|
||||
}
|
||||
],
|
||||
|
||||
@@ -3594,7 +3594,7 @@
|
||||
"isOptional": true
|
||||
},
|
||||
"comment": {
|
||||
"shortText": "Automatically refreshes the token for logged in users."
|
||||
"shortText": "Automatically refreshes the token for logged-in users. Defaults to true."
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
@@ -3617,7 +3617,7 @@
|
||||
"isOptional": true
|
||||
},
|
||||
"comment": {
|
||||
"shortText": "Detect a session from the URL. Used for OAuth login callbacks."
|
||||
"shortText": "Detect a session from the URL. Used for OAuth login callbacks. Defaults to true."
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
@@ -3640,7 +3640,7 @@
|
||||
"isOptional": true
|
||||
},
|
||||
"comment": {
|
||||
"shortText": "Whether to persist a logged in session to storage."
|
||||
"shortText": "Whether to persist a logged-in session to storage. Defaults to true."
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
@@ -3663,7 +3663,7 @@
|
||||
"isOptional": true
|
||||
},
|
||||
"comment": {
|
||||
"shortText": "A storage provider. Used to store the logged in session."
|
||||
"shortText": "A storage provider. Used to store the logged-in session."
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
@@ -3694,7 +3694,7 @@
|
||||
"isOptional": true
|
||||
},
|
||||
"comment": {
|
||||
"shortText": "Optional key name used for storing tokens in local storage"
|
||||
"shortText": "Optional key name used for storing tokens in local storage."
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
@@ -3735,7 +3735,7 @@
|
||||
"isOptional": true
|
||||
},
|
||||
"comment": {
|
||||
"shortText": "The Postgres schema which your tables belong to. Must be on the list of exposed schemas in Supabase. Defaults to 'public'."
|
||||
"shortText": "The Postgres schema which your tables belong to. Must be on the list of exposed schemas in Supabase. Defaults to `public`."
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
|
||||
@@ -283,7 +283,7 @@
|
||||
"isOptional": true
|
||||
},
|
||||
"comment": {
|
||||
"shortText": "Automatically refreshes the token for logged in users."
|
||||
"shortText": "Automatically refreshes the token for logged-in users. Defaults to true."
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
@@ -306,7 +306,7 @@
|
||||
"isOptional": true
|
||||
},
|
||||
"comment": {
|
||||
"shortText": "Detect a session from the URL. Used for OAuth login callbacks."
|
||||
"shortText": "Detect a session from the URL. Used for OAuth login callbacks. Defaults to true."
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
@@ -329,7 +329,7 @@
|
||||
"isOptional": true
|
||||
},
|
||||
"comment": {
|
||||
"shortText": "Whether to persist a logged in session to storage."
|
||||
"shortText": "Whether to persist a logged-in session to storage. Defaults to true."
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
@@ -352,7 +352,7 @@
|
||||
"isOptional": true
|
||||
},
|
||||
"comment": {
|
||||
"shortText": "A storage provider. Used to store the logged in session."
|
||||
"shortText": "A storage provider. Used to store the logged-in session."
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
@@ -383,7 +383,7 @@
|
||||
"isOptional": true
|
||||
},
|
||||
"comment": {
|
||||
"shortText": "Optional key name used for storing tokens in local storage"
|
||||
"shortText": "Optional key name used for storing tokens in local storage."
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
@@ -424,7 +424,7 @@
|
||||
"isOptional": true
|
||||
},
|
||||
"comment": {
|
||||
"shortText": "The Postgres schema which your tables belong to. Must be on the list of exposed schemas in Supabase. Defaults to 'public'."
|
||||
"shortText": "The Postgres schema which your tables belong to. Must be on the list of exposed schemas in Supabase. Defaults to `public`."
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
@@ -2598,7 +2598,7 @@
|
||||
"isOptional": true
|
||||
},
|
||||
"comment": {
|
||||
"shortText": "Automatically refreshes the token for logged in users."
|
||||
"shortText": "Automatically refreshes the token for logged-in users. Defaults to true."
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
@@ -2621,7 +2621,7 @@
|
||||
"isOptional": true
|
||||
},
|
||||
"comment": {
|
||||
"shortText": "Detect a session from the URL. Used for OAuth login callbacks."
|
||||
"shortText": "Detect a session from the URL. Used for OAuth login callbacks. Defaults to true."
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
@@ -2644,7 +2644,7 @@
|
||||
"isOptional": true
|
||||
},
|
||||
"comment": {
|
||||
"shortText": "Whether to persist a logged in session to storage."
|
||||
"shortText": "Whether to persist a logged-in session to storage. Defaults to true."
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
@@ -2667,7 +2667,7 @@
|
||||
"isOptional": true
|
||||
},
|
||||
"comment": {
|
||||
"shortText": "A storage provider. Used to store the logged in session."
|
||||
"shortText": "A storage provider. Used to store the logged-in session."
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
@@ -2698,7 +2698,7 @@
|
||||
"isOptional": true
|
||||
},
|
||||
"comment": {
|
||||
"shortText": "Optional key name used for storing tokens in local storage"
|
||||
"shortText": "Optional key name used for storing tokens in local storage."
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
@@ -2739,7 +2739,7 @@
|
||||
"isOptional": true
|
||||
},
|
||||
"comment": {
|
||||
"shortText": "The Postgres schema which your tables belong to. Must be on the list of exposed schemas in Supabase. Defaults to 'public'."
|
||||
"shortText": "The Postgres schema which your tables belong to. Must be on the list of exposed schemas in Supabase. Defaults to `public`."
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
@@ -3702,7 +3702,7 @@
|
||||
"isOptional": true
|
||||
},
|
||||
"comment": {
|
||||
"shortText": "Automatically refreshes the token for logged in users."
|
||||
"shortText": "Automatically refreshes the token for logged-in users. Defaults to true."
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
@@ -3725,7 +3725,7 @@
|
||||
"isOptional": true
|
||||
},
|
||||
"comment": {
|
||||
"shortText": "Detect a session from the URL. Used for OAuth login callbacks."
|
||||
"shortText": "Detect a session from the URL. Used for OAuth login callbacks. Defaults to true."
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
@@ -3748,7 +3748,7 @@
|
||||
"isOptional": true
|
||||
},
|
||||
"comment": {
|
||||
"shortText": "Whether to persist a logged in session to storage."
|
||||
"shortText": "Whether to persist a logged-in session to storage. Defaults to true."
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
@@ -3771,7 +3771,7 @@
|
||||
"isOptional": true
|
||||
},
|
||||
"comment": {
|
||||
"shortText": "A storage provider. Used to store the logged in session."
|
||||
"shortText": "A storage provider. Used to store the logged-in session."
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
@@ -3802,7 +3802,7 @@
|
||||
"isOptional": true
|
||||
},
|
||||
"comment": {
|
||||
"shortText": "Optional key name used for storing tokens in local storage"
|
||||
"shortText": "Optional key name used for storing tokens in local storage."
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
@@ -3843,7 +3843,7 @@
|
||||
"isOptional": true
|
||||
},
|
||||
"comment": {
|
||||
"shortText": "The Postgres schema which your tables belong to. Must be on the list of exposed schemas in Supabase. Defaults to 'public'."
|
||||
"shortText": "The Postgres schema which your tables belong to. Must be on the list of exposed schemas in Supabase. Defaults to `public`."
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
@@ -4151,7 +4151,7 @@
|
||||
"isOptional": true
|
||||
},
|
||||
"comment": {
|
||||
"shortText": "Automatically refreshes the token for logged in users."
|
||||
"shortText": "Automatically refreshes the token for logged-in users. Defaults to true."
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
@@ -4174,7 +4174,7 @@
|
||||
"isOptional": true
|
||||
},
|
||||
"comment": {
|
||||
"shortText": "Detect a session from the URL. Used for OAuth login callbacks."
|
||||
"shortText": "Detect a session from the URL. Used for OAuth login callbacks. Defaults to true."
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
@@ -4197,7 +4197,7 @@
|
||||
"isOptional": true
|
||||
},
|
||||
"comment": {
|
||||
"shortText": "Whether to persist a logged in session to storage."
|
||||
"shortText": "Whether to persist a logged-in session to storage. Defaults to true."
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
@@ -4220,7 +4220,7 @@
|
||||
"isOptional": true
|
||||
},
|
||||
"comment": {
|
||||
"shortText": "A storage provider. Used to store the logged in session."
|
||||
"shortText": "A storage provider. Used to store the logged-in session."
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
@@ -4251,7 +4251,7 @@
|
||||
"isOptional": true
|
||||
},
|
||||
"comment": {
|
||||
"shortText": "Optional key name used for storing tokens in local storage"
|
||||
"shortText": "Optional key name used for storing tokens in local storage."
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
@@ -4292,7 +4292,7 @@
|
||||
"isOptional": true
|
||||
},
|
||||
"comment": {
|
||||
"shortText": "The Postgres schema which your tables belong to. Must be on the list of exposed schemas in Supabase. Defaults to 'public'."
|
||||
"shortText": "The Postgres schema which your tables belong to. Must be on the list of exposed schemas in Supabase. Defaults to `public`."
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
@@ -5531,7 +5531,7 @@
|
||||
"isOptional": true
|
||||
},
|
||||
"comment": {
|
||||
"shortText": "Automatically refreshes the token for logged in users."
|
||||
"shortText": "Automatically refreshes the token for logged-in users. Defaults to true."
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
@@ -5554,7 +5554,7 @@
|
||||
"isOptional": true
|
||||
},
|
||||
"comment": {
|
||||
"shortText": "Detect a session from the URL. Used for OAuth login callbacks."
|
||||
"shortText": "Detect a session from the URL. Used for OAuth login callbacks. Defaults to true."
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
@@ -5577,7 +5577,7 @@
|
||||
"isOptional": true
|
||||
},
|
||||
"comment": {
|
||||
"shortText": "Whether to persist a logged in session to storage."
|
||||
"shortText": "Whether to persist a logged-in session to storage. Defaults to true."
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
@@ -5600,7 +5600,7 @@
|
||||
"isOptional": true
|
||||
},
|
||||
"comment": {
|
||||
"shortText": "A storage provider. Used to store the logged in session."
|
||||
"shortText": "A storage provider. Used to store the logged-in session."
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
@@ -5631,7 +5631,7 @@
|
||||
"isOptional": true
|
||||
},
|
||||
"comment": {
|
||||
"shortText": "Optional key name used for storing tokens in local storage"
|
||||
"shortText": "Optional key name used for storing tokens in local storage."
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
@@ -5672,7 +5672,7 @@
|
||||
"isOptional": true
|
||||
},
|
||||
"comment": {
|
||||
"shortText": "The Postgres schema which your tables belong to. Must be on the list of exposed schemas in Supabase. Defaults to 'public'."
|
||||
"shortText": "The Postgres schema which your tables belong to. Must be on the list of exposed schemas in Supabase. Defaults to `public`."
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
|
||||
@@ -11,11 +11,9 @@ info:
|
||||
title: 'GoTrue' # {string} A readable name.
|
||||
source: 'https://github.com/supabase/gotrue' # {string} Where developers can find the source code.
|
||||
bugs: 'https://github.com/supabase/gotrue/issues' # {string} Where developers can file bugs.
|
||||
spec: 'https://github.com/supabase/supabase/blob/master/web/spec/cli_config_001.yml' # {string} Where developers can find this spec (to link directly in the docs).
|
||||
spec: 'https://github.com/supabase/supabase/blob/master/spec/gotrue_v1_config.yaml' # {string} Where developers can find this spec (to link directly in the docs).
|
||||
description: |
|
||||
A `config.toml` file is generated after running `supabase init`.
|
||||
|
||||
This file is located in the `supabase` folder under `supabase/config.toml`.
|
||||
A `config.toml` file is generated after running `supabase init`. This file is located in the `supabase` folder under `supabase/config.toml`.
|
||||
tags:
|
||||
- id: general
|
||||
title: General
|
||||
@@ -30,3 +28,52 @@ parameters:
|
||||
# default: '5432'
|
||||
description: |
|
||||
A string used to distinguish different Supabase projects on the same host. Defaults to the working directory name when running `supabase init`.
|
||||
- id: 'GOTRUE_EXTERNAL_GITHUB' # {string} A unique identifier for this param.
|
||||
title: 'auth.external.github' # {string} Any name.
|
||||
tags: ['general'] # {string[]} These tags are useful for grouping parameters
|
||||
required: true
|
||||
# default: '5432'
|
||||
description: |
|
||||
Describes whether the Github provider is enabled or not.
|
||||
- id: 'GOTRUE_SITE_URL' # {string} A unique identifier for this param.
|
||||
title: 'auth.site_url' # {string} Any name.
|
||||
tags: ['general'] # {string[]} These tags are useful for grouping parameters
|
||||
required: true
|
||||
description: |
|
||||
The base URL of your website. Used as an allow-list for redirects and for constructing URLs used in emails.
|
||||
- id: 'GOTRUE_URI_ALLOW_LIST' # {string} A unique identifier for this param.
|
||||
title: 'auth.additional_redirect_urls' # {string} Any name.
|
||||
tags: ['general'] # {string[]} These tags are useful for grouping parameters
|
||||
required: true
|
||||
description: |
|
||||
A list of *exact* URLs that auth providers are permitted to redirect to post authentication.
|
||||
- id: 'GOTRUE_JWT_EXP' # {string} A unique identifier for this param.
|
||||
title: 'auth.jwt_expiry' # {string} Any name.
|
||||
tags: ['general'] # {string[]} These tags are useful for grouping parameters
|
||||
required: true
|
||||
description: |
|
||||
How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 seconds (one week).
|
||||
- id: 'GOTRUE_DISABLE_SIGNUP' # {string} A unique identifier for this param.
|
||||
title: 'auth.enable_signup' # {string} Any name.
|
||||
tags: ['general'] # {string[]} These tags are useful for grouping parameters
|
||||
required: true
|
||||
description: |
|
||||
Allow/disallow new user signups to your project.
|
||||
- id: 'GOTRUE_EXTERNAL_EMAIL_ENABLED' # {string} A unique identifier for this param.
|
||||
title: 'auth.email.enable_signup' # {string} Any name.
|
||||
tags: ['general', 'email'] # {string[]} These tags are useful for grouping parameters
|
||||
required: true
|
||||
description: |
|
||||
Allow/disallow new user signups via email to your project.
|
||||
- id: 'GOTRUE_MAILER_SECURE_EMAIL_CHANGE_ENABLED' # {string} A unique identifier for this param.
|
||||
title: 'auth.email.double_confirm_changes' # {string} Any name.
|
||||
tags: ['general', 'email'] # {string[]} These tags are useful for grouping parameters
|
||||
required: true
|
||||
description: |
|
||||
If enabled, a user will be required to confirm any email change on both the old, and new email addresses. If disabled, only the new email is required to confirm.
|
||||
- id: 'GOTRUE_MAILER_AUTOCONFIRM' # {string} A unique identifier for this param.
|
||||
title: 'auth.email.enable_confirmations' # {string} Any name.
|
||||
tags: ['general', 'email'] # {string[]} These tags are useful for grouping parameters
|
||||
required: true
|
||||
description: |
|
||||
If enabled, users need to confirm their email address before signing in.
|
||||
@@ -643,7 +643,7 @@ functions:
|
||||
- Defaults to return 50 users per page.
|
||||
examples:
|
||||
- id: get-a-full-list-of-users
|
||||
name: Get a full list of users
|
||||
name: Get a page of users
|
||||
isSpotlight: true
|
||||
code: |
|
||||
```js
|
||||
@@ -1062,7 +1062,7 @@ functions:
|
||||
If you're in a situation where your tables are **NOT** directly
|
||||
related, but instead are joined by a _join table_, you can still use
|
||||
the `select()` method to query the related data. The join table needs
|
||||
to have the foreign keys as part of its composite primary key.
|
||||
to have the foreign keys as part of its composite primary key.
|
||||
hideCodeBlock: true
|
||||
- id: query-the-same-foreign-table-multiple-times
|
||||
name: Query the same foreign table multiple times
|
||||
|
||||
@@ -64,10 +64,10 @@ const AddPaymentMethodForm: FC<Props> = ({ returnUrl, onCancel }) => {
|
||||
<Modal.Separator />
|
||||
<Modal.Content>
|
||||
<div className="flex items-center space-x-2 pt-2">
|
||||
<Button block htmlType="submit" type="primary" loading={isSaving}>
|
||||
<Button block htmlType="submit" type="primary" loading={isSaving} disabled={isSaving}>
|
||||
Save
|
||||
</Button>
|
||||
<Button htmlType="button" type="default" onClick={onCancel} block>
|
||||
<Button htmlType="button" type="default" onClick={onCancel} block disabled={isSaving}>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -15,6 +15,9 @@ 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 xl:grid-cols-3' : 'gap-4 xl:grid-cols-4'
|
||||
|
||||
return (
|
||||
<Transition
|
||||
show={visible}
|
||||
@@ -22,13 +25,12 @@ const PlanSelection: FC<Props> = ({ visible, tiers, currentPlan, onSelectPlan })
|
||||
enterFrom="transform opacity-0 -translate-x-10"
|
||||
enterTo="transform opacity-100 translate-x-0"
|
||||
>
|
||||
<div className="space-y-8">
|
||||
<h4 className="text-lg">Change your project's subscription</h4>
|
||||
{/* FE will make a call to fetch all plans first at the page level */}
|
||||
<div className="grid lg:grid-cols-3 gap-8 py-8">
|
||||
<div>
|
||||
<h4 className="text-lg mb-8">Change your project's subscription</h4>
|
||||
<div className={`grid py-8 grid-cols-2 ${gridCols}`}>
|
||||
<Plans plans={formattedTiers} currentPlan={currentPlan} onSelectPlan={onSelectPlan} />
|
||||
</div>
|
||||
<div className="flex justify-center items-center">
|
||||
<div className="flex justify-center items-center mt-4">
|
||||
<Link href="https://supabase.com/pricing">
|
||||
<a
|
||||
target="_blank"
|
||||
|
||||
@@ -14,8 +14,13 @@ export const formatTierOptions = (tiers: any[]) => {
|
||||
}
|
||||
|
||||
// Fix the order of plans here
|
||||
return tiers
|
||||
.filter((tier: any) => tier.id === STRIPE_PRODUCT_IDS.FREE)
|
||||
.concat(tiers.filter((tier: any) => tier.id === STRIPE_PRODUCT_IDS.PRO))
|
||||
.concat([enterpriseOption])
|
||||
return (
|
||||
tiers
|
||||
.filter((tier: any) => tier.id === STRIPE_PRODUCT_IDS.FREE)
|
||||
.concat(tiers.filter((tier: any) => tier.id === STRIPE_PRODUCT_IDS.PRO))
|
||||
.concat(tiers.filter((tier: any) => tier.id === STRIPE_PRODUCT_IDS.TEAM))
|
||||
.concat([enterpriseOption])
|
||||
// Team plan might not be enabled yet, make sure to filter out null/undefined
|
||||
.filter(Boolean)
|
||||
)
|
||||
}
|
||||
@@ -16,10 +16,18 @@ interface Props {
|
||||
const PlanCTAButton: FC<Props> = ({ plan, currentPlan, onSelectPlan }) => {
|
||||
const projectUpdateDisabled = useFlag('disableProjectCreationAndUpdate')
|
||||
|
||||
const getButtonType = (plan: any) => {
|
||||
if (plan.name === 'Enterprise' || plan.id === STRIPE_PRODUCT_IDS.FREE) {
|
||||
const getButtonType = (plan: any, currentPlan: any) => {
|
||||
if (['Enterprise', 'Free tier'].includes(plan.name)) {
|
||||
// Enterprise/Free are 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 && plan.name !== 'Team tier') {
|
||||
// Non-free plans are default (pro), when team tier is selected
|
||||
return 'default'
|
||||
}
|
||||
|
||||
return 'primary'
|
||||
}
|
||||
|
||||
@@ -37,6 +45,8 @@ const PlanCTAButton: FC<Props> = ({ plan, currentPlan, onSelectPlan }) => {
|
||||
if (plan.id === STRIPE_PRODUCT_IDS.PRO) {
|
||||
if (currentPlan.prod_id === STRIPE_PRODUCT_IDS.FREE) {
|
||||
return 'Upgrade to Pro'
|
||||
} else if (currentPlan.prod_id === STRIPE_PRODUCT_IDS.TEAM) {
|
||||
return 'Downgrade to Pro'
|
||||
} else if (
|
||||
currentPlan.prod_id === STRIPE_PRODUCT_IDS.PRO ||
|
||||
currentPlan.prod_id === STRIPE_PRODUCT_IDS.PAYG
|
||||
@@ -46,6 +56,20 @@ const PlanCTAButton: FC<Props> = ({ plan, currentPlan, onSelectPlan }) => {
|
||||
return 'Contact sales'
|
||||
}
|
||||
}
|
||||
|
||||
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 (!currentPlan)
|
||||
@@ -57,7 +81,7 @@ const PlanCTAButton: FC<Props> = ({ plan, currentPlan, onSelectPlan }) => {
|
||||
</div>
|
||||
)
|
||||
|
||||
const type = getButtonType(plan)
|
||||
const type = getButtonType(plan, currentPlan)
|
||||
const ctaText = getButtonText(plan, currentPlan)
|
||||
const disabled =
|
||||
plan.id === STRIPE_PRODUCT_IDS.FREE && currentPlan.prod_id === STRIPE_PRODUCT_IDS.FREE
|
||||
|
||||
@@ -34,7 +34,7 @@ export const PRICING_META = {
|
||||
href: '#',
|
||||
from: true,
|
||||
priceMonthly: 25,
|
||||
warning: '+ additional use',
|
||||
warning: '+ any additional usage',
|
||||
description: 'For production applications with the option to scale.',
|
||||
features: [
|
||||
'8GB database & 100GB file storage',
|
||||
@@ -52,10 +52,34 @@ export const PRICING_META = {
|
||||
],
|
||||
scale: 'Additional fees apply for usage and storage beyond the limits above.',
|
||||
shutdown: '',
|
||||
preface: 'Everything below included in the base plan',
|
||||
preface: 'Everything in the base plan, plus',
|
||||
additional: 'Need more? Turn off your spend cap to Pay As You Grow ',
|
||||
cta: 'Get Started',
|
||||
},
|
||||
[STRIPE_PRODUCT_IDS.TEAM]: {
|
||||
name: 'Team',
|
||||
href: '#',
|
||||
from: true,
|
||||
priceMonthly: 599,
|
||||
warning: '+ usage',
|
||||
description: 'For collaborative teams with different permissions and access.',
|
||||
features: [
|
||||
'Usage-based pricing',
|
||||
'100,000 monthly active users included',
|
||||
'Organization member roles (ABAC)',
|
||||
'Standardised Security Questionnaire',
|
||||
'SOC2',
|
||||
'SSO for Supabase Dashboard',
|
||||
'Priority email support & SLAs',
|
||||
'1 XS compute instance',
|
||||
'14 day backups',
|
||||
'28 day log retention',
|
||||
],
|
||||
scale: '',
|
||||
shutdown: '',
|
||||
additional: '',
|
||||
cta: 'Get Started',
|
||||
},
|
||||
Enterprise: {
|
||||
name: 'Enterprise',
|
||||
href: '/contact/enterprise',
|
||||
|
||||
@@ -6,17 +6,11 @@ import { StripeProduct } from 'components/interfaces/Billing'
|
||||
import PlanCTAButton from './PlanCTAButton'
|
||||
import { PRICING_META } from './Plans.Constants'
|
||||
|
||||
interface Props {
|
||||
plans: any[]
|
||||
currentPlan?: StripeProduct
|
||||
onSelectPlan: (plan: any) => void
|
||||
}
|
||||
|
||||
const AnimatedGradientBackground = () => (
|
||||
<div
|
||||
className={[
|
||||
'absolute top-0 left-0 h-full w-full animate-sway',
|
||||
'bg-gradient-to-b from-transparent via-transparent to-green-800',
|
||||
'bg-gradient-to-b from-transparent via-transparent to-green-900',
|
||||
].join(' ')}
|
||||
/>
|
||||
)
|
||||
@@ -34,6 +28,12 @@ const CurrentSubscriptionBanner = () => (
|
||||
</div>
|
||||
)
|
||||
|
||||
interface Props {
|
||||
plans: any[]
|
||||
currentPlan?: StripeProduct
|
||||
onSelectPlan: (plan: any) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* JSX below for the mapped plans is also very similar to www/pages/pricing
|
||||
* TO DO: move to use shared components for these
|
||||
@@ -44,6 +44,7 @@ const CurrentSubscriptionBanner = () => (
|
||||
const Plans: FC<Props> = ({ plans, currentPlan, onSelectPlan }) => {
|
||||
return (
|
||||
<>
|
||||
{/* Existing available plans */}
|
||||
{plans.map((plan) => {
|
||||
const isCurrentPlan =
|
||||
plan.id === currentPlan?.prod_id ||
|
||||
@@ -59,7 +60,7 @@ const Plans: FC<Props> = ({ plans, currentPlan, onSelectPlan }) => {
|
||||
)}
|
||||
<div className="flex h-full flex-col overflow-hidden rounded border">
|
||||
{isCurrentPlan && <CurrentSubscriptionBanner />}
|
||||
<div className="bg-white px-8 py-6 dark:bg-scale-300">
|
||||
<div className="bg-white px-6 py-6 dark:bg-scale-300">
|
||||
<span
|
||||
className="text-cd inline-flex rounded-full text-base font-normal tracking-wide text-scale-1200"
|
||||
id="tier-standard"
|
||||
@@ -70,20 +71,18 @@ const Plans: FC<Props> = ({ plans, currentPlan, onSelectPlan }) => {
|
||||
<div className="flex space-x-2">
|
||||
{PRICING_META[plan.id].priceMonthly !== undefined ? (
|
||||
<>
|
||||
<div className="flex items-end gap-1">
|
||||
<div>
|
||||
{PRICING_META[plan.id].from && (
|
||||
<span className="text-base font-medium text-scale-1200">From</span>
|
||||
<span className="text-sm font-medium text-scale-1200 mr-1">From</span>
|
||||
)}
|
||||
<div>
|
||||
<span className="text-2xl">${PRICING_META[plan.id].priceMonthly}</span>
|
||||
<span className="ml-1 text-xl font-medium text-scale-900">/mo</span>
|
||||
</div>
|
||||
<span className="text-2xl">${PRICING_META[plan.id].priceMonthly}</span>
|
||||
<span className="text-xl font-medium text-scale-900">/mo</span>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<span className="text-2xl">Contact Us</span>
|
||||
)}
|
||||
<div className="flex h-8">
|
||||
<div className="flex h-8 -mt-[2px]">
|
||||
{PRICING_META[plan.id].warning && (
|
||||
<div className="mt-2 rounded-md bg-brand-300 bg-opacity-30 px-2 py-1 text-xs text-brand-1000">
|
||||
{PRICING_META[plan.id].warning}
|
||||
@@ -106,26 +105,30 @@ const Plans: FC<Props> = ({ plans, currentPlan, onSelectPlan }) => {
|
||||
'flex-1 flex-col justify-between',
|
||||
'space-y-6 border-t dark:border-scale-400',
|
||||
'bg-scale-100 dark:bg-scale-300',
|
||||
'hidden h-full px-8 py-6 lg:flex',
|
||||
'hidden h-full px-6 py-6 lg:flex',
|
||||
].join(' ')}
|
||||
>
|
||||
{PRICING_META[plan.id].preface && (
|
||||
<p className="text-sm text-scale-1200">{PRICING_META[plan.id].preface}</p>
|
||||
)}
|
||||
<ul role="list" className="divide-y dark:divide-scale-400">
|
||||
{PRICING_META[plan.id].features.map((feature) => (
|
||||
<li key={feature} className="flex items-center py-2">
|
||||
<IconCheck
|
||||
className="h-3 w-3 text-brand-900 "
|
||||
aria-hidden="true"
|
||||
strokeWidth={3}
|
||||
/>
|
||||
<p className="mb-0 ml-3 text-xs text-scale-1100 dark:text-scale-1200">
|
||||
{feature}
|
||||
</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="space-y-6">
|
||||
{PRICING_META[plan.id].preface && (
|
||||
<p className="text-sm text-scale-1200">{PRICING_META[plan.id].preface}</p>
|
||||
)}
|
||||
<ul role="list" className="divide-y dark:divide-scale-400">
|
||||
{PRICING_META[plan.id].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="mb-0 ml-3 text-xs text-scale-1100 dark:text-scale-1200">
|
||||
{feature}
|
||||
</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="space-y-2">
|
||||
{PRICING_META[plan.id].additional && (
|
||||
|
||||
@@ -0,0 +1,348 @@
|
||||
import { FC, useEffect, useRef, useState } from 'react'
|
||||
import { Transition } from '@headlessui/react'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
import { useStore, useFlag } from 'hooks'
|
||||
import { post, patch } from 'lib/common/fetch'
|
||||
import { API_URL, PROJECT_STATUS } from 'lib/constants'
|
||||
import { getURL } from 'lib/helpers'
|
||||
import Divider from 'components/ui/Divider'
|
||||
import {
|
||||
PaymentSummaryPanel,
|
||||
ComputeSizeSelection,
|
||||
PITRDurationSelection,
|
||||
CustomDomainSelection,
|
||||
StripeSubscription,
|
||||
AddNewPaymentMethodModal,
|
||||
} from './'
|
||||
import { STRIPE_PRODUCT_IDS } from 'lib/constants'
|
||||
import UpdateSuccess from './UpdateSuccess'
|
||||
import { PaymentMethod, SubscriptionPreview } from './Billing.types'
|
||||
import { formSubscriptionUpdatePayload, getCurrentAddons } from './Billing.utils'
|
||||
import { SubscriptionAddon } from './AddOns/AddOns.types'
|
||||
import {
|
||||
formatComputeSizes,
|
||||
formatCustomDomainOptions,
|
||||
formatPITROptions,
|
||||
} from './AddOns/AddOns.utils'
|
||||
import BackButton from 'components/ui/BackButton'
|
||||
import SupportPlan from './AddOns/SupportPlan'
|
||||
import HCaptcha from '@hcaptcha/react-hcaptcha'
|
||||
|
||||
// Do not allow compute size changes for af-south-1
|
||||
|
||||
interface Props {
|
||||
products: { tiers: any[]; addons: SubscriptionAddon[] }
|
||||
paymentMethods?: PaymentMethod[]
|
||||
currentSubscription: StripeSubscription
|
||||
isLoadingPaymentMethods: boolean
|
||||
onSelectBack: () => void
|
||||
}
|
||||
|
||||
const TeamUpgrade: FC<Props> = ({
|
||||
products,
|
||||
paymentMethods,
|
||||
currentSubscription,
|
||||
isLoadingPaymentMethods,
|
||||
onSelectBack,
|
||||
}) => {
|
||||
const { app, ui } = useStore()
|
||||
const router = useRouter()
|
||||
const isCustomDomainsEnabled = useFlag('customDomains')
|
||||
const isPITRSelfServeEnabled = useFlag('pitrSelfServe')
|
||||
|
||||
const [captchaToken, setCaptchaToken] = useState<string | null>(null)
|
||||
const captchaRef = useRef<HCaptcha>(null)
|
||||
|
||||
const { addons } = products
|
||||
const computeSizes = formatComputeSizes(addons)
|
||||
const pitrDurationOptions = formatPITROptions(addons)
|
||||
const customDomainOptions = formatCustomDomainOptions(addons)
|
||||
const currentAddons = getCurrentAddons(currentSubscription, addons)
|
||||
|
||||
const projectId = ui.selectedProject?.id ?? -1
|
||||
const projectRef = ui.selectedProject?.ref
|
||||
const projectRegion = ui.selectedProject?.region ?? ''
|
||||
|
||||
const [isRefreshingPreview, setIsRefreshingPreview] = useState(false)
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
const [isSuccessful, setIsSuccessful] = useState(false)
|
||||
const [showAddPaymentMethodModal, setShowAddPaymentMethodModal] = useState(false)
|
||||
|
||||
// [Joshen TODO] Ideally we just have a state to hold all the add ons selection, rather than individual
|
||||
// Even better if we can just use the <Form> component to handle all of these. Mainly to reduce the amount
|
||||
// of unnecessary state management on this complex page.
|
||||
const [selectedComputeSize, setSelectedComputeSize] = useState<SubscriptionAddon>(
|
||||
currentAddons.computeSize
|
||||
)
|
||||
const [selectedPITRDuration, setSelectedPITRDuration] = useState<SubscriptionAddon>(
|
||||
currentAddons.pitrDuration
|
||||
)
|
||||
const [selectedCustomDomainOption, setSelectedCustomDomainOption] = useState<SubscriptionAddon>(
|
||||
currentAddons.customDomains
|
||||
)
|
||||
|
||||
// [Joshen TODO] Future - We may need to also include any add ons outside of
|
||||
// compute size, pitr, custom domain and support plan, although we dont have any now
|
||||
const nonChangeableAddons = [currentAddons.supportPlan].filter(
|
||||
(x) => x !== undefined
|
||||
) as SubscriptionAddon[]
|
||||
|
||||
// [Joshen] Scaffolded here
|
||||
const selectedAddons = {
|
||||
computeSize: selectedComputeSize,
|
||||
pitrDuration: selectedPITRDuration,
|
||||
customDomains: selectedCustomDomainOption,
|
||||
}
|
||||
|
||||
const [selectedPaymentMethodId, setSelectedPaymentMethodId] = useState<string>('')
|
||||
const [subscriptionPreview, setSubscriptionPreview] = useState<SubscriptionPreview>()
|
||||
|
||||
const selectedTier = products?.tiers.find((tier: any) => tier.id === STRIPE_PRODUCT_IDS.TEAM)
|
||||
|
||||
const isManagingTeamSubscription = currentSubscription.tier.prod_id === STRIPE_PRODUCT_IDS.TEAM
|
||||
|
||||
const isChangingComputeSize = currentAddons.computeSize?.id !== selectedAddons.computeSize.id
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoadingPaymentMethods && paymentMethods && paymentMethods.length > 0) {
|
||||
setSelectedPaymentMethodId(paymentMethods[0].id)
|
||||
}
|
||||
}, [isLoadingPaymentMethods, paymentMethods])
|
||||
|
||||
useEffect(() => {
|
||||
getSubscriptionPreview()
|
||||
}, [selectedComputeSize, selectedPITRDuration, selectedCustomDomainOption])
|
||||
|
||||
const getSubscriptionPreview = async () => {
|
||||
const payload = formSubscriptionUpdatePayload(
|
||||
currentSubscription,
|
||||
selectedTier,
|
||||
selectedAddons,
|
||||
nonChangeableAddons,
|
||||
selectedPaymentMethodId,
|
||||
projectRegion
|
||||
)
|
||||
|
||||
setIsRefreshingPreview(true)
|
||||
const preview = await post(`${API_URL}/projects/${projectRef}/subscription/preview`, payload)
|
||||
if (preview.error) {
|
||||
ui.setNotification({
|
||||
category: 'error',
|
||||
message: `Failed to fetch subscription preview: ${preview.error.message}`,
|
||||
})
|
||||
}
|
||||
setSubscriptionPreview(preview)
|
||||
setIsRefreshingPreview(false)
|
||||
}
|
||||
|
||||
const resetCaptcha = () => {
|
||||
setCaptchaToken(null)
|
||||
captchaRef.current?.resetCaptcha()
|
||||
}
|
||||
|
||||
const beforeConfirmPayment = async (): Promise<boolean> => {
|
||||
setIsSubmitting(true)
|
||||
let token = captchaToken
|
||||
|
||||
try {
|
||||
if (!token) {
|
||||
const captchaResponse = await captchaRef.current?.execute({ async: true })
|
||||
token = captchaResponse?.response ?? null
|
||||
setCaptchaToken(token)
|
||||
}
|
||||
} catch (error) {
|
||||
setIsSubmitting(false)
|
||||
return false
|
||||
}
|
||||
|
||||
setIsSubmitting(false)
|
||||
return true
|
||||
}
|
||||
|
||||
const teamTierEnabled = useFlag('teamTier')
|
||||
|
||||
const onConfirmPayment = async () => {
|
||||
if (!teamTierEnabled) {
|
||||
return ui.setNotification({
|
||||
category: 'error',
|
||||
message: 'Team Plan is not enabled yet.',
|
||||
})
|
||||
}
|
||||
const payload = formSubscriptionUpdatePayload(
|
||||
currentSubscription,
|
||||
selectedTier,
|
||||
selectedAddons,
|
||||
nonChangeableAddons,
|
||||
selectedPaymentMethodId,
|
||||
projectRegion
|
||||
)
|
||||
const res = await patch(`${API_URL}/projects/${projectRef}/subscription`, payload)
|
||||
resetCaptcha()
|
||||
|
||||
if (res?.error) {
|
||||
ui.setNotification({
|
||||
category: 'error',
|
||||
message: `Failed to update subscription: ${res?.error?.message}`,
|
||||
})
|
||||
} else {
|
||||
if (isChangingComputeSize) {
|
||||
app.onProjectStatusUpdated(projectId, PROJECT_STATUS.RESTORING)
|
||||
ui.setNotification({
|
||||
category: 'success',
|
||||
message:
|
||||
'Your project has been updated and is currently restarting to update its instance size',
|
||||
duration: 8000,
|
||||
})
|
||||
router.push(`/project/${projectRef}`)
|
||||
} else {
|
||||
setIsSuccessful(true)
|
||||
}
|
||||
}
|
||||
setIsSubmitting(false)
|
||||
}
|
||||
|
||||
if (!isSubmitting && isSuccessful) {
|
||||
return (
|
||||
<UpdateSuccess
|
||||
projectRef={projectRef || ''}
|
||||
title="Your project has been updated!"
|
||||
message="Let us know if you have any feedback at sales@supabase.io."
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Transition
|
||||
show
|
||||
enter="transition ease-out duration-300"
|
||||
enterFrom="transform opacity-0 translate-x-10"
|
||||
enterTo="transform opacity-100 translate-x-0"
|
||||
className="flex items-start justify-between w-full"
|
||||
>
|
||||
<div className="flex-grow mt-10">
|
||||
<div className="relative space-y-4">
|
||||
<div className="px-32 mx-auto space-y-4 2xl:min-w-5xl">
|
||||
<BackButton onClick={() => onSelectBack()} />
|
||||
<h4 className="text-lg text-scale-900 !mb-8">Change your project's subscription</h4>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="px-32 pb-8 mx-auto space-y-8 overflow-y-auto 2xl:min-w-5xl"
|
||||
style={{ height: 'calc(100vh - 9rem - 57px)' }}
|
||||
>
|
||||
<div className="space-y-2">
|
||||
{!isManagingTeamSubscription ? (
|
||||
<>
|
||||
<h3 className="text-xl">
|
||||
Welcome to <span className="text-brand-900">Team</span>
|
||||
</h3>
|
||||
<p className="text-base text-scale-1100">
|
||||
Your new subscription will begin immediately after payment
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<h3 className="text-3xl">
|
||||
Managing your <span className="text-brand-900">Team</span> plan
|
||||
</h3>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{projectRegion !== 'af-south-1' && (
|
||||
<>
|
||||
{currentAddons.supportPlan !== undefined && (
|
||||
<>
|
||||
<Divider light />
|
||||
<SupportPlan currentOption={currentAddons.supportPlan} />
|
||||
</>
|
||||
)}
|
||||
{isCustomDomainsEnabled && customDomainOptions.length > 0 && (
|
||||
<>
|
||||
<Divider light />
|
||||
<CustomDomainSelection
|
||||
options={customDomainOptions}
|
||||
currentOption={
|
||||
isManagingTeamSubscription ? currentAddons.customDomains : undefined
|
||||
}
|
||||
selectedOption={selectedAddons.customDomains}
|
||||
onSelectOption={setSelectedCustomDomainOption}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{isPITRSelfServeEnabled && pitrDurationOptions.length > 0 && (
|
||||
<>
|
||||
<Divider light />
|
||||
<PITRDurationSelection
|
||||
pitrDurationOptions={pitrDurationOptions}
|
||||
currentPitrDuration={
|
||||
isManagingTeamSubscription ? currentAddons.pitrDuration : undefined
|
||||
}
|
||||
selectedPitrDuration={selectedAddons.pitrDuration}
|
||||
onSelectOption={setSelectedPITRDuration}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Divider light />
|
||||
<ComputeSizeSelection
|
||||
computeSizes={computeSizes || []}
|
||||
currentComputeSize={currentAddons.computeSize}
|
||||
selectedComputeSize={selectedAddons.computeSize}
|
||||
onSelectOption={setSelectedComputeSize}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-[34rem]">
|
||||
<PaymentSummaryPanel
|
||||
isRefreshingPreview={isRefreshingPreview}
|
||||
subscriptionPreview={subscriptionPreview}
|
||||
isSpendCapEnabled={false}
|
||||
// Current subscription configuration based on DB
|
||||
currentPlan={currentSubscription.tier}
|
||||
currentAddons={currentAddons}
|
||||
currentSubscription={currentSubscription}
|
||||
// Selected subscription configuration based on UI
|
||||
selectedPlan={selectedTier}
|
||||
selectedAddons={selectedAddons}
|
||||
paymentMethods={paymentMethods}
|
||||
isLoadingPaymentMethods={isLoadingPaymentMethods}
|
||||
selectedPaymentMethod={selectedPaymentMethodId}
|
||||
onSelectPaymentMethod={setSelectedPaymentMethodId}
|
||||
onSelectAddNewPaymentMethod={() => {
|
||||
setShowAddPaymentMethodModal(true)
|
||||
}}
|
||||
beforeConfirmPayment={beforeConfirmPayment}
|
||||
onConfirmPayment={onConfirmPayment}
|
||||
isSubmitting={isSubmitting}
|
||||
captcha={
|
||||
<HCaptcha
|
||||
ref={captchaRef}
|
||||
sitekey={process.env.NEXT_PUBLIC_HCAPTCHA_SITE_KEY!}
|
||||
size="invisible"
|
||||
onVerify={(token) => {
|
||||
setCaptchaToken(token)
|
||||
}}
|
||||
onExpire={() => {
|
||||
setCaptchaToken(null)
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<AddNewPaymentMethodModal
|
||||
visible={showAddPaymentMethodModal}
|
||||
returnUrl={`${getURL()}/project/${projectRef}/settings/billing/update/team`}
|
||||
onCancel={() => setShowAddPaymentMethodModal(false)}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default TeamUpgrade
|
||||
@@ -1,21 +1,19 @@
|
||||
import { useContext, useEffect } from 'react'
|
||||
import { useEffect } from 'react'
|
||||
import { Form, Input } from 'ui'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
import { useStore, checkPermissions } from 'hooks'
|
||||
import { useStore, checkPermissions, useParams } from 'hooks'
|
||||
import { API_URL } from 'lib/constants'
|
||||
import { patch } from 'lib/common/fetch'
|
||||
|
||||
import { FormActions, FormPanel, FormSection, FormSectionContent } from 'components/ui/Forms'
|
||||
|
||||
import { PageContext } from 'pages/org/[slug]/settings'
|
||||
|
||||
const BillingEmail = () => {
|
||||
const { ui } = useStore()
|
||||
const PageState: any = useContext(PageContext)
|
||||
const { app, ui } = useStore()
|
||||
const { slug } = useParams()
|
||||
const { name, billing_email } = ui.selectedOrganization ?? {}
|
||||
|
||||
const formId = 'org-billing-email'
|
||||
const initialValues = {
|
||||
billing_email: PageState.organization?.billing_email ?? '',
|
||||
billing_email: billing_email ?? '',
|
||||
}
|
||||
|
||||
const canUpdateOrganization = checkPermissions(PermissionAction.UPDATE, 'organizations')
|
||||
@@ -30,9 +28,9 @@ const BillingEmail = () => {
|
||||
}
|
||||
|
||||
setSubmitting(true)
|
||||
const response = await patch(`${API_URL}/organizations/${PageState.organization.slug}`, {
|
||||
const response = await patch(`${API_URL}/organizations/${slug}`, {
|
||||
...values,
|
||||
name: PageState.organization.name,
|
||||
name,
|
||||
})
|
||||
if (response.error) {
|
||||
ui.setNotification({
|
||||
@@ -46,7 +44,7 @@ const BillingEmail = () => {
|
||||
initialValues: { billing_email },
|
||||
})
|
||||
|
||||
PageState.onOrgUpdated(response)
|
||||
app.onOrgUpdated(response)
|
||||
ui.setNotification({
|
||||
category: 'success',
|
||||
message: 'Successfully saved settings',
|
||||
@@ -66,9 +64,9 @@ const BillingEmail = () => {
|
||||
const hasChanges = JSON.stringify(values) !== JSON.stringify(initialValues)
|
||||
|
||||
useEffect(() => {
|
||||
const values = { billing_email: PageState.organization?.billing_email ?? '' }
|
||||
const values = { billing_email: billing_email ?? '' }
|
||||
resetForm({ values, initialValues: values })
|
||||
}, [PageState.organization.slug])
|
||||
}, [slug])
|
||||
|
||||
return (
|
||||
<FormPanel
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { FC, useState, useEffect } from 'react'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
|
||||
import { useStore } from 'hooks'
|
||||
import { Project } from 'types'
|
||||
import { useParams, useStore } from 'hooks'
|
||||
import { API_URL } from 'lib/constants'
|
||||
import { get } from 'lib/common/fetch'
|
||||
|
||||
@@ -11,14 +13,12 @@ import BillingAddress from './BillingAddress/BillingAddress'
|
||||
import TaxID from './TaxID/TaxID'
|
||||
import BillingEmail from './BillingEmail'
|
||||
|
||||
interface Props {
|
||||
organization: any
|
||||
projects: any[]
|
||||
}
|
||||
const BillingSettings = () => {
|
||||
const { app, ui } = useStore()
|
||||
const { slug } = useParams()
|
||||
|
||||
const BillingSettings: FC<Props> = ({ organization, projects = [] }) => {
|
||||
const { ui } = useStore()
|
||||
const { slug } = organization
|
||||
const organization = ui.selectedOrganization
|
||||
const projects = app.projects.list((x: Project) => x.organization_id == organization?.id) || []
|
||||
|
||||
const [customer, setCustomer] = useState<any>(null)
|
||||
const [isLoadingCustomer, setIsLoadingCustomer] = useState(false)
|
||||
@@ -122,4 +122,4 @@ const BillingSettings: FC<Props> = ({ organization, projects = [] }) => {
|
||||
)
|
||||
}
|
||||
|
||||
export default BillingSettings
|
||||
export default observer(BillingSettings)
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
import * as Tooltip from '@radix-ui/react-tooltip'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
|
||||
import { checkPermissions, useStore, useFlag } from 'hooks'
|
||||
import { checkPermissions, useStore } from 'hooks'
|
||||
import { delete_, patch } from 'lib/common/fetch'
|
||||
import { getURL } from 'lib/helpers'
|
||||
import { API_URL } from 'lib/constants'
|
||||
@@ -39,7 +39,6 @@ const PaymentMethods: FC<Props> = ({
|
||||
}) => {
|
||||
const { ui } = useStore()
|
||||
const orgSlug = ui.selectedOrganization?.slug ?? ''
|
||||
const isOwner = ui.selectedOrganization?.is_owner
|
||||
|
||||
const [selectedMethodForDefault, setSelectedMethodForDefault] = useState<any>()
|
||||
const [selectedMethodToDelete, setSelectedMethodToDelete] = useState<any>()
|
||||
@@ -244,7 +243,7 @@ const PaymentMethods: FC<Props> = ({
|
||||
|
||||
<AddNewPaymentMethodModal
|
||||
visible={showAddPaymentMethodModal}
|
||||
returnUrl={`${getURL()}/org/${orgSlug}/settings`}
|
||||
returnUrl={`${getURL()}/org/${orgSlug}/billing`}
|
||||
onCancel={() => setShowAddPaymentMethodModal(false)}
|
||||
/>
|
||||
|
||||
|
||||
@@ -48,8 +48,8 @@ const ProjectSummary: FC<ProjectSummaryProps> = ({ project }) => {
|
||||
<div className="flex w-[15%] items-center justify-end">
|
||||
<Link href={`/project/${project.ref}/settings/billing/subscription`}>
|
||||
<a className="group flex items-center space-x-2">
|
||||
<p className="text-sm opacity-0 transition group-hover:opacity-100">View details</p>
|
||||
<IconChevronRight />
|
||||
<p className="text-xs opacity-0 transition group-hover:opacity-100">View details</p>
|
||||
<IconChevronRight strokeWidth={1.5} />
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -7,14 +7,12 @@ import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
import { useStore, checkPermissions } from 'hooks'
|
||||
import { delete_ } from 'lib/common/fetch'
|
||||
import { API_URL } from 'lib/constants'
|
||||
import { PageContext } from 'pages/org/[slug]/settings'
|
||||
|
||||
const DeleteOrganizationButton = observer(() => {
|
||||
const PageState: any = useContext(PageContext)
|
||||
const router = useRouter()
|
||||
const { ui } = useStore()
|
||||
const { app, ui } = useStore()
|
||||
|
||||
const { slug: orgSlug, name: orgName } = PageState.organization
|
||||
const { slug: orgSlug, name: orgName } = ui.selectedOrganization || {}
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const [value, setValue] = useState('')
|
||||
@@ -49,7 +47,7 @@ const DeleteOrganizationButton = observer(() => {
|
||||
})
|
||||
setSubmitting(false)
|
||||
} else {
|
||||
PageState.onOrgDeleted(PageState.organization)
|
||||
app.onOrgDeleted(ui.selectedOrganization)
|
||||
setSubmitting(false)
|
||||
router.push('/')
|
||||
ui.setNotification({
|
||||
@@ -62,7 +60,7 @@ const DeleteOrganizationButton = observer(() => {
|
||||
return (
|
||||
<>
|
||||
<div className="mt-2">
|
||||
<Button onClick={() => setIsOpen(true)} type="danger">
|
||||
<Button loading={!orgSlug} onClick={() => setIsOpen(true)} type="danger">
|
||||
Delete organization
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useContext, useEffect } from 'react'
|
||||
import { useEffect } from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { Form, Input } from 'ui'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
|
||||
import { useStore, checkPermissions } from 'hooks'
|
||||
import { useStore, checkPermissions, useParams } from 'hooks'
|
||||
import { API_URL } from 'lib/constants'
|
||||
import { patch } from 'lib/common/fetch'
|
||||
|
||||
@@ -16,14 +16,12 @@ import {
|
||||
FormSectionLabel,
|
||||
} from 'components/ui/Forms'
|
||||
|
||||
import { PageContext } from 'pages/org/[slug]/settings'
|
||||
|
||||
const GeneralSettings = observer(() => {
|
||||
const { ui } = useStore()
|
||||
const PageState: any = useContext(PageContext)
|
||||
const GeneralSettings = () => {
|
||||
const { app, ui } = useStore()
|
||||
const { slug } = useParams()
|
||||
|
||||
const formId = 'org-general-settings'
|
||||
const initialValues = { name: PageState.organization.name }
|
||||
const initialValues = { name: ui.selectedOrganization?.name ?? '' }
|
||||
const canUpdateOrganization = checkPermissions(PermissionAction.UPDATE, 'organizations')
|
||||
const canDeleteOrganization = checkPermissions(PermissionAction.UPDATE, 'organizations')
|
||||
|
||||
@@ -37,9 +35,9 @@ const GeneralSettings = observer(() => {
|
||||
|
||||
setSubmitting(true)
|
||||
|
||||
const response = await patch(`${API_URL}/organizations/${PageState.organization.slug}`, {
|
||||
const response = await patch(`${API_URL}/organizations/${slug}`, {
|
||||
...values,
|
||||
billing_email: PageState.organization?.billing_email ?? '',
|
||||
billing_email: ui.selectedOrganization?.billing_email ?? '',
|
||||
})
|
||||
|
||||
if (response.error) {
|
||||
@@ -54,7 +52,7 @@ const GeneralSettings = observer(() => {
|
||||
initialValues: { name },
|
||||
})
|
||||
|
||||
PageState.onOrgUpdated(response)
|
||||
app.onOrgUpdated(response)
|
||||
ui.setNotification({
|
||||
category: 'success',
|
||||
message: 'Successfully saved settings',
|
||||
@@ -70,9 +68,9 @@ const GeneralSettings = observer(() => {
|
||||
const hasChanges = JSON.stringify(values) !== JSON.stringify(initialValues)
|
||||
|
||||
useEffect(() => {
|
||||
const values = { name: PageState.organization.name }
|
||||
const values = { name: ui.selectedOrganization?.name ?? '' }
|
||||
resetForm({ values, initialValues: values })
|
||||
}, [PageState.organization.slug])
|
||||
}, [ui.selectedOrganization?.slug])
|
||||
|
||||
return (
|
||||
<FormPanel
|
||||
@@ -105,6 +103,6 @@ const GeneralSettings = observer(() => {
|
||||
{canDeleteOrganization && <OrganizationDeletePanel />}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default GeneralSettings
|
||||
export default observer(GeneralSettings)
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FC, useState, useEffect } from 'react'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Button, Loading, IconFileText, IconDownload, IconChevronLeft, IconChevronRight } from 'ui'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
|
||||
@@ -10,15 +10,9 @@ import NoPermission from 'components/ui/NoPermission'
|
||||
|
||||
const PAGE_LIMIT = 10
|
||||
|
||||
/**
|
||||
* Eventually deprecate this - as we move on to show invoices by project on the organization billing page
|
||||
*/
|
||||
// Refactor: Split invoices by projects so it's easier for users to identify
|
||||
|
||||
interface Props {
|
||||
organization: any
|
||||
}
|
||||
|
||||
const InvoicesSettings: FC<Props> = ({ organization }) => {
|
||||
const InvoicesSettings = () => {
|
||||
const { ui } = useStore()
|
||||
const [loading, setLoading] = useState<any>(false)
|
||||
|
||||
@@ -26,7 +20,7 @@ const InvoicesSettings: FC<Props> = ({ organization }) => {
|
||||
const [count, setCount] = useState(0)
|
||||
const [invoices, setInvoices] = useState<any>([])
|
||||
|
||||
const { stripe_customer_id } = organization
|
||||
const { stripe_customer_id } = ui.selectedOrganization ?? {}
|
||||
const offset = (page - 1) * PAGE_LIMIT
|
||||
|
||||
const canReadInvoices = checkPermissions(PermissionAction.READ, 'invoices')
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import * as Tooltip from '@radix-ui/react-tooltip'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FC, useContext, useState } from 'react'
|
||||
import { FC, useState } from 'react'
|
||||
import { Button, Dropdown, IconMoreHorizontal, IconTrash } from 'ui'
|
||||
|
||||
import { confirmAlert } from 'components/to-be-cleaned/ModalsDeprecated/ConfirmModal'
|
||||
import { checkPermissions, useOrganizationDetail, useStore } from 'hooks'
|
||||
import { checkPermissions, useOrganizationDetail, useParams, useStore } from 'hooks'
|
||||
import { delete_, post } from 'lib/common/fetch'
|
||||
import { API_URL } from 'lib/constants'
|
||||
import { Member, Role } from 'types'
|
||||
|
||||
import { PageContext } from 'pages/org/[slug]/settings'
|
||||
import { isInviteExpired } from '../Organization.utils'
|
||||
import { getRolesManagementPermissions } from './TeamSettings.utils'
|
||||
|
||||
@@ -21,12 +20,10 @@ interface Props {
|
||||
}
|
||||
|
||||
const MemberActions: FC<Props> = ({ members, member, roles }) => {
|
||||
const PageState: any = useContext(PageContext)
|
||||
const { id, slug, name: orgName } = PageState.organization
|
||||
const { ui } = useStore()
|
||||
const { slug } = useParams()
|
||||
const { rolesRemovable } = getRolesManagementPermissions(roles)
|
||||
|
||||
const { app, ui } = useStore()
|
||||
const { mutateOrgMembers } = useOrganizationDetail(ui.selectedOrganization?.slug || '')
|
||||
const { mutateOrgMembers } = useOrganizationDetail(slug || '')
|
||||
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
@@ -72,35 +69,6 @@ const MemberActions: FC<Props> = ({ members, member, roles }) => {
|
||||
})
|
||||
}
|
||||
|
||||
// [Joshen] This will be deprecated after ABAC is fully rolled out
|
||||
const handleTransferOwnership = async () => {
|
||||
setLoading(true)
|
||||
|
||||
const response = await post(`${API_URL}/organizations/${slug}/transfer`, {
|
||||
org_id: id,
|
||||
member_id: member.id,
|
||||
})
|
||||
if (response.error) {
|
||||
ui.setNotification({
|
||||
category: 'error',
|
||||
message: `Failed to transfer ownership: ${response.error.message}`,
|
||||
})
|
||||
setLoading(false)
|
||||
} else {
|
||||
const updatedMembers = members.map((m: any) => {
|
||||
if (m.is_owner) return { ...m, is_owner: false }
|
||||
if (m.id === member.id) return { ...m, is_owner: true }
|
||||
else return { ...m }
|
||||
})
|
||||
|
||||
mutateOrgMembers(updatedMembers)
|
||||
|
||||
ui.setNotification({ category: 'success', message: 'Successfully transferred organization' })
|
||||
}
|
||||
|
||||
app.organizations.load()
|
||||
}
|
||||
|
||||
async function handleResendInvite(member: Member) {
|
||||
setLoading(true)
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import Image from 'next/image'
|
||||
import * as Tooltip from '@radix-ui/react-tooltip'
|
||||
import { useState, useContext, Fragment } from 'react'
|
||||
import { useState, FC, Fragment } from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { Badge, Button, Loading, Listbox, IconUser, Modal } from 'ui'
|
||||
import { Badge, Button, Loading, Listbox, IconUser, Modal, IconAlertCircle } from 'ui'
|
||||
|
||||
import { Member, Role } from 'types'
|
||||
import { useStore, useOrganizationDetail } from 'hooks'
|
||||
import { useStore, useOrganizationDetail, useParams } from 'hooks'
|
||||
import { patch } from 'lib/common/fetch'
|
||||
import { API_URL } from 'lib/constants'
|
||||
import { isInviteExpired, getUserDisplayName } from '../Organization.utils'
|
||||
@@ -13,7 +13,6 @@ import { isInviteExpired, getUserDisplayName } from '../Organization.utils'
|
||||
import Table from 'components/to-be-cleaned/Table'
|
||||
import MemberActions from './MemberActions'
|
||||
import RolesHelperModal from './RolesHelperModal/RolesHelperModal'
|
||||
import { PageContext } from 'pages/org/[slug]/settings'
|
||||
import { getRolesManagementPermissions } from './TeamSettings.utils'
|
||||
|
||||
interface SelectedMember extends Member {
|
||||
@@ -21,20 +20,42 @@ interface SelectedMember extends Member {
|
||||
newRoleId: number
|
||||
}
|
||||
|
||||
const MembersView = () => {
|
||||
const PageState: any = useContext(PageContext)
|
||||
const { roles }: { roles: Role[] } = PageState
|
||||
const { rolesAddable, rolesRemovable } = getRolesManagementPermissions(roles)
|
||||
interface Props {
|
||||
roles: Role[]
|
||||
members: Member[]
|
||||
searchString: string
|
||||
}
|
||||
|
||||
const MembersView: FC<Props> = ({ searchString, roles, members }) => {
|
||||
const { ui } = useStore()
|
||||
const slug = ui.selectedOrganization?.slug || ''
|
||||
const { slug } = useParams()
|
||||
|
||||
const { mutateOrgMembers } = useOrganizationDetail(slug)
|
||||
const user = ui.profile
|
||||
|
||||
const { mutateOrgMembers } = useOrganizationDetail(slug ?? '')
|
||||
const { rolesAddable, rolesRemovable } = getRolesManagementPermissions(roles)
|
||||
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [selectedMember, setSelectedMember] = useState<SelectedMember>()
|
||||
const [userRoleChangeModalVisible, setUserRoleChangeModalVisible] = useState(false)
|
||||
|
||||
if (!members) return <div />
|
||||
|
||||
const filteredMembers = (
|
||||
!searchString
|
||||
? members
|
||||
: members.filter((x: any) => {
|
||||
if (x.invited_at) {
|
||||
return x.primary_email.includes(searchString)
|
||||
}
|
||||
if (x.id || x.gotrue_id) {
|
||||
return x.username.includes(searchString) || x.primary_email.includes(searchString)
|
||||
}
|
||||
})
|
||||
)
|
||||
.slice()
|
||||
.sort((a: any, b: any) => a.username.localeCompare(b.username))
|
||||
|
||||
const getRoleNameById = (id: number | undefined) => {
|
||||
if (!roles) return id
|
||||
return roles.find((x: any) => x.id === id)?.name
|
||||
@@ -55,7 +76,7 @@ const MembersView = () => {
|
||||
message: `Failed to update role for ${getUserDisplayName(selectedMember)}`,
|
||||
})
|
||||
} else {
|
||||
const updatedMembers = PageState.members.map((member: Member) => {
|
||||
const updatedMembers = members.map((member: Member) => {
|
||||
if (member.gotrue_id === selectedMember.gotrue_id) {
|
||||
return { ...member, role_ids: [newRoleId] }
|
||||
} else {
|
||||
@@ -76,7 +97,7 @@ const MembersView = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="rounded">
|
||||
<Loading active={!PageState.filteredMembers}>
|
||||
<Loading active={!filteredMembers}>
|
||||
<Table
|
||||
head={[
|
||||
<Table.th key="header-user">User</Table.th>,
|
||||
@@ -88,10 +109,10 @@ const MembersView = () => {
|
||||
<Table.th key="header-action"></Table.th>,
|
||||
]}
|
||||
body={[
|
||||
PageState.filteredMembers.map((x: Member, i: number) => {
|
||||
...filteredMembers.map((x: Member, i: number) => {
|
||||
const [memberRoleId] = x.role_ids ?? []
|
||||
const role = (roles || []).find((role) => role.id === memberRoleId)
|
||||
const memberIsUser = x.primary_email == PageState.user.primary_email
|
||||
const memberIsUser = x.primary_email == user?.primary_email
|
||||
const memberIsPendingInvite = !!x.invited_id
|
||||
const canRemoveRole = rolesRemovable.includes(memberRoleId)
|
||||
const disableRoleEdit = !canRemoveRole || memberIsUser || memberIsPendingInvite
|
||||
@@ -216,22 +237,38 @@ const MembersView = () => {
|
||||
</Table.td>
|
||||
<Table.td>
|
||||
{!memberIsUser && (
|
||||
<MemberActions members={PageState.members} member={x} roles={roles} />
|
||||
<MemberActions members={members} member={x} roles={roles} />
|
||||
)}
|
||||
</Table.td>
|
||||
</Table.tr>
|
||||
</Fragment>
|
||||
)
|
||||
}),
|
||||
...(searchString.length > 0 && filteredMembers.length === 0
|
||||
? [
|
||||
<Table.tr
|
||||
key="no-results"
|
||||
className="bg-panel-secondary-light dark:bg-panel-secondary-dark"
|
||||
>
|
||||
<Table.td colSpan={12}>
|
||||
<div className="flex items-center space-x-3 opacity-75">
|
||||
<IconAlertCircle size={16} strokeWidth={2} />
|
||||
<p className="text-scale-1100">
|
||||
No users matched the search query "{searchString}"
|
||||
</p>
|
||||
</div>
|
||||
</Table.td>
|
||||
</Table.tr>,
|
||||
]
|
||||
: []),
|
||||
<Table.tr
|
||||
key="footer"
|
||||
className="bg-panel-secondary-light dark:bg-panel-secondary-dark"
|
||||
>
|
||||
<Table.td colSpan={4}>
|
||||
<p className="text-scale-1100">
|
||||
{PageState.membersFilterString ? `${PageState.filteredMembers.length} of ` : ''}
|
||||
{PageState.members.length || '0'}{' '}
|
||||
{PageState.members.length == 1 ? 'user' : 'users'}
|
||||
{searchString ? `${filteredMembers.length} of ` : ''}
|
||||
{members.length || '0'} {members.length == 1 ? 'user' : 'users'}
|
||||
</p>
|
||||
</Table.td>
|
||||
</Table.tr>,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useState, useContext } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { Button, Input, IconSearch } from 'ui'
|
||||
import * as Tooltip from '@radix-ui/react-tooltip'
|
||||
|
||||
import { useStore } from 'hooks'
|
||||
import { useStore, useFlag, useOrganizationRoles, useOrganizationDetail, useParams } from 'hooks'
|
||||
import { post } from 'lib/common/fetch'
|
||||
import { API_URL } from 'lib/constants'
|
||||
import InviteMemberButton from './InviteMemberButton'
|
||||
@@ -11,26 +11,28 @@ import MembersView from './MembersView'
|
||||
import { getRolesManagementPermissions, hasMultipleOwners } from './TeamSettings.utils'
|
||||
import { confirmAlert } from 'components/to-be-cleaned/ModalsDeprecated/ConfirmModal'
|
||||
|
||||
import { PageContext } from 'pages/org/[slug]/settings'
|
||||
|
||||
const TeamSettings = observer(() => {
|
||||
const PageState: any = useContext(PageContext)
|
||||
const { user, members, roles } = PageState
|
||||
const { rolesAddable } = getRolesManagementPermissions(roles)
|
||||
|
||||
const TeamSettings = () => {
|
||||
const { ui } = useStore()
|
||||
const slug = ui.selectedOrganization?.slug ?? ''
|
||||
const { slug } = useParams()
|
||||
|
||||
const user = ui.profile
|
||||
const isOwner = ui.selectedOrganization?.is_owner
|
||||
|
||||
const { members } = useOrganizationDetail(slug || '')
|
||||
const { roles: allRoles } = useOrganizationRoles(slug)
|
||||
|
||||
const enableBillingOnlyReadOnly = useFlag('enableBillingOnlyReadOnlyRoles')
|
||||
const roles = enableBillingOnlyReadOnly
|
||||
? allRoles
|
||||
: (allRoles ?? []).filter((role) => ['Owner', 'Administrator', 'Developer'].includes(role.name))
|
||||
const { rolesAddable } = getRolesManagementPermissions(roles)
|
||||
|
||||
const [isLeaving, setIsLeaving] = useState(false)
|
||||
const [searchString, setSearchString] = useState('')
|
||||
|
||||
const canAddMembers = rolesAddable.length > 0
|
||||
|
||||
const canLeave = !isOwner || (isOwner && hasMultipleOwners(members, roles))
|
||||
|
||||
function onFilterMemberChange(e: any) {
|
||||
PageState.membersFilterString = e.target.value
|
||||
}
|
||||
|
||||
const leaveTeam = async () => {
|
||||
setIsLeaving(true)
|
||||
try {
|
||||
@@ -63,14 +65,14 @@ const TeamSettings = observer(() => {
|
||||
<Input
|
||||
icon={<IconSearch size="tiny" />}
|
||||
size="small"
|
||||
value={PageState.membersFilterString}
|
||||
onChange={onFilterMemberChange}
|
||||
value={searchString}
|
||||
onChange={(e: any) => setSearchString(e.target.value)}
|
||||
name="email"
|
||||
id="email"
|
||||
placeholder="Filter members"
|
||||
/>
|
||||
<div className="flex items-center space-x-4">
|
||||
{canAddMembers && (
|
||||
{canAddMembers && user !== undefined && (
|
||||
<div>
|
||||
<InviteMemberButton
|
||||
user={user}
|
||||
@@ -113,10 +115,10 @@ const TeamSettings = observer(() => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="container my-4 max-w-4xl space-y-8">
|
||||
<MembersView />
|
||||
<MembersView searchString={searchString} roles={roles} members={members} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default TeamSettings
|
||||
export default observer(TeamSettings)
|
||||
@@ -1,6 +1,8 @@
|
||||
import dayjs from 'dayjs'
|
||||
import { DatetimeHelper, FilterTableSet, LogTemplate } from '.'
|
||||
|
||||
export const LOGS_EXPLORER_DOCS_URL = "https://supabase.com/docs/guides/platform/logs#querying-with-the-logs-explorer"
|
||||
|
||||
export const LOGS_LARGE_DATE_RANGE_DAYS_THRESHOLD = 4
|
||||
|
||||
export const TEMPLATES: LogTemplate[] = [
|
||||
@@ -631,5 +633,6 @@ export const TIER_QUERY_LIMITS: {
|
||||
FREE: { text: '1 day', value: 1, unit: 'day', promptUpgrade: true },
|
||||
PRO: { text: '7 days', value: 7, unit: 'day', promptUpgrade: true },
|
||||
PAYG: { text: '90 days', value: 90, unit: 'day', promptUpgrade: false },
|
||||
TEAM: { text: '90 days', value: 90, unit: 'day', promptUpgrade: false },
|
||||
ENTERPRISE: { text: '90 days', value: 90, unit: 'day', promptUpgrade: false },
|
||||
}
|
||||
@@ -173,16 +173,22 @@ export const genSingleLogQuery = (table: LogsTableName, id: string) =>
|
||||
*/
|
||||
export const maybeShowUpgradePrompt = (
|
||||
from: string | null | undefined,
|
||||
tierKey?: StripeSubscription['tier']["key"]
|
||||
tierKey?: StripeSubscription['tier']['key']
|
||||
) => {
|
||||
const day = Math.abs(dayjs().diff(dayjs(from), 'day'))
|
||||
|
||||
return (day > 1 && tierKey === 'FREE') || (day > 7 && tierKey === 'PRO') || day > 90 && tierKey === 'ENTERPRISE'
|
||||
return (
|
||||
(day > 1 && tierKey === 'FREE') ||
|
||||
(day > 7 && tierKey === 'PRO') ||
|
||||
(day > 28 && tierKey === 'TEAM') ||
|
||||
(day > 90 && tierKey === 'ENTERPRISE')
|
||||
)
|
||||
}
|
||||
|
||||
export const genCountQuery = (table: LogsTableName, filters: Filters): string => {
|
||||
const where = _genWhereStatement(table, filters)
|
||||
return `SELECT count(*) as count FROM ${table} ${where}`}
|
||||
return `SELECT count(*) as count FROM ${table} ${where}`
|
||||
}
|
||||
|
||||
/** calculates how much the chart start datetime should be offset given the current datetime filter params */
|
||||
export const calcChartStart = (params: Partial<LogsEndpointParams>): [Dayjs, string] => {
|
||||
@@ -224,10 +230,11 @@ SELECT
|
||||
FROM
|
||||
${table} t
|
||||
cross join unnest(t.metadata) as metadata
|
||||
${where
|
||||
${
|
||||
where
|
||||
? where + ` and t.timestamp > '${startOffset.toISOString()}'`
|
||||
: `where t.timestamp > '${startOffset.toISOString()}'`
|
||||
}
|
||||
}
|
||||
GROUP BY
|
||||
timestamp
|
||||
ORDER BY
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
EXPLORER_DATEPICKER_HELPERS,
|
||||
LogsTableName,
|
||||
LogsWarning,
|
||||
LOGS_EXPLORER_DOCS_URL,
|
||||
LOGS_SOURCE_DESCRIPTION,
|
||||
LogTemplate,
|
||||
} from '.'
|
||||
@@ -13,6 +14,7 @@ import DatePickers from './Logs.DatePickers'
|
||||
import Link from 'next/link'
|
||||
import React from 'react'
|
||||
import { checkPermissions, useStore } from 'hooks'
|
||||
import { IconBookOpen } from '@supabase/ui'
|
||||
|
||||
interface Props {
|
||||
templates?: LogTemplate[]
|
||||
@@ -130,6 +132,11 @@ const LogsQueryPanel: React.FC<Props> = ({
|
||||
<div>
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<a target="_blank" href={LOGS_EXPLORER_DOCS_URL}>
|
||||
<Button type="default" icon={<IconBookOpen size={12} />}>
|
||||
Docs
|
||||
</Button>
|
||||
</a>
|
||||
<Button type="default" onClick={onClear}>
|
||||
Clear query
|
||||
</Button>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Button, Modal } from 'ui'
|
||||
import { TIER_QUERY_LIMITS } from '.'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useFlag } from 'hooks'
|
||||
|
||||
interface Props {
|
||||
show: boolean
|
||||
@@ -13,6 +14,8 @@ const UpgradePrompt: React.FC<Props> = ({ show, setShowUpgradePrompt }) => {
|
||||
const router = useRouter()
|
||||
const { ref } = router.query
|
||||
|
||||
const teamTierEnabled = useFlag('teamTier')
|
||||
|
||||
return (
|
||||
<Modal
|
||||
hideFooter
|
||||
@@ -43,6 +46,12 @@ const UpgradePrompt: React.FC<Props> = ({ show, setShowUpgradePrompt }) => {
|
||||
<p className="w-[40%] text-sm">Pro</p>
|
||||
<p className="w-[60%] text-sm">{TIER_QUERY_LIMITS.PRO.text}</p>
|
||||
</div>
|
||||
{teamTierEnabled && (
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<p className="w-[40%] text-sm">Team</p>
|
||||
<p className="w-[60%] text-sm">{TIER_QUERY_LIMITS.TEAM.text}</p>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<p className="w-[40%] text-sm">Enterprise</p>
|
||||
<p className="w-[60%] text-sm">{TIER_QUERY_LIMITS.ENTERPRISE.text}</p>
|
||||
|
||||
@@ -23,6 +23,7 @@ const ProjectUsage: FC<Props> = ({ projectRef }) => {
|
||||
|
||||
const projectHasNoLimits =
|
||||
subscriptionTier === PRICING_TIER_PRODUCT_IDS.PAYG ||
|
||||
subscriptionTier === PRICING_TIER_PRODUCT_IDS.TEAM ||
|
||||
subscriptionTier === PRICING_TIER_PRODUCT_IDS.ENTERPRISE
|
||||
|
||||
const showUsageExceedMessage = subscriptionTier !== undefined && !projectHasNoLimits
|
||||
@@ -31,6 +32,7 @@ const ProjectUsage: FC<Props> = ({ projectRef }) => {
|
||||
[PRICING_TIER_PRODUCT_IDS.FREE]: 'Free',
|
||||
[PRICING_TIER_PRODUCT_IDS.PRO]: 'Pro',
|
||||
[PRICING_TIER_PRODUCT_IDS.PAYG]: 'Pro',
|
||||
[PRICING_TIER_PRODUCT_IDS.TEAM]: 'Team',
|
||||
[PRICING_TIER_PRODUCT_IDS.ENTERPRISE]: 'Enterprise',
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,8 @@ const ProjectUsageMinimal: FC<ProjectUsageMinimalProps> = ({ projectRef, filter
|
||||
|
||||
if (
|
||||
subscription?.tier?.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.PAYG ||
|
||||
subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.ENTERPRISE
|
||||
subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.ENTERPRISE ||
|
||||
subscription?.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.TEAM
|
||||
) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ const SupportForm: FC<Props> = ({ setSentCategory }) => {
|
||||
[PRICING_TIER_PRODUCT_IDS.FREE]: 'Free',
|
||||
[PRICING_TIER_PRODUCT_IDS.PRO]: 'Pro',
|
||||
[PRICING_TIER_PRODUCT_IDS.PAYG]: 'Pro',
|
||||
[PRICING_TIER_PRODUCT_IDS.TEAM]: 'Team',
|
||||
[PRICING_TIER_PRODUCT_IDS.ENTERPRISE]: 'Enterprise',
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ const AccountLayout: FC<Props> = ({ children, title, breadcrumbs }) => {
|
||||
isActive:
|
||||
router.pathname.startsWith('/org/') && ui.selectedOrganization?.slug === organization.slug,
|
||||
label: organization.name,
|
||||
href: `/org/${organization.slug}/settings`,
|
||||
href: `/org/${organization.slug}/general`,
|
||||
key: organization.slug,
|
||||
}))
|
||||
.sort((a, b) => a.label.localeCompare(b.label))
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { useStore } from 'hooks'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FC, ReactNode } from 'react'
|
||||
import { AccountLayout } from './'
|
||||
|
||||
interface Props {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
const OrganizationLayout: FC<Props> = ({ children }) => {
|
||||
const { ui } = useStore()
|
||||
|
||||
return (
|
||||
<AccountLayout
|
||||
title={ui.selectedOrganization?.name ?? 'Supabase'}
|
||||
breadcrumbs={[{ key: `org-settings`, label: 'Settings' }]}
|
||||
>
|
||||
{children}
|
||||
</AccountLayout>
|
||||
)
|
||||
}
|
||||
|
||||
export default observer(OrganizationLayout)
|
||||
@@ -23,7 +23,8 @@ const LayoutHeader = ({ customHeaderComponents, breadcrumbs = [], headerBorder =
|
||||
const resourcesExceededLimits = getResourcesExceededLimits(usage)
|
||||
const projectHasNoLimits =
|
||||
ui.selectedProject?.subscription_tier === PRICING_TIER_PRODUCT_IDS.PAYG ||
|
||||
ui.selectedProject?.subscription_tier === PRICING_TIER_PRODUCT_IDS.ENTERPRISE
|
||||
ui.selectedProject?.subscription_tier === PRICING_TIER_PRODUCT_IDS.ENTERPRISE ||
|
||||
ui.selectedProject?.subscription_tier === PRICING_TIER_PRODUCT_IDS.TEAM
|
||||
const showOverUsageBadge =
|
||||
selectedProject?.subscription_tier !== undefined &&
|
||||
!projectHasNoLimits &&
|
||||
|
||||
@@ -40,7 +40,7 @@ const OrgDropdown = () => {
|
||||
}
|
||||
|
||||
router.push({
|
||||
pathname: `/org/[slug]/settings`,
|
||||
pathname: `/org/[slug]/general`,
|
||||
query: { slug },
|
||||
hash: router.asPath.split('#')[1]?.toLowerCase(),
|
||||
})
|
||||
|
||||
@@ -16,6 +16,7 @@ import VercelIntegrationLayout from './VercelIntegrationLayout'
|
||||
import BillingLayout from './BillingLayout'
|
||||
import LogsLayout from './LogsLayout/LogsLayout'
|
||||
import ReportsLayout from './ReportsLayout/ReportsLayout'
|
||||
import OrganizationLayout from './OrganizationLayout'
|
||||
|
||||
export {
|
||||
ProjectLayoutWithAuth,
|
||||
@@ -36,6 +37,7 @@ export {
|
||||
BillingLayout,
|
||||
LogsLayout,
|
||||
ReportsLayout,
|
||||
OrganizationLayout,
|
||||
}
|
||||
|
||||
export default ProjectLayout
|
||||
@@ -30,7 +30,8 @@ const OveragesBanner: FC<Props> = ({ tier, minimal }) => {
|
||||
error ||
|
||||
(!isApproachingUsageLimits && !isOverUsageLimits) ||
|
||||
tier === PRICING_TIER_PRODUCT_IDS.PAYG ||
|
||||
tier === PRICING_TIER_PRODUCT_IDS.ENTERPRISE
|
||||
tier === PRICING_TIER_PRODUCT_IDS.ENTERPRISE ||
|
||||
tier === PRICING_TIER_PRODUCT_IDS.TEAM
|
||||
) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
@@ -7,6 +7,5 @@ export * from './useProfile'
|
||||
export * from './useProjectAuthConfig'
|
||||
export * from './useProjectSettings'
|
||||
export * from './useProjectSubscription'
|
||||
export * from './useSubscriptionStats'
|
||||
export * from './useProjectUsage'
|
||||
export * from './useProjectPostgrestConfig'
|
||||
@@ -1,29 +0,0 @@
|
||||
import useSWR, { mutate } from 'swr'
|
||||
import { get } from 'lib/common/fetch'
|
||||
import { API_URL } from 'lib/constants'
|
||||
|
||||
export interface SubscriptionStats {
|
||||
total_paid_projects: number
|
||||
total_free_projects: number
|
||||
total_active_free_projects: number
|
||||
total_paused_free_projects: number
|
||||
total_pro_projects: number
|
||||
total_payg_projects: number
|
||||
}
|
||||
|
||||
export function useSubscriptionStats() {
|
||||
const url = `${API_URL}/profile/subscriptions`
|
||||
const { data, error } = useSWR<any>(url, get)
|
||||
const anyError = data?.error || error
|
||||
|
||||
return {
|
||||
total_paid_projects: data?.total_paid_projects ?? 0,
|
||||
total_free_projects: data?.total_free_projects ?? 0,
|
||||
total_active_free_projects: data?.total_active_free_projects ?? 0,
|
||||
total_paused_free_projects: data?.total_paused_free_projects ?? 0,
|
||||
total_pro_projects: data?.total_pro_projects ?? 0,
|
||||
total_payg_projects: data?.total_payg_projects ?? 0,
|
||||
isLoading: !anyError && !data,
|
||||
isError: !!anyError,
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ export const PRICING_TIER_PRODUCT_IDS = {
|
||||
FREE: 'tier_free',
|
||||
PRO: 'tier_pro',
|
||||
PAYG: 'tier_payg',
|
||||
TEAM: 'tier_team',
|
||||
ENTERPRISE: 'tier_enterprise',
|
||||
}
|
||||
|
||||
@@ -112,6 +113,7 @@ export const STRIPE_PRODUCT_IDS: { [x: string]: string } = {
|
||||
FREE: process?.env?.NEXT_PUBLIC_STRIPE_FREE_TIER_ID || 'prod_Ip4vqwv3EJ7Mi0',
|
||||
PRO: process?.env?.NEXT_PUBLIC_STRIPE_PRO_TIER_ID || 'prod_IsRLOp58Z7V4XN',
|
||||
PAYG: process?.env?.NEXT_PUBLIC_STRIPE_PAYG_TIER_ID || 'prod_JlTbw91xcM6NY4',
|
||||
TEAM: process?.env?.NEXT_PUBLIC_STRIPE_TEAM_TIER_ID || 'prod_MeUrsWN6pj0oXM',
|
||||
}
|
||||
|
||||
export const DEFAULT_MINIMUM_PASSWORD_STRENGTH = 4
|
||||
|
||||
@@ -115,6 +115,11 @@ const nextConfig = {
|
||||
destination: '/project/:ref/logs/explorer',
|
||||
permanent: true,
|
||||
},
|
||||
{
|
||||
source: '/org/:slug/settings',
|
||||
destination: '/org/:slug/general',
|
||||
permanent: true,
|
||||
},
|
||||
]
|
||||
},
|
||||
async headers() {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import apiWrapper from 'lib/api/apiWrapper'
|
||||
import { Member } from 'types'
|
||||
|
||||
export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler)
|
||||
|
||||
@@ -27,6 +26,7 @@ const handleGetAll = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
total_paid_projects: 0,
|
||||
total_free_projects: 0,
|
||||
total_pro_projects: 0,
|
||||
total_team_projects: 0,
|
||||
total_payg_projects: 0,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ import {
|
||||
useStore,
|
||||
useFlag,
|
||||
withAuth,
|
||||
useSubscriptionStats,
|
||||
checkPermissions,
|
||||
useFreeProjectLimitCheck,
|
||||
} from 'hooks'
|
||||
@@ -47,7 +46,6 @@ const Wizard: NextPageWithLayout = () => {
|
||||
|
||||
const projectCreationDisabled = useFlag('disableProjectCreationAndUpdate')
|
||||
const kpsEnabled = useFlag('initWithKps')
|
||||
const subscriptionStats = useSubscriptionStats()
|
||||
const { membersExceededLimit, isLoading: isLoadingFreeProjectLimitCheck } =
|
||||
useFreeProjectLimitCheck(slug as string)
|
||||
|
||||
@@ -74,10 +72,7 @@ const Wizard: NextPageWithLayout = () => {
|
||||
const hasMembersExceedingFreeTierLimit = (membersExceededLimit || []).length > 0
|
||||
|
||||
const canCreateProject =
|
||||
isAdmin &&
|
||||
!subscriptionStats.isError &&
|
||||
!subscriptionStats.isLoading &&
|
||||
(!isSelectFreeTier || (isSelectFreeTier && !hasMembersExceedingFreeTierLimit))
|
||||
isAdmin && (!isSelectFreeTier || (isSelectFreeTier && !hasMembersExceedingFreeTierLimit))
|
||||
|
||||
const canSubmit =
|
||||
projectName !== '' &&
|
||||
@@ -204,7 +199,6 @@ const Wizard: NextPageWithLayout = () => {
|
||||
hideHeaderStyling
|
||||
loading={
|
||||
!app.organizations.isInitialized ||
|
||||
subscriptionStats.isLoading ||
|
||||
isLoadingFreeProjectLimitCheck
|
||||
}
|
||||
title={
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import { useRouter } from 'next/router'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { Tabs } from 'ui'
|
||||
|
||||
import { NextPageWithLayout } from 'types'
|
||||
import { useParams, useStore } from 'hooks'
|
||||
import Loading from 'components/ui/Loading'
|
||||
import { OrganizationLayout } from 'components/layouts'
|
||||
import { BillingSettings } from 'components/interfaces/Organization'
|
||||
|
||||
const OrgBillingSettings: NextPageWithLayout = () => {
|
||||
const { ui } = useStore()
|
||||
const { slug } = useParams()
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<>
|
||||
{ui.selectedOrganization === undefined && (ui?.permissions ?? []).length === 0 ? (
|
||||
<Loading />
|
||||
) : (
|
||||
<div className="p-4 pt-0">
|
||||
<div className="space-y-3">
|
||||
<section className="mt-4">
|
||||
<h1 className="text-3xl">
|
||||
{ui.selectedOrganization?.name ?? 'Organization'} settings
|
||||
</h1>
|
||||
</section>
|
||||
<nav>
|
||||
<Tabs
|
||||
size="small"
|
||||
type="underlined"
|
||||
activeId="billing"
|
||||
onChange={(id: any) => {
|
||||
if (id !== 'billing') router.push(`/org/${slug}/${id}`)
|
||||
}}
|
||||
>
|
||||
<Tabs.Panel id="general" label="General" />
|
||||
<Tabs.Panel id="team" label="Team" />
|
||||
<Tabs.Panel id="billing" label="Billing" />
|
||||
<Tabs.Panel id="invoices" label="Invoices" />
|
||||
</Tabs>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div className="mb-8">
|
||||
<BillingSettings />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
OrgBillingSettings.getLayout = (page) => <OrganizationLayout>{page}</OrganizationLayout>
|
||||
export default observer(OrgBillingSettings)
|
||||
@@ -0,0 +1,55 @@
|
||||
import { useRouter } from 'next/router'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { Tabs } from 'ui'
|
||||
|
||||
import { NextPageWithLayout } from 'types'
|
||||
import { useParams, useStore } from 'hooks'
|
||||
import Loading from 'components/ui/Loading'
|
||||
import { OrganizationLayout } from 'components/layouts'
|
||||
import { GeneralSettings } from 'components/interfaces/Organization'
|
||||
|
||||
const OrgGeneralSettings: NextPageWithLayout = () => {
|
||||
const { ui } = useStore()
|
||||
const { slug } = useParams()
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<>
|
||||
{ui.selectedOrganization === undefined && (ui?.permissions ?? []).length === 0 ? (
|
||||
<Loading />
|
||||
) : (
|
||||
<div className="p-4 pt-0">
|
||||
<div className="space-y-3">
|
||||
<section className="mt-4">
|
||||
<h1 className="text-3xl">
|
||||
{ui.selectedOrganization?.name ?? 'Organization'} settings
|
||||
</h1>
|
||||
</section>
|
||||
<nav>
|
||||
<Tabs
|
||||
size="small"
|
||||
type="underlined"
|
||||
activeId="general"
|
||||
onChange={(id: any) => {
|
||||
if (id !== 'general') router.push(`/org/${slug}/${id}`)
|
||||
}}
|
||||
>
|
||||
<Tabs.Panel id="general" label="General" />
|
||||
<Tabs.Panel id="team" label="Team" />
|
||||
<Tabs.Panel id="billing" label="Billing" />
|
||||
<Tabs.Panel id="invoices" label="Invoices" />
|
||||
</Tabs>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div className="mb-8">
|
||||
<GeneralSettings />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
OrgGeneralSettings.getLayout = (page) => <OrganizationLayout>{page}</OrganizationLayout>
|
||||
export default observer(OrgGeneralSettings)
|
||||
@@ -0,0 +1,55 @@
|
||||
import { useRouter } from 'next/router'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { Tabs } from 'ui'
|
||||
|
||||
import { NextPageWithLayout } from 'types'
|
||||
import { useParams, useStore } from 'hooks'
|
||||
import Loading from 'components/ui/Loading'
|
||||
import { OrganizationLayout } from 'components/layouts'
|
||||
import { InvoicesSettings } from 'components/interfaces/Organization'
|
||||
|
||||
const OrgInvoices: NextPageWithLayout = () => {
|
||||
const { ui } = useStore()
|
||||
const { slug } = useParams()
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<>
|
||||
{ui.selectedOrganization === undefined && (ui?.permissions ?? []).length === 0 ? (
|
||||
<Loading />
|
||||
) : (
|
||||
<div className="p-4 pt-0">
|
||||
<div className="space-y-3">
|
||||
<section className="mt-4">
|
||||
<h1 className="text-3xl">
|
||||
{ui.selectedOrganization?.name ?? 'Organization'} settings
|
||||
</h1>
|
||||
</section>
|
||||
<nav>
|
||||
<Tabs
|
||||
size="small"
|
||||
type="underlined"
|
||||
activeId="invoices"
|
||||
onChange={(id: any) => {
|
||||
if (id !== 'invoices') router.push(`/org/${slug}/${id}`)
|
||||
}}
|
||||
>
|
||||
<Tabs.Panel id="general" label="General" />
|
||||
<Tabs.Panel id="team" label="Team" />
|
||||
<Tabs.Panel id="billing" label="Billing" />
|
||||
<Tabs.Panel id="invoices" label="Invoices" />
|
||||
</Tabs>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div className="mb-8">
|
||||
<InvoicesSettings />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
OrgInvoices.getLayout = (page) => <OrganizationLayout>{page}</OrganizationLayout>
|
||||
export default observer(OrgInvoices)
|
||||
@@ -1,189 +0,0 @@
|
||||
import { Tabs } from 'ui'
|
||||
import { observer, useLocalObservable } from 'mobx-react-lite'
|
||||
import { useRouter } from 'next/router'
|
||||
import { createContext, PropsWithChildren, useContext, useEffect, useState } from 'react'
|
||||
|
||||
import { Member, NextPageWithLayout, Organization, Project, Role, User } from 'types'
|
||||
import {
|
||||
useFlag,
|
||||
useOrganizationDetail,
|
||||
useOrganizationRoles,
|
||||
useStore,
|
||||
withAuth,
|
||||
useParams,
|
||||
} from 'hooks'
|
||||
import { AccountLayoutWithoutAuth } from 'components/layouts'
|
||||
import {
|
||||
GeneralSettings,
|
||||
TeamSettings,
|
||||
BillingSettings,
|
||||
InvoicesSettings,
|
||||
} from 'components/interfaces/Organization'
|
||||
|
||||
export const PageContext = createContext(null)
|
||||
|
||||
const OrgSettingsLayout = withAuth(
|
||||
observer<PropsWithChildren<{}>>(({ children }) => {
|
||||
const { app, ui } = useStore()
|
||||
const router = useRouter()
|
||||
const { slug } = useParams()
|
||||
|
||||
const { roles: allRoles } = useOrganizationRoles(slug)
|
||||
const enableBillingOnlyReadOnly = useFlag('enableBillingOnlyReadOnlyRoles')
|
||||
const roles = enableBillingOnlyReadOnly
|
||||
? allRoles
|
||||
: (allRoles ?? []).filter((role) =>
|
||||
['Owner', 'Administrator', 'Developer'].includes(role.name)
|
||||
)
|
||||
|
||||
// [Refactor] Eventually move away from useLocalObservable
|
||||
const PageState: any = useLocalObservable(() => ({
|
||||
user: {} as User,
|
||||
organization: {} as Organization,
|
||||
projects: [] as Project[],
|
||||
members: [] as Member[],
|
||||
roles: [] as Role[],
|
||||
membersFilterString: '',
|
||||
|
||||
get filteredMembers() {
|
||||
const temp = !this.membersFilterString
|
||||
? this.members
|
||||
: this.members.filter((x: any) => {
|
||||
if (x.invited_at) {
|
||||
return x.primary_email.includes(this.membersFilterString)
|
||||
}
|
||||
if (x.id || x.gotrue_id) {
|
||||
return (
|
||||
x.username.includes(this.membersFilterString) ||
|
||||
x.primary_email.includes(this.membersFilterString)
|
||||
)
|
||||
}
|
||||
})
|
||||
return temp.slice().sort((a: any, b: any) => a.username.localeCompare(b.username))
|
||||
},
|
||||
initData(organization: Organization, user: User, projects: Project[], roles: Role[]) {
|
||||
this.user = user
|
||||
this.projects = projects
|
||||
this.organization = organization
|
||||
this.roles = roles
|
||||
},
|
||||
onOrgUpdated(updatedOrg: any) {
|
||||
app.onOrgUpdated(updatedOrg)
|
||||
},
|
||||
onOrgDeleted() {
|
||||
app.onOrgDeleted(this.organization)
|
||||
},
|
||||
}))
|
||||
|
||||
useEffect(() => {
|
||||
// User added a new payment method
|
||||
if (router.query.setup_intent && router.query.redirect_status) {
|
||||
ui.setNotification({
|
||||
category: 'success',
|
||||
message: 'Successfully added new payment method',
|
||||
})
|
||||
}
|
||||
}, [router.query])
|
||||
|
||||
useEffect(() => {
|
||||
const user = ui.profile
|
||||
const organization = ui.selectedOrganization
|
||||
const projects = app.projects.list((x: Project) => x.organization_id == organization?.id)
|
||||
PageState.initData(organization, user, projects, roles)
|
||||
}, [ui.selectedOrganization, ui.profile, roles])
|
||||
|
||||
return (
|
||||
<AccountLayoutWithoutAuth
|
||||
title={PageState.organization?.name || 'Supabase'}
|
||||
breadcrumbs={[
|
||||
{
|
||||
key: `org-settings`,
|
||||
label: 'Settings',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<PageContext.Provider value={PageState}>{children}</PageContext.Provider>
|
||||
</AccountLayoutWithoutAuth>
|
||||
)
|
||||
})
|
||||
)
|
||||
|
||||
const OrganizationSettings: NextPageWithLayout = () => {
|
||||
const router = useRouter()
|
||||
const { slug } = useParams()
|
||||
const PageState: any = useContext(PageContext)
|
||||
const { ui, app } = useStore()
|
||||
const [selectedTab, setSelectedTab] = useState('GENERAL')
|
||||
const { members, isError: isOrgDetailError } = useOrganizationDetail(slug || '')
|
||||
const hash = router.asPath.split('#')[1]?.toUpperCase()
|
||||
|
||||
useEffect(() => {
|
||||
if (hash) {
|
||||
setSelectedTab(hash)
|
||||
}
|
||||
}, [hash])
|
||||
|
||||
function handleChangeTab(id: string) {
|
||||
if (!slug) {
|
||||
// The user should not see this error as the page should
|
||||
// be rerendered with the value of slug before they can click.
|
||||
// It is just here in case they are the flash.
|
||||
return ui.setNotification({
|
||||
category: 'error',
|
||||
message: 'Could not navigate to organization settings, please try again or contact support',
|
||||
})
|
||||
}
|
||||
|
||||
setSelectedTab(id)
|
||||
router.push({
|
||||
pathname: `/org/[slug]/settings`,
|
||||
query: { slug },
|
||||
hash: id.toLowerCase(),
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOrgDetailError) {
|
||||
PageState.members = members ?? []
|
||||
}
|
||||
}, [members, isOrgDetailError])
|
||||
|
||||
if (!ui.selectedOrganization || !PageState.organization) return <div />
|
||||
|
||||
const organization = ui.selectedOrganization
|
||||
const projects = app.projects.list((x: Project) => x.organization_id == organization?.id)
|
||||
|
||||
return (
|
||||
<div className="p-4 pt-0">
|
||||
<div className="space-y-3">
|
||||
<section className="mt-4">
|
||||
<h1 className="text-3xl">{organization?.name || 'Organization'} settings</h1>
|
||||
</section>
|
||||
<nav className="">
|
||||
<Tabs size="small" onChange={(id: string) => handleChangeTab(id)} type="underlined">
|
||||
<Tabs.Panel id="GENERAL" label="General" />
|
||||
<Tabs.Panel id="TEAM" label="Team" />
|
||||
<Tabs.Panel id="BILLING" label="Billing" />
|
||||
<Tabs.Panel id="INVOICES" label="Invoices" />
|
||||
</Tabs>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div className="mb-8">
|
||||
{selectedTab == 'GENERAL' ? (
|
||||
<GeneralSettings />
|
||||
) : selectedTab == 'TEAM' ? (
|
||||
<TeamSettings />
|
||||
) : selectedTab == 'BILLING' ? (
|
||||
<BillingSettings organization={organization} projects={projects} />
|
||||
) : selectedTab == 'INVOICES' ? (
|
||||
<InvoicesSettings organization={organization} />
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
OrganizationSettings.getLayout = (page) => <OrgSettingsLayout>{page}</OrgSettingsLayout>
|
||||
|
||||
export default observer(OrganizationSettings)
|
||||
@@ -0,0 +1,55 @@
|
||||
import { useRouter } from 'next/router'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { Tabs } from 'ui'
|
||||
|
||||
import { NextPageWithLayout } from 'types'
|
||||
import { useParams, useStore } from 'hooks'
|
||||
import Loading from 'components/ui/Loading'
|
||||
import { OrganizationLayout } from 'components/layouts'
|
||||
import { TeamSettings } from 'components/interfaces/Organization'
|
||||
|
||||
const OrgTeamSettings: NextPageWithLayout = () => {
|
||||
const { ui } = useStore()
|
||||
const { slug } = useParams()
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<>
|
||||
{ui.selectedOrganization === undefined && (ui?.permissions ?? []).length === 0 ? (
|
||||
<Loading />
|
||||
) : (
|
||||
<div className="p-4 pt-0">
|
||||
<div className="space-y-3">
|
||||
<section className="mt-4">
|
||||
<h1 className="text-3xl">
|
||||
{ui.selectedOrganization?.name ?? 'Organization'} settings
|
||||
</h1>
|
||||
</section>
|
||||
<nav>
|
||||
<Tabs
|
||||
size="small"
|
||||
type="underlined"
|
||||
activeId="team"
|
||||
onChange={(id: any) => {
|
||||
if (id !== 'team') router.push(`/org/${slug}/${id}`)
|
||||
}}
|
||||
>
|
||||
<Tabs.Panel id="general" label="General" />
|
||||
<Tabs.Panel id="team" label="Team" />
|
||||
<Tabs.Panel id="billing" label="Billing" />
|
||||
<Tabs.Panel id="invoices" label="Invoices" />
|
||||
</Tabs>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div className="mb-8">
|
||||
<TeamSettings />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
OrgTeamSettings.getLayout = (page) => <OrganizationLayout>{page}</OrganizationLayout>
|
||||
export default observer(OrgTeamSettings)
|
||||
@@ -3,7 +3,7 @@ import { useRouter } from 'next/router'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { Button, Modal } from 'ui'
|
||||
|
||||
import { useFreeProjectLimitCheck, useStore } from 'hooks'
|
||||
import { useFlag, useFreeProjectLimitCheck, useStore } from 'hooks'
|
||||
import { get } from 'lib/common/fetch'
|
||||
import { API_URL, PRICING_TIER_PRODUCT_IDS, STRIPE_PRODUCT_IDS } from 'lib/constants'
|
||||
|
||||
@@ -85,6 +85,8 @@ const BillingUpdate: NextPageWithLayout = () => {
|
||||
const onSelectPlan = (plan: any) => {
|
||||
if (plan.id === STRIPE_PRODUCT_IDS.PRO) {
|
||||
router.push(`/project/${projectRef}/settings/billing/update/pro`)
|
||||
} else if (plan.id === STRIPE_PRODUCT_IDS.TEAM) {
|
||||
router.push(`/project/${projectRef}/settings/billing/update/team`)
|
||||
} else if (plan.id === STRIPE_PRODUCT_IDS.FREE) {
|
||||
setSelectedPlan(plan)
|
||||
setShowConfirmDowngrade(true)
|
||||
@@ -109,12 +111,18 @@ const BillingUpdate: NextPageWithLayout = () => {
|
||||
)
|
||||
}
|
||||
|
||||
const teamTierEnabled = useFlag('teamTier')
|
||||
|
||||
const productTiers = (products?.tiers ?? []).filter(
|
||||
(tier) => teamTierEnabled || tier.id !== STRIPE_PRODUCT_IDS.TEAM
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mx-auto my-10 max-w-6xl px-6">
|
||||
<div className={`mx-auto my-10 ${teamTierEnabled ? 'max-w-[90vw]' : 'max-w-[80vw]'}`}>
|
||||
<PlanSelection
|
||||
visible={!selectedPlan || (selectedPlan && showConfirmDowngrade)}
|
||||
tiers={products?.tiers ?? []}
|
||||
tiers={productTiers}
|
||||
currentPlan={subscription?.tier}
|
||||
onSelectPlan={onSelectPlan}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
import { useStore, useFlag } from 'hooks'
|
||||
import { get } from 'lib/common/fetch'
|
||||
import { API_URL, PRICING_TIER_PRODUCT_IDS } from 'lib/constants'
|
||||
|
||||
import { NextPageWithLayout } from 'types'
|
||||
import { BillingLayout } from 'components/layouts'
|
||||
import Connecting from 'components/ui/Loading/Loading'
|
||||
import { StripeSubscription } from 'components/interfaces/Billing'
|
||||
import { PaymentMethod } from 'components/interfaces/Billing/Billing.types'
|
||||
import { SubscriptionAddon } from 'components/interfaces/Billing/AddOns/AddOns.types'
|
||||
import TeamUpgrade from 'components/interfaces/Billing/TeamUpgrade'
|
||||
|
||||
const BillingUpdateTeam: NextPageWithLayout = () => {
|
||||
const { ui } = useStore()
|
||||
const router = useRouter()
|
||||
|
||||
const projectRef = ui.selectedProject?.ref
|
||||
const orgSlug = ui.selectedOrganization?.slug
|
||||
|
||||
const projectUpdateDisabled = useFlag('disableProjectCreationAndUpdate')
|
||||
|
||||
const [subscription, setSubscription] = useState<StripeSubscription>()
|
||||
const [products, setProducts] = useState<{ tiers: any[]; addons: SubscriptionAddon[] }>()
|
||||
const [paymentMethods, setPaymentMethods] = useState<PaymentMethod[]>()
|
||||
const [isLoadingPaymentMethods, setIsLoadingPaymentMethods] = useState(false)
|
||||
|
||||
const isEnterprise =
|
||||
subscription && subscription.tier.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.ENTERPRISE
|
||||
|
||||
useEffect(() => {
|
||||
// User added a new payment method
|
||||
if (router.query.setup_intent && router.query.redirect_status) {
|
||||
ui.setNotification({ category: 'success', message: 'Successfully added new payment method' })
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (projectUpdateDisabled) {
|
||||
router.push(`/project/${projectRef}/settings/billing/update`)
|
||||
} else if (projectRef) {
|
||||
getStripeProducts()
|
||||
getSubscription()
|
||||
}
|
||||
}, [projectRef])
|
||||
|
||||
useEffect(() => {
|
||||
if (orgSlug) {
|
||||
getPaymentMethods()
|
||||
}
|
||||
}, [orgSlug])
|
||||
|
||||
useEffect(() => {
|
||||
if (isEnterprise) {
|
||||
router.push(`/project/${projectRef}/settings/billing/update/enterprise`)
|
||||
}
|
||||
}, [subscription])
|
||||
|
||||
const getStripeProducts = async () => {
|
||||
try {
|
||||
const products = await get(`${API_URL}/stripe/products`)
|
||||
setProducts(products)
|
||||
} catch (error: any) {
|
||||
ui.setNotification({
|
||||
error,
|
||||
category: 'error',
|
||||
message: `Failed to get products: ${error.message}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const getPaymentMethods = async () => {
|
||||
const orgSlug = ui.selectedOrganization?.slug ?? ''
|
||||
try {
|
||||
setIsLoadingPaymentMethods(true)
|
||||
const { data: paymentMethods, error } = await get(
|
||||
`${API_URL}/organizations/${orgSlug}/payments`
|
||||
)
|
||||
if (error) throw error
|
||||
setIsLoadingPaymentMethods(false)
|
||||
setPaymentMethods(paymentMethods)
|
||||
} catch (error: any) {
|
||||
ui.setNotification({
|
||||
error,
|
||||
category: 'error',
|
||||
message: `Failed to get available payment methods: ${error.message}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const getSubscription = async () => {
|
||||
try {
|
||||
if (!ui.selectedProject?.subscription_id) {
|
||||
throw new Error('Unable to get subscription ID of project')
|
||||
}
|
||||
|
||||
const subscription = await get(`${API_URL}/projects/${projectRef}/subscription`)
|
||||
if (subscription.error) throw subscription.error
|
||||
setSubscription(subscription)
|
||||
} catch (error: any) {
|
||||
ui.setNotification({
|
||||
error,
|
||||
category: 'error',
|
||||
message: `Failed to get subscription: ${error.message}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (!products || !subscription || isEnterprise)
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Connecting />
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<TeamUpgrade
|
||||
products={products}
|
||||
currentSubscription={subscription}
|
||||
isLoadingPaymentMethods={isLoadingPaymentMethods}
|
||||
paymentMethods={paymentMethods || []}
|
||||
onSelectBack={() => router.push(`/project/${projectRef}/settings/billing/update`)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
BillingUpdateTeam.getLayout = (page) => <BillingLayout>{page}</BillingLayout>
|
||||
|
||||
export default observer(BillingUpdateTeam)
|
||||