mirror of
https://github.com/supabase/supabase.git
synced 2026-10-07 02:15:05 +03:00
24 files changed
+9717
-62133
No files matched your search
@@ -131,7 +131,7 @@ Our approach for client libraries is modular. Each sub-library is a standalone i
|
||||
<td>Go</td>
|
||||
<td>-</td>
|
||||
<td><a href="https://github.com/supabase-community/postgrest-go" target="_blank" rel="noopener noreferrer">postgrest-go</a></td>
|
||||
<td>-</td>
|
||||
<td><a href="https://github.com/supabase-community/gotrue-go" target="_blank" rel="noopener noreferrer">gotrue-go</a></td>
|
||||
<td>-</td>
|
||||
<td><a href="https://github.com/supabase-community/storage-go" target="_blank" rel="noopener noreferrer">storage-go</a></td>
|
||||
<td><a href="https://github.com/supabase-community/functions-go" target="_blank" rel="noopener noreferrer">functions-go</a></td>
|
||||
@@ -147,12 +147,12 @@ Our approach for client libraries is modular. Each sub-library is a standalone i
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Kotlin</td>
|
||||
<td>-</td>
|
||||
<td><a href="https://github.com/supabase-community/supabase-kt" target="_blank" rel="noopener noreferrer">supabase-kt</a></td>
|
||||
<td><a href="https://github.com/supabase-community/postgrest-kt" target="_blank" rel="noopener noreferrer">postgrest-kt</a></td>
|
||||
<td><a href="https://github.com/supabase-community/gotrue-kt" target="_blank" rel="noopener noreferrer">gotrue-kt</a></td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td>-</td>
|
||||
<td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Realtime" target="_blank" rel="noopener noreferrer">realtime-kt</a></td>
|
||||
<td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Storage" target="_blank" rel="noopener noreferrer">storage-kt</a></td>
|
||||
<td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Functions" target="_blank" rel="noopener noreferrer">functions-kt</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Python</td>
|
||||
@@ -188,7 +188,16 @@ Our approach for client libraries is modular. Each sub-library is a standalone i
|
||||
<td><a href="https://github.com/supabase-community/gotrue-swift" target="_blank" rel="noopener noreferrer">gotrue-swift</a></td>
|
||||
<td><a href="https://github.com/supabase-community/realtime-swift" target="_blank" rel="noopener noreferrer">realtime-swift</a></td>
|
||||
<td><a href="https://github.com/supabase-community/storage-swift" target="_blank" rel="noopener noreferrer">storage-swift</a></td>
|
||||
<td>-</td>
|
||||
<td><a href="https://github.com/supabase-community/functions-swift" target="_blank" rel="noopener noreferrer">functions-swift</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Godot Engine (GDScript)</td>
|
||||
<td><a href="https://github.com/supabase-community/godot-engine.supabase" target="_blank" rel="noopener noreferrer">supabase-gdscript</a></td>
|
||||
<td><a href="https://github.com/supabase-community/postgrest-gdscript" target="_blank" rel="noopener noreferrer">postgrest-gdscript</a></td>
|
||||
<td><a href="https://github.com/supabase-community/gotrue-gdscript" target="_blank" rel="noopener noreferrer">gotrue-gdscript</a></td>
|
||||
<td><a href="https://github.com/supabase-community/realtime-gdscript" target="_blank" rel="noopener noreferrer">realtime-gdscript</a></td>
|
||||
<td><a href="https://github.com/supabase-community/storage-gdscript" target="_blank" rel="noopener noreferrer">storage-gdscript</a></td>
|
||||
<td><a href="https://github.com/supabase-community/functions-gdscript" target="_blank" rel="noopener noreferrer">functions-gdscript</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
||||
@@ -38,6 +38,11 @@ const examples = [
|
||||
'Generate Open Graph images with Deno and Supabase Edge Functions, no framework needed.',
|
||||
href: 'https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/opengraph',
|
||||
},
|
||||
{
|
||||
name: 'OG Image Generation & Storage CDN Caching',
|
||||
description: 'Cache generated images with Supabase Storage CDN.',
|
||||
href: 'https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/og-image-with-storage-cdn',
|
||||
},
|
||||
{
|
||||
name: 'Get User Location',
|
||||
description: `Get user location data from user's IP address.`,
|
||||
|
||||
@@ -307,6 +307,7 @@ export const menuItems: NavMenu = {
|
||||
name: 'Developer Tools',
|
||||
url: undefined,
|
||||
items: [
|
||||
{ name: 'Estuary', url: '/guides/integrations/estuary', items: [] },
|
||||
{ name: 'pgMustard', url: '/guides/integrations/pgmustard', items: [] },
|
||||
{ name: 'Prisma', url: '/guides/integrations/prisma', items: [] },
|
||||
{ name: 'Sequin', url: '/guides/integrations/sequin', items: [] },
|
||||
|
||||
@@ -27,7 +27,7 @@ async function gen_v001(spec: CliSpec, dest: string) {
|
||||
|
||||
const commands = spec.commands.map((x) => {
|
||||
const isChild = x.subcommands.length < 1
|
||||
const heading = isChild ? `### ${x.summary} {#${x.id}}` : `## ${x.summary} {#${x.id}}`
|
||||
const heading = isChild ? `### ${x.summary} [#${x.id}]` : `## ${x.summary} [#${x.id}]`
|
||||
|
||||
return {
|
||||
...x,
|
||||
|
||||
@@ -21,7 +21,7 @@ By creating a supabase client with the auth context from the function, you can d
|
||||
|
||||
```js lines=14,17-19,22-23 title=supabase/functions/select-from-table-with-auth-rls/index.ts
|
||||
import { serve } from 'https://deno.land/std@0.131.0/http/server.ts'
|
||||
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.0.0-rc.12'
|
||||
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
|
||||
|
||||
serve(async (req: Request) => {
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
|
||||
export const meta = {
|
||||
id: 'estuary',
|
||||
title: 'Estuary',
|
||||
description: 'Create a real-time data pipeline connecting Firestore and Supabase to make migration simple.',
|
||||
}
|
||||
|
||||
Estuary Flow is a platform for creating [real-time data pipelines at scale](https://www.estuary.dev/).
|
||||
It combines the intuitive interface of an ELT service with an event-driven runtime and a variety of open-source connectors.
|
||||
|
||||
You can use Flow to migrate your data from Firestore to the Postgres database in your Supabase project.
|
||||
You do this by building a real-time pipeline that captures data from Firestore and materializes (loads) that data to Postgres.
|
||||
Once created, the pipeline backfills all your historical data from Firestore and continues to process new data events in real time.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before you begin, you'll need:
|
||||
|
||||
* An [Estuary account](go.estuary.dev/sign-up).
|
||||
|
||||
* For your Firestore database:
|
||||
* A Google service account with read access to your Firestore database, via [roles/datastore.viewer](https://cloud.google.com/datastore/docs/access/iam). You can assign this role when you [create the service account](https://cloud.google.com/iam/docs/creating-managing-service-accounts#creating), or [add it to an existing service account](https://cloud.google.com/iam/docs/granting-changing-revoking-access#single-role).
|
||||
* A generated [JSON service account key](https://cloud.google.com/iam/docs/creating-managing-service-account-keys#creating) for the service account.
|
||||
|
||||
* A Supabase project
|
||||
|
||||
## Step 1: Capture your Firestore data
|
||||
|
||||
You'll start by creating a **capture**, a task in Flow that connects to your data source system: in this case, Firestore. This process will create one or more data **collections**, backed by a real-time data lake.
|
||||
|
||||
1. Go to the Flow web application at [dashboard.estuary.dev](http://dashboard.estuary.dev) and sign in using the credentials provided by your Estuary account manager.
|
||||
|
||||
2. Click the **Captures** tab and choose **New Capture**.
|
||||
|
||||
3. Locate and select the **Google Firestore** card.
|
||||
|
||||
A form appears with the properties required for a Firestore capture.
|
||||
|
||||
4. Set a name for your capture.
|
||||
|
||||
Click inside the **Name** field to generate a drop-down menu of available **prefixes** and select one (likely, this will be the name of your organization). Append a unique capture name after the `/` to create the full name, for example, `acmeCo/myFirestoreCapture`.
|
||||
|
||||
5. Fill out the required properties for Firestore.
|
||||
|
||||
* **Database**: Flow can autodetect the database name, but you may optionally specify it here. This is helpful if the service account used has access to multiple Firebase projects. Your database name usually follows the format `projects/$PROJECTID/databases/(default)`.
|
||||
* **Credentials**: The JSON service account key created per the prerequisites.
|
||||
|
||||
6. Click **Discover Endpoint**.
|
||||
|
||||
Flow uses the provided configuration to initiate a connection with Firestore. It generates a capture specification and details of the collections that it will create, once published.
|
||||
|
||||
7. Use the **Specification Editor** to view the [JSON schemas](https://docs.estuary.dev/concepts/schemas/) for each collection and make sure they are formatted correctly for your needs. If they're not, you can edit them.
|
||||
|
||||
8. Click **Save and publish**.
|
||||
|
||||
You'll see a notification when the capture publishes successfully.
|
||||
|
||||
The data currently in your Firestore database has been captured, and future updates to it will be captured continuously.
|
||||
|
||||
Click **Materialize Collections** to continue.
|
||||
|
||||
## Step 2: Materialize your collections to Postgres
|
||||
|
||||
Next, you'll add a Postgres materialization to connect the captured collections to tables in your Supabase Postgres database.
|
||||
|
||||
1. On the **Create Materialization** page, search for and select the **PostgreSQL** tile.
|
||||
|
||||
A form appears with the properties required for a Postgres materialization.
|
||||
|
||||
2. Choose a unique name for your materialization like you did when naming your capture; for example, `acmeCo/mySupabaseMaterialization`.
|
||||
|
||||
3. Fill out the required properties for PostgreSQL. You can find most of these in Supabase by going to the **Settings** section and clicking **Database**.
|
||||
|
||||
* **Address**: Format at `<host>:<port>`.
|
||||
* **User**: Usually, this is `postgres`.
|
||||
* **Password**: The password you set when you created your Supabase project.
|
||||
|
||||
4. Scroll down to view the **Collection Selector** and fill in the **Table** field for each collection.
|
||||
|
||||
The collections you just created have already been selected, but you must provide names for the tables to which they'll be materialized.
|
||||
|
||||
5. Click **Discover Endpoint**.
|
||||
|
||||
Flow uses the provided configuration to initiate a connection to your Supabase Postgres database and generate a specification.
|
||||
|
||||
6. Click **Save and Publish**. You'll see a notification when the full materialization publishes successfully.
|
||||
|
||||
Your Firestore collections are copied to tables in Supabase. As long as you leave the capture and materialation running, any changes to the Firestore data will be reflected in Supabase in milliseconds.
|
||||
|
||||
## Resources
|
||||
|
||||
For more information, visit the [Flow docs](https://docs.estuary.dev/). In particular:
|
||||
|
||||
* [Guide to create a Data Flow](https://docs.estuary.dev/guides/create-dataflow/)
|
||||
* [Firestore capture connector](https://docs.estuary.dev/reference/Connectors/capture-connectors/google-firestore/)
|
||||
* [Postgres materializaiton connector](https://docs.estuary.dev/reference/Connectors/materialization-connectors/PostgreSQL/)
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
@@ -1185,7 +1185,7 @@ class SplashPageState extends State<SplashPage> {
|
||||
}
|
||||
} catch (_) {
|
||||
context.showErrorSnackBar(
|
||||
message: 'Error occured during session refresh',
|
||||
message: 'Error occurred during session refresh',
|
||||
);
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
RegisterPage.route(), (_) => false);
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# Open Graph (OG) Image Generation with Supabase Storage CDN Caching
|
||||
|
||||
Generate Open Graph images with Deno and Supabase Edge Functions and cache the generated image with Supabase Storage CDN.
|
||||
|
||||
- Docs: https://deno.land/x/og_edge@0.0.2
|
||||
- Examples: https://vercel.com/docs/concepts/functions/edge-functions/og-image-examples
|
||||
- Demo: https://obuldanrptloktxcffvn.functions.supabase.co/launchweek-ticket-og?ticketNumber=1234&username=thorwebdev&name=Thor%20%E9%9B%B7%E7%A5%9E%20Schaeff
|
||||
|
||||
## Run locally
|
||||
|
||||
```bash
|
||||
supabase functions serve og-image-with-storage-cdn --no-verify-jwt
|
||||
```
|
||||
|
||||
Navigate to http://localhost:54321/functions/v1/og-image-with-storage-cdn?ticketNumber=3524&username=thorwebdev&name=Thor%20%E9%9B%B7%E7%A5%9E%20Schaeff
|
||||
|
||||
## Deploy
|
||||
|
||||
```bash
|
||||
supabase functions deploy og-image-with-storage-cdn --no-verify-jwt
|
||||
```
|
||||
@@ -0,0 +1,205 @@
|
||||
import React from 'https://esm.sh/react@18.2.0?deno-std=0.140.0'
|
||||
import { ImageResponse } from 'https://deno.land/x/og_edge@0.0.4/mod.ts'
|
||||
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
|
||||
import { corsHeaders } from '../_shared/cors.ts'
|
||||
|
||||
const STORAGE_URL = 'https://obuldanrptloktxcffvn.supabase.co/storage/v1/object/public/images/lw6'
|
||||
const BACKGROUND_IMAGE_STD = `${STORAGE_URL}/lw6_ticket_regular.jpg`
|
||||
const BACKGROUND_IMAGE_GOLDEN = `${STORAGE_URL}/lw6_ticket_gold.jpg`
|
||||
const SUPA_CHECKMARK = `${STORAGE_URL}/supaverified.png`
|
||||
|
||||
// Load custom font
|
||||
const FONT_URL = `${STORAGE_URL}/CircularStd-Bold.otf`
|
||||
const font = fetch(new URL(FONT_URL, import.meta.url)).then((res) => res.arrayBuffer())
|
||||
|
||||
export async function handler(req: Request) {
|
||||
const url = new URL(req.url)
|
||||
const ticketNumber = url.searchParams.get('ticketNumber')
|
||||
const username = url.searchParams.get('username') ?? url.searchParams.get('amp;username')
|
||||
const name = url.searchParams.get('name') ?? url.searchParams.get('amp;name')
|
||||
const golden = url.searchParams.get('golden') ?? url.searchParams.get('amp;golden')
|
||||
|
||||
if (!username || !ticketNumber || !name)
|
||||
return new Response(JSON.stringify({ error: 'missing params' }), {
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
||||
status: 400,
|
||||
})
|
||||
|
||||
try {
|
||||
// Try to get image from Supabase Storage CDN.
|
||||
const storageResponse = await fetch(`${STORAGE_URL}/tickets/${username}.png?version=dev`) // TODO change for prod.
|
||||
if (storageResponse.ok) return storageResponse
|
||||
|
||||
// Else, generate image ad upload to storage.
|
||||
const fontData = await font
|
||||
const numDigits = `${Number(ticketNumber)}`.length
|
||||
const prefix = `00000000`.slice(numDigits)
|
||||
|
||||
const generatedImage = new ImageResponse(
|
||||
(
|
||||
<div
|
||||
style={{
|
||||
width: '2000px',
|
||||
height: '1000px',
|
||||
backgroundColor: '#000',
|
||||
color: '#fff',
|
||||
fontFamily: '"Circular"',
|
||||
overflow: 'hidden',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
{/* Background image */}
|
||||
<img
|
||||
width="2000"
|
||||
height="1000"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '0',
|
||||
left: '0',
|
||||
zIndex: '-9000',
|
||||
}}
|
||||
src={golden ? BACKGROUND_IMAGE_GOLDEN : BACKGROUND_IMAGE_STD}
|
||||
/>
|
||||
{/* GitHub Avatar image */}
|
||||
<img
|
||||
width="270"
|
||||
height="270"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '360',
|
||||
left: '460',
|
||||
borderRadius: 135,
|
||||
}}
|
||||
src={`https://github.com/${username}.png`}
|
||||
/>
|
||||
{/* Name & username */}
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'column',
|
||||
position: 'absolute',
|
||||
top: '360',
|
||||
left: '815',
|
||||
width: '600',
|
||||
height: '270',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'clip',
|
||||
}}
|
||||
>
|
||||
<p
|
||||
style={{
|
||||
fontSize: '70',
|
||||
lineHeight: 0.9,
|
||||
}}
|
||||
>
|
||||
{name}
|
||||
</p>
|
||||
{/* Username and supaverified checkmark */}
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
fontSize: '35',
|
||||
}}
|
||||
>
|
||||
<span>{`@${username}`}</span>
|
||||
<span
|
||||
style={{
|
||||
marginTop: '5',
|
||||
marginLeft: '10',
|
||||
}}
|
||||
>
|
||||
<img width="44" height="44" src={SUPA_CHECKMARK} />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{/* Date */}
|
||||
<p
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '750',
|
||||
left: '815',
|
||||
fontSize: '25',
|
||||
}}
|
||||
>
|
||||
December 12th, 2022
|
||||
</p>
|
||||
{/* URL */}
|
||||
<p
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: '750',
|
||||
left: '1120',
|
||||
fontSize: '25',
|
||||
}}
|
||||
>
|
||||
supabase.com/launch-week
|
||||
</p>
|
||||
{/* Ticket No */}
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
position: 'absolute',
|
||||
bottom: '385',
|
||||
right: '60',
|
||||
width: '705',
|
||||
height: '215',
|
||||
transform: 'rotate(90deg)',
|
||||
}}
|
||||
>
|
||||
<p
|
||||
style={{
|
||||
fontSize: '80',
|
||||
}}
|
||||
>
|
||||
{`№ ${prefix}${ticketNumber}`}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
{
|
||||
width: 2000,
|
||||
height: 1000,
|
||||
fonts: [
|
||||
{
|
||||
name: 'Circular',
|
||||
data: fontData,
|
||||
style: 'normal',
|
||||
},
|
||||
],
|
||||
headers: {
|
||||
'content-type': 'image/png',
|
||||
'cache-control': 'public, max-age=31536000, s-maxage=31536000, no-transform, immutable',
|
||||
'cdn-cache-control': 'max-age=31536000',
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const supabaseAdminClient = createClient(
|
||||
// Supabase API URL - env var exported by default when deployed.
|
||||
Deno.env.get('SUPABASE_URL') ?? '',
|
||||
// Supabase API SERVICE ROLE KEY - env var exported by default when deployed.
|
||||
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? ''
|
||||
)
|
||||
|
||||
// Upload image to storage.
|
||||
const { error } = await supabaseAdminClient.storage
|
||||
.from('images')
|
||||
.upload(`lw6/tickets/${username}.png`, generatedImage.body!, {
|
||||
// cacheControl: '31536000', // TODO add for prod
|
||||
upsert: false,
|
||||
})
|
||||
if (error) throw error
|
||||
|
||||
return generatedImage
|
||||
} catch (error) {
|
||||
return new Response(JSON.stringify({ error: error.message }), {
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
||||
status: 400,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
// Follow this setup guide to integrate the Deno language server with your editor:
|
||||
// https://deno.land/manual/getting_started/setup_your_environment
|
||||
// This enables autocomplete, go to definition, etc.
|
||||
|
||||
import { serve } from 'https://deno.land/std@0.140.0/http/server.ts'
|
||||
|
||||
import { handler } from './handler.tsx'
|
||||
|
||||
console.log(`Function "og-image-with-storage-cdn" up and running!`)
|
||||
|
||||
serve(handler)
|
||||
@@ -5,6 +5,14 @@ Generate Open Graph images with Deno and Supabase Edge Functions, no framework n
|
||||
- Docs: https://deno.land/x/og_edge@0.0.2
|
||||
- Demo: https://zdtdtxajzydjqzuktnqx.functions.supabase.co/opengraph
|
||||
|
||||
## Run locally
|
||||
|
||||
```bash
|
||||
supabase functions serve opengraph --no-verify-jwt
|
||||
```
|
||||
|
||||
Navigate to http://localhost:54321/functions/v1/opengraph
|
||||
|
||||
## Deploy
|
||||
|
||||
```bash
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'https://esm.sh/react@18.2.0?deno-std=0.140.0'
|
||||
import { ImageResponse } from 'https://deno.land/x/og_edge@0.0.2/mod.ts'
|
||||
import { ImageResponse } from 'https://deno.land/x/og_edge@0.0.4/mod.ts'
|
||||
|
||||
export async function handler(req: Request) {
|
||||
export function handler(req: Request) {
|
||||
return new ImageResponse(
|
||||
(
|
||||
<div
|
||||
|
||||
@@ -6,7 +6,6 @@ import { serve } from 'https://deno.land/std@0.140.0/http/server.ts'
|
||||
|
||||
import { handler } from './handler.tsx'
|
||||
|
||||
// @ts-ignore
|
||||
serve(handler)
|
||||
console.log(`Function "opengraph" up and running!`)
|
||||
|
||||
// To deploy: supabase functions deploy opengraph --no-verify-jwt
|
||||
serve(handler)
|
||||
@@ -4977,9 +4977,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.0.tgz",
|
||||
"integrity": "sha512-4KzwW3F3bk+KlzSOY57fj/Jx6LyRQ1nbcyIadehl+AnXjKT7gDO0ORdRi/84ixvMKTym6ZKuxvbzN62HDDU1Lg==",
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.1.tgz",
|
||||
"integrity": "sha512-ECceEFcAaNRybd3lsGQKas3ZlMVjN3cyWwMP25D2i0zWfyiytVbTpRPa34qrr+FHddtpBVOmq4H/DCv1O0lZRA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/cookie": "^0.4.1",
|
||||
@@ -15688,9 +15688,9 @@
|
||||
}
|
||||
},
|
||||
"engine.io": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.0.tgz",
|
||||
"integrity": "sha512-4KzwW3F3bk+KlzSOY57fj/Jx6LyRQ1nbcyIadehl+AnXjKT7gDO0ORdRi/84ixvMKTym6ZKuxvbzN62HDDU1Lg==",
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.1.tgz",
|
||||
"integrity": "sha512-ECceEFcAaNRybd3lsGQKas3ZlMVjN3cyWwMP25D2i0zWfyiytVbTpRPa34qrr+FHddtpBVOmq4H/DCv1O0lZRA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/cookie": "^0.4.1",
|
||||
|
||||
Generated
+9153
-10987
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,108 @@
|
||||
import HCaptcha from '@hcaptcha/react-hcaptcha'
|
||||
import { useStore } from 'hooks'
|
||||
import { auth } from 'lib/gotrue'
|
||||
import { useRef, useState } from 'react'
|
||||
import { useSWRConfig } from 'swr'
|
||||
import { Button, Form, Input } from 'ui'
|
||||
import { object, string } from 'yup'
|
||||
|
||||
const signInSchema = object({
|
||||
email: string().email('Must be a valid email').required('Email is required'),
|
||||
})
|
||||
|
||||
const SignInSSOForm = () => {
|
||||
const { ui } = useStore()
|
||||
const { cache } = useSWRConfig()
|
||||
|
||||
const [captchaToken, setCaptchaToken] = useState<string | null>(null)
|
||||
const captchaRef = useRef<HCaptcha>(null)
|
||||
|
||||
const onSignIn = async ({ email, password }: { email: string; password: string }) => {
|
||||
const toastId = ui.setNotification({
|
||||
category: 'loading',
|
||||
message: `Signing in...`,
|
||||
})
|
||||
|
||||
let token = captchaToken
|
||||
if (!token) {
|
||||
const captchaResponse = await captchaRef.current?.execute({ async: true })
|
||||
token = captchaResponse?.response ?? null
|
||||
}
|
||||
|
||||
const { data, error } = await auth.signInWithSSO({
|
||||
domain: email.split('@')[1],
|
||||
options: { captchaToken: token ?? undefined },
|
||||
})
|
||||
|
||||
if (!error) {
|
||||
// .clear() does actually exist on the cache object, but it's not in the types 🤦🏻
|
||||
// @ts-ignore
|
||||
cache.clear()
|
||||
|
||||
if (data) {
|
||||
// redirect to SSO identity provider page
|
||||
window.location.href = data.url
|
||||
}
|
||||
} else {
|
||||
captchaRef.current?.resetCaptcha()
|
||||
|
||||
ui.setNotification({
|
||||
id: toastId,
|
||||
category: 'error',
|
||||
message: error.message,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Form
|
||||
validateOnBlur
|
||||
id="signIn-form"
|
||||
initialValues={{ email: '' }}
|
||||
validationSchema={signInSchema}
|
||||
onSubmit={onSignIn}
|
||||
>
|
||||
{({ isSubmitting }: { isSubmitting: boolean }) => {
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<Input
|
||||
id="email"
|
||||
name="email"
|
||||
type="email"
|
||||
label="Email"
|
||||
placeholder="gavin@hooli.com"
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
|
||||
<div className="self-center">
|
||||
<HCaptcha
|
||||
ref={captchaRef}
|
||||
sitekey={process.env.NEXT_PUBLIC_HCAPTCHA_SITE_KEY!}
|
||||
size="invisible"
|
||||
onVerify={(token) => {
|
||||
setCaptchaToken(token)
|
||||
}}
|
||||
onExpire={() => {
|
||||
setCaptchaToken(null)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
block
|
||||
form="signIn-form"
|
||||
htmlType="submit"
|
||||
size="large"
|
||||
disabled={isSubmitting}
|
||||
loading={isSubmitting}
|
||||
>
|
||||
Sign In
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
||||
export default SignInSSOForm
|
||||
@@ -0,0 +1,16 @@
|
||||
import Link from 'next/link'
|
||||
import { Button, IconLock } from 'ui'
|
||||
|
||||
const SignInWithSSO = () => {
|
||||
return (
|
||||
<Link href="/sign-in-sso">
|
||||
<a>
|
||||
<Button block size="large" type="outline" icon={<IconLock width={18} height={18} />}>
|
||||
Continue with SSO
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
export default SignInWithSSO
|
||||
@@ -455,8 +455,8 @@ const SupportForm: FC<Props> = ({ setSentCategory }) => {
|
||||
id="message"
|
||||
label="Message"
|
||||
placeholder="Describe the issue you're facing, along with any relevant information. Please be as detailed and specific as possible."
|
||||
limit={500}
|
||||
labelOptional="500 character limit"
|
||||
limit={5000}
|
||||
labelOptional="5000 character limit"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-4 px-6">
|
||||
|
||||
+1
-1
@@ -71,7 +71,7 @@ const NotificationRow: FC<Props> = ({
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-grow mr-8 flex flex-col gap-4">
|
||||
<div className="w-full flex justify-end">
|
||||
<div className="w-full flex justify-between">
|
||||
<div className="w-9/10 space-y-2">
|
||||
{formatNotificationText(project, notification, ownerReassignStatus)}
|
||||
{formatNotificationCTAText(availableActions, ownerReassignStatus)}
|
||||
|
||||
@@ -487,7 +487,7 @@ curl -X POST '${endpoint}/auth/v1/signup' \\
|
||||
js: {
|
||||
language: 'js',
|
||||
code: `
|
||||
let { user, error } = await supabase.auth.signUp({
|
||||
let { data, error } = await supabase.auth.signUp({
|
||||
email: 'someone@email.com',
|
||||
password: '${randomPassword}'
|
||||
})
|
||||
@@ -511,7 +511,7 @@ curl -X POST '${endpoint}/auth/v1/token?grant_type=password' \\
|
||||
js: {
|
||||
language: 'js',
|
||||
code: `
|
||||
let { user, error } = await supabase.auth.signInWithPassword({
|
||||
let { data, error } = await supabase.auth.signInWithPassword({
|
||||
email: 'someone@email.com',
|
||||
password: '${randomPassword}'
|
||||
})
|
||||
@@ -534,7 +534,7 @@ curl -X POST '${endpoint}/auth/v1/magiclink' \\
|
||||
js: {
|
||||
language: 'js',
|
||||
code: `
|
||||
let { user, error } = await supabase.auth.signInWithOtp({
|
||||
let { data, error } = await supabase.auth.signInWithOtp({
|
||||
email: 'someone@email.com'
|
||||
})
|
||||
`,
|
||||
@@ -557,7 +557,7 @@ curl -X POST '${endpoint}/auth/v1/signup' \\
|
||||
js: {
|
||||
language: 'js',
|
||||
code: `
|
||||
let { user, error } = await supabase.auth.signUp({
|
||||
let { data, error } = await supabase.auth.signUp({
|
||||
phone: '+13334445555',
|
||||
password: 'some-password'
|
||||
})
|
||||
@@ -580,7 +580,7 @@ curl -X POST '${endpoint}/auth/v1/otp' \\
|
||||
js: {
|
||||
language: 'js',
|
||||
code: `
|
||||
let { user, error } = await supabase.auth.signInWithOtp({
|
||||
let { data, error } = await supabase.auth.signInWithOtp({
|
||||
phone: '+13334445555'
|
||||
})
|
||||
`,
|
||||
@@ -604,7 +604,7 @@ curl -X POST '${endpoint}/auth/v1/verify' \\
|
||||
js: {
|
||||
language: 'js',
|
||||
code: `
|
||||
let { session, error } = await supabase.auth.verifyOTP({
|
||||
let { data, error } = await supabase.auth.verifyOTP({
|
||||
phone: '+13334445555',
|
||||
token: '123456'
|
||||
})
|
||||
@@ -641,7 +641,7 @@ let { data, error } = await supabase.auth.api.inviteUserByEmail('someone@email.c
|
||||
js: {
|
||||
language: 'js',
|
||||
code: `
|
||||
let { user, error } = await supabase.auth.signInWithOAuth({
|
||||
let { data, error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'github'
|
||||
})
|
||||
`,
|
||||
@@ -660,7 +660,7 @@ curl -X GET '${endpoint}/auth/v1/user' \\
|
||||
js: {
|
||||
language: 'js',
|
||||
code: `
|
||||
const user = supabase.auth.user()
|
||||
const { data: { user } } = await supabase.auth.getUser()
|
||||
`,
|
||||
},
|
||||
}),
|
||||
@@ -705,7 +705,7 @@ let { data, error } = await supabase.auth.api.resetPasswordForEmail(email)
|
||||
js: {
|
||||
language: 'js',
|
||||
code: `
|
||||
const { user, error } = await supabase.auth.update({
|
||||
const { data, error } = await supabase.auth.updateUser({
|
||||
email: "new@email.com",
|
||||
password: "new-password",
|
||||
data: { hello: 'world' }
|
||||
|
||||
Generated
-51111
File diff suppressed because it is too large.
Load diff
+1
-1
@@ -28,7 +28,7 @@
|
||||
"@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.0.0",
|
||||
"@supabase/supabase-js": "^2.1.0",
|
||||
"ajv": "^8.6.3",
|
||||
"analytics-node": "^3.5.0",
|
||||
"awesome-debounce-promise": "^2.1.0",
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import SignInSSOForm from 'components/interfaces/SignIn/SignInSSOForm'
|
||||
import { SignInLayout } from 'components/layouts'
|
||||
import Link from 'next/link'
|
||||
import { NextPageWithLayout } from 'types'
|
||||
|
||||
const SignInSSOPage: NextPageWithLayout = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col gap-5">
|
||||
<SignInSSOForm />
|
||||
</div>
|
||||
|
||||
<div className="my-8 self-center text-sm">
|
||||
<div>
|
||||
<span className="text-scale-1000">Interested in SSO?</span>{' '}
|
||||
<a
|
||||
href="https://supabase.com/contact/enterprise"
|
||||
rel="noopener noreferrer"
|
||||
className="underline text-scale-1200 hover:text-scale-1100 transition"
|
||||
>
|
||||
Let Us Know
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
SignInSSOPage.getLayout = (page) => (
|
||||
<SignInLayout
|
||||
heading="Welcome back"
|
||||
subheading="Sign in to your enterprise account"
|
||||
logoLinkToMarketingSite={true}
|
||||
>
|
||||
{page}
|
||||
</SignInLayout>
|
||||
)
|
||||
|
||||
export default SignInSSOPage
|
||||
@@ -1,14 +1,20 @@
|
||||
import Link from 'next/link'
|
||||
|
||||
import { useFlag } from 'hooks'
|
||||
import { NextPageWithLayout } from 'types'
|
||||
import SignInForm from 'components/interfaces/SignIn/SignInForm'
|
||||
import SignInWithGitHub from 'components/interfaces/SignIn/SignInWithGitHub'
|
||||
import SignInWithSSO from 'components/interfaces/SignIn/SignInWithSSO'
|
||||
import { SignInLayout } from 'components/layouts'
|
||||
import Link from 'next/link'
|
||||
import { NextPageWithLayout } from 'types'
|
||||
|
||||
const SignInPage: NextPageWithLayout = () => {
|
||||
const showSsoLogin = useFlag('mfaSso')
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col gap-5">
|
||||
<SignInWithGitHub />
|
||||
{showSsoLogin && <SignInWithSSO />}
|
||||
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
|
||||
Reference in new issue
Block a user