diff --git a/apps/docs/components/DocSearch.tsx b/apps/docs/components/DocSearch.tsx
index 52fc0956ecd..d8bde374580 100644
--- a/apps/docs/components/DocSearch.tsx
+++ b/apps/docs/components/DocSearch.tsx
@@ -74,11 +74,14 @@ export function SearchProvider({ children }: any) {
appId={APP_ID}
// @ts-ignore
navigator={{
- navigate({ suggestionUrl }) {
+ navigate({ itemUrl }) {
setIsOpen(false)
- router.push(suggestionUrl)
+ router.push(itemUrl)
},
}}
+ getMissingResultsUrl={({ query }) => {
+ return `https://github.com/supabase/supabase/issues/new?title=Unable+to+search+docs+with+query:+${query}`
+ }}
hitComponent={Hit}
transformItems={(items) => {
return items.map((item, index) => {
diff --git a/apps/docs/data/nav/supabase-dart/v1.ts b/apps/docs/data/nav/supabase-dart/v1.ts
index fb1491e2555..8fe2fad4791 100644
--- a/apps/docs/data/nav/supabase-dart/v1.ts
+++ b/apps/docs/data/nav/supabase-dart/v1.ts
@@ -118,6 +118,11 @@ const Nav = [
url: '/reference/dart/storage-from-createsignedurl',
items: [],
},
+ {
+ name: 'from.createSignedUrls()',
+ url: '/reference/dart/storage-from-createsignedurls',
+ items: [],
+ },
{
name: 'from.getPublicUrl()',
url: '/reference/dart/storage-from-getpublicurl',
diff --git a/apps/docs/docs/reference/dart/initializing.mdx b/apps/docs/docs/reference/dart/initializing.mdx
index 00a69bbf1f3..7d914d71efd 100644
--- a/apps/docs/docs/reference/dart/initializing.mdx
+++ b/apps/docs/docs/reference/dart/initializing.mdx
@@ -28,6 +28,33 @@ Once you initialize Supabase in your `main()` method, you can access the `Supaba
final supabase = Supabase.instance.client;
```
+### Call `initialize()` with custom headers
+
+You can pass `headers` to initialize your Supabase client with customer headers.
+Here is an example of passing a custom auth header to Supabase client.
+```dart title=main.dart
+Future main() async {
+ await Supabase.initialize(
+ url: 'https://xyzcompany.supabase.co',
+ anonKey: 'public-anon-key',
+ headers: {
+ 'Authorization': 'Bearer $accessToken',
+ },
+ );
+ runApp(const MyApp());
+}
+```
+
+### Call `initialize()`multiple times
+
+You can initialize Supabase multiple times in your app. When doing so, call `dispose()` each time before you initialize again.
+
+```dart
+Supabase.instance.dispose();
+
+await Supabase.initialize(url: 'https://xyzcompany.supabase.co', anonKey: 'public-anon-key');
+```
+
## Other Dart Projects
You can initialize a new Supabase client using the `SupabaseClient()` method.
diff --git a/apps/docs/docs/reference/javascript/v1/initializing.mdx b/apps/docs/docs/reference/javascript/v1/initializing.mdx
index 46d057e1cca..3ad13aa9f13 100644
--- a/apps/docs/docs/reference/javascript/v1/initializing.mdx
+++ b/apps/docs/docs/reference/javascript/v1/initializing.mdx
@@ -117,7 +117,7 @@ const supabase = createClient('https://xyzcompany.supabase.co', 'public-anon-key
```
By default the API server points to the `public` schema. You can enable other database schemas within the Dashboard.
-Go to `Settings > API > Schema` and add the schema which you want to expose to the API.
+Go to `Settings > API > Schema` and add the schema which you want to expose to the API. You also need to grant `USAGE` on your new schema with the grants you desire, such as `SELECT, INSERT, UPDATE, DELETE`.
Note: each client connection can only access a single schema, so the code above can access the `other_schema` schema but cannot access the `public` schema.
diff --git a/apps/docs/package.json b/apps/docs/package.json
index cdad7d64c77..7a6df1f501a 100644
--- a/apps/docs/package.json
+++ b/apps/docs/package.json
@@ -36,7 +36,7 @@
"@algolia/autocomplete-js": "^1.7.2",
"@algolia/autocomplete-plugin-recent-searches": "^1.7.2",
"@apollo/client": "^3.6.6",
- "@docsearch/react": "1.0.0-alpha.27",
+ "@docsearch/react": "^3.3.0",
"@mdx-js/loader": "^1.6.22",
"@mdx-js/react": "^1.6.22",
"@next/mdx": "^12.0.4",
diff --git a/apps/docs/pages/guides/auth.mdx b/apps/docs/pages/guides/auth.mdx
index 75e514e5b7e..71986837e96 100644
--- a/apps/docs/pages/guides/auth.mdx
+++ b/apps/docs/pages/guides/auth.mdx
@@ -55,31 +55,43 @@ You can enable third-party providers with the click of a button by navigating to
When using third-party providers, the [Supabase client library](/docs/reference/javascript/auth-signinwithoauth#sign-in-using-a-third-party-provider-with-redirect) redirects the user to the provider. When the third-party provider successfully authenticates the user, the provider redirects the user to the Supabase Auth callback URL where they are further redirected to the URL specified in the `redirectTo` parameter. This parameter defaults to the [`SITE_URL`](/docs/reference/auth/config#site_url). You can modify the `SITE_URL` or [add additional redirect URLs](https://app.supabase.com/project/_/auth/settings).
-You can use wildcard match patterns to support preview URLs from providers like Netlify and Vercel. See the [full list of supported patterns](https://pkg.go.dev/github.com/gobwas/glob#Compile).
+You can use wildcard match patterns to support preview URLs from providers like Netlify and Vercel. See the [full list of supported patterns](https://pkg.go.dev/github.com/gobwas/glob#Compile). Use [this tool](https://www.digitalocean.com/community/tools/glob?comments=true&glob=http%3A%2F%2Flocalhost%3A3000%2F%2A%2A&matches=false&tests=http%3A%2F%2Flocalhost%3A3000&tests=http%3A%2F%2Flocalhost%3A3000%2F&tests=http%3A%2F%2Flocalhost%3A3000%2F%3Ftest%3Dtest&tests=http%3A%2F%2Flocalhost%3A3000%2Ftest-test%3Ftest%3Dtest&tests=http%3A%2F%2Flocalhost%3A3000%2Ftest%2Ftest%3Ftest%3Dtest) to test your patterns.
+
+> ⚠️ WARNING: While the "globstar" (`**`) is useful for local development and preview URLs, we recommend setting the exact redirect URL path for your site URL in production.
#### Netlify preview URLs
For deployments with Netlify, set the `SITE_URL` to your official site URL. Add the following additional redirect URLs for local development and deployment previews:
-- `http://localhost:3000/*/*`
-- `https://**--my_org.netlify.app/*`
+- `http://localhost:3000/**`
+- `https://**--my_org.netlify.app/**`
#### Vercel preview URLs
For deployments with Vercel, set the `SITE_URL` to your official site URL. Add the following additional redirect URLs for local development and deployment previews:
-- `http://localhost:3000/*/*`
-- `https://**vercel.app/*/*`
+- `http://localhost:3000/**`
+- `https://**.vercel.app/**`
Vercel provides an environment variable for the URL of the deployment called `NEXT_PUBLIC_VERCEL_URL`. See the [Vercel docs](https://vercel.com/docs/concepts/projects/environment-variables#system-environment-variables) for more details. You can use this variable to dynamically redirect depending on the environment:
```js
+const getURL = () => {
+ let url =
+ process?.env?.NEXT_PUBLIC_SITE_URL ?? // Set this to your site URL in production env.
+ process?.env?.NEXT_PUBLIC_VERCEL_URL ?? // Automatically set by Vercel.
+ 'http://localhost:3000/';
+ // Make sure to include `https://` when not localhost.
+ url = url.includes('http') ? url : `https://${url}`;
+ // Make sure to including trailing `/`.
+ url = url.charAt(url.length - 1) === '/' ? url : `${url}/`;
+ return url;
+};
+
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'github'
options: {
- redirectTo: process.env.NEXT_PUBLIC_VERCEL_URL
- ? `https://${process.env.NEXT_PUBLIC_VERCEL_URL}`
- : "http://localhost:3000"
+ redirectTo: getURL()
}
}
```
diff --git a/apps/docs/pages/guides/database.mdx b/apps/docs/pages/guides/database.mdx
index d557ac7a79e..7efa2ad7513 100644
--- a/apps/docs/pages/guides/database.mdx
+++ b/apps/docs/pages/guides/database.mdx
@@ -85,7 +85,7 @@ Read about resetting your database password [here](/docs/guides/database/managin
## Next steps
-- Read more about [Postgres](/docs/postgres/server/about)
+- Read more about [Postgres](https://www.postgresql.org/about/)
- Sign in: [app.supabase.com](https://app.supabase.com)
export const Page = ({ children }) =>
diff --git a/apps/docs/scripts/build-search.js b/apps/docs/scripts/build-search.js
index 489354b56e5..91e69c3fae8 100644
--- a/apps/docs/scripts/build-search.js
+++ b/apps/docs/scripts/build-search.js
@@ -4,6 +4,7 @@ const path = require('path')
const matter = require('gray-matter')
const dotenv = require('dotenv')
const algoliasearch = require('algoliasearch/lite')
+const { isEmpty } = require('lodash')
// [Joshen] We initially thought of building out our search using Algolia directly,
// but eventually decided to just use DocSearch since it provides us a UI and some
@@ -17,12 +18,20 @@ const algoliasearch = require('algoliasearch/lite')
// A lot of them are not even linked to within the docs site, so just need to
// double check if they can be removed or if we want them in the side bars.
const ignoredFiles = [
- 'docs/404.mdx',
- 'docs/support.mdx',
- 'docs/faqs.mdx',
- 'docs/guides.mdx',
- 'docs/guides/database/arrays.mdx',
- 'docs/guides/database/json.mdx',
+ 'pages/404.mdx',
+ 'pages/faq.mdx',
+ 'pages/support.mdx',
+ 'pages/oss.tsx',
+ 'pages/_app.tsx',
+ 'pages/_document.tsx',
+ 'pages/[...slug].tsx',
+ 'pages/company/aup.md',
+ 'pages/company/privacy.md',
+ 'pages/company/sla.md',
+ 'pages/company/terms.md',
+ 'pages/handbook/contributing.mdx',
+ 'pages/handbook/introduction.mdx',
+ 'pages/handbook/supasquad.mdx',
]
const nameMap = {
@@ -69,16 +78,39 @@ async function walk(dir) {
)
const index = client.initIndex(indexName)
- const slugs = (await walk('docs')).filter((slug) => !ignoredFiles.includes(slug))
+ const referencePages = await walk('docs')
+ const guidePages = (await walk('pages')).filter((slug) => !ignoredFiles.includes(slug))
+ const allPages = guidePages.concat(referencePages)
- const searchObjects = slugs
+ const searchObjects = allPages
.map((slug) => {
+ let id, title, description
const fileContents = fs.readFileSync(slug, 'utf8')
const { data, content } = matter(fileContents)
- const { id, title, description } = data
+ if (isEmpty(data)) {
+ // Guide pages do not have front-matter meta, unlike reference pages, have to manually extract
+ const metaIndex = fileContents.indexOf('export const meta = {')
+ if (metaIndex !== -1) {
+ const metaString =
+ fileContents
+ .slice(metaIndex + 20, fileContents.indexOf('}') + 1)
+ .replace(/\n/g, '')
+ .slice(0, -2) + '}'
+ const meta = eval(`(${metaString})`)
+ id = meta.id
+ title = meta.title
+ description = meta.description
+ }
+ } else {
+ id = data.id
+ title = data.title
+ description = data.description
+ }
+
const url = (slug.includes('/generated') ? slug.replace('/generated', '') : slug)
.replace('docs', '')
+ .replace('pages', '')
.replace(/\.mdx$/, '')
const source = slug.includes('/reference') ? 'reference' : 'guide'
const object = {
diff --git a/apps/docs/styles/docsearch.scss b/apps/docs/styles/docsearch.scss
index 6ae9773850b..a9ca46569c5 100644
--- a/apps/docs/styles/docsearch.scss
+++ b/apps/docs/styles/docsearch.scss
@@ -384,8 +384,25 @@
@apply text-slate-1200 dark:text-scale-1200 font-normal;
}
-.DocSearch-StartScreen .DocSearch-Help {
- @apply py-16 px-6 text-center text-slate-400;
+.DocSearch-NoResults-Prefill-List {
+ .DocSearch-Help {
+ color: var(--colors-scale11) !important;
+ }
+}
+
+.DocSearch-StartScreen {
+ .DocSearch-Help {
+ @apply text-center py-16;
+ }
+}
+
+.DocSearch-Help {
+ @apply text-scale-1100;
+ font-size: 0.875rem !important;
+ a {
+ color: var(--colors-brand9) !important;
+ font-weight: 400 !important;
+ }
}
.DocSearch-NoResults {
diff --git a/apps/www/_blog/2022-11-22-flutter-authentication-and-authorization-with-rls.mdx b/apps/www/_blog/2022-11-22-flutter-authentication-and-authorization-with-rls.mdx
index c76f1e18ffd..7183c9fb539 100644
--- a/apps/www/_blog/2022-11-22-flutter-authentication-and-authorization-with-rls.mdx
+++ b/apps/www/_blog/2022-11-22-flutter-authentication-and-authorization-with-rls.mdx
@@ -52,7 +52,7 @@ flutter_bloc: ^8.0.0
Since the app has evolved, we also need to update our table schema. In order to store rooms data, we will add a rooms table. We will also modify the messages table to add a foreign key constraint to the rooms table so that we can tell which message belongs to which room.
-We will also intorudce a `create_new_room` function, which is a [database function](https://supabase.com/docs/guides/database/functions) that handles chat room creation. It knows to create a new room if a chat room with the two users does not exist yet, or to just return the room ID if it already exists.
+We will also introduce a `create_new_room` function, which is a [database function](https://supabase.com/docs/guides/database/functions) that handles chat room creation. It knows to create a new room if a chat room with the two users does not exist yet, or to just return the room ID if it already exists.
```sql
-- *** Table definitions ***
@@ -1154,7 +1154,7 @@ import 'package:my_chat_app/pages/rooms_page.dart';
import 'package:my_chat_app/utils/constants.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
-/// Page to redirect users to the appropreate page depending on the initial auth state
+/// Page to redirect users to the appropriate page depending on the initial auth state
class SplashPage extends StatefulWidget {
const SplashPage({Key? key}) : super(key: key);
diff --git a/apps/www/_blog/2022-11-24-sql-or-nosql-both-with-postgresql.mdx b/apps/www/_blog/2022-11-24-sql-or-nosql-both-with-postgresql.mdx
new file mode 100644
index 00000000000..5198e23f3df
--- /dev/null
+++ b/apps/www/_blog/2022-11-24-sql-or-nosql-both-with-postgresql.mdx
@@ -0,0 +1,336 @@
+---
+title: SQL or NoSQL? Why not use both (with PostgreSQL)?
+description: How to turn Postgres into an easy-to-use NoSQL database that retains all the power of SQL
+author: burggraf
+image: 2022-11-24-sql-nosql-postgresql/sql-nosql-postgresql.jpg
+thumb: 2022-11-24-sql-nosql-postgresql/sql-nosql-postgresql.jpg
+tags:
+ - postgres
+date: '2022-11-24'
+toc_depth: 3
+---
+
+It's a tough decision for any developer starting a new project. Should you store your data in a standard, time-tested SQL database, or go with one of the newer NoSQL document-based databases? This seemingly simple decision can literally make or break your project down the line. Choose correctly and structure your data well, and you may sail smoothly into production and watch your app take off. Make the wrong choice and you could be headed for nightmares (and maybe even some major re-writes) before your app ever makes it out the door.
+
+## Simplicity vs Power
+There are tradeoffs with both SQL and NoSQL solutions. Typically it's easier to get started with NoSQL data structures, especially when the data is complex or hierarchical. You can just take a JSON data object from your front-end code and throw it in the database and be done with it. But later when you need to access that data to answer some basic business questions, it's much more difficult. A SQL solution makes it easier to gather data and draw conclusions down the line. Let's look at an example:
+
+Each day I track the food I eat, along with the number of calories in each item:
+
+| Day | Food Item | Calories | Meal |
+|--------|-----------|----------|------|
+| 01 Jan | Apple | 72 | Breakfast |
+| 01 Jan | Oatmeal | 146 | Breakfast |
+| 01 Jan | Sandwich | 445 | Lunch |
+| 01 Jan | Chips | 280 | Lunch |
+| 01 Jan | Cookie | 108 | Lunch |
+| 01 Jan | Mixed Nuts | 175 | Snack |
+| 01 Jan | Pasta/Sauce | 380 | Dinner |
+| 01 Jan | Garlic Bread | 200 | Dinner |
+| 01 Jan | Broccoli | 32 | Dinner |
+
+I also track the number of cups of water I drink and when I drink them:
+
+| Day | Time | Cups |
+|--------|-------|------|
+| Jan 01 | 08:15 | 1 |
+| Jan 01 | 09:31 | 1 |
+| Jan 01 | 10:42 | 2 |
+| Jan 01 | 12:07 | 2 |
+| Jan 01 | 14:58 | 1 |
+| Jan 01 | 17:15 | 1 |
+| Jan 01 | 18:40 | 1 |
+| Jan 01 | 19:05 | 1 |
+
+And finally, I track my exercise:
+
+| Day | Time | Duration | Exercise |
+|--------|-------|----------|----------|
+| Jan 01 | 11:02 | 0.5 | Walking |
+| Jan 02 | 09:44 | 0.75 | Bicycling |
+| Jan 02 | 17:00 | 0.25 | Walking |
+
+For each day I also track my current weight along with any notes for the day:
+
+| Day | Weight | Notes |
+|--------|--------|-------|
+| Jan 01 | 172.6 | This new diet is awesome! |
+| Jan 14 | 170.2 | Not sure all this is worth it. |
+| Jan 22 | 169.8 | Jogged past a McDonald's today. It was hard. |
+| Feb 01 | 168.0 | I feel better, but sure miss all that greasy food. |
+
+## Gathering All That Data
+
+That's a lot of different data that needs to be gathered, stored, retrieved, and later analyzed. It's organized simply and easily, but the number of records varies from day to day. On any given day I may have zero or more entries for food, water, and exercise, and I may have zero or one entry for weight & notes.
+
+In my app, I gather all the data for a single day on one page, to make it easier for my users. So, I get a JSON object for each day that looks like this:
+
+```json
+{ "date": "2022-01-01",
+ "weight": 172.6,
+ "notes": "This new diet is awesome!",
+ "food": [
+ { "title": "Apple", "calories": 72, "meal": "Breakfast"},
+ { "title": "Oatmeal", "calories": 146, "meal": "Breakfast"},
+ { "title": "Sandwich", "calories": 445, "meal": "Lunch"},
+ { "title": "Chips", "calories": 280, "meal": "Lunch"},
+ { "title": "Cookie", "calories": 108, "meal": "Lunch"},
+ { "title": "Mixed Nuts", "calories": 175, "meal": "Snack"},
+ { "title": "Pasta/Sauce", "calories": 380, "meal": "Dinner"},
+ { "title": "Garlic Bread", "calories": 200, "meal": "Dinner"},
+ { "title": "Broccoli", "calories": 32, "meal": "Dinner"}
+ ],
+ "water": [
+ {"time": "08:15", "qty": 1},
+ {"time": "09:31", "qty": 1},
+ {"time": "10:42", "qty": 2},
+ {"time": "10:42", "qty": 2},
+ {"time": "12:07", "qty": 1},
+ {"time": "14:58", "qty": 1},
+ {"time": "17:15", "qty": 1},
+ {"time": "18:40", "qty": 1},
+ {"time": "19:05", "qty": 1}
+ ],
+ "exercise": [
+ {"time": "11:02", "duration": 0.5, "type": "Walking"}
+ ]
+}
+```
+
+## Saving the Data
+
+Once we've gathered all the data for a day, we need to store it in our database. In a NoSQL database, this can be a pretty easy process, as we can just create a record (document) for a specific user for a specific date and throw document into a collection and we're done. With SQL, we have some structure we have to work within, and in this case it looks like 4 separate tables: food, water, exercise, and notes. We'd want to do 4 separate inserts here, one for each table. If we don't have data for a specific table (say no exercise was recorded today) then we can skip that table.
+
+If you're using SQL to store this data, you might want to save each table's data as it's entered in your data entry form (and not wait until all the data is entered.) Or you might want to create a database function that takes all the JSON data, parses it, and writes it to all the related tables in a single transaction. There's a lot of ways to handle this, but suffice it to say this: it's a bit more complicated than saving the data in a NoSQL database.
+
+### Retrieving the Data
+If we want to display all the data for a single day, it's pretty much the same. With NoSQL you can grab the data for the user's day and then use it in your application. Nice! With SQL we need to query 4 tables to get all the data (or we could use a function to get it all in a single call.) Of course, when displaying the data, we'd need to first break up our JSON data into pieces that are needed by each section of our dashboard screen, and you could argue that it's simpler to map each SQL table with the dashboard section on the screen, but that's a trivial point.
+
+### Analyzing the Data
+Now that we've saved the data and we can retrieve it and display it, let's use it for some analysis. *Let's display a graph of how many total calories I've eaten over the past month.* With SQL, this is a simple task:
+
+```sql
+select
+ date, sum(calories) as total_calories
+from food_log
+group by date
+where user_id = 'xyz'
+and day between '2022-01-01' and '2022-01-31'
+order by date;
+```
+
+Bam! Done! Now we can send those results to our graphing library and make a nice pretty picture of my eating habits.
+
+But if we've stored this data in NoSQL, it gets a little more complicated. We'll need to:
+
+- grab all the data for the user for the month
+- parse each day's data to get the food log information
+- loop through each day and total the calories
+- send the aggregate data to our graphing module
+
+If this is something we're going to do regularly, it makes sense to calculate the total calories for each day and store it in the day's document so we can get at that data faster. But that requires more work up front, and we still need to pull the data for each day and parse out that calorie total first. And if we update the data we still need to recalculate things and update that total. Eventually we'll want to do that with the water and exercise totals as well. The code will eventually start to get longer and more complex.
+
+### SQL and NoSQL Together - FTW
+Let's see how we can use the power of SQL together with the ease-of-use of NoSQL in the same database to make this all a bit easier. We'll create a table for each day of data (for each user) and store the basic fields such as `weight` and `notes` first. Then we'll just throw the `food_log`, `water_log`, and `exercise_log` fields in a `JSONB` field.
+
+```sql
+CREATE TABLE calendar (
+ id uuid DEFAULT uuid_generate_v4() NOT NULL,
+ date date,
+ user_id uuid NOT NULL,
+ weight numeric,
+ notes text,
+ food_log jsonb,
+ water_log jsonb,
+ exercise_log jsonb
+);
+-- (Optional) - create a foreign key relationship for the user_id field
+ALTER TABLE ONLY calendar
+ ADD CONSTRAINT calendar_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id);
+```
+
+Now let's insert some data into the table. PostgreSQL offers both JSON and JSONB fields, and since the latter are more optimized by the database and much faster for query processing, we’ll almost always want to use JSONB. We’ll use JSONB fields for `food_log`, `water_log`, and `exercise_log` and just dump the data we got from our app right into those fields as a string:
+
+```sql
+insert into calendar (date, user_id, weight, notes, food_log, water_log, exercise_log)
+values (
+ '2022-01-01',
+ 'xyz',
+ 172.6,
+ 'This new diet is awesome!',
+ '[
+ { "title": "Apple", "calories": 72, "meal": "Breakfast"},
+ { "title": "Oatmeal", "calories": 146, "meal": "Breakfast"},
+ { "title": "Sandwich", "calories": 445, "meal": "Lunch"},
+ { "title": "Chips", "calories": 280, "meal": "Lunch"},
+ { "title": "Cookie", "calories": 108, "meal": "Lunch"},
+ { "title": "Mixed Nuts", "calories": 175, "meal": "Snack"},
+ { "title": "Pasta/Sauce", "calories": 380, "meal": "Dinner"},
+ { "title": "Garlic Bread", "calories": 200, "meal": "Dinner"},
+ { "title": "Broccoli", "calories": 32, "meal": "Dinner"}
+ ]',
+ '[
+ {"time": "08:15", "qty": 1},
+ {"time": "09:31", "qty": 1},
+ {"time": "10:42", "qty": 2},
+ {"time": "10:42", "qty": 2},
+ {"time": "12:07", "qty": 1},
+ {"time": "14:58", "qty": 1},
+ {"time": "17:15", "qty": 1},
+ {"time": "18:40", "qty": 1},
+ {"time": "19:05", "qty": 1}
+ ]',
+ '[
+ {"time": "11:02", "duration": 0.5, "type": "Walking"}
+ ]'
+);
+```
+
+While that's a big insert statement, it sure beats doing inserts on 4 separate tables. With all those food entries and water log entries, we would have had to made 1 entry in the main table, then 9 food_log entries, 9 water_log entries, and one exercise_log entry for a total of 20 database records. We've wrapped that into a single record.
+
+## But How Do We Query This Data?
+
+Great, we're collecting the data now, and it's easy to insert the data into the database. Editing the data isn't too bad either because we're just downloading the data to the client, updating the JSON field(s) as needed, and throwing them back into the database. Not too hard. But how can I query this data? What about that task from before? *Let's display a graph of how many total calories I've eaten over the past month.*
+
+In this case, that data is stored inside the `food_log` field inside the `calendar` table. If only PostgreSQL had a way of converting JSONB arrays into individual database records (recordsets). Well, it does! The `jsonb_array_elements` function will do this for us, allowing to create a simple table we can use to calculate our caloric intake.
+
+Here's some SQL to turn that `food_log` array into individual output records:
+
+```sql
+select
+ user_id,
+ date,
+ jsonb_array_elements(food_log)->>'title' as title,
+ jsonb_array_elements(food_log)->'calories' as calories,
+ jsonb_array_elements(food_log)->'meal' as meal
+from calendar
+where user_id = 'xyz'
+ and date between
+ '2022-01-01' and '2022-01-31';
+```
+
+This returns a table that looks like this:
+
+| date | title | calories | meal |
+| ---------- | ------------ | -------- | --------- |
+| 2022-01-01 | Apple | 72 | Breakfast |
+| 2022-01-01 | Oatmeal | 146 | Breakfast |
+| 2022-01-01 | Sandwich | 445 | Lunch |
+| 2022-01-01 | Chips | 280 | Lunch |
+| 2022-01-01 | Cookie | 108 | Lunch |
+| 2022-01-01 | Mixed Nuts | 175 | Snack |
+| 2022-01-01 | Pasta/Sauce | 380 | Dinner |
+| 2022-01-01 | Garlic Bread | 200 | Dinner |
+| 2022-01-01 | Broccoli | 32 | Dinner |
+
+A couple things to note:
+
+- `jsonb_array_elements(food_log)->>'title' as title` this returns a text field, since the `->>` operator returns TEXT
+- `jsonb_array_elements(food_log)->'calories' as calories` this returns a JSON object, since the `->` operator return JSON
+
+If we want to `sum` the calories to get some totals, we can't have a JSON object, so we need to cast that to something more useful, like an `INTEGER`:
+
+- `(jsonb_array_elements(food_log)->'calories')::INTEGER as calories` this returns an INTEGER
+
+Now we can't just throw the `sum` operator on this to get the total calories by day. If we try this:
+
+```sql
+select
+ date,
+ sum((jsonb_array_elements(food_log)->'calories')::integer) as total_calories
+from calendar where user_id = 'xyz'
+ and date between '2022-01-01' and '2022-01-31'
+group by date;
+```
+
+we get an error back from PostgreSQL: **Failed to run sql query: aggregate function calls cannot contain set-returning function calls**.
+
+Instead, we need to think of this as a set of building blocks, where our first SQL statement returns a table:
+
+```sql
+select
+ date,
+ (jsonb_array_elements(food_log)->'calories')::integer as calories
+from calendar where user_id = 'xyz'
+ and date between
+ '2022-01-01' and '2022-01-31';
+```
+
+Now we can take that "table" statement, throw some (parenthesis) around it, and query **it**:
+
+```sql
+with data as
+ (
+ select
+ date,
+ (jsonb_array_elements(food_log)->'calories')::integer as calories
+ from calendar
+ where user_id = 'xyz'
+ and date between
+ '2022-01-01' and '2022-01-31'
+ )
+select date, sum(calories) from
+ data group by date;
+```
+
+This gives us exactly what we want:
+
+| date | sum |
+| ---------- | ---- |
+| 2022-01-01 | 1838 |
+
+If we add more data for the rest of the days of the month, we'll have all the data we need for a beautiful graph.
+
+### Searching the Data
+What if we want to answer the question: *How many calories were in the garlic bread I ate last month?* This data is stored inside the `food_log` field in the `calendar` table. We can use the same type of query we used before to "flatten" the `food_log` data so we can search it.
+
+To get every item I ate during the month of January, we can use:
+
+```sql
+select
+ date,
+ jsonb_array_elements(food_log)->>'title' as title,
+ (jsonb_array_elements(food_log)->'calories')::integer as calories
+from calendar
+where user_id = 'xyz'
+ and date between
+ '2022-01-01' and '2022-01-31'
+```
+
+Now to search for the **garlic bread** we can just put (parenthesis) around this to make a "table" and then search for the item we want:
+
+```sql
+with my_food as
+(
+ select
+ date,
+ jsonb_array_elements(food_log)->>'title' as title,
+ (jsonb_array_elements(food_log)->'calories')::integer as calories
+ from calendar
+ where user_id = 'xyz'
+ and date between
+ '2022-01-01' and '2022-01-31'
+)
+select title, calories
+from my_food
+where title = 'Garlic Bread';
+```
+which gives us:
+
+| title | calories |
+| ------------ | -------- |
+| Garlic Bread | 200 |
+
+## Conclusion
+
+If we take a little time to study the [JSON Functions and Operators](https://www.postgresql.org/docs/9.5/functions-json.html) that PostgreSQL offers, we can turn Postgres into an easy-to-use NoSQL database that still retains all the power of SQL. This gives us a super easy way to store our complex JSON data coming from our application code in our database. Then we can use powerful SQL capabilities to analyze and present that data in our application. It's the best of both worlds!
+
+## More Postgres resources
+
+- [Postgres Full Text Search vs the rest](https://supabase.com/blog/postgres-full-text-search-vs-the-rest)
+- [Postgres WASM by Snaplet and Supabase](https://supabase.com/blog/postgres-wasm)
+- [Choosing a Postgres Primary Key](https://supabase.com/blog/choosing-a-postgres-primary-key)
+- [Partial data dumps using Postgres Row Level Security](https://supabase.com/blog/partial-postgresql-data-dumps-with-rls)
+- [Postgres Views](https://supabase.com/blog/postgresql-views)
+- [Realtime Postgres RLS on Supabase](https://supabase.com/blog/realtime-row-level-security-in-postgresql)
+
diff --git a/apps/www/components/LaunchWeek/Ticket/TicketContainer.tsx b/apps/www/components/LaunchWeek/Ticket/TicketContainer.tsx
new file mode 100644
index 00000000000..632c731bfc4
--- /dev/null
+++ b/apps/www/components/LaunchWeek/Ticket/TicketContainer.tsx
@@ -0,0 +1,55 @@
+import { useState } from 'react'
+import { PageState, ConfDataContext, UserData } from './hooks/use-conf-data'
+import Ticket from './ticket'
+import ConfContainer from './conf-container'
+import Form from './form'
+import { SupabaseClient, Session } from '@supabase/supabase-js'
+
+type Props = {
+ supabase: SupabaseClient
+ session: Session | null
+ defaultUserData: UserData
+ sharePage?: boolean
+ defaultPageState?: PageState
+}
+
+export default function Conf({
+ supabase,
+ session,
+ defaultUserData,
+ sharePage,
+ defaultPageState = 'registration',
+}: Props) {
+ const [userData, setUserData] = useState(defaultUserData)
+ const [pageState, setPageState] = useState(defaultPageState)
+
+ return (
+
+
+ {pageState === 'registration' && !sharePage ? (
+ <>
+ {/* */}
+
+ {/* */}
+ >
+ ) : (
+
+ )}
+
+
+ )
+}
diff --git a/apps/www/components/LaunchWeek/Ticket/conf-container.module.css b/apps/www/components/LaunchWeek/Ticket/conf-container.module.css
new file mode 100644
index 00000000000..2a7eac88ac0
--- /dev/null
+++ b/apps/www/components/LaunchWeek/Ticket/conf-container.module.css
@@ -0,0 +1,11 @@
+.container {
+ margin: auto 0;
+ padding: 0 var(--space-4x);
+}
+
+@media (min-width: 768px) {
+ .container {
+ margin: auto;
+ padding: 0 var(--space-8x);
+ }
+}
diff --git a/apps/www/components/LaunchWeek/Ticket/conf-container.tsx b/apps/www/components/LaunchWeek/Ticket/conf-container.tsx
new file mode 100644
index 00000000000..e331c3e882c
--- /dev/null
+++ b/apps/www/components/LaunchWeek/Ticket/conf-container.tsx
@@ -0,0 +1,5 @@
+import styles from './conf-container.module.css'
+
+export default function ConfContainer({ children }: { children: React.ReactNode }) {
+ return {children}
+}
diff --git a/apps/www/components/LaunchWeek/Ticket/form.module.css b/apps/www/components/LaunchWeek/Ticket/form.module.css
new file mode 100644
index 00000000000..fa1a4736cc3
--- /dev/null
+++ b/apps/www/components/LaunchWeek/Ticket/form.module.css
@@ -0,0 +1,246 @@
+.form {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-direction: column;
+ margin-bottom: 12px;
+ gap: 12px;
+}
+
+.formLeft {
+ align-items: center;
+}
+.formCenter {
+ align-items: center;
+}
+
+@media (min-width: 1200px) {
+ .formLeft {
+ align-items: start;
+ }
+ .formCenter {
+ align-items: center;
+ }
+}
+
+.formInfo {
+ max-width: 520px;
+}
+
+.formInfo h3 {
+ font-size: 1.25em;
+ font-weight: 400;
+}
+
+.formInfo p {
+ font-size: 1.1em;
+ line-height: 1.4;
+ margin-bottom: 32px;
+ color: var(--lw-secondary-color);
+}
+
+@media (min-width: 1200px) {
+ /* .form.share-page {
+ justify-content: flex-start;
+ } */
+}
+
+.formInfoLeft {
+ text-align: center;
+ margin: 0 auto;
+}
+.formInfoCenter {
+ text-align: center;
+ margin: 0 auto;
+}
+
+@media (min-width: 1200px) {
+ .formInfoLeft {
+ text-align: left;
+ margin: inherit;
+ }
+ .formInfoCenter {
+ text-align: center;
+ margin: 0 auto;
+ }
+}
+
+.input {
+ border: none;
+ width: 100%;
+ background: transparent;
+ outline: none;
+ height: 56px;
+ padding-left: 15px;
+ padding-right: 15px;
+ font-size: var(--text-md);
+ color: #fff;
+ font-family: inherit;
+}
+
+@media (min-width: 768px) {
+ .input {
+ width: calc(100% - var(--space-32x));
+ padding-right: 0;
+ }
+}
+
+.input::placeholder {
+ color: var(--lw-secondary-color);
+}
+
+.input-label {
+ background-color: var(--gray);
+ border-radius: var(--space-2x);
+ border: 1px solid transparent;
+ transition: border-color 0.2s ease, background-color 0.2s ease;
+ display: block;
+}
+
+.input-label.focused {
+ background-color: #33373c;
+}
+
+.input-label.error {
+ background: red;
+}
+
+.input-label.success {
+ background: #0070f3;
+}
+
+.input-text {
+ display: flex;
+ align-items: center;
+ width: 100%;
+}
+
+.form-row {
+ position: relative;
+ max-width: 400px;
+ width: 100%;
+}
+
+@media (min-width: 768px) {
+ .form-row {
+ max-width: 480px;
+ }
+}
+
+.submit {
+ width: 100%;
+ height: 56px;
+ margin-top: var(--space-4x);
+ border-radius: var(--space-2x);
+ border: 1px solid var(--brand);
+ background: var(--brand);
+ cursor: pointer;
+ font-family: inherit;
+ font-size: var(--text-md);
+ letter-spacing: -0.02em;
+ outline: none;
+ font-weight: 500;
+ color: white;
+ transition: background-color 0.2s ease, color 0.2s ease;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.submit.generate-with-github {
+ display: flex;
+ margin-bottom: 12px;
+ position: relative;
+}
+
+@media (min-width: 768px) {
+ .submit.register {
+ width: 120px;
+ height: 40px;
+ margin-top: 0;
+ position: absolute;
+ border-radius: 5px;
+ right: var(--space-2x);
+ top: var(--space-2x);
+ }
+}
+
+.submit.loading {
+ cursor: default;
+}
+
+.submit.default:hover,
+.submit.default:focus {
+ background: black;
+ color: var(--brand);
+}
+
+.submit.default.generate-with-github:hover path,
+.submit.default.generate-with-github:focus path {
+ fill: var(--brand);
+}
+
+.submit.error:hover,
+.submit.error:focus {
+ background: #000;
+ color: #fff;
+}
+
+.submit.default:disabled,
+.submit.default:disabled:hover,
+.submit.default:disabled:focus {
+ cursor: default;
+ background: var(--gray);
+ border-color: var(--gray);
+ color: #fff;
+ justify-content: flex-start;
+ overflow: hidden;
+}
+
+.submit.default.generate-with-github:disabled path,
+.submit.default.generate-with-github:disabled:hover path,
+.submit.default.generate-with-github:disabled:focus path {
+ fill: #fff;
+}
+
+.submit.default.generate-with-github.not-allowed:disabled {
+ cursor: not-allowed;
+}
+
+@media (min-width: 1200px) {
+ .form-row {
+ margin: 0;
+ }
+
+ .submit.generate-with-github {
+ width: 240px;
+ }
+}
+
+.stage-btn {
+ background: #323332;
+ border: 2px solid #34b27b;
+}
+
+.github-wrapper {
+ display: flex;
+ align-items: center;
+ flex-direction: column;
+}
+
+.or-divider {
+ width: 100%;
+ text-align: center;
+ color: var(--lw-secondary-color);
+ margin: var(--space-4x) 0;
+}
+
+@media (min-width: 1200px) {
+ .github-wrapper {
+ flex-direction: row;
+ }
+ .or-divider {
+ width: 240px;
+ margin: 0;
+ }
+}
diff --git a/apps/www/components/LaunchWeek/Ticket/form.tsx b/apps/www/components/LaunchWeek/Ticket/form.tsx
new file mode 100644
index 00000000000..6cbe2532aee
--- /dev/null
+++ b/apps/www/components/LaunchWeek/Ticket/form.tsx
@@ -0,0 +1,233 @@
+import { useState, useCallback, useEffect } from 'react'
+import cn from 'classnames'
+import useConfData from '~/components/LaunchWeek/Ticket/hooks/use-conf-data'
+import { useRouter } from 'next/router'
+import LoadingDots from './loading-dots'
+import styleUtils from './utils.module.css'
+import styles from './form.module.css'
+import useEmailQueryParam from '~/components/LaunchWeek/Ticket/hooks/use-email-query-param'
+// import Captcha, { useCaptcha } from './captcha'
+
+type FormState = 'default' | 'loading' | 'error'
+
+export type ConfUser = {
+ id?: string
+ email?: string
+ ticketNumber?: number
+ name?: string
+ username?: string
+ createdAt?: number
+ golden?: boolean
+}
+
+type Props = {
+ sharePage?: boolean
+ align?: 'Left' | 'Center'
+}
+
+export default function Form({ sharePage, align = 'Center' }: Props) {
+ const [email, setEmail] = useState('')
+ const [errorMsg, setErrorMsg] = useState('')
+ const [errorTryAgain, setErrorTryAgain] = useState(false)
+ const [focused, setFocused] = useState(false)
+ const [formState, setFormState] = useState('default')
+ const { setPageState, setUserData, session, userData, supabase } = useConfData()
+ const router = useRouter()
+ const isCaptchaEnabled = false
+
+ useEffect(() => {
+ if (session?.user) {
+ document.body.classList.add('ticket-generated')
+ const username = session.user.user_metadata.user_name
+ const name = session.user.user_metadata.full_name
+ const email = session.user.email
+ supabase
+ .from('lw6_tickets')
+ .upsert({ email, name, username }, { onConflict: 'email', ignoreDuplicates: false })
+ .eq('email', email)
+ .select()
+ .single()
+ .then(async ({ error }) => {
+ if (error) return supabase.auth.signOut()
+ const { data } = await supabase
+ .from('lw6_tickets_golden')
+ .select('*')
+ .eq('username', username)
+ .single()
+ setUserData(data)
+ setFormState('default')
+
+ // Prefetch GitHub avatar
+ new Image().src = `https://github.com/${username}.png`
+
+ // Prefetch the twitter share URL to eagerly generate the page
+ fetch(`/launch-week/tickets/${username}`).catch((_) => {})
+
+ setPageState('ticket')
+ })
+ }
+ }, [session])
+
+ async function register(email: string, token?: string): Promise {
+ const { error } = await supabase!.from('lw6_tickets').insert({ email })
+ if (error) {
+ console.log({ error })
+ return {
+ id: 'new',
+ ticketNumber: 1234,
+ name: '',
+ username: '',
+ golden: false,
+ }
+ }
+ const { data } = await supabase!.from('lw6_tickets_golden').select('*').limit(1).single()
+ return {
+ id: data?.id ?? 'new',
+ ticketNumber: data?.ticketNumber ?? 1234,
+ name: data?.name ?? '',
+ username: data?.username ?? '',
+ golden: data?.golden ?? false,
+ }
+ }
+
+ const handleRegister = useCallback(
+ (token?: string) => {
+ register(email, token)
+ .then(async (params) => {
+ if (!params) {
+ throw new Error()
+ }
+
+ if (sharePage) {
+ const queryString = Object.keys(params)
+ .map(
+ (key) =>
+ `${encodeURIComponent(key)}=${encodeURIComponent(
+ params[key as keyof typeof params] || ''
+ )}`
+ )
+ .join('&')
+ await router.replace(`/launch-week/tickets?${queryString}`, '/launch-week/tickets')
+ } else {
+ setUserData(params)
+ setPageState('ticket')
+ }
+ })
+ .catch(async (err) => {
+ let message = 'Error! Please try again.'
+
+ setErrorMsg(message)
+ setFormState('error')
+ })
+ },
+ [email, router, setPageState, setUserData, sharePage]
+ )
+
+ const onSubmit = useCallback(
+ (e: React.FormEvent) => {
+ e.preventDefault()
+
+ if (formState === 'default') {
+ setFormState('loading')
+
+ if (isCaptchaEnabled) {
+ // return executeCaptcha()
+ }
+
+ return handleRegister()
+ } else {
+ setFormState('default')
+ }
+ },
+ [formState, isCaptchaEnabled, handleRegister]
+ )
+
+ const onTryAgainClick = useCallback((e: React.MouseEvent) => {
+ e.preventDefault()
+
+ setFormState('default')
+ setErrorTryAgain(true)
+ // resetCaptcha()
+ }, [])
+
+ useEmailQueryParam('email', setEmail)
+
+ return (
+ <>
+
+
Get a ticket
+
+ A few of the lucky attendees will get a limited edition Supabase goodie bag. Make sure you
+ don’t skip your chance.
+
+
+ {formState === 'error' ? (
+
+
+
+
{errorMsg}
+
+ Try Again
+
+
+
+
+ ) : (
+
+ )}
+ >
+ )
+}
diff --git a/apps/www/components/LaunchWeek/Ticket/hooks/use-conf-data.ts b/apps/www/components/LaunchWeek/Ticket/hooks/use-conf-data.ts
new file mode 100644
index 00000000000..4855f4f9a56
--- /dev/null
+++ b/apps/www/components/LaunchWeek/Ticket/hooks/use-conf-data.ts
@@ -0,0 +1,30 @@
+import { Session, SupabaseClient } from '@supabase/supabase-js'
+import { createContext, useContext } from 'react'
+
+export type PageState = 'registration' | 'ticket'
+
+export type UserData = {
+ id?: string
+ ticketNumber?: number
+ username?: string
+ name?: string
+ golden?: boolean
+}
+
+type ConfDataContextType = {
+ supabase: SupabaseClient
+ session: Session | null
+ userData: UserData
+ setUserData: React.Dispatch>
+ setPageState: React.Dispatch>
+}
+
+export const ConfDataContext = createContext(null)
+
+export default function useConfData() {
+ const result = useContext(ConfDataContext)
+ if (!result) {
+ throw new Error()
+ }
+ return result
+}
diff --git a/apps/www/components/LaunchWeek/Ticket/hooks/use-email-query-param.ts b/apps/www/components/LaunchWeek/Ticket/hooks/use-email-query-param.ts
new file mode 100644
index 00000000000..16304ef5f79
--- /dev/null
+++ b/apps/www/components/LaunchWeek/Ticket/hooks/use-email-query-param.ts
@@ -0,0 +1,32 @@
+import { useEffect } from 'react'
+import { useRouter } from 'next/router'
+
+/**
+ * If `paramName` exists in query string, then call `setEmail()` with the value
+ * and delete it from the URL.
+ */
+export default function useEmailQueryParam(
+ paramName: string,
+ setEmail: (email: string) => unknown
+) {
+ const router = useRouter()
+ useEffect(() => {
+ if ('URLSearchParams' in window) {
+ const { search, pathname } = window.location
+ const params = new URLSearchParams(search)
+ const email = params.get(paramName)
+ if (email) {
+ setEmail(email)
+ params.delete(paramName)
+ const newSearch = params.toString()
+ const newAsPath = pathname + (newSearch ? `?${newSearch}` : '')
+ const newPathname = router.pathname + (newSearch ? `?${newSearch}` : '')
+ history.replaceState(
+ { url: newPathname, as: newAsPath, options: { shallow: true } },
+ '',
+ newAsPath
+ )
+ }
+ }
+ }, [setEmail, router.pathname, paramName])
+}
diff --git a/apps/www/components/LaunchWeek/Ticket/loading-dots.module.css b/apps/www/components/LaunchWeek/Ticket/loading-dots.module.css
new file mode 100644
index 00000000000..181f48448e8
--- /dev/null
+++ b/apps/www/components/LaunchWeek/Ticket/loading-dots.module.css
@@ -0,0 +1,48 @@
+.loading {
+ display: inline-flex;
+ align-items: center;
+ --loading-dots-height: auto;
+ --loading-dots-size: 2px;
+ height: var(--loading-dots-height);
+}
+
+.loading .spacer {
+ margin-right: var(--space-3x);
+}
+
+.loading span {
+ animation-name: blink;
+ animation-duration: 1.4s;
+ animation-iteration-count: infinite;
+ animation-fill-mode: both;
+ width: var(--loading-dots-size);
+ height: var(--loading-dots-size);
+ border-radius: 50%;
+ background-color: var(--accents-6);
+ display: inline-block;
+ margin: 0 1px;
+}
+
+.loading.reverse span {
+ background-color: var(--accents-2);
+}
+
+.loading span:nth-of-type(2) {
+ animation-delay: 0.2s;
+}
+
+.loading span:nth-of-type(3) {
+ animation-delay: 0.4s;
+}
+
+@keyframes blink {
+ 0% {
+ opacity: 0.2;
+ }
+ 20% {
+ opacity: 1;
+ }
+ 100% {
+ opacity: 0.2;
+ }
+}
diff --git a/apps/www/components/LaunchWeek/Ticket/loading-dots.tsx b/apps/www/components/LaunchWeek/Ticket/loading-dots.tsx
new file mode 100644
index 00000000000..2d4cf1a669e
--- /dev/null
+++ b/apps/www/components/LaunchWeek/Ticket/loading-dots.tsx
@@ -0,0 +1,27 @@
+import { toPixels as px } from '~/lib/helpers'
+import cn from 'classnames'
+import styles from './loading-dots.module.css'
+
+interface Props {
+ size?: number
+ height?: number | string
+ reverse?: boolean
+ children?: React.ReactNode
+}
+
+export default function LoadingDots({ size = 2, height, children, reverse }: Props) {
+ return (
+
+ {children && {children}
}
+
+
+
+
+ )
+}
diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-actions.module.css b/apps/www/components/LaunchWeek/Ticket/ticket-actions.module.css
new file mode 100644
index 00000000000..d13c42418d7
--- /dev/null
+++ b/apps/www/components/LaunchWeek/Ticket/ticket-actions.module.css
@@ -0,0 +1,70 @@
+.button {
+ border-radius: var(--space-2x);
+ border: 1px solid #fff;
+ color: #000;
+ background: #fff;
+ cursor: pointer;
+ font-family: inherit;
+ font-size: var(--text-md);
+ letter-spacing: -0.02em;
+ font-weight: 500;
+ outline: none;
+ transition: background-color 0.2s ease;
+ display: grid;
+ grid-template-columns: 24px 1fr;
+ text-align: center;
+ align-items: center;
+ padding: 0 var(--space-4x);
+ height: 55px;
+ width: 100%;
+ max-width: 400px;
+ margin: var(--space-2x) auto;
+}
+
+.loading {
+ grid-template-columns: 1fr;
+ justify-items: center;
+}
+
+@media (min-width: 768px) {
+ .button {
+ width: 200px;
+ margin: 0 6px;
+ }
+}
+
+.button:hover,
+.button:focus {
+ background: black;
+ color: #fff;
+}
+
+.first {
+ animation-delay: 2s;
+}
+.second {
+ animation-delay: 2.2s;
+}
+.third {
+ animation-delay: 2.4s;
+}
+
+:global(.ticket-generated) .first {
+ animation-delay: 0.6s;
+}
+:global(.ticket-generated) .second {
+ animation-delay: 0.8s;
+}
+:global(.ticket-generated) .third {
+ animation-delay: 1s;
+}
+
+.linkedin-button {
+ display: none !important;
+}
+
+@media (min-width: 768px) {
+ .linkedin-button {
+ display: grid !important;
+ }
+}
diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-actions.tsx b/apps/www/components/LaunchWeek/Ticket/ticket-actions.tsx
new file mode 100644
index 00000000000..1a86e3aed8d
--- /dev/null
+++ b/apps/www/components/LaunchWeek/Ticket/ticket-actions.tsx
@@ -0,0 +1,90 @@
+import { useState, useRef, useEffect } from 'react'
+import cn from 'classnames'
+import { SITE_URL, TWEET_TEXT, TWEET_TEXT_GOLDEN } from '~/lib/constants'
+// import IconTwitter from '~/components/LaunchWeek/Ticket/icons/icon-twitter'
+// import IconLinkedin from '~/components/LaunchWeek/Ticket/icons/icon-linkedin'
+// import IconDownload from '~/components/LaunchWeek/Ticket/icons/icon-download'
+import LoadingDots from './loading-dots'
+import styleUtils from './utils.module.css'
+import styles from './ticket-actions.module.css'
+import useConfData from '~/components/LaunchWeek/Ticket//hooks/use-conf-data'
+
+type Props = {
+ username: string
+ golden?: boolean
+}
+
+export default function TicketActions({ username, golden = false }: Props) {
+ const [imgReady, setImgReady] = useState(false)
+ const [loading, setLoading] = useState(false)
+ const downloadLink = useRef()
+ const permalink = encodeURIComponent(`${SITE_URL}/tickets/${username}`)
+ const text = encodeURIComponent(golden ? TWEET_TEXT_GOLDEN : TWEET_TEXT)
+ const { userData } = useConfData()
+ const tweetUrl = `https://twitter.com/intent/tweet?url=${permalink}&via=supabase&text=${text}`
+ const linkedInUrl = `https://www.linkedin.com/sharing/share-offsite/?url=${permalink}`
+ const downloadUrl = `https://obuldanrptloktxcffvn.functions.supabase.co/launchweek-ticket-og?username=${encodeURIComponent(
+ userData.username ?? ''
+ )}`
+
+ useEffect(() => {
+ setImgReady(false)
+
+ const img = new Image()
+
+ img.src = downloadUrl
+ img.onload = () => {
+ setImgReady(true)
+ setLoading(false)
+ if (downloadLink.current) {
+ downloadLink.current.click()
+ downloadLink.current = undefined
+ }
+ }
+ }, [downloadUrl])
+
+ return (
+ <>
+
+ {/* Tweet it! */}
+
+
+ {/* Share on LinkedIn */}
+
+ {
+ if (imgReady) return
+
+ e.preventDefault()
+ downloadLink.current = e.currentTarget
+ // Wait for the image download to finish
+ setLoading(true)
+ }}
+ download="ticket.png"
+ >
+ {loading ? : <>{/* Download */}>}
+
+ >
+ )
+}
diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-copy.module.css b/apps/www/components/LaunchWeek/Ticket/ticket-copy.module.css
new file mode 100644
index 00000000000..f9a49f205fd
--- /dev/null
+++ b/apps/www/components/LaunchWeek/Ticket/ticket-copy.module.css
@@ -0,0 +1,190 @@
+.wrapper {
+ color: var(--lw-secondary-color);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ animation-delay: 2.6s;
+ flex-direction: column;
+}
+
+:global(.ticket-generated) .wrapper {
+ animation-delay: 1.2s;
+}
+
+.label {
+ width: 100%;
+ margin-bottom: var(--space-2x);
+}
+
+.label-wrapper {
+ display: flex;
+ align-items: center;
+ max-width: 400px;
+ width: 100%;
+ justify-content: space-between;
+}
+
+@media (min-width: 768px) {
+ .wrapper {
+ flex-direction: row;
+ }
+
+ .label {
+ width: auto;
+ margin-right: 10px;
+ margin-bottom: 0px;
+ }
+
+ .label-wrapper {
+ width: auto;
+ }
+}
+
+.field {
+ position: relative;
+ border-radius: var(--space-2x);
+ overflow: hidden;
+ background: var(--gray);
+}
+
+@media (min-width: 768px) {
+ .field {
+ padding-right: 50px;
+ }
+ .field.desktop-copy-disabled {
+ padding-right: 0px;
+ }
+}
+
+.mobile-copy {
+ margin-bottom: 10px;
+ display: flex;
+}
+
+.mobile-copy-disabled.mobile-copy-disabled {
+ display: none;
+}
+
+.mobile-copy * {
+ color: var(--lw-secondary-color);
+ transition: color 0.2s ease;
+}
+
+.mobile-copy:hover * {
+ color: #fff;
+ transition: color 0.2s ease;
+}
+
+.field * {
+ color: var(--lw-secondary-color);
+ transition: color 0.2s ease;
+}
+
+.field:hover * {
+ color: #fff;
+ transition: color 0.2s ease;
+}
+
+.url {
+ width: calc(100vw - 36px);
+ user-select: all;
+ overflow-x: scroll;
+ white-space: nowrap;
+ display: block;
+
+ padding: var(--space-2x) 14px;
+ -ms-overflow-style: none; /* IE and Edge */
+ scrollbar-width: none; /* Firefox */
+}
+
+@media (min-width: 436px) {
+ .url {
+ width: 400px;
+ }
+}
+
+@media (min-width: 768px) {
+ .url {
+ width: 400px;
+ }
+ .mobile-copy {
+ margin-bottom: 0;
+ display: none;
+ }
+}
+
+.url::-webkit-scrollbar {
+ display: none;
+}
+
+.fade {
+ position: absolute;
+ background: linear-gradient(90deg, rgba(37, 39, 41, 0%) 0%, #252729 100%);
+ top: 0;
+ bottom: 0;
+ right: 0px;
+ width: 50px;
+ z-index: 1;
+ display: block;
+ border-radius: var(--space-2x);
+ pointer-events: none;
+}
+
+.desktop-copy.desktop-copy-disabled {
+ display: none;
+}
+
+@media (min-width: 768px) {
+ .fade {
+ right: 50px;
+ }
+ .fade.desktop-copy-disabled {
+ right: 0px;
+ }
+}
+
+.copied {
+ display: flex;
+ z-index: 2;
+ align-items: center;
+ justify-content: flex-end;
+ opacity: 0;
+ transition: opacity 0.2s ease !important;
+}
+
+.copied.visible {
+ opacity: 1;
+}
+
+.copy-button {
+ background: none;
+ outline: none;
+ border: none;
+ cursor: pointer;
+ z-index: 2;
+ border-radius: var(--space-2x);
+ width: 40px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-right: -10px;
+}
+
+@media (min-width: 768px) {
+ .copied {
+ position: absolute;
+ right: 50px;
+ top: 0px;
+ bottom: 0px;
+ background: linear-gradient(90deg, rgba(37, 39, 41, 0%) 0px, #252729 40px);
+ padding-left: 50px;
+ }
+ .copy-button {
+ position: absolute;
+ top: 0px;
+ right: 0px;
+ bottom: 0px;
+ margin-right: 0px;
+ width: 50px;
+ }
+}
diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-copy.tsx b/apps/www/components/LaunchWeek/Ticket/ticket-copy.tsx
new file mode 100644
index 00000000000..3ec81fbb729
--- /dev/null
+++ b/apps/www/components/LaunchWeek/Ticket/ticket-copy.tsx
@@ -0,0 +1,109 @@
+import { useEffect, useState, useRef } from 'react'
+import cn from 'classnames'
+import { SITE_URL } from '~/lib/constants'
+import styleUtils from './utils.module.css'
+// import IconCopy from '~/components/LaunchWeek/Ticket/icons/icon-copy'
+import styles from './ticket-copy.module.css'
+
+type Props = {
+ username: string
+}
+
+export default function TicketCopy({ username }: Props) {
+ const [fadeOpacity, setFadeOpacity] = useState(1)
+ const [scrolling, setScrolling] = useState(false)
+ const [copyEnabled, setCopyEnabled] = useState(false)
+ const [copied, setCopied] = useState(false)
+ const scrollRef = useRef(null)
+ const buttonRef = useRef(null)
+ const url = `${SITE_URL}/tickets/${username}`
+ useEffect(() => {
+ if (navigator.clipboard) {
+ setCopyEnabled(true)
+ }
+ }, [])
+
+ const copiedText = (
+
+ Copied!
+
+ )
+
+ const copyButton = (
+ {
+ navigator.clipboard.writeText(url).then(() => {
+ setCopied(true)
+ setTimeout(() => {
+ setCopied(false)
+ }, 2000)
+ })
+ }}
+ >
+ {/* */}
+
+ )
+
+ return (
+
+
+
Your ticket URL:
+
+ {copiedText}
+ {copyButton}
+
+
+
+
{
+ if (!scrolling) {
+ setScrolling(true)
+ const animationFrame = requestAnimationFrame(() => {
+ const scrollableWidth =
+ (scrollRef.current?.scrollWidth || 0) - (scrollRef.current?.clientWidth || 0)
+ setFadeOpacity(
+ (scrollableWidth - (scrollRef.current?.scrollLeft || 0)) / (scrollableWidth || 1)
+ )
+ cancelAnimationFrame(animationFrame)
+ setScrolling(false)
+ })
+ }
+ }}
+ >
+ {url}
+
+
+
+ {copiedText}
+ {copyButton}
+
+
+
+ )
+}
diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-form.module.css b/apps/www/components/LaunchWeek/Ticket/ticket-form.module.css
new file mode 100644
index 00000000000..d10b43a0284
--- /dev/null
+++ b/apps/www/components/LaunchWeek/Ticket/ticket-form.module.css
@@ -0,0 +1,65 @@
+.githubIcon {
+ margin-left: 12px;
+ margin-right: var(--space-4x);
+ display: inline-flex;
+}
+
+.checkIcon {
+ position: absolute;
+ right: var(--space-4x);
+ display: inline-flex;
+}
+
+.generateWithGithub {
+ display: flex;
+ align-items: center;
+ font-weight: 500;
+}
+
+.stageIcon {
+ position: absolute;
+ left: var(--space-4x);
+ display: inline-flex;
+}
+
+.form-row {
+ margin-left: auto;
+ margin-right: auto;
+ max-width: 600px;
+}
+
+.description {
+ color: var(--secondary-color);
+ margin: 0;
+ text-align: center;
+ margin-left: var(--space-4x);
+}
+
+@media (min-width: 768px) {
+ .form-row {
+ margin-bottom: 20px;
+ }
+}
+
+@media (min-width: 1200px) {
+ .form-row {
+ margin: 0;
+ }
+
+ .description {
+ text-align: left;
+ }
+
+ .generateWithGithub {
+ width: 100%;
+ }
+}
+
+.learn-more {
+ color: #fff;
+}
+
+.learn-more:hover {
+ text-decoration: underline;
+ color: #fff;
+}
diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-form.tsx b/apps/www/components/LaunchWeek/Ticket/ticket-form.tsx
new file mode 100644
index 00000000000..1674bd7c2ba
--- /dev/null
+++ b/apps/www/components/LaunchWeek/Ticket/ticket-form.tsx
@@ -0,0 +1,103 @@
+import { useState, useRef } from 'react'
+// import { scrollTo } from '@lib/smooth-scroll';
+import cn from 'classnames'
+// import GithubIcon from '~/components/LaunchWeek/Ticket/icons/icon-github'
+// import CheckIcon from '~/components/LaunchWeek/Ticket/icons/icon-check'
+import { SITE_ORIGIN } from '~/lib/constants'
+import useConfData from '~/components/LaunchWeek/Ticket//hooks/use-conf-data'
+import LoadingDots from './loading-dots'
+import formStyles from './form.module.css'
+import ticketFormStyles from './ticket-form.module.css'
+
+type FormState = 'default' | 'loading' | 'error'
+type TicketGenerationState = 'default' | 'loading'
+
+type Props = {
+ defaultUsername?: string
+ setTicketGenerationState: React.Dispatch>
+}
+
+export default function Form({ defaultUsername = '', setTicketGenerationState }: Props) {
+ const [username] = useState(defaultUsername)
+ const [formState, setFormState] = useState('default')
+ const [errorMsg] = useState('')
+ const { supabase } = useConfData()
+ const formRef = useRef(null)
+
+ return formState === 'error' ? (
+
+
+
+
{errorMsg}
+
{
+ setFormState('default')
+ setTicketGenerationState('default')
+ }}
+ >
+ Try Again
+
+
+
+
+ ) : (
+
+ )
+}
diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-image.module.css b/apps/www/components/LaunchWeek/Ticket/ticket-image.module.css
new file mode 100644
index 00000000000..8af0bcf2aa6
--- /dev/null
+++ b/apps/www/components/LaunchWeek/Ticket/ticket-image.module.css
@@ -0,0 +1,17 @@
+.background {
+ width: 2000px;
+ height: 1000px;
+ background: #000;
+ color: #fff;
+ overflow: hidden;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.page {
+ width: 1700px;
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',
+ 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 'Noto Sans', 'Noto Sans JP',
+ 'Noto Sans KR', 'Noto Sans SC', 'Noto Sans TC', 'Noto Sans HK', sans-serif;
+}
diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-image.tsx b/apps/www/components/LaunchWeek/Ticket/ticket-image.tsx
new file mode 100644
index 00000000000..43fe8a2f68f
--- /dev/null
+++ b/apps/www/components/LaunchWeek/Ticket/ticket-image.tsx
@@ -0,0 +1,37 @@
+import { useRouter } from 'next/router'
+import Head from 'next/head'
+import TicketVisual from './ticket-visual'
+import styles from './ticket-image.module.css'
+
+export default function TicketImage() {
+ const { query } = useRouter()
+ if (query.ticketNumber) {
+ return (
+
+ )
+ }
+ return <>>
+}
diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-info.module.css b/apps/www/components/LaunchWeek/Ticket/ticket-info.module.css
new file mode 100644
index 00000000000..117922b8074
--- /dev/null
+++ b/apps/www/components/LaunchWeek/Ticket/ticket-info.module.css
@@ -0,0 +1,93 @@
+.info {
+ display: flex;
+ flex-direction: column;
+ gap: 0px;
+ margin-bottom: 64px;
+ padding: 32px;
+}
+
+@media (min-width: 768px) {
+ .info {
+ /* display: grid;
+ grid-template-columns: 1fr 2fr;
+ gap: calc(var(--space-4x) * var(--size));
+ font-size: calc(1em * var(--size)); */
+ gap: calc(8px * var(--size));
+ padding: 0;
+ margin-bottom: calc(32px * var(--size));
+ margin-left: calc(160px * var(--size));
+ }
+}
+
+.logo {
+ /* margin-bottom: var(--space-6x); */
+ /* max-width: 96px; */
+ font-size: 18px;
+}
+
+/* @media (min-width: 768px) {
+ .logo {
+ margin-bottom: 0;
+ font-size: calc(16px * var(--size));
+ }
+} */
+
+.date {
+ /* text-transform: uppercase; */
+ font-size: calc(18px * var(--size));
+
+ color: var(--lw-secondary-color);
+}
+
+.date-golden {
+ color: var(--gold-primary);
+}
+
+@media (min-width: 768px) {
+ .date {
+ margin-bottom: 0;
+ line-height: 1.15;
+ font-size: calc(20px * var(--size));
+ }
+}
+
+.created-by {
+ display: flex;
+ align-items: center;
+ color: var(--accents-4);
+}
+
+@media (min-width: 768px) {
+ .created-by {
+ margin-right: var(--space-4x);
+ }
+}
+
+.created-by-text {
+ white-space: nowrap;
+ margin-right: var(--space);
+}
+
+/* .created-by-logo {
+ height: calc(16px * var(--size));
+ display: inline-flex;
+} */
+
+.url {
+ margin-bottom: var(--space-12x);
+ font-size: calc(18px * var(--size));
+
+ color: var(--lw-secondary-color);
+}
+
+.url-golden {
+ color: var(--gold-primary);
+}
+
+@media (min-width: 768px) {
+ .url {
+ margin-bottom: 0;
+ font-size: calc(20px * var(--size));
+ line-height: 1.15;
+ }
+}
diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-info.tsx b/apps/www/components/LaunchWeek/Ticket/ticket-info.tsx
new file mode 100644
index 00000000000..0bf9bb1f9c8
--- /dev/null
+++ b/apps/www/components/LaunchWeek/Ticket/ticket-info.tsx
@@ -0,0 +1,20 @@
+import styles from './ticket-info.module.css'
+import cn from 'classnames'
+import { DATE, SITE_URL } from '~/lib/constants'
+
+const siteUrl = new URL(SITE_URL)
+const siteUrlForTicket = `${siteUrl.host}${siteUrl.pathname}`.replace(/\/$/, '')
+
+export default function TicketInfo({
+ logoTextSecondaryColor = 'var(--accents-5)',
+ golden = false,
+}) {
+ return (
+
+ )
+}
diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-mono-mobile.tsx b/apps/www/components/LaunchWeek/Ticket/ticket-mono-mobile.tsx
new file mode 100644
index 00000000000..f55de57df16
--- /dev/null
+++ b/apps/www/components/LaunchWeek/Ticket/ticket-mono-mobile.tsx
@@ -0,0 +1,34 @@
+// NOTE: When you export SVG from Figma, you must:
+// - Update width and height as 100%
+// - Change id to "mobile-..."
+// - Change url(#) to url(#mobile-)
+// This is because if a page has two same IDs it will fail.
+export default function TicketMonoMobile({ golden = false }: { golden?: boolean }) {
+ const frameColor = golden ? '#F2C94C' : '#3fcf8e'
+
+ const perforationColor = golden ? 'var(--gold-accent)' : '#252729'
+
+ return (
+
+ {/* */}
+ {/* */}
+
+
+ )
+}
diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-mono.tsx b/apps/www/components/LaunchWeek/Ticket/ticket-mono.tsx
new file mode 100644
index 00000000000..c4e5eb46b0f
--- /dev/null
+++ b/apps/www/components/LaunchWeek/Ticket/ticket-mono.tsx
@@ -0,0 +1,23 @@
+export default function TicketMono({ golden = false }: { golden?: boolean }) {
+ const frameColor = golden ? '#F2C94C' : '#252729'
+
+ const perforationColor = golden ? 'var(--gold-accent)' : '#252729'
+
+ return (
+
+
+
+
+ )
+}
diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-number.tsx b/apps/www/components/LaunchWeek/Ticket/ticket-number.tsx
new file mode 100644
index 00000000000..f5f80bbacc6
--- /dev/null
+++ b/apps/www/components/LaunchWeek/Ticket/ticket-number.tsx
@@ -0,0 +1,14 @@
+type Props = {
+ number: number
+}
+
+export default function TicketNumber({ number }: Props) {
+ const numDigits = `${number}`.length
+ const prefix = `000000`.slice(numDigits)
+ return (
+ <>
+ № {prefix}
+ {number}
+ >
+ )
+}
diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-profile.module.css b/apps/www/components/LaunchWeek/Ticket/ticket-profile.module.css
new file mode 100644
index 00000000000..0eda9d1fd2f
--- /dev/null
+++ b/apps/www/components/LaunchWeek/Ticket/ticket-profile.module.css
@@ -0,0 +1,155 @@
+.profile {
+ display: flex;
+ align-items: center;
+}
+
+.image {
+ width: 60px;
+ height: 60px;
+ border-radius: 50%;
+ border: 1px solid #333;
+}
+
+@media (min-width: 768px) {
+ .image {
+ width: calc(80px * var(--size));
+ height: calc(80px * var(--size));
+ }
+}
+
+.text {
+ flex: 1;
+ margin-left: 10px;
+}
+
+@media (min-width: 768px) {
+ .text {
+ margin-left: calc(15px * var(--size));
+ }
+}
+
+.name {
+ font-size: 24px;
+ display: inline-block;
+ line-height: 1.15;
+ letter-spacing: -0.02em;
+ margin: 0;
+}
+
+.name-golden {
+ color: var(--gold-primary);
+}
+
+@media (min-width: 768px) {
+ .name {
+ font-size: calc(var(--space-8x) * var(--size));
+ margin-bottom: 5px;
+ }
+}
+
+.username {
+ display: inline-block;
+ font-size: calc(1em * var(--size));
+ color: var(--lw-secondary-color);
+ display: flex;
+ align-items: center;
+ margin: 0;
+}
+
+.username-golden {
+ color: var(--gold-secondary);
+}
+
+.githubIcon {
+ margin-right: calc(5px * var(--size));
+ display: inline-flex;
+}
+
+.githubIcon-golden svg {
+ color: var(--gold-secondary);
+ opacity: 0.5;
+}
+
+.empty-icon {
+ display: block;
+ background: linear-gradient(135deg, #b4b6b8, #7f8183);
+ width: 60px;
+}
+
+@media (min-width: 768px) {
+ .empty-icon {
+ width: calc(80px * var(--size));
+ }
+}
+
+@keyframes loading {
+ 0% {
+ background-position: 200% 0;
+ }
+ 100% {
+ background-position: -200% 0;
+ }
+}
+
+.skeleton {
+ display: flex;
+ align-items: center;
+ border-radius: 5px;
+}
+
+.skeleton.loaded {
+ width: unset !important;
+}
+
+.skeleton:not(.wrapper):not(.show) {
+ display: none;
+}
+
+.wrapper:not(.show)::before {
+ content: none;
+}
+
+.skeleton:not(.wrapper):not(.loaded) {
+ border-radius: var(--space);
+ background-image: linear-gradient(270deg, #252729, #34383d, #34383d, #252729);
+ background-size: 200% 100%;
+ animation: loading 2s ease-in-out infinite;
+}
+
+.wrapper {
+ position: relative;
+}
+
+.wrapper::before {
+ content: '';
+ position: absolute;
+ top: -1px;
+ left: -1px;
+ right: 0;
+ bottom: 0;
+ border-radius: var(--space);
+ z-index: 100;
+ background-image: linear-gradient(270deg, #111111, #333333, #333333, #111111);
+ background-size: 200% 100%;
+ animation: loading 2s ease-in-out infinite;
+}
+
+.inline {
+ display: inline-block !important;
+}
+
+.rounded,
+.rounded.wrapper::before {
+ width: 62px;
+ height: 62px;
+ border-radius: 50% !important;
+}
+
+@media (min-width: 768px) {
+ .rounded,
+ .rounded.wrapper::before {
+ width: calc(82px * var(--size));
+ height: calc(82px * var(--size));
+ border-radius: 50% !important;
+ }
+}
diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-profile.tsx b/apps/www/components/LaunchWeek/Ticket/ticket-profile.tsx
new file mode 100644
index 00000000000..6df6e14961d
--- /dev/null
+++ b/apps/www/components/LaunchWeek/Ticket/ticket-profile.tsx
@@ -0,0 +1,69 @@
+// import GithubIcon from '~/components/LaunchWeek/Ticket/icons/icon-github'
+import cn from 'classnames'
+// import IconAvatar from '~/components/LaunchWeek/Ticket/icons/icon-avatar'
+import styles from './ticket-profile.module.css'
+
+type TicketGenerationState = 'default' | 'loading'
+type Props = {
+ name?: string
+ username?: string
+ size?: number
+ ticketGenerationState: TicketGenerationState
+ golden?: boolean
+}
+
+export default function TicketProfile({
+ name,
+ username,
+ size = 1,
+ ticketGenerationState,
+ golden = false,
+}: Props) {
+ return (
+
+
+ {username ? (
+
+ ) : (
+ {/* */}
+ )}
+
+
+
+
+ {name || username || 'Your Name'}
+
+
+
+
+
+ {/* */}
+
+ {username || <>username>}
+
+
+
+
+ )
+}
diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-visual.module.css b/apps/www/components/LaunchWeek/Ticket/ticket-visual.module.css
new file mode 100644
index 00000000000..2ee2f212a38
--- /dev/null
+++ b/apps/www/components/LaunchWeek/Ticket/ticket-visual.module.css
@@ -0,0 +1,173 @@
+.visual {
+ position: relative;
+ transform: translateZ(0);
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ /* background-color: black; */
+ border: 2px solid rgba(0, 0, 0, 0.1);
+ border-radius: 16px;
+
+ background-color: rgba(0, 0, 0, 1);
+ -webkit-backdrop-filter: blur(5px);
+ backdrop-filter: blur(5px);
+
+ /* background: linear-gradient(#fff000, var(--gold-secondary)); */
+ border: 1px solid rgba(255, 255, 0, 0.629);
+ animation: blank-svg-shadow 2s ease-in-out infinite alternate;
+}
+
+.visual--gold {
+ background-color: gold;
+ /* border: 10px solid;
+ border-image-slice: 1;
+ border-width: 2px;
+ border-radius: 16px; */
+ background: linear-gradient(#fff000, var(--gold-secondary));
+ border: 1px solid rgba(255, 255, 0, 0.629);
+ animation: svg-shadow 2s ease-in-out infinite alternate;
+ /* filter: drop-shadow(2.45223px 8.5828px 73.5669px #FFBD14); */
+ /* border-image-source: linear-gradient(to left, var(--gold-secondary), var(--gold-accent)); */
+}
+
+/* .svg-shadow {
+} */
+
+@keyframes svg-shadow {
+ from {
+ filter: drop-shadow(0 0 5px #fff) drop-shadow(0 0 15px var(--gold-primary))
+ drop-shadow(0 0 20px var(--gold-accent));
+ }
+
+ to {
+ filter: drop-shadow(0 0 20px #fff) drop-shadow(0 0 10px var(--gold-accent))
+ drop-shadow(0 0 40px var(--gold-primary));
+ }
+}
+
+@keyframes blank-svg-shadow {
+ from {
+ filter: drop-shadow(0 0 5px #fff) drop-shadow(0 0 15px #12f28d) drop-shadow(0 0 20px #12f28d);
+ }
+
+ to {
+ filter: drop-shadow(0 0 20px #fff) drop-shadow(0 0 10px #12f28d) drop-shadow(0 0 40px #12f28d);
+ }
+}
+
+.horizontal-ticket {
+ display: none;
+}
+
+.vertical-ticket {
+ display: flex;
+}
+
+@media (min-width: 768px) {
+ .horizontal-ticket {
+ display: flex;
+ }
+
+ .vertical-ticket {
+ display: none;
+ }
+}
+
+.logo {
+ z-index: 1;
+ position: absolute;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ /* padding: 46px 29px;/ */
+}
+
+.logo img {
+ /* margin-top: 64px; */
+ margin-top: calc(64px * var(--size));
+ margin-left: calc(32px * var(--size));
+ margin-left: calc(32px * var(--size));
+ /* width: 272px; */
+ width: calc(272px * var(--size));
+}
+
+@media (min-width: 768px) {
+ .logo img {
+ margin-top: calc(32px * var(--size));
+ /* width: 280px; */
+ width: calc(280px * var(--size));
+ margin-left: calc(160px * var(--size));
+ }
+}
+
+.profile {
+ z-index: 1;
+ position: absolute;
+ top: 90px;
+ width: 100%;
+ height: 100%;
+ padding: 46px 29px;
+}
+
+@media (min-width: 768px) {
+ .profile {
+ top: calc(48px * var(--size));
+ padding: calc(44px * var(--size)) calc(160px * var(--size)) calc(44px * var(--size))
+ calc(63px * var(--size));
+ }
+}
+
+.info {
+ z-index: 1;
+ position: absolute;
+ bottom: 0;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-end;
+ /* padding: calc(46px * var(--size)) calc(29px * var(--size)); */
+}
+
+@media (min-width: 768px) {
+ .info {
+ bottom: 0;
+ /* padding: 0 calc(160px * var(--size)) calc(44px * var(--size))
+ calc(63px * var(--size)); */
+ }
+}
+
+.ticket-number-wrapper {
+ position: absolute;
+ bottom: 40px;
+ right: 0;
+}
+
+@media (min-width: 768px) {
+ .ticket-number-wrapper {
+ bottom: 0;
+ }
+}
+
+.ticket-number {
+ font-weight: bold;
+ font-size: calc(40px * var(--size));
+ line-height: 1;
+ width: calc(330px * var(--size));
+ text-align: center;
+}
+
+.ticket-number-golden {
+ color: var(--gold-primary);
+}
+
+@media (min-width: 768px) {
+ .ticket-number {
+ transform: rotate(90deg) translateY(calc(100px * var(--size)));
+ transform-origin: bottom right;
+ }
+
+ .ticket-number-golden {
+ color: var(--gold-accent);
+ }
+}
diff --git a/apps/www/components/LaunchWeek/Ticket/ticket-visual.tsx b/apps/www/components/LaunchWeek/Ticket/ticket-visual.tsx
new file mode 100644
index 00000000000..bec9aceced7
--- /dev/null
+++ b/apps/www/components/LaunchWeek/Ticket/ticket-visual.tsx
@@ -0,0 +1,83 @@
+import styles from './ticket-visual.module.css'
+import TicketProfile from './ticket-profile'
+import TicketNumber from './ticket-number'
+import TicketMono from './ticket-mono'
+import TicketInfo from './ticket-info'
+import TicketMonoMobile from './ticket-mono-mobile'
+import cn from 'classnames'
+import { useRouter } from 'next/router'
+
+type TicketGenerationState = 'default' | 'loading'
+type Props = {
+ size?: number
+ name?: string
+ ticketNumber?: number
+ username?: string
+ ticketGenerationState?: TicketGenerationState
+ golden?: boolean
+}
+
+export default function TicketVisual({
+ size = 1,
+ name,
+ username,
+ ticketNumber,
+ ticketGenerationState = 'default',
+ golden = false,
+}: Props) {
+ // golden = true;
+
+ const router = useRouter()
+ const basePath = router.basePath
+ return (
+ <>
+
+
+ {/* {username ? : } */}
+
+
+
+
+ {/* {username ? (
+
+ ) : (
+
+ )} */}
+
+
+
+
+
+
+
+
+
+
+
+ {ticketNumber && (
+
+ )}
+
+ >
+ )
+}
diff --git a/apps/www/components/LaunchWeek/Ticket/ticket.module.css b/apps/www/components/LaunchWeek/Ticket/ticket.module.css
new file mode 100644
index 00000000000..4101fabfef0
--- /dev/null
+++ b/apps/www/components/LaunchWeek/Ticket/ticket.module.css
@@ -0,0 +1,161 @@
+.hero {
+ font-size: 30px;
+ line-height: 1.15;
+ letter-spacing: -0.05em;
+ font-weight: 400;
+ margin: 0;
+ text-align: center;
+}
+
+@media (min-width: 768px) {
+ .hero {
+ font-size: 30px;
+ line-height: 1;
+ /* font-weight: 800; */
+ /* max-width: 800px; */
+ /* margin-left: auto;
+ margin-right: auto; */
+ }
+}
+
+.description {
+ font-size: 20px;
+ line-height: 1.4;
+ color: var(--lw-secondary-color);
+ text-align: center;
+ margin: 30px auto;
+ max-width: 350px;
+}
+
+@media (min-width: 768px) {
+ .description {
+ font-size: 24px;
+ max-width: 415px;
+ }
+}
+
+.ticket-text {
+ margin-bottom: 30px;
+}
+
+.ticket-layout {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 45px;
+ margin: auto;
+ padding: var(--space-4x);
+ padding-top: 64px;
+ padding-bottom: 64px;
+}
+
+@media (min-width: 1200px) {
+ .ticket-layout {
+ padding-top: 0;
+ padding-bottom: 0;
+ }
+}
+
+.ticket-share-layout {
+ align-items: center;
+ margin-bottom: auto;
+}
+
+.ticket-visual {
+ position: relative;
+ border-radius: calc(16px * var(--size));
+ /* overflow: hidden; */
+ width: 330px;
+ margin: 0 auto;
+}
+
+/* .ticket-visual:before,
+.ticket-visual:after { */
+/* content: '';
+ display: block;
+ width: 72px;
+ height: 58px; */
+/* background-color: #1C1C1C; */
+/* position: absolute;
+ z-index: 100;
+ border-radius: 50%; */
+/* } */
+
+.ticket-visual:before {
+ left: 50%;
+ top: -7.5%;
+ transform: translateZ(0) translateX(-50%) rotate(90deg);
+}
+
+.ticket-visual:after {
+ left: 50%;
+ bottom: -7.5%;
+ transform: translateZ(0) translateX(-50%) rotate(90deg);
+}
+
+@media (min-width: 768px) {
+ .ticket-visual {
+ width: 650px;
+ }
+
+ .ticket-visual:before {
+ top: 50%;
+ left: -7%;
+ transform: translateZ(0) translateY(-50%);
+ }
+
+ .ticket-visual:after {
+ top: 50%;
+ left: 96%;
+ transform: translateZ(0) translateY(-50%);
+ }
+}
+
+.ticket-actions {
+ margin-top: 30px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+}
+
+.ticket-copy {
+ margin-top: 30px;
+}
+
+.ticket-actions-placeholder {
+ height: 280px;
+}
+
+@media (min-width: 768px) {
+ .ticket-actions {
+ flex-direction: row;
+ }
+ .ticket-actions-placeholder {
+ height: 157px;
+ }
+}
+
+@media (min-width: 1200px) {
+ .ticket-layout {
+ grid-template-columns: 1fr 1fr;
+ }
+ .hero {
+ font-size: 48px;
+ text-align: left;
+ }
+ .description {
+ text-align: left;
+ margin: 30px 0;
+ }
+}
+
+.ticket-visual-wrapper {
+ border-radius: calc(16px * var(--size));
+ width: 100%;
+}
+
+@media screen and (min-width: 1280px) {
+ .ticket-layout {
+ padding: 0 40px;
+ }
+}
diff --git a/apps/www/components/LaunchWeek/Ticket/ticket.tsx b/apps/www/components/LaunchWeek/Ticket/ticket.tsx
new file mode 100644
index 00000000000..3af3b7eb048
--- /dev/null
+++ b/apps/www/components/LaunchWeek/Ticket/ticket.tsx
@@ -0,0 +1,132 @@
+import cn from 'classnames'
+import Tilt from 'vanilla-tilt'
+import { useRef, useEffect, useState } from 'react'
+// import { TicketGenerationState } from '@lib/constants'
+// import {isMobileOrTablet} from '~/lib/helpers'
+// import { scrollTo } from '@lib/smooth-scroll'
+import styles from './ticket.module.css'
+import styleUtils from './utils.module.css'
+import TicketForm from './ticket-form'
+import TicketVisual from './ticket-visual'
+import TicketActions from './ticket-actions'
+import TicketCopy from './ticket-copy'
+import { DATE, SITE_NAME } from '~/lib/constants'
+import Form from './form'
+import { UserData } from './hooks/use-conf-data'
+
+type TicketGenerationState = 'default' | 'loading'
+type Props = {
+ username: UserData['username']
+ ticketNumber: UserData['ticketNumber']
+ name: UserData['name']
+ golden: UserData['golden']
+ sharePage?: boolean
+}
+
+export default function Ticket({ username, name, ticketNumber, sharePage, golden }: Props) {
+ const ticketRef = useRef(null)
+ const [ticketGenerationState, setTicketGenerationState] =
+ useState('default')
+ const divRef = useRef(null)
+
+ useEffect(() => {
+ if (ticketRef.current && !window.matchMedia('(pointer: coarse)').matches) {
+ Tilt.init(ticketRef.current, {
+ glare: true,
+ max: 5,
+ 'max-glare': 0.16,
+ 'full-page-listening': true,
+ })
+ }
+ }, [ticketRef])
+
+ // useEffect(() => {
+ // if (!sharePage && divRef && divRef.current && isMobileOrTablet()) {
+ // scrollTo(divRef.current, -30)
+ // }
+ // }, [divRef, sharePage])
+
+ return (
+
+
+
+
+ {sharePage ? (
+ name ? (
+ <>{name}’s Ticket>
+ ) : (
+ <>{SITE_NAME}>
+ )
+ ) : golden ? (
+ <>
+ You won a golden ticket! Claim it now!
+ >
+ ) : (
+ <>
+ You're in. Make it unique.
+ >
+ )}
+
+
+ {sharePage ? (
+ <>
+ Join {name ?? 'us'} on {DATE}.
+ >
+ ) : golden ? (
+ <>Claim your ticket with GitHub and Tweet it to redeem your swag pack!>
+ ) : (
+ <>
+ Generate a unique ticket image with
+ your GitHub profile.
+ >
+ )}
+
+
+
+ {!sharePage ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+ {!sharePage && (
+ <>
+ {username ? (
+
+ ) : (
+
+ )}
+ >
+ )}
+
+
+ )
+}
diff --git a/apps/www/components/LaunchWeek/Ticket/utils.module.css b/apps/www/components/LaunchWeek/Ticket/utils.module.css
new file mode 100644
index 00000000000..c4aba2ab686
--- /dev/null
+++ b/apps/www/components/LaunchWeek/Ticket/utils.module.css
@@ -0,0 +1,76 @@
+.appear {
+ opacity: 0;
+ animation: appear 0.8s cubic-bezier(0.1, 0, 0.175, 1) forwards;
+}
+
+.appear-first {
+ animation-delay: 0.4s;
+}
+
+.appear-second {
+ animation-delay: 0.8s;
+}
+
+.appear-third {
+ animation-delay: 1.2s;
+}
+
+.appear-fourth {
+ animation-delay: 1.6s;
+}
+
+.appear-fifth {
+ animation-delay: 2s;
+}
+
+.appear-sixth {
+ animation-delay: 2.4s;
+}
+
+@keyframes appear {
+ to {
+ opacity: 1;
+ }
+}
+
+@media (min-width: 1200px) {
+ .hide-on-desktop {
+ display: none;
+ }
+}
+
+.show-on-mobile {
+ display: block;
+}
+
+.hide-on-mobile {
+ display: none;
+}
+
+@media (min-width: 768px) {
+ .show-on-mobile {
+ display: none;
+ }
+
+ .hide-on-mobile {
+ display: block;
+ }
+}
+
+.hide-on-tablet {
+ display: block;
+}
+
+.show-on-tablet {
+ display: none;
+}
+
+@media (max-width: 1199px) and (min-width: 768px) {
+ .show-on-tablet {
+ display: block;
+ }
+
+ .hide-on-tablet {
+ display: none;
+ }
+}
diff --git a/apps/www/lib/constants.ts b/apps/www/lib/constants.ts
index e72ab554946..f6c16abf7f4 100644
--- a/apps/www/lib/constants.ts
+++ b/apps/www/lib/constants.ts
@@ -1,2 +1,15 @@
export const APP_NAME = 'Supabase'
export const DESCRIPTION = 'The Open Source Alternative to Firebase.'
+
+// Launch Weeek
+export const SAMPLE_TICKET_NUMBER = 1234
+export const SITE_URL = process.env.NEXT_PUBLIC_VERCEL_URL
+ ? `https://${process.env.NEXT_PUBLIC_VERCEL_URL}/launch-week`
+ : 'http://localhost:3000/launch-week'
+export const SITE_ORIGIN = new URL(SITE_URL).origin
+export const TWITTER_USER_NAME = 'supabase'
+export const COOKIE = 'supa-launch-week-ticke-user-id'
+export const DATE = 'December 12 8:00 PT | 11:00 ET'
+export const TWEET_TEXT = 'Got my ticket for #SupaLaunchWeek 6!'
+export const TWEET_TEXT_GOLDEN = `Woop! I got a Golden Ticket for #SupaLaunchWeek 6! SupaSwag on its way!`
+export const SITE_NAME = 'Supabase'
diff --git a/apps/www/lib/helpers.tsx b/apps/www/lib/helpers.tsx
index 33b8fb8ed3f..2d9722c10f1 100644
--- a/apps/www/lib/helpers.tsx
+++ b/apps/www/lib/helpers.tsx
@@ -11,3 +11,21 @@ export const generateReadingTime = (text: string) => {
export function capitalize(word: string) {
return word[0].toUpperCase() + word.substring(1).toLowerCase()
}
+
+export function isMobileOrTablet() {
+ // https://stackoverflow.com/a/8876069/114157
+ const viewportWidth = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0)
+ return viewportWidth < 1200
+}
+
+// Convert numbers or strings to pixel value
+// Helpful for styled-jsx when using a prop
+// height: ${toPixels(height)}; (supports height={20} and height="20px")
+
+export const toPixels = (value: string | number) => {
+ if (typeof value === 'number') {
+ return `${value}px`
+ }
+
+ return value
+}
diff --git a/apps/www/lib/redirects.js b/apps/www/lib/redirects.js
index 40838ca9228..c370fc9ce29 100644
--- a/apps/www/lib/redirects.js
+++ b/apps/www/lib/redirects.js
@@ -1545,4 +1545,19 @@ module.exports = [
source: '/docs/guides/platform/disk-usage',
destination: '/docs/guides/platform/database-usage',
},
+ {
+ permanent: true,
+ source: '/sign-in',
+ destination: 'https://app.supabase.com/sign-in',
+ },
+ {
+ permanent: true,
+ source: '/sign-up',
+ destination: 'https://app.supabase.com/sign-up',
+ },
+ {
+ permanent: true,
+ source: '/forgot-password',
+ destination: 'https://app.supabase.com/forgot-password',
+ },
]
diff --git a/apps/www/lib/rewrites.js b/apps/www/lib/rewrites.js
index b5f9efbea19..1fd564e3039 100644
--- a/apps/www/lib/rewrites.js
+++ b/apps/www/lib/rewrites.js
@@ -17,20 +17,6 @@ module.exports = [
source: '/docs/:path*',
destination: `${process.env.NEXT_PUBLIC_DOCS_URL}/:path*`,
},
- // rewrite to keep existing ticket urls working
- {
- source: '/launch-week/tickets/:path',
- destination: `${process.env.NEXT_PUBLIC_LAUNCHWEEKSITE_URL}/tickets/:path`,
- },
- // rewrite to move ticket website to another path
- {
- source: '/launch-week-register',
- destination: `${process.env.NEXT_PUBLIC_LAUNCHWEEKSITE_URL}`,
- },
- {
- source: '/launch-week-register/:path*',
- destination: `${process.env.NEXT_PUBLIC_LAUNCHWEEKSITE_URL}/:path*`,
- },
{
source: '/new-docs',
destination: `${process.env.NEXT_PUBLIC_REFERENCE_DOCS_URL}`,
diff --git a/apps/www/package.json b/apps/www/package.json
index ad8d7654ba9..b8808779fdf 100644
--- a/apps/www/package.json
+++ b/apps/www/package.json
@@ -29,7 +29,7 @@
"gray-matter": "^4.0.2",
"markdown-toc": "^1.2.0",
"marked": "^4.0.12",
- "next": "latest",
+ "next": "12.1.0",
"next-compose-plugins": "^2.2.1",
"next-mdx-remote": "^4.1.0",
"next-seo": "^4.20.0",
@@ -55,6 +55,7 @@
"ui": "*",
"use-debounce": "^7.0.1",
"uuid": "^8.3.2",
+ "vanilla-tilt": "1.7.0",
"yup": "^0.32.11"
},
"devDependencies": {
diff --git a/apps/www/pages/launch-week/ticket-image.tsx b/apps/www/pages/launch-week/ticket-image.tsx
new file mode 100644
index 00000000000..9fa19e85f7f
--- /dev/null
+++ b/apps/www/pages/launch-week/ticket-image.tsx
@@ -0,0 +1,5 @@
+import TicketImage from '~/components/LaunchWeek/Ticket/ticket-image'
+
+export default function TicketOnlyPage() {
+ return
+}
diff --git a/apps/www/pages/launch-week/tickets/[username].tsx b/apps/www/pages/launch-week/tickets/[username].tsx
new file mode 100644
index 00000000000..97ac38e47ee
--- /dev/null
+++ b/apps/www/pages/launch-week/tickets/[username].tsx
@@ -0,0 +1,114 @@
+import { NextSeo } from 'next-seo'
+import { GetStaticProps, GetStaticPaths } from 'next'
+import Error from 'next/error'
+import DefaultLayout from '~/components/Layouts/Default'
+import SectionContainer from '~/components/Layouts/SectionContainer'
+import TicketContainer from '~/components/LaunchWeek/Ticket/TicketContainer'
+import { SITE_URL, SAMPLE_TICKET_NUMBER } from '~/lib/constants'
+import { createClient } from '@supabase/supabase-js'
+import { useState } from 'react'
+import { FastGlobOptionsWithoutCwd } from 'globby'
+
+type Props = {
+ username: string | null
+ name: string | null
+ ticketNumber: number | null
+ golden: boolean
+}
+
+const supabaseAdmin = createClient(
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
+ process.env.SUPABASE_SERVICE_ROLE_SECRET!
+)
+
+export default function TicketShare({ username, ticketNumber, name, golden }: Props) {
+ const [supabase] = useState(() =>
+ createClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!)
+ )
+ const description = 'Supabase Launch Week 6 | 12-16 Dec 2022'
+
+ if (!ticketNumber) {
+ return
+ }
+
+ const ogImageUrl = `https://obuldanrptloktxcffvn.functions.supabase.co/launchweek-ticket-og?username=${encodeURIComponent(
+ username ?? ''
+ )}`
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+ >
+ )
+}
+
+export const getStaticProps: GetStaticProps = async ({ params }) => {
+ const username = params?.username?.toString() || null
+ let name: string | null | undefined
+ let ticketNumber: number | null | undefined
+ let golden = false
+
+ if (username) {
+ const { data: user } = await supabaseAdmin!
+ .from('lw6_tickets_golden')
+ .select('name, ticketNumber, golden')
+ .eq('username', username)
+ .single()
+ name = user?.name
+ ticketNumber = user?.ticketNumber
+ golden = user?.golden ?? false
+ }
+ return {
+ props: {
+ username: ticketNumber ? username : null,
+ usernameFromParams: username || null,
+ name: ticketNumber ? name || username || null : null,
+ ticketNumber: ticketNumber || SAMPLE_TICKET_NUMBER,
+ golden,
+ },
+ revalidate: 5,
+ }
+}
+
+export const getStaticPaths: GetStaticPaths = async () => {
+ return Promise.resolve({
+ paths: [],
+ fallback: 'blocking',
+ })
+}
diff --git a/apps/www/pages/launch-week/tickets/index.tsx b/apps/www/pages/launch-week/tickets/index.tsx
new file mode 100644
index 00000000000..56c1a368a47
--- /dev/null
+++ b/apps/www/pages/launch-week/tickets/index.tsx
@@ -0,0 +1,67 @@
+import { NextSeo } from 'next-seo'
+import DefaultLayout from '~/components/Layouts/Default'
+import SectionContainer from '~/components/Layouts/SectionContainer'
+import TicketContainer from '~/components/LaunchWeek/Ticket/TicketContainer'
+import { SITE_URL } from '~/lib/constants'
+import { useRouter } from 'next/router'
+import { createClient, Session } from '@supabase/supabase-js'
+import { useState, useEffect } from 'react'
+
+export default function TicketHome() {
+ const [supabase] = useState(() =>
+ createClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!)
+ )
+ const [session, setSession] = useState(null)
+ const description = 'Supabase Launch Week 6 | 12-16 Dec 2022'
+ const { query } = useRouter()
+ const ticketNumber = query.ticketNumber?.toString()
+ const defaultUserData = {
+ id: query.id?.toString(),
+ ticketNumber: ticketNumber ? parseInt(ticketNumber, 10) : undefined,
+ name: query.name?.toString(),
+ username: query.username?.toString(),
+ }
+
+ useEffect(() => {
+ supabase.auth.onAuthStateChange((event, session) => {
+ setSession(session)
+ })
+ }, [])
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+ >
+ )
+}
diff --git a/apps/www/pages/partners/[slug].tsx b/apps/www/pages/partners/[slug].tsx
index 9eb63b5c310..dc498cae9fc 100644
--- a/apps/www/pages/partners/[slug].tsx
+++ b/apps/www/pages/partners/[slug].tsx
@@ -224,6 +224,7 @@ export const getStaticProps: GetStaticProps = async ({ params }) => {
let { data: partner } = await supabase
.from('partners')
.select('*')
+ .eq('approved', true)
.eq('slug', params!.slug as string)
.single()
diff --git a/apps/www/public/images/blog/2022-11-24-sql-nosql-postgresql/sql-nosql-postgresql.jpg b/apps/www/public/images/blog/2022-11-24-sql-nosql-postgresql/sql-nosql-postgresql.jpg
new file mode 100644
index 00000000000..4bd166328fd
Binary files /dev/null and b/apps/www/public/images/blog/2022-11-24-sql-nosql-postgresql/sql-nosql-postgresql.jpg differ
diff --git a/apps/www/public/rss.xml b/apps/www/public/rss.xml
index 34781c63723..4a306486453 100644
--- a/apps/www/public/rss.xml
+++ b/apps/www/public/rss.xml
@@ -5,924 +5,931 @@
https://supabase.com
Latest news from Supabase
en
- Wed, 16 Nov 2022 03:03:30 +0000
+ Mon, 21 Nov 2022 16:04:00 +0000
-
+
https://supabase.com/blog/flutter-authentication-and-authorization-with-rls
+ Flutter Authentication and Authorization with RLS
+ https://supabase.com/blog/flutter-authentication-and-authorization-with-rls
+ Learn how you can secure your Flutter app using Supabase Row Level Security.
+ Mon, 21 Nov 2022 16:04:00 +0000
+
+-
https://supabase.com/blog/fetching-and-caching-supabase-data-in-next-js-server-components
Fetching and caching Supabase data in Next.js 13 Server Components
https://supabase.com/blog/fetching-and-caching-supabase-data-in-next-js-server-components
Next.js 13 introduces new data fetching and caching methods to enable React Server Components and Suspense.
- Wed, 16 Nov 2022 03:03:30 +0000
+ Wed, 16 Nov 2022 16:04:00 +0000
-
https://supabase.com/blog/authentication-in-ionic-angular
Authentication in Ionic Angular with Supabase
https://supabase.com/blog/authentication-in-ionic-angular
Learn how to build an Ionic Angular app with authentication, Row Level Security, and Magic Link auth.
- Mon, 07 Nov 2022 03:03:30 +0000
+ Mon, 07 Nov 2022 16:04:00 +0000
-
https://supabase.com/blog/supabase-beta-update-october-2022
Supabase Beta October 2022
https://supabase.com/blog/supabase-beta-update-october-2022
New SDKs, quickstarts, Functions tricks, and more. But, more importantly, Launch Week 6️ has a date!
- Tue, 01 Nov 2022 02:02:30 +0000
+ Tue, 01 Nov 2022 16:04:00 +0000
-
https://supabase.com/blog/postgresql-commitfest
What is PostgreSQL commitfest and how to contribute
https://supabase.com/blog/postgresql-commitfest
A time-tested method for contributing to the core Postgres code
- Wed, 26 Oct 2022 02:02:30 +0000
+ Wed, 26 Oct 2022 16:04:00 +0000
-
https://supabase.com/blog/supabase-flutter-sdk-v1-released
supabase-flutter v1 Released
https://supabase.com/blog/supabase-flutter-sdk-v1-released
We've released supabase-flutter v1. More intuitive way of accessing Supabase from your Flutter application.
- Thu, 20 Oct 2022 02:02:30 +0000
+ Thu, 20 Oct 2022 16:04:00 +0000
-
https://supabase.com/blog/supabase-js-v2-released
supabase-js v2 Released
https://supabase.com/blog/supabase-js-v2-released
We've released supabase-js v2. Updated examples, quickstarts, and an improved experience.
- Wed, 19 Oct 2022 02:02:30 +0000
+ Wed, 19 Oct 2022 16:04:00 +0000
-
https://supabase.com/blog/postgres-full-text-search-vs-the-rest
Postgres Full Text Search vs the rest
https://supabase.com/blog/postgres-full-text-search-vs-the-rest
Comparing one of the most popular Postgres features against alternatives
- Thu, 13 Oct 2022 02:02:30 +0000
+ Thu, 13 Oct 2022 16:04:00 +0000
-
https://supabase.com/blog/supabase-beta-update-september-2022
Supabase Beta September 2022
https://supabase.com/blog/supabase-beta-update-september-2022
We were too focused on clearing out the backlog so we didn't ship anything new last month... or did we?!
- Tue, 04 Oct 2022 02:02:30 +0000
+ Tue, 04 Oct 2022 16:04:00 +0000
-
https://supabase.com/blog/postgres-wasm
Postgres WASM by Snaplet and Supabase
https://supabase.com/blog/postgres-wasm
We're open sourcing postgres-wasm, a PostgresQL server that runs inside a browser, with our friends at Snaplet.
- Sun, 02 Oct 2022 02:02:30 +0000
+ Sun, 02 Oct 2022 16:04:00 +0000
-
https://supabase.com/blog/choosing-a-postgres-primary-key
Choosing a Postgres Primary Key
https://supabase.com/blog/choosing-a-postgres-primary-key
Turns out the question of which identifier to use as a Primary Key is complicated -- we're going to dive into some of the complexity and inherent trade-offs, and figure things out
- Wed, 07 Sep 2022 02:02:30 +0000
+ Wed, 07 Sep 2022 16:04:00 +0000
-
https://supabase.com/blog/supabase-beta-update-august-2022
Supabase Beta August 2022
https://supabase.com/blog/supabase-beta-update-august-2022
Launch Week Special. See everything we shipped, plus winners of the Hackathon and the extended Community Highlights
- Tue, 06 Sep 2022 02:02:30 +0000
+ Tue, 06 Sep 2022 16:04:00 +0000
-
https://supabase.com/blog/launch-week-5-hackathon-winners
Launch Week 5 Hackathon Winners
https://supabase.com/blog/launch-week-5-hackathon-winners
Announcing the winners of the Launch Week 5 Hackathon!
- Wed, 24 Aug 2022 02:02:30 +0000
+ Wed, 24 Aug 2022 16:04:00 +0000
-
https://supabase.com/blog/building-a-realtime-trello-board-with-supabase-and-angular
Building a Realtime Trello Board with Supabase and Angular
https://supabase.com/blog/building-a-realtime-trello-board-with-supabase-and-angular
Go beyond the hello world example with this real world project.
- Tue, 23 Aug 2022 02:02:30 +0000
+ Tue, 23 Aug 2022 16:04:00 +0000
-
https://supabase.com/blog/supabase-vault
Supabase Vault
https://supabase.com/blog/supabase-vault
Today we're announcing Vault, a Postgres extension for managing secrets and encryption inside your database.
- Thu, 18 Aug 2022 02:02:30 +0000
+ Thu, 18 Aug 2022 16:04:00 +0000
-
https://supabase.com/blog/postgrest-v10
PostgREST v10: EXPLAIN and Improved Relationship Detection
https://supabase.com/blog/postgrest-v10
Today, PostgREST 10 was released. Let's take a look at some of the new features that go hand in hand with supabase-js v2.
- Thu, 18 Aug 2022 02:02:30 +0000
+ Thu, 18 Aug 2022 16:04:00 +0000
-
https://supabase.com/blog/pg-jsonschema-a-postgres-extension-for-json-validation
pg_jsonschema: JSON Schema support for Postgres
https://supabase.com/blog/pg-jsonschema-a-postgres-extension-for-json-validation
Today we're releasing pg_jsonschema, a Postgres extension for JSON validation.
- Thu, 18 Aug 2022 02:02:30 +0000
+ Thu, 18 Aug 2022 16:04:00 +0000
-
https://supabase.com/blog/launch-week-5-one-more-thing
One more thing
https://supabase.com/blog/launch-week-5-one-more-thing
Let's be honest, it's never just one more thing.
- Thu, 18 Aug 2022 02:02:30 +0000
+ Thu, 18 Aug 2022 16:04:00 +0000
-
https://supabase.com/blog/launch-week-5-community-day
Community Day
https://supabase.com/blog/launch-week-5-community-day
Wrapping up Launch Week 5 with contributors, partners, and friends.
- Thu, 18 Aug 2022 02:02:30 +0000
+ Thu, 18 Aug 2022 16:04:00 +0000
-
https://supabase.com/blog/supabase-realtime-multiplayer-general-availability
Realtime: Multiplayer Edition
https://supabase.com/blog/supabase-realtime-multiplayer-general-availability
Announcing the general availability of Realtime's Broadcast and Presence.
- Wed, 17 Aug 2022 02:02:30 +0000
+ Wed, 17 Aug 2022 16:04:00 +0000
-
https://supabase.com/blog/supabase-soc2
Supabase is SOC2 compliant
https://supabase.com/blog/supabase-soc2
Supabase is now SOC2 compliant. Learn how we got here and what it means for our customers.
- Tue, 16 Aug 2022 02:02:30 +0000
+ Tue, 16 Aug 2022 16:04:00 +0000
-
https://supabase.com/blog/supabase-js-v2
supabase-js v2
https://supabase.com/blog/supabase-js-v2
A look at supabase-js v2, which brings type support and focuses on quality-of-life improvements for developers.
- Mon, 15 Aug 2022 02:02:30 +0000
+ Mon, 15 Aug 2022 16:04:00 +0000
-
https://supabase.com/blog/supabase-cli-v1-and-admin-api-beta
Supabase CLI v1 and Management API Beta
https://supabase.com/blog/supabase-cli-v1-and-admin-api-beta
We are moving Supabase CLI v1 out of beta, and releasing Management API beta.
- Sun, 14 Aug 2022 02:02:30 +0000
+ Sun, 14 Aug 2022 16:04:00 +0000
-
https://supabase.com/blog/supabase-series-b
Supabase Series B
https://supabase.com/blog/supabase-series-b
Supabase raised $80M in May, bringing our total funding to $116M.
- Thu, 11 Aug 2022 02:02:30 +0000
+ Thu, 11 Aug 2022 16:04:00 +0000
-
https://supabase.com/blog/launch-week-5-hackathon
Launch Week 5 Hackathon
https://supabase.com/blog/launch-week-5-hackathon
Build to win $1500 - Friday 12th to Monday 21st August 2022
- Tue, 09 Aug 2022 02:02:30 +0000
+ Tue, 09 Aug 2022 16:04:00 +0000
-
https://supabase.com/blog/slack-consolidate-slackbot-to-consolidate-messages
Slack Consolidate: a slackbot built with Python and Supabase
https://supabase.com/blog/slack-consolidate-slackbot-to-consolidate-messages
A slackbot to consolidate messages from different channels using Supabase, Slack SDK and Python
- Mon, 08 Aug 2022 02:02:30 +0000
+ Mon, 08 Aug 2022 16:04:00 +0000
-
https://supabase.com/blog/supabase-beta-update-july-2022
Supabase Beta July 2022
https://supabase.com/blog/supabase-beta-update-july-2022
Launch Week Golden Tickets, Flutter SDK 1.0 Developer Preview and more...
- Tue, 02 Aug 2022 02:02:30 +0000
+ Tue, 02 Aug 2022 16:04:00 +0000
-
https://supabase.com/blog/supabase-flutter-sdk-1-developer-preview
Supabase Flutter SDK 1.0 Developer Preview
https://supabase.com/blog/supabase-flutter-sdk-1-developer-preview
Supabase Flutter SDK is getting a major update and we need your help making it better.
- Mon, 01 Aug 2022 02:02:30 +0000
+ Mon, 01 Aug 2022 16:04:00 +0000
-
https://supabase.com/blog/seen-by-in-postgresql
Implementing "seen by" functionality with Postgres
https://supabase.com/blog/seen-by-in-postgresql
Different approaches for tracking visitor counts with PostgreSQL.
- Sun, 17 Jul 2022 02:02:30 +0000
+ Sun, 17 Jul 2022 16:04:00 +0000
-
https://supabase.com/blog/supabase-auth-helpers-with-sveltekit-support
Revamped Auth Helpers for Supabase (with SvelteKit support)
https://supabase.com/blog/supabase-auth-helpers-with-sveltekit-support
Supabase Auth Helpers now have improved developer experience, Sveltekit support, and more.
- Tue, 12 Jul 2022 02:02:30 +0000
+ Tue, 12 Jul 2022 16:04:00 +0000
-
https://supabase.com/blog/beta-update-june-2022
Supabase Beta June 2022
https://supabase.com/blog/beta-update-june-2022
Auth Helpers, Unlimited Free Projects, CLI and more...
- Tue, 05 Jul 2022 02:02:30 +0000
+ Tue, 05 Jul 2022 16:04:00 +0000
-
https://supabase.com/blog/flutter-tutorial-building-a-chat-app
Flutter Tutorial: building a Flutter chat app
https://supabase.com/blog/flutter-tutorial-building-a-chat-app
Learn how to build a Flutter chat app with open source and scalable backend (inc. auth, realtime, database, and more).
- Wed, 29 Jun 2022 02:02:30 +0000
+ Wed, 29 Jun 2022 16:04:00 +0000
-
https://supabase.com/blog/visualizing-supabase-data-using-metabase
Visualizing Supabase Data using Metabase
https://supabase.com/blog/visualizing-supabase-data-using-metabase
How to create different kinds of charts out of data stored in Supabase using Metabase.
- Tue, 28 Jun 2022 02:02:30 +0000
+ Tue, 28 Jun 2022 16:04:00 +0000
-
https://supabase.com/blog/partial-postgresql-data-dumps-with-rls
Partial data dumps using Postgres Row Level Security
https://supabase.com/blog/partial-postgresql-data-dumps-with-rls
Using RLS to create seed files for local PostgreSQL testing.
- Mon, 27 Jun 2022 02:02:30 +0000
+ Mon, 27 Jun 2022 16:04:00 +0000
-
https://supabase.com/blog/loading-data-supabase-python
Python data loading with Supabase
https://supabase.com/blog/loading-data-supabase-python
An example of how to load data into Supabase using supabase-py
- Thu, 16 Jun 2022 02:02:30 +0000
+ Thu, 16 Jun 2022 16:04:00 +0000
-
https://supabase.com/blog/beta-update-may-2022
Supabase Beta May 2022
https://supabase.com/blog/beta-update-may-2022
Product and community updates including wildcard auth redirects, edge functions with webhooks, and Prometheus endpoints for everybody.
- Tue, 31 May 2022 02:02:30 +0000
+ Tue, 31 May 2022 16:04:00 +0000
-
https://supabase.com/blog/how-supabase-accelerates-development-of-all-pull-together
How Mike Lyndon is using Supabase to accelerate development of AllPullTogether
https://supabase.com/blog/how-supabase-accelerates-development-of-all-pull-together
Mike Lyndon is learning web development as he builds AllPullTogether, and Supabase is helping him accelerate what he can accomplish.
- Wed, 25 May 2022 02:02:30 +0000
+ Wed, 25 May 2022 16:04:00 +0000
-
https://supabase.com/blog/partner-gallery-works-with-supabase
Works With Supabase - announcing our Partner Gallery
https://supabase.com/blog/partner-gallery-works-with-supabase
Introducing our Partner Gallery - open source and made with Supabase.
- Tue, 19 Apr 2022 02:02:30 +0000
+ Tue, 19 Apr 2022 16:04:00 +0000
-
https://supabase.com/blog/bring-the-func-hackathon-winners
Bring the Func Hackathon Winners 2022
https://supabase.com/blog/bring-the-func-hackathon-winners
Celebrating many amazing OSS Hackathon projects using GraphQL and Edge Functions.
- Sun, 17 Apr 2022 02:02:30 +0000
+ Sun, 17 Apr 2022 16:04:00 +0000
-
https://supabase.com/blog/beta-update-march-2022
Supabase Beta March 2022
https://supabase.com/blog/beta-update-march-2022
Functions, GraphQL, and much more.
- Thu, 14 Apr 2022 02:02:30 +0000
+ Thu, 14 Apr 2022 16:04:00 +0000
-
https://supabase.com/blog/supabrew
Supabrew - Never Code Thirsty
https://supabase.com/blog/supabrew
A light and refreshing non-alcoholic beer for devs.
- Fri, 01 Apr 2022 02:02:30 +0000
+ Thu, 31 Mar 2022 16:04:00 +0000
-
https://supabase.com/blog/supabase-realtime-with-multiplayer-features
Supabase Realtime, with Multiplayer Features
https://supabase.com/blog/supabase-realtime-with-multiplayer-features
Today we're announced Realtime, with multiplayer features. Realtime enables broadcast, presence, and listening to database changes delivered over WebSockets.
- Fri, 01 Apr 2022 02:02:30 +0000
+ Thu, 31 Mar 2022 16:04:00 +0000
-
https://supabase.com/blog/hackathon-bring-the-func
Hackathon: Bring the Func(🕺)
https://supabase.com/blog/hackathon-bring-the-func
Build open-source projects with our latest features, win limited edition swag and plant a tree!
- Fri, 01 Apr 2022 02:02:30 +0000
+ Thu, 31 Mar 2022 16:04:00 +0000
-
https://supabase.com/blog/supabase-edge-functions
Edge Functions are now available in Supabase
https://supabase.com/blog/supabase-edge-functions
Today we're launching Edge Functions. Edge Functions let you execute Typescript code close to your users, no matter where they're located.
- Wed, 30 Mar 2022 02:02:30 +0000
+ Wed, 30 Mar 2022 16:04:00 +0000
-
https://supabase.com/blog/supabase-enterprise
Introducing Supabase Enterprise
https://supabase.com/blog/supabase-enterprise
Today we are releasing Supabase Enterprise, a suite of features to scale your project.
- Tue, 29 Mar 2022 02:02:30 +0000
+ Tue, 29 Mar 2022 16:04:00 +0000
-
https://supabase.com/blog/graphql-now-available
GraphQL is now available in Supabase
https://supabase.com/blog/graphql-now-available
GraphQL support is now in general availability on the Supabase platform via our open source PostgreSQL extension, pg_graphql (beta).
- Mon, 28 Mar 2022 02:02:30 +0000
+ Mon, 28 Mar 2022 16:04:00 +0000
-
https://supabase.com/blog/community-day-lw4
Community Day
https://supabase.com/blog/community-day-lw4
Kicking off Launch Week 4 with contributors, partners, and friends.
- Sun, 27 Mar 2022 02:02:30 +0000
+ Sun, 27 Mar 2022 16:04:00 +0000
-
https://supabase.com/blog/supabase-launch-week-four
Supabase Launch Week 4
https://supabase.com/blog/supabase-launch-week-four
Launch Week 4: One new feature every day for an entire week. Starting Monday 28th March.
- Thu, 24 Mar 2022 02:02:30 +0000
+ Thu, 24 Mar 2022 16:04:00 +0000
-
https://supabase.com/blog/should-i-open-source-my-company
Should I Open Source my Company?
https://supabase.com/blog/should-i-open-source-my-company
The unexpected upsides of building in public
- Thu, 24 Mar 2022 02:02:30 +0000
+ Thu, 24 Mar 2022 16:04:00 +0000
-
https://supabase.com/blog/audit
Postgres Auditing in 150 lines of SQL
https://supabase.com/blog/audit
PostgreSQL has a robust set of features which we can leverage to create a generic auditing solution in 150 lines of SQL.
- Mon, 07 Mar 2022 03:03:30 +0000
+ Mon, 07 Mar 2022 16:04:00 +0000
-
https://supabase.com/blog/supabase-beta-january-2022
Supabase Beta January 2022
https://supabase.com/blog/supabase-beta-january-2022
New auth providers, SMS providers, and new videos.
- Mon, 21 Feb 2022 03:03:30 +0000
+ Mon, 21 Feb 2022 16:04:00 +0000
-
https://supabase.com/blog/supabase-beta-december-2021
Supabase Beta December 2021
https://supabase.com/blog/supabase-beta-december-2021
New crypto extension, Postgres videos, and a bunch of cool integrations.
- Wed, 19 Jan 2022 03:03:30 +0000
+ Wed, 19 Jan 2022 16:04:00 +0000
-
https://supabase.com/blog/product-hunt-golden-kitty-awards-2021
Golden Kitty Awards Ceremony Watch Party with Supabase
https://supabase.com/blog/product-hunt-golden-kitty-awards-2021
Hang out with us while watching the Product Hunt Golden Kitty Awards Ceremony
- Wed, 19 Jan 2022 03:03:30 +0000
+ Wed, 19 Jan 2022 16:04:00 +0000
-
https://supabase.com/blog/holiday-hackdays-winners-2021
Holiday Hackdays Winners 2021
https://supabase.com/blog/holiday-hackdays-winners-2021
Celebrating many amazing projects submitted to our Holiday Hackdays Hackathon.
- Thu, 16 Dec 2021 03:03:30 +0000
+ Thu, 16 Dec 2021 16:04:00 +0000
-
https://supabase.com/blog/beta-november-2021-launch-week-recap
Supabase Beta November 2021: Launch Week Recap
https://supabase.com/blog/beta-november-2021-launch-week-recap
We wrapped up November with Supabase's third Launch Week. Here's all the awesome stuff that got shipped ...
- Tue, 14 Dec 2021 03:03:30 +0000
+ Tue, 14 Dec 2021 16:04:00 +0000
-
https://supabase.com/blog/supabase-holiday-hackdays-hackathon
Kicking off the Holiday Hackdays
https://supabase.com/blog/supabase-holiday-hackdays-hackathon
Build cool stuff and celebrate open-source software with us during the Holiday Hackdays!
- Thu, 02 Dec 2021 03:03:30 +0000
+ Thu, 02 Dec 2021 16:04:00 +0000
-
https://supabase.com/blog/pg-graphql
pg_graphql: A GraphQL extension for PostgreSQL
https://supabase.com/blog/pg-graphql
GraphQL support is in development for PostgreSQL + Supabase.
- Thu, 02 Dec 2021 03:03:30 +0000
+ Thu, 02 Dec 2021 16:04:00 +0000
-
https://supabase.com/blog/launch-week-three-friday-five-more-things
Five more things
https://supabase.com/blog/launch-week-three-friday-five-more-things
It's never just one more thing!
- Thu, 02 Dec 2021 03:03:30 +0000
+ Thu, 02 Dec 2021 16:04:00 +0000
-
https://supabase.com/blog/supabase-acquires-logflare
Supabase acquires Logflare
https://supabase.com/blog/supabase-acquires-logflare
Today, we're ecstatic to announce that Logflare is joining Supabase.
- Wed, 01 Dec 2021 03:03:30 +0000
+ Wed, 01 Dec 2021 16:04:00 +0000
-
https://supabase.com/blog/realtime-row-level-security-in-postgresql
Realtime Postgres RLS now available on Supabase
https://supabase.com/blog/realtime-row-level-security-in-postgresql
Realtime database changes are now broadcast to authenticated users, respecting the same PostgreSQL policies that you use for Row Level Security.
- Tue, 30 Nov 2021 03:03:30 +0000
+ Tue, 30 Nov 2021 16:04:00 +0000
-
https://supabase.com/blog/supabase-studio
Supabase Studio
https://supabase.com/blog/supabase-studio
The same Dashboard that you're using on our Platform is now available for local development and Self-Hosting.
- Mon, 29 Nov 2021 03:03:30 +0000
+ Mon, 29 Nov 2021 16:04:00 +0000
-
https://supabase.com/blog/community-day-lw3
Community Day
https://supabase.com/blog/community-day-lw3
Kicking off launch week by highlighting the communities around Supabase.
- Sun, 28 Nov 2021 03:03:30 +0000
+ Sun, 28 Nov 2021 16:04:00 +0000
-
https://supabase.com/blog/whats-new-in-postgres-14
New in PostgreSQL 14: What every developer should know
https://supabase.com/blog/whats-new-in-postgres-14
A quick look at some new features and functionality in PostgreSQL 14.
- Sat, 27 Nov 2021 03:03:30 +0000
+ Sat, 27 Nov 2021 16:04:00 +0000
-
https://supabase.com/blog/postgrest-9
PostgREST 9
https://supabase.com/blog/postgrest-9
New features and updates in PostgREST version 9.
- Fri, 26 Nov 2021 03:03:30 +0000
+ Fri, 26 Nov 2021 16:04:00 +0000
-
https://supabase.com/blog/supabase-launch-week-the-trilogy
Supabase Launch Week III: Holiday Special
https://supabase.com/blog/supabase-launch-week-the-trilogy
Tis the season to be shipping.
- Thu, 25 Nov 2021 03:03:30 +0000
+ Thu, 25 Nov 2021 16:04:00 +0000
-
https://supabase.com/blog/supabase-how-we-launch
How we launch at Supabase
https://supabase.com/blog/supabase-how-we-launch
The history and methodology of Supabase Launch Week.
- Thu, 25 Nov 2021 03:03:30 +0000
+ Thu, 25 Nov 2021 16:04:00 +0000
-
https://supabase.com/blog/supabase-beta-october-2021
Supabase Beta October 2021
https://supabase.com/blog/supabase-beta-october-2021
Three new Auth providers, multi-schema support, and we're gearing up for another Launch Week.
- Sat, 06 Nov 2021 02:02:30 +0000
+ Sat, 06 Nov 2021 16:04:00 +0000
-
https://supabase.com/blog/supabase-series-a
Supabase $30m Series A
https://supabase.com/blog/supabase-series-a
Supabase just raised $30M, bringing our total funding to $36M.
- Wed, 27 Oct 2021 02:02:30 +0000
+ Wed, 27 Oct 2021 16:04:00 +0000
-
https://supabase.com/blog/replenysh-time-to-value-in-less-than-24-hours
Replenysh uses Supabase to implement OTP in less than 24-hours
https://supabase.com/blog/replenysh-time-to-value-in-less-than-24-hours
Learn how Replenysh uses Supabase to power the circular economy, redefining how brands interact with their customers and products.
- Mon, 18 Oct 2021 02:02:30 +0000
+ Mon, 18 Oct 2021 16:04:00 +0000
-
https://supabase.com/blog/hacktoberfest-hackathon-winners-2021
Hacktoberfest Hackathon Winners 2021
https://supabase.com/blog/hacktoberfest-hackathon-winners-2021
Celebrating many amazing projects submitted to our Hacktoberfest Hackathon.
- Wed, 13 Oct 2021 02:02:30 +0000
+ Wed, 13 Oct 2021 16:04:00 +0000
-
https://supabase.com/blog/supabase-beta-sept-2021
Supabase Beta Sept 2021
https://supabase.com/blog/supabase-beta-sept-2021
Hackathon, Aborting request, UI updates, and now Hiring.
- Sun, 03 Oct 2021 02:02:30 +0000
+ Sun, 03 Oct 2021 16:04:00 +0000
-
https://supabase.com/blog/supabase-hacktoberfest-hackathon-2021
Supabase Hacktoberfest Hackathon 2021
https://supabase.com/blog/supabase-hacktoberfest-hackathon-2021
We're running another Supabase Hackathon during Hacktoberfest!
- Mon, 27 Sep 2021 02:02:30 +0000
+ Mon, 27 Sep 2021 16:04:00 +0000
-
https://supabase.com/blog/supabase-beta-august-2021
Supabase Beta August 2021
https://supabase.com/blog/supabase-beta-august-2021
Fundraising, Realtime Security, custom SMS templates, and deployments in South Korea.
- Thu, 09 Sep 2021 02:02:30 +0000
+ Thu, 09 Sep 2021 16:04:00 +0000
-
https://supabase.com/blog/supabase-beta-july-2021
Supabase Beta July 2021
https://supabase.com/blog/supabase-beta-july-2021
Discord Logins, Vercel Integration, Full text search, and OAuth guides.
- Wed, 11 Aug 2021 02:02:30 +0000
+ Wed, 11 Aug 2021 16:04:00 +0000
-
https://supabase.com/blog/hackathon-winners
Open Source Hackathon Winners
https://supabase.com/blog/hackathon-winners
Let the medal ceremony begin for the best projects submitted during the Supabase Hackathon.
- Sun, 08 Aug 2021 02:02:30 +0000
+ Sun, 08 Aug 2021 16:04:00 +0000
-
https://supabase.com/blog/supabase-swag-store
Supabase Swag Store
https://supabase.com/blog/supabase-swag-store
Today we are officially launching the Supabase Swag Store.
- Thu, 29 Jul 2021 02:02:30 +0000
+ Thu, 29 Jul 2021 16:04:00 +0000
-
https://supabase.com/blog/supabase-functions-updates
Updates for Supabase Functions
https://supabase.com/blog/supabase-functions-updates
The question on everyone's mind - are we launching Supabase Functions? Well, it's complicated.
- Thu, 29 Jul 2021 02:02:30 +0000
+ Thu, 29 Jul 2021 16:04:00 +0000
-
https://supabase.com/blog/1-the-supabase-hackathon
The Supabase Hackathon
https://supabase.com/blog/1-the-supabase-hackathon
A whole week of Hacking for Fun and Prizes.
- Thu, 29 Jul 2021 02:02:30 +0000
+ Thu, 29 Jul 2021 16:04:00 +0000
-
https://supabase.com/blog/supabase-reports-and-metrics
Supabase Reports and Metrics
https://supabase.com/blog/supabase-reports-and-metrics
We're exposing a full set of metrics in your projects, so that you can build better (and faster) products for your users.
- Wed, 28 Jul 2021 02:02:30 +0000
+ Wed, 28 Jul 2021 16:04:00 +0000
-
https://supabase.com/blog/supabase-auth-passwordless-sms-login
Supabase Auth v2: Phone Auth now available
https://supabase.com/blog/supabase-auth-passwordless-sms-login
Phone Auth is available today on all new and existing Supabase projects.
- Tue, 27 Jul 2021 02:02:30 +0000
+ Tue, 27 Jul 2021 16:04:00 +0000
-
https://supabase.com/blog/mobbin-supabase-200000-users
Mobbin uses Supabase to authenticate 200,000 users
https://supabase.com/blog/mobbin-supabase-200000-users
Learn how Mobbin migrated 200,000 users from Firebase for a better authentication experience.
- Tue, 27 Jul 2021 02:02:30 +0000
+ Tue, 27 Jul 2021 16:04:00 +0000
-
https://supabase.com/blog/storage-beta
Supabase Storage now in Beta
https://supabase.com/blog/storage-beta
Supabase Storage moves into Beta.
- Mon, 26 Jul 2021 02:02:30 +0000
+ Mon, 26 Jul 2021 16:04:00 +0000
-
https://supabase.com/blog/spot-flutter-with-postgres
Spot: a video sharing app built with Flutter
https://supabase.com/blog/spot-flutter-with-postgres
Spot is a geolocation-based video-sharing app with some social networking features.
- Mon, 26 Jul 2021 02:02:30 +0000
+ Mon, 26 Jul 2021 16:04:00 +0000
-
https://supabase.com/blog/supabase-postgres-13
Supabase is now on Postgres 13.3
https://supabase.com/blog/supabase-postgres-13
From today, new Supabase projects will be on a version of Supabase Postgres that runs on Postgres 13.3.
- Sun, 25 Jul 2021 02:02:30 +0000
+ Sun, 25 Jul 2021 16:04:00 +0000
-
https://supabase.com/blog/supabase-community-day
Supabase Community Day
https://supabase.com/blog/supabase-community-day
Community Day
- Sun, 25 Jul 2021 02:02:30 +0000
+ Sun, 25 Jul 2021 16:04:00 +0000
-
https://supabase.com/blog/epsilon3-self-hosting
Epsilon3 Self-Host Supabase To Revolutionize Space Operations
https://supabase.com/blog/epsilon3-self-hosting
Learn how the team at Epsilon3 use Supabase to help teams execute secure and reliable operations in an industry that project spend runs into the billions.
- Sun, 25 Jul 2021 02:02:30 +0000
+ Sun, 25 Jul 2021 16:04:00 +0000
-
https://supabase.com/blog/supabase-launch-week-sql
Supabase Launch Week II: The SQL
https://supabase.com/blog/supabase-launch-week-sql
Five days of Supabase. Again.
- Wed, 21 Jul 2021 02:02:30 +0000
+ Wed, 21 Jul 2021 16:04:00 +0000
-
https://supabase.com/blog/roles-postgres-hooks
Protecting reserved roles with PostgreSQL Hooks
https://supabase.com/blog/roles-postgres-hooks
Using Postgres Hooks to protect functionality in your Postgres database.
- Thu, 01 Jul 2021 02:02:30 +0000
+ Thu, 01 Jul 2021 16:04:00 +0000
-
https://supabase.com/blog/supabase-beta-june-2021
Supabase Beta June 2021
https://supabase.com/blog/supabase-beta-june-2021
Discord Logins, Vercel Integration, Full text search, and OAuth guides.
- Tue, 01 Jun 2021 02:02:30 +0000
+ Tue, 01 Jun 2021 16:04:00 +0000
-
https://supabase.com/blog/supabase-beta-may-2021
Supabase Beta May 2021
https://supabase.com/blog/supabase-beta-may-2021
Apple & Twitter Logins, Supabase Grid, Go & Swift Libraries.
- Tue, 01 Jun 2021 02:02:30 +0000
+ Tue, 01 Jun 2021 16:04:00 +0000
-
https://supabase.com/blog/supabase-beta-april-2021
Supabase Beta April 2021
https://supabase.com/blog/supabase-beta-april-2021
Supabase "gardening" - stability, security, and community support.
- Tue, 04 May 2021 02:02:30 +0000
+ Tue, 04 May 2021 16:04:00 +0000
-
https://supabase.com/blog/supabase-beta-march-2021
Supabase Beta March 2021
https://supabase.com/blog/supabase-beta-march-2021
Launch week, Storage, Supabase CLI, Connection Pooling, Supabase UI, and Pricing.
- Mon, 05 Apr 2021 02:02:30 +0000
+ Mon, 05 Apr 2021 16:04:00 +0000
-
https://supabase.com/blog/supabase-workflows
Workflows are coming to Supabase
https://supabase.com/blog/supabase-workflows
Functions are great, but you know what's better?
- Thu, 01 Apr 2021 02:02:30 +0000
+ Thu, 01 Apr 2021 16:04:00 +0000
-
https://supabase.com/blog/supabase-pgbouncer
PgBouncer is now available in Supabase
https://supabase.com/blog/supabase-pgbouncer
Better support for Serverless and Postgres.
- Thu, 01 Apr 2021 02:02:30 +0000
+ Thu, 01 Apr 2021 16:04:00 +0000
-
https://supabase.com/blog/supabase-dot-com
Supabase Dot Com
https://supabase.com/blog/supabase-dot-com
The Supabase Domain name is changing.
- Thu, 01 Apr 2021 02:02:30 +0000
+ Thu, 01 Apr 2021 16:04:00 +0000
-
https://supabase.com/blog/supabase-nft-marketplace
Supabase Launches NFT Marketplace
https://supabase.com/blog/supabase-nft-marketplace
A fully encrypted NFT platform to protect and transact your digital assets
- Thu, 01 Apr 2021 02:02:30 +0000
+ Wed, 31 Mar 2021 16:04:00 +0000
-
https://supabase.com/blog/supabase-cli
Supabase CLI
https://supabase.com/blog/supabase-cli
Local development, database migrations, and self-hosting.
- Wed, 31 Mar 2021 02:02:30 +0000
+ Tue, 30 Mar 2021 16:04:00 +0000
-
https://supabase.com/blog/supabase-storage
Storage is now available in Supabase
https://supabase.com/blog/supabase-storage
Launching Supabase Storage and how you can use it in your apps
- Tue, 30 Mar 2021 02:02:30 +0000
+ Mon, 29 Mar 2021 16:04:00 +0000
-
https://supabase.com/blog/pricing
Supabase Beta Pricing
https://supabase.com/blog/pricing
Supabase launches Beta pricing structure
- Mon, 29 Mar 2021 02:02:30 +0000
+ Sun, 28 Mar 2021 16:04:00 +0000
-
https://supabase.com/blog/launch-week
Launch week
https://supabase.com/blog/launch-week
Five days of Supabase.
- Wed, 24 Mar 2021 02:02:30 +0000
+ Wed, 24 Mar 2021 16:04:00 +0000
-
https://supabase.com/blog/angels-of-supabase
Angels of Supabase
https://supabase.com/blog/angels-of-supabase
Meet the investors of Supabase.
- Wed, 24 Mar 2021 02:02:30 +0000
+ Wed, 24 Mar 2021 16:04:00 +0000
-
https://supabase.com/blog/In-The-Loop
Developers stay up to date with intheloop.dev
https://supabase.com/blog/In-The-Loop
Learn why Kevin is building intheloop.dev with Supabase
- Mon, 22 Mar 2021 02:02:30 +0000
+ Sun, 21 Mar 2021 16:04:00 +0000
-
https://supabase.com/blog/using-supabase-replit
Using Supabase in Replit
https://supabase.com/blog/using-supabase-replit
Free hosted relational database from within your node.js repl
- Thu, 11 Mar 2021 03:03:30 +0000
+ Wed, 10 Mar 2021 16:04:00 +0000
-
https://supabase.com/blog/toad-a-link-shortener-with-simple-apis-for-low-coders
Toad, a link shortener with simple APIs for low-coders
https://supabase.com/blog/toad-a-link-shortener-with-simple-apis-for-low-coders
An easy-to-use link shortening tool with simple APIs
- Mon, 08 Mar 2021 03:03:30 +0000
+ Sun, 07 Mar 2021 16:04:00 +0000
-
https://supabase.com/blog/postgres-as-a-cron-server
Postgres as a CRON Server
https://supabase.com/blog/postgres-as-a-cron-server
Running repetitive tasks with your Postgres database.
- Fri, 05 Mar 2021 03:03:30 +0000
+ Thu, 04 Mar 2021 16:04:00 +0000
-
https://supabase.com/blog/supabase-beta-february-2021
Supabase Beta February 2021
https://supabase.com/blog/supabase-beta-february-2021
One year of building.
- Tue, 02 Mar 2021 03:03:30 +0000
+ Mon, 01 Mar 2021 16:04:00 +0000
-
https://supabase.com/blog/cracking-postgres-interview
Cracking PostgreSQL Interview Questions
https://supabase.com/blog/cracking-postgres-interview
Understand the top PostgreSQL Interview Questions
- Sat, 27 Feb 2021 03:03:30 +0000
+ Fri, 26 Feb 2021 16:04:00 +0000
-
https://supabase.com/blog/case-study-roboflow
Roboflow.com choose Supabase to power Paint.wtf leaderboard
https://supabase.com/blog/case-study-roboflow
Learn how Roboflow.com used Supabase to build their Paint.wtf leaderboard
- Tue, 09 Feb 2021 03:03:30 +0000
+ Mon, 08 Feb 2021 16:04:00 +0000
-
https://supabase.com/blog/supabase-beta-january-2021
Supabase Beta January 2021
https://supabase.com/blog/supabase-beta-january-2021
Eleven months of building.
- Tue, 02 Feb 2021 03:03:30 +0000
+ Mon, 01 Feb 2021 16:04:00 +0000
-
https://supabase.com/blog/supabase-beta-december-2020
Supabase Beta December 2020
https://supabase.com/blog/supabase-beta-december-2020
Ten months of building.
- Sat, 02 Jan 2021 03:03:30 +0000
+ Fri, 01 Jan 2021 16:04:00 +0000
-
https://supabase.com/blog/supabase-dashboard-performance
Making the Supabase Dashboard Supa-fast
https://supabase.com/blog/supabase-dashboard-performance
Improving the performance of the Supabase dashboard
- Sun, 13 Dec 2020 03:03:30 +0000
+ Sat, 12 Dec 2020 16:04:00 +0000
-
https://supabase.com/blog/supabase-striveschool
Supabase Partners With Strive School To Help Teach Open Source
https://supabase.com/blog/supabase-striveschool
Supabase Partners With Strive School To Help Teach Open Source To The Next Generation Of Developers
- Wed, 02 Dec 2020 03:03:30 +0000
+ Tue, 01 Dec 2020 16:04:00 +0000
-
https://supabase.com/blog/case-study-xendit
Xendit Built a Counter-Fraud Watchlist for the Fintech Industry
https://supabase.com/blog/case-study-xendit
See how Xendit use Supabase to build a full-text search engine.
- Wed, 02 Dec 2020 03:03:30 +0000
+ Tue, 01 Dec 2020 16:04:00 +0000
-
https://supabase.com/blog/case-study-tayfa
TAYFA Built a No-Code Website Builder in Seven Days
https://supabase.com/blog/case-study-tayfa
See how Tayfa went from idea to paying customer in less than 30 days.
- Wed, 02 Dec 2020 03:03:30 +0000
+ Tue, 01 Dec 2020 16:04:00 +0000
-
https://supabase.com/blog/case-study-monitoro
Monitoro Built a Web Crawler Handling Millions of API Requests
https://supabase.com/blog/case-study-monitoro
See how Monitoro built an automated scraping platform using Supabase.
- Wed, 02 Dec 2020 03:03:30 +0000
+ Tue, 01 Dec 2020 16:04:00 +0000
-
https://supabase.com/blog/supabase-alpha-november-2020
Supabase Alpha November 2020
https://supabase.com/blog/supabase-alpha-november-2020
Nine months of building.
- Tue, 01 Dec 2020 03:03:30 +0000
+ Mon, 30 Nov 2020 16:04:00 +0000
-
https://supabase.com/blog/postgresql-views
Postgres Views
https://supabase.com/blog/postgresql-views
Creating and using a view in PostgreSQL.
- Wed, 18 Nov 2020 03:03:30 +0000
+ Tue, 17 Nov 2020 16:04:00 +0000
-
https://supabase.com/blog/supabase-alpha-october-2020
Supabase Alpha October 2020
https://supabase.com/blog/supabase-alpha-october-2020
Eight months of building.
- Mon, 02 Nov 2020 03:03:30 +0000
+ Sun, 01 Nov 2020 16:04:00 +0000
-
https://supabase.com/blog/improved-dx
Supabase.js 1.0
https://supabase.com/blog/improved-dx
We're releasing a new version of our Supabase client with some awesome new improvements.
- Fri, 30 Oct 2020 02:02:30 +0000
+ Thu, 29 Oct 2020 16:04:00 +0000
-
https://supabase.com/blog/supabase-alpha-september-2020
Supabase Alpha September 2020
https://supabase.com/blog/supabase-alpha-september-2020
Seven months of building.
- Sat, 03 Oct 2020 02:02:30 +0000
+ Fri, 02 Oct 2020 16:04:00 +0000
-
https://supabase.com/blog/supabase-hacktoberfest-2020
Supabase Hacktoberfest 2020
https://supabase.com/blog/supabase-hacktoberfest-2020
Join us for a celebration of open source software and learn how to contribute to Supabase.
- Fri, 11 Sep 2020 02:02:30 +0000
+ Thu, 10 Sep 2020 16:04:00 +0000
-
https://supabase.com/blog/supabase-alpha-august-2020
Supabase Alpha August 2020
https://supabase.com/blog/supabase-alpha-august-2020
Six months of building
- Thu, 03 Sep 2020 02:02:30 +0000
+ Wed, 02 Sep 2020 16:04:00 +0000
-
https://supabase.com/blog/supabase-auth
Supabase Auth
https://supabase.com/blog/supabase-auth
Authenticate and authorize your users with Supabase Auth
- Wed, 05 Aug 2020 02:02:30 +0000
+ Tue, 04 Aug 2020 16:04:00 +0000
-
https://supabase.com/blog/supabase-alpha-july-2020
Supabase Alpha July 2020
https://supabase.com/blog/supabase-alpha-july-2020
Five months of building
- Sun, 02 Aug 2020 02:02:30 +0000
+ Sat, 01 Aug 2020 16:04:00 +0000
-
https://supabase.com/blog/continuous-postgresql-backup-walg
Continuous PostgreSQL Backups using WAL-G
https://supabase.com/blog/continuous-postgresql-backup-walg
Have you ever wanted to restore your database's state to a particular moment in time? This post explains how, using WAL-G.
- Sun, 02 Aug 2020 02:02:30 +0000
+ Sat, 01 Aug 2020 16:04:00 +0000
-
https://supabase.com/blog/alpha-launch-postmortem
Alpha Launch Postmortem
https://supabase.com/blog/alpha-launch-postmortem
Everything that went wrong with Supabase's launch
- Fri, 10 Jul 2020 02:02:30 +0000
+ Thu, 09 Jul 2020 16:04:00 +0000
-
https://supabase.com/blog/postgresql-templates
What are PostgreSQL Templates?
https://supabase.com/blog/postgresql-templates
What are PostgreSQL templates and what are they used for?
- Thu, 09 Jul 2020 02:02:30 +0000
+ Wed, 08 Jul 2020 16:04:00 +0000
-
https://supabase.com/blog/postgresql-physical-logical-backups
Physical vs Logical Backups in PostgreSQL
https://supabase.com/blog/postgresql-physical-logical-backups
What are physical and logical backups in Postgres?
- Tue, 07 Jul 2020 02:02:30 +0000
+ Mon, 06 Jul 2020 16:04:00 +0000
-
https://supabase.com/blog/supabase-alpha-june-2020
Supabase Alpha June 2020
https://supabase.com/blog/supabase-alpha-june-2020
Four months of building
- Wed, 01 Jul 2020 02:02:30 +0000
+ Tue, 30 Jun 2020 16:04:00 +0000
-
https://supabase.com/blog/supabase-steve-chavez
Steve Chavez has joined Supabase
https://supabase.com/blog/supabase-steve-chavez
Steve joins Supabase to help build Auth.
- Mon, 15 Jun 2020 02:02:30 +0000
+ Sun, 14 Jun 2020 16:04:00 +0000
-
https://supabase.com/blog/supabase-alpha-may-2020
Supabase Alpha May 2020
https://supabase.com/blog/supabase-alpha-may-2020
Three months of building
- Mon, 01 Jun 2020 02:02:30 +0000
+ Sun, 31 May 2020 16:04:00 +0000
-
https://supabase.com/blog/supabase-alpha-april-2020
Supabase Alpha April 2020
https://supabase.com/blog/supabase-alpha-april-2020
Two months of building
- Mon, 01 Jun 2020 02:02:30 +0000
+ Sun, 31 May 2020 16:04:00 +0000
diff --git a/apps/www/public/sitemap_www.xml b/apps/www/public/sitemap_www.xml
index 3d294d2668b..3573f809d82 100644
--- a/apps/www/public/sitemap_www.xml
+++ b/apps/www/public/sitemap_www.xml
@@ -84,6 +84,12 @@
0.5
+
+ https://supabase.com/launch-week/ticket-image
+ weekly
+ 0.5
+
+
https://supabase.com/legal/dpa
weekly
@@ -902,6 +908,12 @@
0.5
+
+ https://supabase.com/blog/flutter-authentication-and-authorization-with-rls
+ weekly
+ 0.5
+
+
https://supabase.com/alternatives/supabase-vs-firebase
weekly
diff --git a/apps/www/styles/index.css b/apps/www/styles/index.css
index b86beb234ef..aa7a528d138 100644
--- a/apps/www/styles/index.css
+++ b/apps/www/styles/index.css
@@ -373,6 +373,7 @@ launch week
:root {
--auth-ui-card-height: 50vh;
+ --lw-secondary-color: #8a8f98;
}
.glow-area {
diff --git a/examples/edge-functions/supabase/functions/oak-server/README.md b/examples/edge-functions/supabase/functions/oak-server/README.md
new file mode 100644
index 00000000000..b1db6166231
--- /dev/null
+++ b/examples/edge-functions/supabase/functions/oak-server/README.md
@@ -0,0 +1,24 @@
+# Writing functions using Oak Server Middleware
+
+This example shows how you can write functions using Oak server middleware (https://oakserver.github.io/oak/)
+
+## Run locally
+
+```bash
+supabase functions serve oak-server
+```
+
+Use cURL or Postman to make a POST request to http://localhost:54321/functions/v1/oak-server.
+
+```
+ curl --location --request POST 'http://localhost:54321/functions/v1/oak-server' \
+--header 'Authorization: Bearer YOUR_TOKEN' \
+--header 'Content-Type: application/json' \
+--data-raw '{ "name": "John Doe" }'
+```
+
+## Deploy
+
+```bash
+supabase functions deploy oak-server
+```
diff --git a/examples/edge-functions/supabase/functions/oak-server/index.ts b/examples/edge-functions/supabase/functions/oak-server/index.ts
new file mode 100644
index 00000000000..8e96d1f0033
--- /dev/null
+++ b/examples/edge-functions/supabase/functions/oak-server/index.ts
@@ -0,0 +1,25 @@
+import { Application, Router } from 'https://deno.land/x/oak/mod.ts'
+
+const router = new Router()
+router
+ // Note: path will be prefixed with function name
+ .get('/oak-server', (context) => {
+ context.response.body = 'This is an example Oak server running on Edge Functions!'
+ })
+ .post('/oak-server/greet', (context) => {
+ // Note: request body will be streamed to the function as chunks, set limit to 0 to fully read it.
+ const result = context.request.body({ type: 'json', limit: 0 })
+ const body = await result.value
+ const name = body.name || 'you'
+
+ context.response.body = { msg: `Hey ${name}!` }
+ })
+ .get('/oak-server/redirect', (context) => {
+ context.response.redirect('https://www.example.com')
+ })
+
+const app = new Application()
+app.use(router.routes())
+app.use(router.allowedMethods())
+
+await app.listen({ port: 8000 })
diff --git a/examples/user-management/flutter-user-management/.gitignore b/examples/user-management/flutter-user-management/.gitignore
index 0fa6b675c0a..6d47c6f3495 100644
--- a/examples/user-management/flutter-user-management/.gitignore
+++ b/examples/user-management/flutter-user-management/.gitignore
@@ -30,6 +30,7 @@
.pub-cache/
.pub/
/build/
+pubspec.lock
# Web related
lib/generated_plugin_registrant.dart
diff --git a/examples/user-management/flutter-user-management/pubspec.lock b/examples/user-management/flutter-user-management/pubspec.lock
deleted file mode 100644
index 306226cee79..00000000000
--- a/examples/user-management/flutter-user-management/pubspec.lock
+++ /dev/null
@@ -1,516 +0,0 @@
-# Generated by pub
-# See https://dart.dev/tools/pub/glossary#lockfile
-packages:
- app_links:
- dependency: transitive
- description:
- name: app_links
- url: "https://pub.dartlang.org"
- source: hosted
- version: "3.2.0"
- app_links_macos:
- dependency: transitive
- description:
- name: app_links_macos
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.0.0"
- app_links_platform_interface:
- dependency: transitive
- description:
- name: app_links_platform_interface
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.0.2"
- app_links_web:
- dependency: transitive
- description:
- name: app_links_web
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.0.0"
- app_links_windows:
- dependency: transitive
- description:
- name: app_links_windows
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.0.0"
- async:
- dependency: transitive
- description:
- name: async
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.9.0"
- boolean_selector:
- dependency: transitive
- description:
- name: boolean_selector
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.1.0"
- characters:
- dependency: transitive
- description:
- name: characters
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.2.1"
- clock:
- dependency: transitive
- description:
- name: clock
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.1.1"
- collection:
- dependency: transitive
- description:
- name: collection
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.16.0"
- cross_file:
- dependency: transitive
- description:
- name: cross_file
- url: "https://pub.dartlang.org"
- source: hosted
- version: "0.3.3+1"
- crypto:
- dependency: transitive
- description:
- name: crypto
- url: "https://pub.dartlang.org"
- source: hosted
- version: "3.0.2"
- fake_async:
- dependency: transitive
- description:
- name: fake_async
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.3.1"
- ffi:
- dependency: transitive
- description:
- name: ffi
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.0.1"
- file:
- dependency: transitive
- description:
- name: file
- url: "https://pub.dartlang.org"
- source: hosted
- version: "6.1.2"
- flutter:
- dependency: "direct main"
- description: flutter
- source: sdk
- version: "0.0.0"
- flutter_plugin_android_lifecycle:
- dependency: transitive
- description:
- name: flutter_plugin_android_lifecycle
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.0.7"
- flutter_test:
- dependency: "direct dev"
- description: flutter
- source: sdk
- version: "0.0.0"
- flutter_web_plugins:
- dependency: transitive
- description: flutter
- source: sdk
- version: "0.0.0"
- functions_client:
- dependency: transitive
- description:
- name: functions_client
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.0.0"
- gotrue:
- dependency: transitive
- description:
- name: gotrue
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.0.0"
- hive:
- dependency: transitive
- description:
- name: hive
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.2.3"
- hive_flutter:
- dependency: transitive
- description:
- name: hive_flutter
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.1.0"
- http:
- dependency: transitive
- description:
- name: http
- url: "https://pub.dartlang.org"
- source: hosted
- version: "0.13.5"
- http_parser:
- dependency: transitive
- description:
- name: http_parser
- url: "https://pub.dartlang.org"
- source: hosted
- version: "4.0.1"
- image_picker:
- dependency: "direct main"
- description:
- name: image_picker
- url: "https://pub.dartlang.org"
- source: hosted
- version: "0.8.5+3"
- image_picker_android:
- dependency: transitive
- description:
- name: image_picker_android
- url: "https://pub.dartlang.org"
- source: hosted
- version: "0.8.5+1"
- image_picker_for_web:
- dependency: transitive
- description:
- name: image_picker_for_web
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.1.8"
- image_picker_ios:
- dependency: transitive
- description:
- name: image_picker_ios
- url: "https://pub.dartlang.org"
- source: hosted
- version: "0.8.5+6"
- image_picker_platform_interface:
- dependency: transitive
- description:
- name: image_picker_platform_interface
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.6.1"
- js:
- dependency: transitive
- description:
- name: js
- url: "https://pub.dartlang.org"
- source: hosted
- version: "0.6.4"
- jwt_decode:
- dependency: transitive
- description:
- name: jwt_decode
- url: "https://pub.dartlang.org"
- source: hosted
- version: "0.3.1"
- lint:
- dependency: "direct dev"
- description:
- name: lint
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.10.0"
- matcher:
- dependency: transitive
- description:
- name: matcher
- url: "https://pub.dartlang.org"
- source: hosted
- version: "0.12.12"
- material_color_utilities:
- dependency: transitive
- description:
- name: material_color_utilities
- url: "https://pub.dartlang.org"
- source: hosted
- version: "0.1.5"
- meta:
- dependency: transitive
- description:
- name: meta
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.8.0"
- mime:
- dependency: transitive
- description:
- name: mime
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.0.2"
- path:
- dependency: transitive
- description:
- name: path
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.8.2"
- path_provider:
- dependency: transitive
- description:
- name: path_provider
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.0.11"
- path_provider_android:
- dependency: transitive
- description:
- name: path_provider_android
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.0.17"
- path_provider_ios:
- dependency: transitive
- description:
- name: path_provider_ios
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.0.11"
- path_provider_linux:
- dependency: transitive
- description:
- name: path_provider_linux
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.1.7"
- path_provider_macos:
- dependency: transitive
- description:
- name: path_provider_macos
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.0.6"
- path_provider_platform_interface:
- dependency: transitive
- description:
- name: path_provider_platform_interface
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.0.4"
- path_provider_windows:
- dependency: transitive
- description:
- name: path_provider_windows
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.1.1"
- platform:
- dependency: transitive
- description:
- name: platform
- url: "https://pub.dartlang.org"
- source: hosted
- version: "3.1.0"
- plugin_platform_interface:
- dependency: transitive
- description:
- name: plugin_platform_interface
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.1.2"
- postgrest:
- dependency: transitive
- description:
- name: postgrest
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.0.0"
- process:
- dependency: transitive
- description:
- name: process
- url: "https://pub.dartlang.org"
- source: hosted
- version: "4.2.4"
- realtime_client:
- dependency: transitive
- description:
- name: realtime_client
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.0.0"
- sky_engine:
- dependency: transitive
- description: flutter
- source: sdk
- version: "0.0.99"
- source_span:
- dependency: transitive
- description:
- name: source_span
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.9.0"
- stack_trace:
- dependency: transitive
- description:
- name: stack_trace
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.10.0"
- storage_client:
- dependency: transitive
- description:
- name: storage_client
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.0.0"
- stream_channel:
- dependency: transitive
- description:
- name: stream_channel
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.1.0"
- string_scanner:
- dependency: transitive
- description:
- name: string_scanner
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.1.1"
- supabase:
- dependency: transitive
- description:
- name: supabase
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.0.1"
- supabase_flutter:
- dependency: "direct main"
- description:
- name: supabase_flutter
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.0.1"
- term_glyph:
- dependency: transitive
- description:
- name: term_glyph
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.2.1"
- test_api:
- dependency: transitive
- description:
- name: test_api
- url: "https://pub.dartlang.org"
- source: hosted
- version: "0.4.12"
- typed_data:
- dependency: transitive
- description:
- name: typed_data
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.3.1"
- universal_io:
- dependency: transitive
- description:
- name: universal_io
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.0.4"
- url_launcher:
- dependency: transitive
- description:
- name: url_launcher
- url: "https://pub.dartlang.org"
- source: hosted
- version: "6.1.5"
- url_launcher_android:
- dependency: transitive
- description:
- name: url_launcher_android
- url: "https://pub.dartlang.org"
- source: hosted
- version: "6.0.17"
- url_launcher_ios:
- dependency: transitive
- description:
- name: url_launcher_ios
- url: "https://pub.dartlang.org"
- source: hosted
- version: "6.0.17"
- url_launcher_linux:
- dependency: transitive
- description:
- name: url_launcher_linux
- url: "https://pub.dartlang.org"
- source: hosted
- version: "3.0.1"
- url_launcher_macos:
- dependency: transitive
- description:
- name: url_launcher_macos
- url: "https://pub.dartlang.org"
- source: hosted
- version: "3.0.1"
- url_launcher_platform_interface:
- dependency: transitive
- description:
- name: url_launcher_platform_interface
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.1.0"
- url_launcher_web:
- dependency: transitive
- description:
- name: url_launcher_web
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.0.12"
- url_launcher_windows:
- dependency: transitive
- description:
- name: url_launcher_windows
- url: "https://pub.dartlang.org"
- source: hosted
- version: "3.0.1"
- vector_math:
- dependency: transitive
- description:
- name: vector_math
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.1.2"
- web_socket_channel:
- dependency: transitive
- description:
- name: web_socket_channel
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.2.0"
- win32:
- dependency: transitive
- description:
- name: win32
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.7.0"
- xdg_directories:
- dependency: transitive
- description:
- name: xdg_directories
- url: "https://pub.dartlang.org"
- source: hosted
- version: "0.2.0+1"
-sdks:
- dart: ">=2.17.0 <3.0.0"
- flutter: ">=3.0.0"
diff --git a/examples/user-management/flutter-user-management/pubspec.yaml b/examples/user-management/flutter-user-management/pubspec.yaml
index c38e49f8498..82b27ca5aea 100644
--- a/examples/user-management/flutter-user-management/pubspec.yaml
+++ b/examples/user-management/flutter-user-management/pubspec.yaml
@@ -13,7 +13,7 @@ dependencies:
sdk: flutter
image_picker: ^0.8.4
- supabase_flutter: ^1.0.0
+ supabase_flutter: ^1.2.0
dev_dependencies:
flutter_test:
diff --git a/package-lock.json b/package-lock.json
index f0c3b85c5ce..b1b1707e092 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -41,7 +41,7 @@
"@algolia/autocomplete-js": "^1.7.2",
"@algolia/autocomplete-plugin-recent-searches": "^1.7.2",
"@apollo/client": "^3.6.6",
- "@docsearch/react": "1.0.0-alpha.27",
+ "@docsearch/react": "^3.3.0",
"@mdx-js/loader": "^1.6.22",
"@mdx-js/react": "^1.6.22",
"@next/mdx": "^12.0.4",
@@ -228,7 +228,7 @@
"gray-matter": "^4.0.2",
"markdown-toc": "^1.2.0",
"marked": "^4.0.12",
- "next": "latest",
+ "next": "12.1.0",
"next-compose-plugins": "^2.2.1",
"next-mdx-remote": "^4.1.0",
"next-seo": "^4.20.0",
@@ -254,6 +254,7 @@
"ui": "*",
"use-debounce": "^7.0.1",
"uuid": "^8.3.2",
+ "vanilla-tilt": "1.7.0",
"yup": "^0.32.11"
},
"devDependencies": {
@@ -2820,25 +2821,62 @@
}
},
"node_modules/@docsearch/css": {
- "version": "1.0.0-alpha.28",
- "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-1.0.0-alpha.28.tgz",
- "integrity": "sha512-1AhRzVdAkrWwhaxTX6/R7SnFHz8yLz1W8I/AldlTrfbNvZs9INk1FZiEFTJdgHaP68nhgQNWSGlQiDiI3y2RYg=="
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.3.0.tgz",
+ "integrity": "sha512-rODCdDtGyudLj+Va8b6w6Y85KE85bXRsps/R4Yjwt5vueXKXZQKYw0aA9knxLBT6a/bI/GMrAcmCR75KYOM6hg=="
},
"node_modules/@docsearch/react": {
- "version": "1.0.0-alpha.27",
- "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-1.0.0-alpha.27.tgz",
- "integrity": "sha512-jcgUHZsrNNRsaVsplqKhXWheh4VzRTCdhsPuVhJMRvfsFUqXEPo/7kVt5xIybtOj9u+/FVdeSO+APJEE2rakYA==",
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.3.0.tgz",
+ "integrity": "sha512-fhS5adZkae2SSdMYEMVg6pxI5a/cE+tW16ki1V0/ur4Fdok3hBRkmN/H8VvlXnxzggkQIIRIVvYPn00JPjen3A==",
"dependencies": {
- "@docsearch/css": "^1.0.0-alpha.27",
- "@francoischalifour/autocomplete-core": "^1.0.0-alpha.27",
- "@francoischalifour/autocomplete-preset-algolia": "^1.0.0-alpha.27",
+ "@algolia/autocomplete-core": "1.7.2",
+ "@algolia/autocomplete-preset-algolia": "1.7.2",
+ "@docsearch/css": "3.3.0",
"algoliasearch": "^4.0.0"
},
"peerDependencies": {
- "react": "^16.8.0",
- "react-dom": "^16.8.0"
+ "@types/react": ">= 16.8.0 < 19.0.0",
+ "react": ">= 16.8.0 < 19.0.0",
+ "react-dom": ">= 16.8.0 < 19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ }
}
},
+ "node_modules/@docsearch/react/node_modules/@algolia/autocomplete-core": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.7.2.tgz",
+ "integrity": "sha512-eclwUDC6qfApNnEfu1uWcL/rudQsn59tjEoUYZYE2JSXZrHLRjBUGMxiCoknobU2Pva8ejb0eRxpIYDtVVqdsw==",
+ "dependencies": {
+ "@algolia/autocomplete-shared": "1.7.2"
+ }
+ },
+ "node_modules/@docsearch/react/node_modules/@algolia/autocomplete-preset-algolia": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.7.2.tgz",
+ "integrity": "sha512-+RYEG6B0QiGGfRb2G3MtPfyrl0dALF3cQNTWBzBX6p5o01vCCGTTinAm2UKG3tfc2CnOMAtnPLkzNZyJUpnVJw==",
+ "dependencies": {
+ "@algolia/autocomplete-shared": "1.7.2"
+ },
+ "peerDependencies": {
+ "@algolia/client-search": ">= 4.9.1 < 6",
+ "algoliasearch": ">= 4.9.1 < 6"
+ }
+ },
+ "node_modules/@docsearch/react/node_modules/@algolia/autocomplete-shared": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.7.2.tgz",
+ "integrity": "sha512-QCckjiC7xXHIUaIL3ektBtjJ0w7tTA3iqKcAE/Hjn1lZ5omp7i3Y4e09rAr9ZybqirL7AbxCLLq0Ra5DDPKeug=="
+ },
"node_modules/@eslint/eslintrc": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz",
@@ -2888,16 +2926,6 @@
"npm": ">=6.0.0"
}
},
- "node_modules/@francoischalifour/autocomplete-core": {
- "version": "1.0.0-alpha.28",
- "resolved": "https://registry.npmjs.org/@francoischalifour/autocomplete-core/-/autocomplete-core-1.0.0-alpha.28.tgz",
- "integrity": "sha512-rL9x+72btViw+9icfBKUJjZj87FgjFrD2esuTUqtj4RAX3s4AuVZiN8XEsfjQBSc6qJk31cxlvqZHC/BIyYXgg=="
- },
- "node_modules/@francoischalifour/autocomplete-preset-algolia": {
- "version": "1.0.0-alpha.28",
- "resolved": "https://registry.npmjs.org/@francoischalifour/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.0.0-alpha.28.tgz",
- "integrity": "sha512-bprfNmYt1opFUFEtD2XfY/kEsm13bzHQgU80uMjhuK0DJ914IjolT1GytpkdM6tJ4MBvyiJPP+bTtWO+BZ7c7w=="
- },
"node_modules/@gar/promisify": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
@@ -9496,6 +9524,75 @@
"tailwindcss": ">=2.0.0"
}
},
+ "node_modules/@tanstack/match-sorter-utils": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.1.1.tgz",
+ "integrity": "sha512-IdmEekEYxQsoLOR0XQyw3jD1GujBpRRYaGJYQUw1eOT1eUugWxdc7jomh1VQ1EKHcdwDLpLaCz/8y4KraU4T9A==",
+ "dependencies": {
+ "remove-accents": "0.4.2"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/kentcdodds"
+ }
+ },
+ "node_modules/@tanstack/query-core": {
+ "version": "4.15.1",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.15.1.tgz",
+ "integrity": "sha512-+UfqJsNbPIVo0a9ANW0ZxtjiMfGLaaoIaL9vZeVycvmBuWywJGtSi7fgPVMCPdZQFOzMsaXaOsDtSKQD5xLRVQ==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/react-query": {
+ "version": "4.16.1",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.16.1.tgz",
+ "integrity": "sha512-PDE9u49wSDykPazlCoLFevUpceLjQ0Mm8i6038HgtTEKb/aoVnUZdlUP7C392ds3Cd75+EGlHU7qpEX06R7d9Q==",
+ "dependencies": {
+ "@tanstack/query-core": "4.15.1",
+ "use-sync-external-store": "^1.2.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0",
+ "react-native": "*"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ },
+ "react-native": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@tanstack/react-query-devtools": {
+ "version": "4.16.1",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-4.16.1.tgz",
+ "integrity": "sha512-VrDYLmG+OOcvGSZL5avG4R8jhqeMFP7pzW2sh2BWEV9UfI+aocG+CW8y8ygacxuKy48m8Tyo/xfe8H1z9BGb+g==",
+ "dependencies": {
+ "@tanstack/match-sorter-utils": "8.1.1",
+ "superjson": "^1.10.0",
+ "use-sync-external-store": "^1.2.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "@tanstack/react-query": "4.16.1",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
"node_modules/@testdeck/core": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@testdeck/core/-/core-0.2.2.tgz",
@@ -13563,6 +13660,20 @@
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
"dev": true
},
+ "node_modules/copy-anything": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.2.tgz",
+ "integrity": "sha512-CzATjGXzUQ0EvuvgOCI6A4BGOo2bcVx8B+eC2nF862iv9fopnPQwlrbACakNCHRIJbCSBj+J/9JeDf60k64MkA==",
+ "dependencies": {
+ "is-what": "^4.1.6"
+ },
+ "engines": {
+ "node": ">=12.13"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mesqueeb"
+ }
+ },
"node_modules/copy-concurrently": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz",
@@ -20395,6 +20506,17 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-what": {
+ "version": "4.1.7",
+ "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.7.tgz",
+ "integrity": "sha512-DBVOQNiPKnGMxRMLIYSwERAS5MVY1B7xYiGnpgctsOFvVDz9f9PFXXxMcTOHuoqYp4NK9qFYQaIC1NRRxLMpBQ==",
+ "engines": {
+ "node": ">=12.13"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mesqueeb"
+ }
+ },
"node_modules/is-whitespace-character": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz",
@@ -31101,6 +31223,11 @@
"sprintf-js": "~1.0.2"
}
},
+ "node_modules/remove-accents": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz",
+ "integrity": "sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA=="
+ },
"node_modules/remove-trailing-separator": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
@@ -32917,6 +33044,17 @@
"resolved": "tests",
"link": true
},
+ "node_modules/superjson": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/superjson/-/superjson-1.11.0.tgz",
+ "integrity": "sha512-6PfAg1FKhqkwWvPb2uXhH4MkMttdc17eJ91+Aoz4s1XUEDZFmLfFx/xVA3wgkPxAGy5dpozgGdK6V/n20Wj9yg==",
+ "dependencies": {
+ "copy-anything": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -34999,6 +35137,14 @@
"react": "^16.8.0 || ^17.0.0"
}
},
+ "node_modules/use-sync-external-store": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
+ "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
"node_modules/utf-8-validate": {
"version": "5.0.10",
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz",
@@ -35157,6 +35303,11 @@
"spdx-expression-parse": "^3.0.0"
}
},
+ "node_modules/vanilla-tilt": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/vanilla-tilt/-/vanilla-tilt-1.7.0.tgz",
+ "integrity": "sha512-u9yUhpSasFeqQCuiTon+RSb0aHzcj9stvWVXQIswzKL5oG491lkYk7U1GmhOAEZt7yPT6EiYZRJhIh2MFBncOA=="
+ },
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@@ -36857,7 +37008,9 @@
"@supabase/postgres-meta": "^0.26.1",
"@supabase/react-data-grid": "^7.1.0-beta.7",
"@supabase/shared-types": "^0.1.29",
- "@supabase/supabase-js": "^2.1.0",
+ "@supabase/supabase-js": "^2.1.1",
+ "@tanstack/react-query": "^4.16.1",
+ "@tanstack/react-query-devtools": "^4.16.1",
"ajv": "^8.6.3",
"analytics-node": "^3.5.0",
"awesome-debounce-promise": "^2.1.0",
@@ -36961,7 +37114,7 @@
"storybook-dark-mode": "^1.0.4",
"tailwindcss": "^3.0.22",
"ts-jest": "^27.0.7",
- "typescript": "4.3.4"
+ "typescript": "4.8.4"
}
},
"studio/node_modules/@next/bundle-analyzer": {
@@ -37418,9 +37571,9 @@
}
},
"studio/node_modules/typescript": {
- "version": "4.3.4",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.4.tgz",
- "integrity": "sha512-uauPG7XZn9F/mo+7MrsRjyvbxFpzemRjKEZXS4AK83oP2KKOJPvb+9cO/gmnv8arWZvhnjVOXz7B49m1l0e9Ew==",
+ "version": "4.8.4",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz",
+ "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
@@ -39308,19 +39461,42 @@
"dev": true
},
"@docsearch/css": {
- "version": "1.0.0-alpha.28",
- "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-1.0.0-alpha.28.tgz",
- "integrity": "sha512-1AhRzVdAkrWwhaxTX6/R7SnFHz8yLz1W8I/AldlTrfbNvZs9INk1FZiEFTJdgHaP68nhgQNWSGlQiDiI3y2RYg=="
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.3.0.tgz",
+ "integrity": "sha512-rODCdDtGyudLj+Va8b6w6Y85KE85bXRsps/R4Yjwt5vueXKXZQKYw0aA9knxLBT6a/bI/GMrAcmCR75KYOM6hg=="
},
"@docsearch/react": {
- "version": "1.0.0-alpha.27",
- "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-1.0.0-alpha.27.tgz",
- "integrity": "sha512-jcgUHZsrNNRsaVsplqKhXWheh4VzRTCdhsPuVhJMRvfsFUqXEPo/7kVt5xIybtOj9u+/FVdeSO+APJEE2rakYA==",
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.3.0.tgz",
+ "integrity": "sha512-fhS5adZkae2SSdMYEMVg6pxI5a/cE+tW16ki1V0/ur4Fdok3hBRkmN/H8VvlXnxzggkQIIRIVvYPn00JPjen3A==",
"requires": {
- "@docsearch/css": "^1.0.0-alpha.27",
- "@francoischalifour/autocomplete-core": "^1.0.0-alpha.27",
- "@francoischalifour/autocomplete-preset-algolia": "^1.0.0-alpha.27",
+ "@algolia/autocomplete-core": "1.7.2",
+ "@algolia/autocomplete-preset-algolia": "1.7.2",
+ "@docsearch/css": "3.3.0",
"algoliasearch": "^4.0.0"
+ },
+ "dependencies": {
+ "@algolia/autocomplete-core": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.7.2.tgz",
+ "integrity": "sha512-eclwUDC6qfApNnEfu1uWcL/rudQsn59tjEoUYZYE2JSXZrHLRjBUGMxiCoknobU2Pva8ejb0eRxpIYDtVVqdsw==",
+ "requires": {
+ "@algolia/autocomplete-shared": "1.7.2"
+ }
+ },
+ "@algolia/autocomplete-preset-algolia": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.7.2.tgz",
+ "integrity": "sha512-+RYEG6B0QiGGfRb2G3MtPfyrl0dALF3cQNTWBzBX6p5o01vCCGTTinAm2UKG3tfc2CnOMAtnPLkzNZyJUpnVJw==",
+ "requires": {
+ "@algolia/autocomplete-shared": "1.7.2"
+ }
+ },
+ "@algolia/autocomplete-shared": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.7.2.tgz",
+ "integrity": "sha512-QCckjiC7xXHIUaIL3ektBtjJ0w7tTA3iqKcAE/Hjn1lZ5omp7i3Y4e09rAr9ZybqirL7AbxCLLq0Ra5DDPKeug=="
+ }
}
},
"@eslint/eslintrc": {
@@ -39360,16 +39536,6 @@
"integrity": "sha512-8YXBE2ZcU/pImVOHX7MWrSR/X5up7t6rPWZlk34RwZEcdr3ua6X+32pSd6XuOQRN+vbuvYNfA6iey8NbrjuMFQ==",
"dev": true
},
- "@francoischalifour/autocomplete-core": {
- "version": "1.0.0-alpha.28",
- "resolved": "https://registry.npmjs.org/@francoischalifour/autocomplete-core/-/autocomplete-core-1.0.0-alpha.28.tgz",
- "integrity": "sha512-rL9x+72btViw+9icfBKUJjZj87FgjFrD2esuTUqtj4RAX3s4AuVZiN8XEsfjQBSc6qJk31cxlvqZHC/BIyYXgg=="
- },
- "@francoischalifour/autocomplete-preset-algolia": {
- "version": "1.0.0-alpha.28",
- "resolved": "https://registry.npmjs.org/@francoischalifour/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.0.0-alpha.28.tgz",
- "integrity": "sha512-bprfNmYt1opFUFEtD2XfY/kEsm13bzHQgU80uMjhuK0DJ914IjolT1GytpkdM6tJ4MBvyiJPP+bTtWO+BZ7c7w=="
- },
"@gar/promisify": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
@@ -44312,6 +44478,38 @@
"lodash.uniq": "^4.5.0"
}
},
+ "@tanstack/match-sorter-utils": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.1.1.tgz",
+ "integrity": "sha512-IdmEekEYxQsoLOR0XQyw3jD1GujBpRRYaGJYQUw1eOT1eUugWxdc7jomh1VQ1EKHcdwDLpLaCz/8y4KraU4T9A==",
+ "requires": {
+ "remove-accents": "0.4.2"
+ }
+ },
+ "@tanstack/query-core": {
+ "version": "4.15.1",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.15.1.tgz",
+ "integrity": "sha512-+UfqJsNbPIVo0a9ANW0ZxtjiMfGLaaoIaL9vZeVycvmBuWywJGtSi7fgPVMCPdZQFOzMsaXaOsDtSKQD5xLRVQ=="
+ },
+ "@tanstack/react-query": {
+ "version": "4.16.1",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.16.1.tgz",
+ "integrity": "sha512-PDE9u49wSDykPazlCoLFevUpceLjQ0Mm8i6038HgtTEKb/aoVnUZdlUP7C392ds3Cd75+EGlHU7qpEX06R7d9Q==",
+ "requires": {
+ "@tanstack/query-core": "4.15.1",
+ "use-sync-external-store": "^1.2.0"
+ }
+ },
+ "@tanstack/react-query-devtools": {
+ "version": "4.16.1",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-4.16.1.tgz",
+ "integrity": "sha512-VrDYLmG+OOcvGSZL5avG4R8jhqeMFP7pzW2sh2BWEV9UfI+aocG+CW8y8ygacxuKy48m8Tyo/xfe8H1z9BGb+g==",
+ "requires": {
+ "@tanstack/match-sorter-utils": "8.1.1",
+ "superjson": "^1.10.0",
+ "use-sync-external-store": "^1.2.0"
+ }
+ },
"@testdeck/core": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/@testdeck/core/-/core-0.2.2.tgz",
@@ -47609,6 +47807,14 @@
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
"dev": true
},
+ "copy-anything": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.2.tgz",
+ "integrity": "sha512-CzATjGXzUQ0EvuvgOCI6A4BGOo2bcVx8B+eC2nF862iv9fopnPQwlrbACakNCHRIJbCSBj+J/9JeDf60k64MkA==",
+ "requires": {
+ "is-what": "^4.1.6"
+ }
+ },
"copy-concurrently": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz",
@@ -49232,7 +49438,7 @@
"@algolia/autocomplete-js": "^1.7.2",
"@algolia/autocomplete-plugin-recent-searches": "^1.7.2",
"@apollo/client": "^3.6.6",
- "@docsearch/react": "1.0.0-alpha.27",
+ "@docsearch/react": "^3.3.0",
"@mdx-js/loader": "^1.6.22",
"@mdx-js/react": "^1.6.22",
"@next/mdx": "^12.0.4",
@@ -52995,6 +53201,11 @@
"get-intrinsic": "^1.1.1"
}
},
+ "is-what": {
+ "version": "4.1.7",
+ "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.7.tgz",
+ "integrity": "sha512-DBVOQNiPKnGMxRMLIYSwERAS5MVY1B7xYiGnpgctsOFvVDz9f9PFXXxMcTOHuoqYp4NK9qFYQaIC1NRRxLMpBQ=="
+ },
"is-whitespace-character": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz",
@@ -61035,6 +61246,11 @@
}
}
},
+ "remove-accents": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz",
+ "integrity": "sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA=="
+ },
"remove-trailing-separator": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
@@ -62428,8 +62644,10 @@
"@supabase/postgres-meta": "^0.26.1",
"@supabase/react-data-grid": "^7.1.0-beta.7",
"@supabase/shared-types": "^0.1.29",
- "@supabase/supabase-js": "^2.1.0",
+ "@supabase/supabase-js": "^2.1.1",
"@tailwindcss/typography": "^0.5.2",
+ "@tanstack/react-query": "^4.16.1",
+ "@tanstack/react-query-devtools": "^4.16.1",
"@testing-library/dom": "^8.11.1",
"@testing-library/react": "^12.1.2",
"@testing-library/user-event": "^13.5.0",
@@ -62514,7 +62732,7 @@
"swr": "^1.2.1",
"tailwindcss": "^3.0.22",
"ts-jest": "^27.0.7",
- "typescript": "4.3.4",
+ "typescript": "4.8.4",
"ui": "*",
"uniforms-bootstrap4": "^3.6.2",
"uniforms-bridge-json-schema": "^3.6.2",
@@ -62813,9 +63031,9 @@
"requires": {}
},
"typescript": {
- "version": "4.3.4",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.4.tgz",
- "integrity": "sha512-uauPG7XZn9F/mo+7MrsRjyvbxFpzemRjKEZXS4AK83oP2KKOJPvb+9cO/gmnv8arWZvhnjVOXz7B49m1l0e9Ew==",
+ "version": "4.8.4",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz",
+ "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==",
"dev": true
},
"webpack-bundle-analyzer": {
@@ -63000,6 +63218,14 @@
}
}
},
+ "superjson": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/superjson/-/superjson-1.11.0.tgz",
+ "integrity": "sha512-6PfAg1FKhqkwWvPb2uXhH4MkMttdc17eJ91+Aoz4s1XUEDZFmLfFx/xVA3wgkPxAGy5dpozgGdK6V/n20Wj9yg==",
+ "requires": {
+ "copy-anything": "^3.0.2"
+ }
+ },
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -64610,6 +64836,12 @@
"object-assign": "^4.1.1"
}
},
+ "use-sync-external-store": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
+ "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
+ "requires": {}
+ },
"utf-8-validate": {
"version": "5.0.10",
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz",
@@ -64746,6 +64978,11 @@
"spdx-expression-parse": "^3.0.0"
}
},
+ "vanilla-tilt": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/vanilla-tilt/-/vanilla-tilt-1.7.0.tgz",
+ "integrity": "sha512-u9yUhpSasFeqQCuiTon+RSb0aHzcj9stvWVXQIswzKL5oG491lkYk7U1GmhOAEZt7yPT6EiYZRJhIh2MFBncOA=="
+ },
"vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@@ -65771,7 +66008,7 @@
"gray-matter": "^4.0.2",
"markdown-toc": "^1.2.0",
"marked": "^4.0.12",
- "next": "latest",
+ "next": "12.1.0",
"next-compose-plugins": "^2.2.1",
"next-mdx-remote": "^4.1.0",
"next-seo": "^4.20.0",
@@ -65802,6 +66039,7 @@
"ui": "*",
"use-debounce": "^7.0.1",
"uuid": "^8.3.2",
+ "vanilla-tilt": "1.7.0",
"yup": "^0.32.11"
},
"dependencies": {
diff --git a/spec/supabase_dart_v1.yml b/spec/supabase_dart_v1.yml
index 382c76bae08..6f3ef71a653 100644
--- a/spec/supabase_dart_v1.yml
+++ b/spec/supabase_dart_v1.yml
@@ -836,7 +836,6 @@ pages:
description: |
Retrieves the details of all Storage buckets within an existing product.
title: 'listBuckets()'
- $ref: '@supabase/storage-js.packages/StorageBucketApi.default.listBuckets'
notes: |
- Policy permissions required:
- `buckets` permissions: `select`
@@ -855,7 +854,6 @@ pages:
description: |
Retrieves the details of an existing Storage bucket.
title: 'getBucket()'
- $ref: '@supabase/storage-js.packages/StorageBucketApi.default.getBucket'
notes: |
- Policy permissions required:
- `buckets` permissions: `select`
@@ -945,7 +943,6 @@ pages:
description: |
Uploads a file to an existing bucket.
title: 'from.upload()'
- $ref: '@supabase/storage-js.packages/StorageFileApi.default.upload'
notes: |
- Policy permissions required:
- `buckets` permissions: none
@@ -1022,6 +1019,24 @@ pages:
.createSignedUrl('avatar1.png', 60);
```
+ storage.from.createSignedUrls():
+ title: 'from.createSignedUrls()'
+ notes: |
+ - RLS policy permissions required:
+ - `buckets` table permissions: none
+ - `objects` table permissions: `select`
+ - Refer to the [Storage guide](/docs/guides/storage#access-control) on how access control works
+ examples:
+ - name: Create Signed URLs
+ isSpotlight: true
+ dart: |
+ ```dart
+ final List signedUrls = await supabase
+ .storage
+ .from('avatars')
+ .createSignedUrls(['folder/avatar1.png', 'folder/avatar2.png'], 60);
+ ```
+
storage.from.getPublicUrl():
description: |
Retrieve URLs for assets in public buckets
@@ -1671,7 +1686,6 @@ pages:
.lte():
description: |
Finds all rows whose value on the stated `column` is less than or equal to the specified `value`.
- $ref: '@supabase/postgrest-js.PostgrestFilterBuilder.lte'
examples:
- name: With `select()`
isSpotlight: true
@@ -1710,7 +1724,6 @@ pages:
.like():
description: |
Finds all rows whose value in the stated `column` matches the supplied `pattern` (case sensitive).
- $ref: '@supabase/postgrest-js.PostgrestFilterBuilder.lte'
examples:
- name: With `select()`
isSpotlight: true
@@ -2045,7 +2058,6 @@ pages:
```
.rangeLte():
- $ref: '@supabase/postgrest-js.PostgrestFilterBuilder.rangeLte'
examples:
- name: With `select()`
isSpotlight: true
diff --git a/studio/components/interfaces/Billing/AddOns/AddOns.utils.ts b/studio/components/interfaces/Billing/AddOns/AddOns.utils.ts
index d3d559d3abf..4440c0a0f78 100644
--- a/studio/components/interfaces/Billing/AddOns/AddOns.utils.ts
+++ b/studio/components/interfaces/Billing/AddOns/AddOns.utils.ts
@@ -91,3 +91,39 @@ export const formatPITROptions = (addons: DatabaseAddon[]) => {
if (pitrOptions.length === 0) return []
else return [noPITROption].concat(pitrOptions)
}
+
+export const formatCustomDomainOptions = (addons: DatabaseAddon[]) => {
+ const customDomainOrder = ['addon_custom_domains']
+ const noCustomDomainOption: DatabaseAddon = {
+ id: undefined,
+ name: 'Disable Custom Domains',
+ metadata: {
+ default_price_id: undefined,
+ supabase_prod_id: 'addon_custom_domains_disabled',
+ features: '',
+ },
+ prices: [
+ {
+ id: undefined,
+ currency: 'usd',
+ recurring: {
+ interval: 'month',
+ usage_type: 'licensed',
+ interval_count: 1,
+ aggregate_usage: null,
+ trial_period_days: null,
+ },
+ unit_amount: 0,
+ },
+ ],
+ }
+
+ const customDomainOptions = customDomainOrder
+ .map((id: string) => {
+ return addons.find((option) => option.metadata.supabase_prod_id === id)
+ })
+ .filter((option) => option !== undefined) as DatabaseAddon[]
+
+ if (customDomainOptions.length === 0) return []
+ else return [noCustomDomainOption].concat(customDomainOptions)
+}
diff --git a/studio/components/interfaces/Billing/AddOns/CustomDomainSelection.tsx b/studio/components/interfaces/Billing/AddOns/CustomDomainSelection.tsx
new file mode 100644
index 00000000000..a43d9f17f34
--- /dev/null
+++ b/studio/components/interfaces/Billing/AddOns/CustomDomainSelection.tsx
@@ -0,0 +1,70 @@
+import { FC } from 'react'
+import { Badge, Radio } from 'ui'
+
+import { useStore, useFlag } from 'hooks'
+import { getProductPrice } from '../Billing.utils'
+import DisabledWarningDueToIncident from 'components/ui/DisabledWarningDueToIncident'
+
+interface Props {
+ options: any[]
+ currentOption?: any
+ selectedOption: any
+ onSelectOption: (option: any) => void
+}
+
+const CustomDomainSelection: FC = ({
+ options,
+ currentOption,
+ selectedOption,
+ onSelectOption,
+}) => {
+ const addonUpdateDisabled = useFlag('disableProjectCreationAndUpdate')
+ return (
+
+
+
+
Custom Domains
+ Optional
+
+
Present a branded experience to your users
+
+ {addonUpdateDisabled ? (
+
+ ) : (
+
+ {options.map((option: any) => {
+ const defaultPrice = getProductPrice(option)
+ return (
+
+ {option.name}
+ {currentOption?.id === option.id && (
+ Current selection
+ )}
+
+ }
+ // @ts-ignore
+ description={
+
+
{option.metadata.features}
+
+ }
+ value={option.id}
+ optionalLabel={${defaultPrice.unit_amount / 100} / month
}
+ checked={selectedOption?.id === option.id}
+ onChange={() => onSelectOption(option)}
+ />
+ )
+ })}
+
+ )}
+
+ )
+}
+
+export default CustomDomainSelection
diff --git a/studio/components/interfaces/Billing/AddOns/PITRDurationSelection.tsx b/studio/components/interfaces/Billing/AddOns/PITRDurationSelection.tsx
index cf97a169668..ec5ed6bf2a6 100644
--- a/studio/components/interfaces/Billing/AddOns/PITRDurationSelection.tsx
+++ b/studio/components/interfaces/Billing/AddOns/PITRDurationSelection.tsx
@@ -29,7 +29,7 @@ const PITRDurationSelection: FC = ({
const addonUpdateDisabled = useFlag('disableProjectCreationAndUpdate')
// Only projects of version greater than supabase-postgrest-14.1.0.44 can use PITR
- const canUsePITR = getSemanticVersion(ui.selectedProject?.dbVersion ?? '') > 141044
+ const canUsePITR = getSemanticVersion(ui.selectedProject?.dbVersion ?? '') >= 141044
return (
diff --git a/studio/components/interfaces/Billing/Billing.constants.tsx b/studio/components/interfaces/Billing/Billing.constants.tsx
index e246a093ddd..00225246d08 100644
--- a/studio/components/interfaces/Billing/Billing.constants.tsx
+++ b/studio/components/interfaces/Billing/Billing.constants.tsx
@@ -1,4 +1,4 @@
-import { IconArchive, IconDatabase, IconKey } from 'ui'
+import { IconArchive, IconCode, IconDatabase, IconKey } from 'ui'
export const CANCELLATION_REASONS = [
'Pricing',
@@ -71,4 +71,25 @@ export const USAGE_BASED_PRODUCTS = [
},
],
},
+ {
+ title: 'Functions',
+ icon:
,
+ features: [
+ {
+ key: 'func_count',
+ attribute: 'func_count',
+ title: 'Function Count',
+ units: 'absolute',
+ costPerUnit: 0.1,
+ },
+
+ {
+ key: 'func_invocations',
+ attribute: 'func_invocations',
+ title: 'Function Invocations',
+ units: 'absolute',
+ costPerUnit: 0.000002,
+ }
+ ],
+ },
]
diff --git a/studio/components/interfaces/Billing/Billing.utils.ts b/studio/components/interfaces/Billing/Billing.utils.ts
index 2c839e9459a..d6d21a9652f 100644
--- a/studio/components/interfaces/Billing/Billing.utils.ts
+++ b/studio/components/interfaces/Billing/Billing.utils.ts
@@ -1,5 +1,9 @@
import { DatabaseAddon } from './AddOns/AddOns.types'
-import { formatComputeSizes, formatPITROptions } from './AddOns/AddOns.utils'
+import {
+ formatComputeSizes,
+ formatCustomDomainOptions,
+ formatPITROptions,
+} from './AddOns/AddOns.utils'
import { StripeSubscription } from './Subscription/Subscription.types'
export const getProductPrice = (product: any) => {
@@ -11,11 +15,15 @@ export const getProductPrice = (product: any) => {
return price
}
-export const validateSubscriptionUpdatePayload = (
- selectedComputeSize: DatabaseAddon,
- selectedPITRDuration?: DatabaseAddon
-) => {
- if (selectedPITRDuration?.id !== undefined && selectedComputeSize.id === undefined) {
+export const validateSubscriptionUpdatePayload = (selectedAddons: {
+ computeSize: DatabaseAddon
+ pitrDuration: DatabaseAddon
+ customDomains: DatabaseAddon
+}) => {
+ if (
+ selectedAddons.pitrDuration?.id !== undefined &&
+ selectedAddons.computeSize.id === undefined
+ ) {
return 'To enable PITR for your project, your project must minimally be on a Small Add-on.'
}
return undefined
@@ -23,18 +31,24 @@ export const validateSubscriptionUpdatePayload = (
export const formSubscriptionUpdatePayload = (
selectedTier: any,
- selectedComputeSize: DatabaseAddon,
- selectedPITRDuration: DatabaseAddon | undefined,
+ selectedAddons: {
+ computeSize: DatabaseAddon
+ pitrDuration: DatabaseAddon
+ customDomains: DatabaseAddon
+ },
selectedPaymentMethod: string,
region: string
) => {
+ const { computeSize, pitrDuration, customDomains } = selectedAddons
const defaultPrice = selectedTier ? getProductPrice(selectedTier) : undefined
const addons =
region === 'af-south-1'
? []
- : [selectedComputeSize.prices[0].id, selectedPITRDuration?.prices?.[0].id].filter(
- (x) => x !== undefined
- )
+ : [
+ computeSize.prices[0].id,
+ pitrDuration?.prices?.[0].id,
+ customDomains?.prices?.[0].id,
+ ].filter((x) => x !== undefined)
const proration_date = Math.floor(Date.now() / 1000)
return {
...(defaultPrice && { tier: defaultPrice.id }),
@@ -50,8 +64,9 @@ export const getCurrentAddons = (
) => {
const computeSizes = formatComputeSizes(addons)
const pitrDurationOptions = formatPITROptions(addons)
+ const customDomainOptions = formatCustomDomainOptions(addons)
- const currentComputeSize =
+ const computeSize =
computeSizes.find((option: any) => {
const subscriptionComputeSize = currentSubscription?.addons.find((addon) =>
addon.supabase_prod_id.includes('_instance_')
@@ -62,7 +77,7 @@ export const getCurrentAddons = (
(option: any) => option.metadata.supabase_prod_id === 'addon_instance_micro'
) as DatabaseAddon)
- const currentPITRDuration =
+ const pitrDuration =
pitrDurationOptions.find((option: any) => {
const subscriptionComputeSize = currentSubscription?.addons.find((addon) =>
addon.supabase_prod_id.includes('_pitr_')
@@ -73,5 +88,16 @@ export const getCurrentAddons = (
(option: any) => option.metadata.supabase_prod_id === 'addon_pitr_0days'
) as DatabaseAddon)
- return { currentComputeSize, currentPITRDuration }
+ const customDomains =
+ customDomainOptions.find((option: any) => {
+ const subscriptionComputeSize = currentSubscription?.addons.find((addon) =>
+ addon.supabase_prod_id.includes('_custom_domains')
+ )
+ return option.id === subscriptionComputeSize?.prod_id
+ }) ||
+ (customDomainOptions.find(
+ (option: any) => option.metadata.supabase_prod_id === 'addon__custom_domains_disabled'
+ ) as DatabaseAddon)
+
+ return { computeSize, pitrDuration, customDomains }
}
diff --git a/studio/components/interfaces/Billing/EnterpriseUpdate.tsx b/studio/components/interfaces/Billing/EnterpriseUpdate.tsx
index 360c2f96987..504d5931f8a 100644
--- a/studio/components/interfaces/Billing/EnterpriseUpdate.tsx
+++ b/studio/components/interfaces/Billing/EnterpriseUpdate.tsx
@@ -2,14 +2,18 @@ import { FC, useState, useEffect } from 'react'
import { useRouter } from 'next/router'
import { Transition } from '@headlessui/react'
-import { useStore } from 'hooks'
+import { useStore, useFlag } from 'hooks'
import { getURL } from 'lib/helpers'
import { post, patch } from 'lib/common/fetch'
import { API_URL, PROJECT_STATUS } from 'lib/constants'
import Divider from 'components/ui/Divider'
import { DatabaseAddon } from './AddOns/AddOns.types'
-import { formatComputeSizes, formatPITROptions } from './AddOns/AddOns.utils'
+import {
+ formatComputeSizes,
+ formatCustomDomainOptions,
+ formatPITROptions,
+} from './AddOns/AddOns.utils'
import {
AddNewPaymentMethodModal,
ComputeSizeSelection,
@@ -17,6 +21,7 @@ import {
StripeSubscription,
PaymentSummaryPanel,
UpdateSuccess,
+ CustomDomainSelection,
} from './'
import { PaymentMethod, SubscriptionPreview } from './Billing.types'
import { formSubscriptionUpdatePayload, getCurrentAddons } from './Billing.utils'
@@ -36,6 +41,8 @@ const EnterpriseUpdate: FC
= ({
}) => {
const { app, ui } = useStore()
const router = useRouter()
+ const isCustomDomainsEnabled = useFlag('customDomains')
+ const isPITRSelfServeEnabled = useFlag('pitrSelfServe')
const projectId = ui.selectedProject?.id ?? -1
const projectRef = ui.selectedProject?.ref ?? 'default'
@@ -44,12 +51,30 @@ const EnterpriseUpdate: FC = ({
const { addons } = products
const computeSizes = formatComputeSizes(addons)
const pitrDurationOptions = formatPITROptions(addons)
- const { currentComputeSize, currentPITRDuration } = getCurrentAddons(currentSubscription, addons)
+ const customDomainOptions = formatCustomDomainOptions(addons)
+ const currentAddons = getCurrentAddons(currentSubscription, addons)
+
+ // [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
{projectRegion !== 'af-south-1' && (
<>
- {pitrDurationOptions.length > 0 && (
+ {isCustomDomainsEnabled && customDomainOptions.length > 0 && (
+ <>
+
+
+ >
+ )}
+ {isPITRSelfServeEnabled && pitrDurationOptions.length > 0 && (
<>
>
@@ -190,8 +224,8 @@ const EnterpriseUpdate: FC = ({
>
@@ -208,11 +242,9 @@ const EnterpriseUpdate: FC = ({
subscriptionPreview={subscriptionPreview}
// Current subscription configuration based on DB
currentPlan={currentSubscription.tier}
- currentComputeSize={currentComputeSize}
- currentPITRDuration={currentPITRDuration}
+ currentAddons={currentAddons}
// Selected subscription configuration based on UI
- selectedComputeSize={selectedComputeSize}
- selectedPITRDuration={selectedPITRDuration}
+ selectedAddons={selectedAddons}
paymentMethods={paymentMethods}
isLoadingPaymentMethods={isLoadingPaymentMethods}
selectedPaymentMethod={selectedPaymentMethodId}
diff --git a/studio/components/interfaces/Billing/PaymentSummaryPanel/PaymentSummaryPanel.tsx b/studio/components/interfaces/Billing/PaymentSummaryPanel/PaymentSummaryPanel.tsx
index 37f77b19b67..a3134c2629e 100644
--- a/studio/components/interfaces/Billing/PaymentSummaryPanel/PaymentSummaryPanel.tsx
+++ b/studio/components/interfaces/Billing/PaymentSummaryPanel/PaymentSummaryPanel.tsx
@@ -18,12 +18,21 @@ import ConfirmPaymentModal from './ConfirmPaymentModal'
interface Props {
isRefreshingPreview: boolean
subscriptionPreview?: SubscriptionPreview
+
currentPlan: any
- currentComputeSize: DatabaseAddon
- currentPITRDuration: DatabaseAddon | undefined
+ currentAddons: {
+ computeSize: DatabaseAddon
+ pitrDuration: DatabaseAddon
+ customDomains: DatabaseAddon
+ }
+
selectedPlan?: any
- selectedComputeSize: DatabaseAddon
- selectedPITRDuration: DatabaseAddon | undefined
+ selectedAddons: {
+ computeSize: DatabaseAddon
+ pitrDuration: DatabaseAddon
+ customDomains: DatabaseAddon
+ }
+
isSpendCapEnabled: boolean
paymentMethods?: any
selectedPaymentMethod: any
@@ -45,12 +54,13 @@ const PaymentSummaryPanel: FC = ({
isRefreshingPreview,
isSpendCapEnabled,
subscriptionPreview,
+
currentPlan,
- currentComputeSize,
- currentPITRDuration,
+ currentAddons,
+
selectedPlan,
- selectedComputeSize,
- selectedPITRDuration,
+ selectedAddons,
+
paymentMethods,
selectedPaymentMethod,
isLoadingPaymentMethods,
@@ -70,9 +80,10 @@ const PaymentSummaryPanel: FC = ({
const [showConfirmModal, setShowConfirmModal] = useState(false)
- const currentPITRDays = currentPITRDuration !== undefined ? getPITRDays(currentPITRDuration) : 0
+ const currentPITRDays =
+ currentAddons.pitrDuration !== undefined ? getPITRDays(currentAddons.pitrDuration) : 0
const selectedPITRDays =
- selectedPITRDuration !== undefined ? getPITRDays(selectedPITRDuration) : 0
+ selectedAddons.pitrDuration !== undefined ? getPITRDays(selectedAddons.pitrDuration) : 0
const isEnterprise = currentPlan.supabase_prod_id === PRICING_TIER_PRODUCT_IDS.ENTERPRISE
const isChangingPlan =
@@ -81,8 +92,10 @@ const PaymentSummaryPanel: FC = ({
currentPlan.prod_id !== selectedPlan.id) ||
(currentPlan.prod_id !== STRIPE_PRODUCT_IDS.PAYG && !isSpendCapEnabled) ||
(currentPlan.prod_id === STRIPE_PRODUCT_IDS.PAYG && isSpendCapEnabled)
- const isChangingComputeSize = currentComputeSize.id !== selectedComputeSize.id
- const isChangingPITRDuration = currentPITRDuration?.id !== selectedPITRDuration?.id
+ const isChangingComputeSize = currentAddons.computeSize.id !== selectedAddons.computeSize.id
+ const isChangingPITRDuration = currentAddons.pitrDuration?.id !== selectedAddons.pitrDuration?.id
+ const isChangingCustomDomains =
+ currentAddons.customDomains?.id !== selectedAddons.customDomains?.id
// If it's enterprise we only only changing of add-ons
const hasChangesToPlan = isEnterprise
@@ -96,7 +109,7 @@ const PaymentSummaryPanel: FC = ({
}
const validateOrder = () => {
- const error = validateSubscriptionUpdatePayload(selectedComputeSize, selectedPITRDuration)
+ const error = validateSubscriptionUpdatePayload(selectedAddons)
if (error) {
return ui.setNotification({
duration: 4000,
@@ -147,45 +160,71 @@ const PaymentSummaryPanel: FC = ({
- {currentComputeSize.name}
+ {currentAddons.computeSize.name}
- ${(getProductPrice(currentComputeSize).unit_amount / 100).toFixed(2)}
+ ${(getProductPrice(currentAddons.computeSize).unit_amount / 100).toFixed(2)}
{isChangingComputeSize && (
-
{selectedComputeSize.name}
+
{selectedAddons.computeSize.name}
- ${(getProductPrice(selectedComputeSize).unit_amount / 100).toFixed(2)}
+ ${(getProductPrice(selectedAddons.computeSize).unit_amount / 100).toFixed(2)}
)}
- {currentPITRDuration?.id !== undefined && (
+ {currentAddons.pitrDuration?.id !== undefined && (
- {currentPITRDuration?.name}
+ {currentAddons.pitrDuration?.name}
- ${(getProductPrice(currentPITRDuration).unit_amount / 100).toFixed(2)}
+ ${(getProductPrice(currentAddons.pitrDuration).unit_amount / 100).toFixed(2)}
)}
{isChangingPITRDuration && (
-
{selectedPITRDuration?.name}
+
{selectedAddons.pitrDuration?.name}
- ${(getProductPrice(selectedPITRDuration).unit_amount / 100).toFixed(2)}
+ ${(getProductPrice(selectedAddons.pitrDuration).unit_amount / 100).toFixed(2)}
+
+
+ )}
+ {currentAddons.customDomains?.id !== undefined && (
+
+
+ {currentAddons.customDomains?.name}
+
+
+ ${(getProductPrice(currentAddons.customDomains).unit_amount / 100).toFixed(2)}
+
+
+ )}
+ {isChangingCustomDomains && (
+
+
{selectedAddons.customDomains?.name}
+
+ ${(getProductPrice(selectedAddons.customDomains).unit_amount / 100).toFixed(2)}
)}
@@ -198,9 +237,9 @@ const PaymentSummaryPanel: FC = ({
defaultVisibility
icon={ }
title={
- currentPITRDuration?.id === undefined
+ currentAddons.pitrDuration?.id === undefined
? 'Enabling PITR'
- : selectedPITRDuration?.id === undefined
+ : selectedAddons.pitrDuration?.id === undefined
? 'Disabling PITR'
: 'Updating PITR duration'
}
diff --git a/studio/components/interfaces/Billing/ProUpgrade.tsx b/studio/components/interfaces/Billing/ProUpgrade.tsx
index 12fea2c0c3b..5ff559adc1a 100644
--- a/studio/components/interfaces/Billing/ProUpgrade.tsx
+++ b/studio/components/interfaces/Billing/ProUpgrade.tsx
@@ -13,6 +13,7 @@ import {
PaymentSummaryPanel,
ComputeSizeSelection,
PITRDurationSelection,
+ CustomDomainSelection,
StripeSubscription,
AddNewPaymentMethodModal,
} from './'
@@ -21,7 +22,11 @@ import UpdateSuccess from './UpdateSuccess'
import { PaymentMethod, SubscriptionPreview } from './Billing.types'
import { formSubscriptionUpdatePayload, getCurrentAddons } from './Billing.utils'
import { DatabaseAddon } from './AddOns/AddOns.types'
-import { formatComputeSizes, formatPITROptions } from './AddOns/AddOns.utils'
+import {
+ formatComputeSizes,
+ formatCustomDomainOptions,
+ formatPITROptions,
+} from './AddOns/AddOns.utils'
import BackButton from 'components/ui/BackButton'
// Do not allow compute size changes for af-south-1
@@ -43,12 +48,14 @@ const ProUpgrade: FC = ({
}) => {
const { app, ui } = useStore()
const router = useRouter()
+ const isCustomDomainsEnabled = useFlag('customDomains')
const isPITRSelfServeEnabled = useFlag('pitrSelfServe')
const { addons } = products
const computeSizes = formatComputeSizes(addons)
const pitrDurationOptions = formatPITROptions(addons)
- const { currentComputeSize, currentPITRDuration } = getCurrentAddons(currentSubscription, addons)
+ const customDomainOptions = formatCustomDomainOptions(addons)
+ const currentAddons = getCurrentAddons(currentSubscription, addons)
const projectId = ui.selectedProject?.id ?? -1
const projectRef = ui.selectedProject?.ref
@@ -67,10 +74,27 @@ const ProUpgrade: FC = ({
const [showAddPaymentMethodModal, setShowAddPaymentMethodModal] = useState(false)
const [showSpendCapHelperModal, setShowSpendCapHelperModal] = 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
+ )
+}
+
+export default observer(CustomDomainsConfigureHostname)
diff --git a/studio/components/interfaces/Settings/General/CustomDomainConfig/CustomDomainsShimmerLoader.tsx b/studio/components/interfaces/Settings/General/CustomDomainConfig/CustomDomainsShimmerLoader.tsx
new file mode 100644
index 00000000000..3caaa3b1b2c
--- /dev/null
+++ b/studio/components/interfaces/Settings/General/CustomDomainConfig/CustomDomainsShimmerLoader.tsx
@@ -0,0 +1,15 @@
+const CustomDomainsShimmerLoader = () => {
+ return (
+
+ )
+}
+
+export default CustomDomainsShimmerLoader
diff --git a/studio/components/interfaces/Settings/General/CustomDomainConfig/DNSRecord.tsx b/studio/components/interfaces/Settings/General/CustomDomainConfig/DNSRecord.tsx
new file mode 100644
index 00000000000..9541c0abf9d
--- /dev/null
+++ b/studio/components/interfaces/Settings/General/CustomDomainConfig/DNSRecord.tsx
@@ -0,0 +1,21 @@
+import { Input } from 'ui'
+
+export type DNSRecordProps = {
+ type: string
+ name: string
+ value: string
+}
+
+const DNSRecord = ({ type, name, value }: DNSRecordProps) => {
+ return (
+
+ )
+}
+
+export default DNSRecord
diff --git a/studio/components/interfaces/Settings/General/DeleteProjectPanel/DeleteProjectPanel.tsx b/studio/components/interfaces/Settings/General/DeleteProjectPanel/DeleteProjectPanel.tsx
index b5bdafb317d..4c8c7267fdb 100644
--- a/studio/components/interfaces/Settings/General/DeleteProjectPanel/DeleteProjectPanel.tsx
+++ b/studio/components/interfaces/Settings/General/DeleteProjectPanel/DeleteProjectPanel.tsx
@@ -5,6 +5,7 @@ import { useStore } from 'hooks'
import Panel from 'components/ui/Panel'
import DeleteProjectButton from './DeleteProjectButton'
+import { FormHeader } from 'components/ui/Forms'
interface Props {}
@@ -16,7 +17,8 @@ const DeleteProjectPanel: FC = ({}) => {
return (
}>
+
+
= ({ projectRef }) => {
/>
) : (
- {feature.units === 'bytes' ? formatBytes(usageValue) : ''}
+ {feature.units === 'bytes'
+ ? formatBytes(usageValue)
+ : usageValue}
)}
diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnType.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnType.tsx
index 33ba1198cc3..a31f97e92ac 100644
--- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnType.tsx
+++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnType.tsx
@@ -37,7 +37,9 @@ const ColumnType: FC = ({
readOnly
disabled
label={showLabel ? 'Type' : ''}
- layout="horizontal"
+ layout={showLabel ? 'horizontal' : undefined}
+ className="md:gap-x-0"
+ size="small"
value={value}
descriptionText={
showLabel
diff --git a/studio/components/layouts/ProjectLayout/LayoutHeader/LayoutHeader.tsx b/studio/components/layouts/ProjectLayout/LayoutHeader/LayoutHeader.tsx
index a218a446c35..ebca922f3e1 100644
--- a/studio/components/layouts/ProjectLayout/LayoutHeader/LayoutHeader.tsx
+++ b/studio/components/layouts/ProjectLayout/LayoutHeader/LayoutHeader.tsx
@@ -63,7 +63,9 @@ const LayoutHeader = ({ customHeaderComponents, breadcrumbs = [], headerBorder =
{/* Project Dropdown */}
- {showOverUsageBadge && (
+
+ {/* [Joshen TODO] Temporarily hidden until usage endpoint is sorted out */}
+ {/* {showOverUsageBadge && (
- )}
+ )} */}
>
)}
>
diff --git a/studio/components/to-be-cleaned/Docs/Snippets.js b/studio/components/to-be-cleaned/Docs/Snippets.js
index 6e4995dc0a8..955409e15bb 100644
--- a/studio/components/to-be-cleaned/Docs/Snippets.js
+++ b/studio/components/to-be-cleaned/Docs/Snippets.js
@@ -366,7 +366,7 @@ curl -X POST '${endpoint}/rest/v1/${resourceId}' \\
-H "apikey: ${apiKey}" \\
-H "Authorization: Bearer ${apiKey}" \\
-H "Content-Type: application/json" \\
--H "Prefer: return=representation" \\
+-H "Prefer: return=minimal" \\
-d '{ "some_column": "someValue", "other_column": "otherValue" }'
`,
},
@@ -436,7 +436,7 @@ curl -X PATCH '${endpoint}/rest/v1/${resourceId}?some_column=eq.someValue' \\
-H "apikey: ${apiKey}" \\
-H "Authorization: Bearer ${apiKey}" \\
-H "Content-Type: application/json" \\
--H "Prefer: return=representation" \\
+-H "Prefer: return=minimal" \\
-d '{ "other_column": "otherValue" }'
`,
},
diff --git a/studio/components/to-be-cleaned/Storage/StorageSettings/StorageSettings.tsx b/studio/components/to-be-cleaned/Storage/StorageSettings/StorageSettings.tsx
index 6f78c2dd369..be80781933a 100644
--- a/studio/components/to-be-cleaned/Storage/StorageSettings/StorageSettings.tsx
+++ b/studio/components/to-be-cleaned/Storage/StorageSettings/StorageSettings.tsx
@@ -1,6 +1,6 @@
import useSWR from 'swr'
import { FC, useState } from 'react'
-import { Button, Form, Input, Listbox } from 'ui'
+import { Button, Form, IconClock, Input, Listbox } from 'ui'
import { useStore } from 'hooks'
import { patch, get } from 'lib/common/fetch'
@@ -173,6 +173,7 @@ const StorageConfig = ({ config, projectRef }: any) => {
{isFreeTier && (
}
primaryText="Free Plan has a fixed upload file size limit of 50 MB."
projectRef={projectRef}
secondaryText="Please upgrade to Pro plan for a configurable upload file size limit of up to 5 GB."
diff --git a/studio/components/ui/Dialogs/ConfirmDialog.js b/studio/components/ui/Dialogs/ConfirmDialog.js
index c9f8d79ee45..33e4df18d70 100644
--- a/studio/components/ui/Dialogs/ConfirmDialog.js
+++ b/studio/components/ui/Dialogs/ConfirmDialog.js
@@ -56,13 +56,20 @@ const ConfirmModal = ({
-
+
Cancel
{buttonLoadingLabel && loading
diff --git a/studio/components/ui/Forms/FormActions.tsx b/studio/components/ui/Forms/FormActions.tsx
index ff5806e0802..d5f4789ceac 100644
--- a/studio/components/ui/Forms/FormActions.tsx
+++ b/studio/components/ui/Forms/FormActions.tsx
@@ -7,6 +7,7 @@ interface Props {
helper?: React.ReactNode // Helper text to show alongside actions
disabled?: boolean
isSubmitting?: boolean
+ submitText?: string
}
const FormActions = ({
@@ -16,6 +17,7 @@ const FormActions = ({
helper,
disabled = false,
isSubmitting,
+ submitText = 'Save',
}: Props) => {
const isDisabled = isSubmitting || disabled || (!hasChanges && hasChanges !== undefined)
@@ -39,7 +41,7 @@ const FormActions = ({
disabled={isDisabled}
loading={isSubmitting}
>
- Save
+ {submitText}
diff --git a/studio/components/ui/UpgradeToPro.tsx b/studio/components/ui/UpgradeToPro.tsx
index 0266397cfdb..0dd4d6e3b46 100644
--- a/studio/components/ui/UpgradeToPro.tsx
+++ b/studio/components/ui/UpgradeToPro.tsx
@@ -1,38 +1,49 @@
-import { Button, IconClock } from 'ui'
+import { Button } from 'ui'
+import { observer } from 'mobx-react-lite'
import Link from 'next/link'
-import { FC } from 'react'
+import { FC, ReactNode } from 'react'
+import { useStore } from 'hooks'
+import { PRICING_TIER_PRODUCT_IDS } from 'lib/constants'
interface Props {
+ icon?: ReactNode
primaryText: string
projectRef: string
secondaryText: string
}
-const UpgradeToPro: FC = ({ primaryText, projectRef, secondaryText }) => (
-
-
-
-
-
-
-
-
{primaryText}
-
-
{secondaryText}
+const UpgradeToPro: FC
= ({ icon, primaryText, projectRef, secondaryText }) => {
+ const { ui } = useStore()
+ const tier = ui.selectedProject?.subscription_tier
+
+ return (
+
+
+ {icon &&
{icon}
}
+
-
-
Upgrade to Pro
-
-
-)
+ )
+}
-export default UpgradeToPro
+export default observer(UpgradeToPro)
diff --git a/studio/data/config/keys.ts b/studio/data/config/keys.ts
new file mode 100644
index 00000000000..875848a4247
--- /dev/null
+++ b/studio/data/config/keys.ts
@@ -0,0 +1,3 @@
+export const configKeys = {
+ settings: (projectRef: string | undefined) => ['projects', projectRef, 'settings'] as const,
+}
diff --git a/studio/data/config/project-settings-query.ts b/studio/data/config/project-settings-query.ts
new file mode 100644
index 00000000000..ad473e32e38
--- /dev/null
+++ b/studio/data/config/project-settings-query.ts
@@ -0,0 +1,122 @@
+import { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query'
+import { get } from 'lib/common/fetch'
+import { API_URL } from 'lib/constants'
+import { useCallback } from 'react'
+import { configKeys } from './keys'
+
+export type ProjectSettingsVariables = {
+ projectRef?: string
+}
+
+export type Project = {
+ id: number
+ name: string
+ ref: string
+ status: string
+ db_host: string
+ db_name: string
+ db_user: string
+ db_schema: string
+ db_port: number
+ db_ssl: boolean
+ services: Service[]
+}
+
+export type App = {
+ id: number
+ name: string
+}
+
+export type AppConfig = {
+ endpoint: string
+ db_schema: string
+ realtime_multitenant_enabled: boolean
+}
+
+export type Service = {
+ id: number
+ name: string
+ app_config: AppConfig
+ app: App
+ service_api_keys: ServiceApiKey[]
+}
+
+export type ServiceApiKey = {
+ api_key_encrypted: string
+ tags: string
+ name: string
+}
+
+export type AutoApiService = Service & {
+ endpoint: string
+ restUrl: string
+ project: Project
+ defaultApiKey: string
+ serviceApiKey: string
+}
+
+export type ProjectSettingsResponse = {
+ project: Project
+ autoApiService: AutoApiService
+}
+
+export async function getProjectSettings(
+ { projectRef }: ProjectSettingsVariables,
+ signal?: AbortSignal
+) {
+ if (!projectRef) {
+ throw new Error('projectRef is required')
+ }
+
+ const response = await get(`${API_URL}/props/project/${projectRef}/api`, {
+ signal,
+ })
+ if (response.error) {
+ throw response.error
+ }
+
+ return response as ProjectSettingsResponse
+}
+
+export type ProjectSettingsData = Awaited
>
+export type ProjectSettingsError = unknown
+
+export const useProjectSettingsQuery = (
+ { projectRef }: ProjectSettingsVariables,
+ {
+ enabled = true,
+ ...options
+ }: UseQueryOptions = {}
+) =>
+ useQuery(
+ configKeys.settings(projectRef),
+ ({ signal }) => getProjectSettings({ projectRef }, signal),
+ {
+ enabled: enabled && typeof projectRef !== 'undefined',
+ refetchInterval(data, query) {
+ if (!data) {
+ return false
+ }
+
+ const { autoApiService } = data as unknown as ProjectSettingsData
+
+ const apiKeys = autoApiService?.service_api_keys ?? []
+ const interval = apiKeys.length === 0 ? 2000 : 0
+
+ return interval
+ },
+ ...options,
+ }
+ )
+
+export const useProjectSettingsPrefetch = ({ projectRef }: ProjectSettingsVariables) => {
+ const client = useQueryClient()
+
+ return useCallback(() => {
+ if (projectRef) {
+ client.prefetchQuery(configKeys.settings(projectRef), ({ signal }) =>
+ getProjectSettings({ projectRef }, signal)
+ )
+ }
+ }, [projectRef])
+}
diff --git a/studio/data/custom-domains/custom-domains-activate-mutation.ts b/studio/data/custom-domains/custom-domains-activate-mutation.ts
new file mode 100644
index 00000000000..2fc29e5d231
--- /dev/null
+++ b/studio/data/custom-domains/custom-domains-activate-mutation.ts
@@ -0,0 +1,49 @@
+import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
+import { post } from 'lib/common/fetch'
+import { customDomainKeys } from './keys'
+
+export type CustomDomainActivateVariables = {
+ projectRef: string
+}
+
+export async function activateCustomDomain({ projectRef }: CustomDomainActivateVariables) {
+ if (!projectRef) {
+ throw new Error('projectRef is required')
+ }
+
+ const response = await post(
+ `${process.env.NEXT_PUBLIC_API_ADMIN_URL}/projects/${projectRef}/custom-hostname/activate`,
+ {}
+ )
+ if (response.error) {
+ throw response.error
+ }
+
+ return response
+}
+
+type CustomDomainActivateData = Awaited>
+
+export const useCustomDomainActivateMutation = ({
+ onSuccess,
+ ...options
+}: Omit<
+ UseMutationOptions,
+ 'mutationFn'
+> = {}) => {
+ const queryClient = useQueryClient()
+
+ return useMutation(
+ (vars) => activateCustomDomain(vars),
+ {
+ async onSuccess(data, variables, context) {
+ const { projectRef } = variables
+
+ await queryClient.invalidateQueries(customDomainKeys.list(projectRef))
+
+ await onSuccess?.(data, variables, context)
+ },
+ ...options,
+ }
+ )
+}
diff --git a/studio/data/custom-domains/custom-domains-create-mutation.ts b/studio/data/custom-domains/custom-domains-create-mutation.ts
new file mode 100644
index 00000000000..689ed60af47
--- /dev/null
+++ b/studio/data/custom-domains/custom-domains-create-mutation.ts
@@ -0,0 +1,55 @@
+import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
+import { post } from 'lib/common/fetch'
+import { customDomainKeys } from './keys'
+
+export type CustomDomainCreateVariables = {
+ projectRef: string
+ customDomain: string
+}
+
+export async function createCustomDomain({
+ projectRef,
+ customDomain,
+}: CustomDomainCreateVariables) {
+ if (!projectRef) {
+ throw new Error('projectRef is required')
+ }
+
+ const response = await post(
+ `${process.env.NEXT_PUBLIC_API_ADMIN_URL}/projects/${projectRef}/custom-hostname/initialize`,
+ {
+ custom_hostname: customDomain,
+ }
+ )
+ if (response.error) {
+ throw response.error
+ }
+
+ return response
+}
+
+type CustomDomainCreateData = Awaited>
+
+export const useCustomDomainCreateMutation = ({
+ onSuccess,
+ ...options
+}: Omit<
+ UseMutationOptions,
+ 'mutationFn'
+> = {}) => {
+ const queryClient = useQueryClient()
+
+ return useMutation(
+ (vars) => createCustomDomain(vars),
+ {
+ async onSuccess(data, variables, context) {
+ const { projectRef } = variables
+
+ await queryClient.invalidateQueries(customDomainKeys.list(projectRef))
+
+ await onSuccess?.(data, variables, context)
+ },
+ ...options,
+ }
+ )
+}
diff --git a/studio/data/custom-domains/custom-domains-delete-mutation.ts b/studio/data/custom-domains/custom-domains-delete-mutation.ts
new file mode 100644
index 00000000000..014ce571e4d
--- /dev/null
+++ b/studio/data/custom-domains/custom-domains-delete-mutation.ts
@@ -0,0 +1,58 @@
+import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
+import { delete_ } from 'lib/common/fetch'
+import { getCustomDomains } from './custom-domains-query'
+import { customDomainKeys } from './keys'
+
+export type CustomDomainDeleteVariables = {
+ projectRef: string
+}
+
+export async function deleteCustomDomain({ projectRef }: CustomDomainDeleteVariables) {
+ if (!projectRef) {
+ throw new Error('projectRef is required')
+ }
+
+ const response = await delete_(
+ `${process.env.NEXT_PUBLIC_API_ADMIN_URL}/projects/${projectRef}/custom-hostname`,
+ {}
+ )
+ if (response.error) {
+ throw response.error
+ }
+
+ return response
+}
+
+type CustomDomainDeleteData = Awaited>
+
+export const useCustomDomainDeleteMutation = ({
+ onSuccess,
+ ...options
+}: Omit<
+ UseMutationOptions,
+ 'mutationFn'
+> = {}) => {
+ const queryClient = useQueryClient()
+
+ return useMutation(
+ (vars) => deleteCustomDomain(vars),
+ {
+ async onSuccess(data, variables, context) {
+ const { projectRef } = variables
+
+ // we manually setQueriesData here instead of using
+ // the standard invalidateQueries is the custom domains
+ // endpoint doesn't immediately return the new state
+ queryClient.setQueriesData(customDomainKeys.list(projectRef), () => {
+ return {
+ customDomain: null,
+ status: '0_no_hostname_configured',
+ }
+ })
+
+ await onSuccess?.(data, variables, context)
+ },
+ ...options,
+ }
+ )
+}
diff --git a/studio/data/custom-domains/custom-domains-query.ts b/studio/data/custom-domains/custom-domains-query.ts
new file mode 100644
index 00000000000..645f756b8be
--- /dev/null
+++ b/studio/data/custom-domains/custom-domains-query.ts
@@ -0,0 +1,147 @@
+import { useQuery, useQueryClient, UseQueryOptions } from '@tanstack/react-query'
+import { get } from 'lib/common/fetch'
+import { useCallback } from 'react'
+import { customDomainKeys } from './keys'
+
+export type CustomDomainsVariables = {
+ projectRef?: string
+}
+
+type CustomDomainStatus =
+ | '0_not_allowed'
+ | '0_no_hostname_configured'
+ | '1_not_started'
+ | '2_initiated'
+ | '3_challenge_verified'
+ | '4_origin_setup_completed'
+ | '5_services_reconfigured'
+
+type Settings = {
+ http2: string
+ tls_1_3: string
+ min_tls_version: string
+}
+
+type Ssl = {
+ id: string
+ type: string
+ method: string
+ status: string
+ txt_name?: string
+ txt_value?: string
+ settings: Settings
+ wildcard: boolean
+ bundle_method: string
+ certificate_authority: string
+ validation_records?: {
+ status: string
+ txt_name: string
+ txt_value: string
+ }[]
+}
+
+type OwnershipVerification = {
+ name: string
+ type: string
+ value: string
+}
+
+type OwnershipVerificationHttp = {
+ http_url: string
+ http_body: string
+}
+
+export type CustomDomainResponse = {
+ id: string
+ ssl: Ssl
+ status: string
+ hostname: string
+ created_at: string
+ custom_metadata: any
+ verification_errors?: string[]
+ custom_origin_server: string
+ ownership_verification?: OwnershipVerification
+ ownership_verification_http?: OwnershipVerificationHttp
+}
+
+export async function getCustomDomains(
+ { projectRef }: CustomDomainsVariables,
+ signal?: AbortSignal
+) {
+ if (!projectRef) {
+ throw new Error('projectRef is required')
+ }
+
+ const response = (await get(
+ `${process.env.NEXT_PUBLIC_API_ADMIN_URL}/projects/${projectRef}/custom-hostname`,
+ {
+ signal,
+ }
+ )) as {
+ data: {
+ errors: any[]
+ result: CustomDomainResponse
+ success: boolean
+ messages: any[]
+ }
+ status: CustomDomainStatus
+ error: unknown
+ }
+
+ if (response.error) {
+ // not allowed error and no hostname configured error are
+ // valid steps in the process of setting up a custom domain
+ // so we convert them to data instead of errors
+
+ const isNotAllowedError =
+ (response.error as any)?.code === 400 &&
+ (response.error as any)?.message?.includes('not allowed to set up custom domain')
+
+ if (isNotAllowedError) {
+ return {
+ customDomain: null,
+ status: '0_not_allowed',
+ } as const
+ }
+
+ const isNoHostnameConfiguredError =
+ (response.error as any)?.code === 400 &&
+ (response.error as any)?.message?.includes('custom hostname configuration')
+
+ if (isNoHostnameConfiguredError) {
+ return {
+ customDomain: null,
+ status: '0_no_hostname_configured',
+ } as const
+ }
+
+ throw response.error
+ }
+
+ return { customDomain: response?.data?.result, status: response.status }
+}
+
+export type CustomDomainsData = Awaited>
+export type CustomDomainsError = unknown
+
+export const useCustomDomainsQuery = (
+ { projectRef }: CustomDomainsVariables,
+ { enabled = true, ...options }: UseQueryOptions = {}
+) =>
+ useQuery(
+ customDomainKeys.list(projectRef),
+ ({ signal }) => getCustomDomains({ projectRef }, signal),
+ { enabled: enabled && typeof projectRef !== 'undefined', ...options }
+ )
+
+export const useCustomDomainsPrefetch = ({ projectRef }: CustomDomainsVariables) => {
+ const client = useQueryClient()
+
+ return useCallback(() => {
+ if (projectRef) {
+ client.prefetchQuery(customDomainKeys.list(projectRef), ({ signal }) =>
+ getCustomDomains({ projectRef }, signal)
+ )
+ }
+ }, [projectRef])
+}
diff --git a/studio/data/custom-domains/custom-domains-reverify-mutation.ts b/studio/data/custom-domains/custom-domains-reverify-mutation.ts
new file mode 100644
index 00000000000..8b7e2c8c671
--- /dev/null
+++ b/studio/data/custom-domains/custom-domains-reverify-mutation.ts
@@ -0,0 +1,49 @@
+import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
+import { post } from 'lib/common/fetch'
+import { customDomainKeys } from './keys'
+
+export type CustomDomainReverifyVariables = {
+ projectRef: string
+}
+
+export async function reverifyCustomDomain({ projectRef }: CustomDomainReverifyVariables) {
+ if (!projectRef) {
+ throw new Error('projectRef is required')
+ }
+
+ const response = await post(
+ `${process.env.NEXT_PUBLIC_API_ADMIN_URL}/projects/${projectRef}/custom-hostname/reverify`,
+ {}
+ )
+ if (response.error) {
+ throw response.error
+ }
+
+ return response
+}
+
+type CustomDomainReverifyData = Awaited>
+
+export const useCustomDomainReverifyMutation = ({
+ onSuccess,
+ ...options
+}: Omit<
+ UseMutationOptions,
+ 'mutationFn'
+> = {}) => {
+ const queryClient = useQueryClient()
+
+ return useMutation(
+ (vars) => reverifyCustomDomain(vars),
+ {
+ async onSuccess(data, variables, context) {
+ const { projectRef } = variables
+
+ await queryClient.invalidateQueries(customDomainKeys.list(projectRef))
+
+ await onSuccess?.(data, variables, context)
+ },
+ ...options,
+ }
+ )
+}
diff --git a/studio/data/custom-domains/keys.ts b/studio/data/custom-domains/keys.ts
new file mode 100644
index 00000000000..e63bfeb67ca
--- /dev/null
+++ b/studio/data/custom-domains/keys.ts
@@ -0,0 +1,3 @@
+export const customDomainKeys = {
+ list: (projectRef: string | undefined) => ['projects', projectRef, 'custom-domains'] as const,
+}
diff --git a/studio/data/query-client.ts b/studio/data/query-client.ts
new file mode 100644
index 00000000000..ee9ce6f2522
--- /dev/null
+++ b/studio/data/query-client.ts
@@ -0,0 +1,21 @@
+import { QueryClient } from '@tanstack/react-query'
+import { useState } from 'react'
+
+let queryClient: QueryClient | undefined
+
+export function getQueryClient() {
+ const _queryClient = queryClient ?? new QueryClient()
+
+ // For SSG and SSR always create a new queryClient
+ if (typeof window === 'undefined') return _queryClient
+ // Create the queryClient once in the client
+ if (!queryClient) queryClient = _queryClient
+
+ return queryClient
+}
+
+export function useRootQueryClient() {
+ const [_queryClient] = useState(() => getQueryClient())
+
+ return _queryClient
+}
diff --git a/studio/lib/constants/metrics.tsx b/studio/lib/constants/metrics.tsx
index 14b79718a50..796925abace 100644
--- a/studio/lib/constants/metrics.tsx
+++ b/studio/lib/constants/metrics.tsx
@@ -144,6 +144,13 @@ export const METRICS = [
// provider: 'daily-stats',
// category: METRIC_CATEGORIES.API_AUTH,
// },
+
+ {
+ key: 'total_auth_billing_period_mau',
+ label: 'Auth Monthly Active User',
+ provider: 'daily-stats',
+ category: METRIC_CATEGORIES.API_AUTH,
+ },
{
key: 'total_auth_ingress',
label: 'Auth Ingress',
diff --git a/studio/package.json b/studio/package.json
index 80597c5488e..f127a053018 100644
--- a/studio/package.json
+++ b/studio/package.json
@@ -28,7 +28,9 @@
"@supabase/postgres-meta": "^0.26.1",
"@supabase/react-data-grid": "^7.1.0-beta.7",
"@supabase/shared-types": "^0.1.29",
- "@supabase/supabase-js": "^2.1.0",
+ "@supabase/supabase-js": "^2.1.1",
+ "@tanstack/react-query": "^4.16.1",
+ "@tanstack/react-query-devtools": "^4.16.1",
"ajv": "^8.6.3",
"analytics-node": "^3.5.0",
"awesome-debounce-promise": "^2.1.0",
@@ -132,6 +134,6 @@
"storybook-dark-mode": "^1.0.4",
"tailwindcss": "^3.0.22",
"ts-jest": "^27.0.7",
- "typescript": "4.3.4"
+ "typescript": "4.8.4"
}
}
diff --git a/studio/pages/_app.tsx b/studio/pages/_app.tsx
index 358300caca2..7272e752b9f 100644
--- a/studio/pages/_app.tsx
+++ b/studio/pages/_app.tsx
@@ -24,11 +24,14 @@ import Head from 'next/head'
import { AppPropsWithLayout } from 'types'
import { useEffect, useState } from 'react'
+import { Hydrate, QueryClientProvider } from '@tanstack/react-query'
+import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import { RootStore } from 'stores'
import { StoreProvider } from 'hooks'
import { GOTRUE_ERRORS } from 'lib/constants'
import { auth } from 'lib/gotrue'
import { dart } from 'lib/constants/prism'
+import { useRootQueryClient } from 'data/query-client'
import { PortalToast, RouteValidationWrapper, AppBannerWrapper } from 'components/interfaces/App'
import PageTelemetry from 'components/ui/PageTelemetry'
@@ -42,6 +45,7 @@ dayjs.extend(timezone)
dart(Prism)
function MyApp({ Component, pageProps }: AppPropsWithLayout) {
+ const queryClient = useRootQueryClient()
const [rootStore] = useState(() => new RootStore())
useEffect(() => {
@@ -96,21 +100,28 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
const getLayout = Component.getLayout ?? ((page) => page)
return (
-
-
-
- Supabase
-
-
-
-
-
- {getLayout( )}
-
-
-
-
-
+
+
+
+
+
+ Supabase
+
+
+
+
+
+
+ {getLayout( )}
+
+
+
+
+
+
+
+
+
)
}
export default MyApp
diff --git a/studio/pages/project/[ref]/database/backups/pitr.tsx b/studio/pages/project/[ref]/database/backups/pitr.tsx
index c0e74fcb7c9..2d0468532e2 100644
--- a/studio/pages/project/[ref]/database/backups/pitr.tsx
+++ b/studio/pages/project/[ref]/database/backups/pitr.tsx
@@ -13,6 +13,7 @@ import InformationBox from 'components/ui/InformationBox'
import UpgradeToPro from 'components/ui/UpgradeToPro'
import { PITRNotice, PITRSelection } from 'components/interfaces/Database/Backups/PITR'
import BackupsError from 'components/interfaces/Database/Backups/BackupsError'
+import { PRICING_TIER_PRODUCT_IDS } from 'lib/constants'
const DatabasePhysicalBackups: NextPageWithLayout = () => {
const { ui } = useStore()
@@ -51,6 +52,7 @@ const PITR = () => {
const { configuration, error, isLoading } = backups
const ref = ui.selectedProject?.ref ?? 'default'
+ const tier = ui.selectedProject?.subscription_tier
const isEnabled = configuration.walg_enabled
const isPITRSelfServeEnabled = useFlag('pitrSelfServe')
@@ -63,8 +65,12 @@ const PITR = () => {
return isPITRSelfServeEnabled ? (
) : (
{
- {projectTier !== undefined && }
+ {/* [Joshen TODO] Temporarily hidden until usage endpoint is sorted out */}
+ {/* {projectTier !== undefined && } */}
{project?.status === PROJECT_STATUS.INACTIVE &&
}
diff --git a/studio/pages/project/[ref]/settings/api.tsx b/studio/pages/project/[ref]/settings/api.tsx
index 15224b86f1e..63af221d05f 100644
--- a/studio/pages/project/[ref]/settings/api.tsx
+++ b/studio/pages/project/[ref]/settings/api.tsx
@@ -6,7 +6,6 @@ import { useStore } from 'hooks'
import { NextPageWithLayout } from 'types'
import { SettingsLayout } from 'components/layouts'
import ServiceList from 'components/interfaces/Settings/API/ServiceList'
-import { FormsContainer } from 'components/ui/Forms'
export const PageContext: any = createContext(null)
diff --git a/studio/pages/project/[ref]/settings/billing/invoices.tsx b/studio/pages/project/[ref]/settings/billing/invoices.tsx
index 29c76ba8d21..45481e4ff1b 100644
--- a/studio/pages/project/[ref]/settings/billing/invoices.tsx
+++ b/studio/pages/project/[ref]/settings/billing/invoices.tsx
@@ -57,7 +57,8 @@ const Settings: FC
= ({ project }) => {
return (
- {projectTier !== undefined &&
}
+ {/* [Joshen TODO] Temporarily hidden until usage endpoint is sorted out */}
+ {/* {projectTier !== undefined &&
} */}
Invoices
diff --git a/studio/pages/project/[ref]/settings/billing/subscription.tsx b/studio/pages/project/[ref]/settings/billing/subscription.tsx
index 34a925b6079..9744f34e911 100644
--- a/studio/pages/project/[ref]/settings/billing/subscription.tsx
+++ b/studio/pages/project/[ref]/settings/billing/subscription.tsx
@@ -58,7 +58,8 @@ const Settings: FC
= ({ project }) => {
return (
- {projectTier !== undefined &&
}
+ {/* [Joshen TODO] Temporarily hidden until usage endpoint is sorted out */}
+ {/* {projectTier !== undefined &&
} */}
{
+ const isCustomDomainsEnabled = useFlag('customDomains')
+
return (
+ {isCustomDomainsEnabled && }
)