Merge branch 'master' into chore/user-management-v2-part-3

This commit is contained in:
Joshen Lim committed 2024-10-02 16:02:43 +08:00
commit 7d17e73bd3
122 files changed
+6939 -740

No files matched your search

+1 -1
View File
@@ -163,7 +163,7 @@ Our approach for client libraries is modular. Each sub-library is a standalone i
<td>Kotlin</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/supabase-kt/tree/master/Postgrest" target="_blank" rel="noopener noreferrer">postgrest-kt</a></td>
<td><a href="https://github.com/supabase-community/supabase-kt/tree/master/GoTrue" target="_blank" rel="noopener noreferrer">gotrue-kt</a></td>
<td><a href="https://github.com/supabase-community/supabase-kt/tree/master/Auth" target="_blank" rel="noopener noreferrer">auth-kt</a></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>
@@ -1,15 +1,14 @@
'use client'
import * as React from 'react'
import { useRouter } from 'next/navigation'
import { DialogProps } from '@radix-ui/react-alert-dialog'
import { CircleIcon, LaptopIcon, MoonIcon, SunIcon } from 'lucide-react'
import { useTheme } from 'next-themes'
import { useRouter } from 'next/navigation'
import * as React from 'react'
import { docsConfig } from '@/config/docs'
import { cn } from '@/lib/utils'
import { Button } from 'ui'
import {
Button,
CommandDialog,
CommandEmpty_Shadcn_,
CommandGroup_Shadcn_,
@@ -17,6 +16,7 @@ import {
CommandItem_Shadcn_,
CommandList_Shadcn_,
CommandSeparator_Shadcn_,
DialogProps,
} from 'ui'
export function CommandMenu({ ...props }: DialogProps) {
@@ -1,15 +1,20 @@
'use client'
import * as React from 'react'
import { DropdownMenuTriggerProps } from '@radix-ui/react-dropdown-menu'
// import { NpmCommands } from 'types/unist'
// import { Event, trackEvent } from '@/lib/events'
import { cn } from 'ui'
import { Button } from 'ui'
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from 'ui'
import { Check, Copy } from 'lucide-react'
import {
Button,
cn,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
DropdownMenuTriggerProps,
} from 'ui'
interface CopyButtonProps extends React.HTMLAttributes<HTMLButtonElement> {
value: string
+2 -6
View File
@@ -1,10 +1,6 @@
import * as React from 'react'
import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'
import { cn as uiCN } from 'ui'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
export const cn = uiCN
export function formatDate(input: string | number): string {
const date = new Date(input)
+9 -2
View File
@@ -13,6 +13,7 @@
"typecheck": "contentlayer2 build && tsc --noEmit -p tsconfig.json"
},
"dependencies": {
"@hookform/resolvers": "^3.1.1",
"@radix-ui/react-alert-dialog": "^1.0.5",
"contentlayer2": "0.4.6",
"eslint-config-supabase": "*",
@@ -23,20 +24,26 @@
"markdown-wasm": "^1.2.0",
"next": "^14.2.10",
"next-contentlayer2": "0.4.6",
"next-themes": "^0.3.0",
"react": "^18.2.0",
"react-docgen": "^7.0.3",
"react-dom": "^18.2.0",
"react-hook-form": "^7.45.0",
"react-inlinesvg": "^4.0.4",
"react-wrap-balancer": "^1.1.0",
"recharts": "^2.12.7",
"rehype-autolink-headings": "^7.1.0",
"rehype-pretty-code": "^0.6.0",
"rehype-slug": "^6.0.0",
"remark": "^14.0.3",
"remark-code-import": "^1.2.0",
"remark-gfm": "^4.0.0",
"sonner": "^1.5.0",
"ts-morph": "^22.0.0",
"ui": "*",
"unist-util-visit": "^5.0.0"
"ui-patterns": "*",
"unist-util-visit": "^5.0.0",
"zod": "^3.22.4"
},
"devDependencies": {
"@shikijs/compat": "^1.1.7",
@@ -52,7 +59,7 @@
"shiki": "^1.1.7",
"tailwindcss": "^3.3.0",
"tsconfig": "*",
"typescript": "^5.5.0",
"typescript": "~5.5.0",
"unist-builder": "3.0.0"
}
}
@@ -1,12 +1,12 @@
'use client'
import * as React from 'react'
import { DropdownMenuCheckboxItemProps } from '@radix-ui/react-dropdown-menu'
import { Button } from 'ui'
import {
Button,
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuCheckboxItemProps,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuSeparator,
@@ -1,6 +1,4 @@
import { REGEXP_ONLY_DIGITS_AND_CHARS } from 'input-otp'
import { InputOTP, InputOTPGroup, InputOTPSlot } from 'ui'
import { InputOTP, InputOTPGroup, InputOTPSlot, REGEXP_ONLY_DIGITS_AND_CHARS } from 'ui'
export default function InputOTPPattern() {
return (
@@ -1,5 +1,4 @@
import { TreeView, TreeViewItem } from 'ui'
import { flattenTree } from 'react-accessible-treeview'
import { TreeView, TreeViewItem, flattenTree } from 'ui'
export default function TreeViewDemo() {
const data = {
@@ -1,5 +1,4 @@
import { TreeView, TreeViewItem } from 'ui'
import { flattenTree } from 'react-accessible-treeview'
import { TreeView, TreeViewItem, flattenTree } from 'ui'
export default function TreeViewDemo() {
const data = {
@@ -1,12 +1,13 @@
import { useState } from 'react'
import { TreeView, TreeViewItem } from 'ui'
import { flattenTree } from 'react-accessible-treeview'
import {
ContextMenu_Shadcn_,
ContextMenuContent_Shadcn_,
ContextMenuItem_Shadcn_,
ContextMenuSeparator_Shadcn_,
ContextMenuTrigger_Shadcn_,
flattenTree,
TreeView,
TreeViewItem,
} from 'ui'
const data = {
@@ -1,12 +1,13 @@
import { useState } from 'react'
import { TreeView, TreeViewItem } from 'ui'
import { flattenTree } from 'react-accessible-treeview'
import {
ContextMenu_Shadcn_,
ContextMenuContent_Shadcn_,
ContextMenuItem_Shadcn_,
ContextMenuSeparator_Shadcn_,
ContextMenuTrigger_Shadcn_,
flattenTree,
TreeView,
TreeViewItem,
} from 'ui'
const data = {
@@ -1188,7 +1188,7 @@ export const functions: NavMenuConstant = {
},
{
name: 'Integrating with Log Drains',
url: '/guides/functions/log-drains',
url: '/guides/platform/log-drains',
},
],
},
@@ -2260,6 +2260,17 @@ export const reference_kotlin_v2 = {
},
}
export const reference_kotlin_v3 = {
icon: 'reference-kotlin',
title: 'kotlin',
url: 'guides/reference/kotlin',
parent: '/reference',
pkg: {
name: '@supabase-community/supabase-kt',
repo: 'https://github.com/supabase-community/supabase-kt',
},
}
export const reference_cli = {
icon: 'reference-cli',
title: 'Supabase CLI',
@@ -2352,7 +2363,7 @@ export const references = [
},
{
label: 'supabase-kt',
versions: ['v2', 'v1'],
versions: ['v3', 'v2', 'v1'],
description: 'something about the reference',
icon: '/docs/img/icons/kotlin-icon.svg',
url: '/reference/kotlin/start',
@@ -30,6 +30,7 @@ enum MenuId {
RefSwiftV2 = 'reference_swift_v2',
RefKotlinV1 = 'reference_kotlin_v1',
RefKotlinV2 = 'reference_kotlin_v2',
RefKotlinV3 = 'reference_kotlin_v3',
RefCli = 'reference_cli',
RefApi = 'reference_api',
SelfHostingAuth = 'reference_self_hosting_auth',
@@ -166,6 +167,11 @@ const menus: Menu[] = [
{
id: MenuId.RefKotlinV2,
type: 'reference',
path: '/reference/kotlin/v2',
},
{
id: MenuId.RefKotlinV3,
type: 'reference',
path: '/reference/kotlin',
},
{
+2 -1
View File
@@ -4,7 +4,8 @@
// Basic UI things
import Link from 'next/link'
import { Accordion, Admonition, Alert, Button, CodeBlock, Image, markdownComponents } from 'ui'
import { Accordion, Alert, Button, CodeBlock, Image, markdownComponents } from 'ui'
import { Admonition } from 'ui-patterns/admonition'
import { GlassPanel } from 'ui-patterns/GlassPanel'
import { IconPanel } from 'ui-patterns/IconPanel'
import { TabPanel, Tabs } from '~/features/ui/Tabs'
+8
View File
@@ -162,6 +162,14 @@ You'll notice that this token is quite a bit longer, since it contains informati
}
```
If using the service role key, you'll need to pass it into both the `apikey` and `authorization` headers (again, only do this from a secure environment such as your own server):
```bash
curl "$YOUR_PROJECT_URL/rest/v1/colors?select=name" \
-H "apikey: $YOUR_SERVICE_ROLE_KEY" \
-H "authorization: Bearer $YOUR_SERVICE_ROLE_KEY"
```
Now that you understand what JWTs are and where they're used in Supabase, you can explore how to use them in combination with Row Level Security to start restricting access to certain tables, rows, and columns in your Postgres database.
## Resources
@@ -80,7 +80,7 @@ In the `build.gradle` (app) file, add these dependencies then press "Sync now."
```kotlin
implementation "io.github.jan-tennert.supabase:postgrest-kt:$supabase_version"
implementation "io.github.jan-tennert.supabase:storage-kt:$supabase_version"
implementation "io.github.jan-tennert.supabase:gotrue-kt:$supabase_version"
implementation "io.github.jan-tennert.supabase:auth-kt:$supabase_version"
implementation "io.ktor:ktor-client-android:$ktor_version"
implementation "io.ktor:ktor-client-core:$ktor_version"
implementation "io.ktor:ktor-utils:$ktor_version"
@@ -65,6 +65,12 @@ Exchange the authorization code for an access and refresh token by calling [`POS
- `redirect_uri`: This must be exactly the same URL used in the first step.
- (Recommended) `code_verifier`: If you used the PKCE flow in the first step, include the code verifier as `code_verifier`.
<Admonition type="note">
If your application need to support dynamically generated Redirect URLs, check out [Handling Dynamic Redirect URLs](#handling-dynamic-redirect-urls) section below.
</Admonition>
As per OAuth2 spec, provide the client id and client secret as basic auth header:
- `client_id`: The unique client ID identifying your OAuth App.
@@ -151,6 +157,11 @@ When creating a new project, you can either ask the user to provide a database p
You can configure the user's [custom SMTP settings](https://supabase.com/docs/guides/auth/auth-smtp) using the [`/config/auth` endpoint](https://api.supabase.com/api/v1#/projects%20config/updateV1AuthConfig).
### Handling Dynamic Redirect URLs
To handle multiple, dynamically generated redirect URLs within the same OAuth app, you can leverage the `state` query parameter. When starting the OAuth process, include the desired, encoded redirect URL in the `state` parameter.
Once authorization is complete, we will sends the `state` value back to your app. You can then verify its integrity and extract the correct redirect URL, decoding it and redirecting the user to the correct URL.
## Current limitations
Only some features are available until we roll out fine-grained access control. If you need full database access, you will need to prompt the user for their database password.
@@ -213,13 +213,17 @@ let data = try await supabase.storage.from("bucket")
```kotlin
val data = supabase.storage.from("bucket").downloadAuthenticated("image.jpg") {
size(800, 300)
transform {
size(800, 300)
}
}
//Or on JVM stream directly to a file
val file = File("image.jpg")
supabase.storage.from("bucket").downloadAuthenticatedTo("image.jpg", file) {
size(800, 300)
transform {
size(800, 300)
}
}
```
@@ -308,15 +312,19 @@ let data = try await supabase.storage.from("bucket")
```kotlin
val data = supabase.storage.from("bucket").downloadAuthenticated("image.jpg") {
size(200, 200)
format = "origin"
transform {
size(200, 200)
format = ImageTransformation.Format.ORIGIN
}
}
//Or on JVM stream directly to a file
val file = File("image.jpg")
supabase.storage.from("bucket").downloadAuthenticatedTo("image.jpg", file) {
size(200, 200)
format = "origin"
transform {
size(200, 200)
format = ImageTransformation.Format.ORIGIN
}
}
```
@@ -431,13 +439,17 @@ let data = try await supabase.storage.from("bucket")
```kotlin
val data = supabase.storage["bucket"].downloadAuthenticated("image.jpg") {
quality = 50
transform {
quality = 50
}
}
//Or on JVM stream directly to a file
val file = File("image.jpg")
supabase.storage["bucket"].downloadAuthenticatedTo("image.jpg", file) {
quality = 50
transform {
quality = 50
}
}
```
@@ -529,15 +541,19 @@ let data = try await supabase.storage.from("bucket")
```kotlin
val data = supabase.storage.from("bucket").downloadAuthenticated("image.jpg") {
size(800, 300)
resize = ImageTransformation.Resize.CONTAIN
transform {
size(800, 300)
resize = ImageTransformation.Resize.CONTAIN
}
}
//Or on JVM stream directly to a file
val file = File("image.jpg")
supabase.storage.from("bucket").downloadAuthenticatedTo("image.jpg", file) {
size(800, 300)
resize = ImageTransformation.Resize.CONTAIN
transform {
size(800, 300)
resize = ImageTransformation.Resize.CONTAIN
}
}
```
@@ -70,18 +70,10 @@ try await supabase.storage.from("bucket_name").upload(path: "file_path", file: f
<TabPanel id="kotlin" label="Kotlin">
```kotlin
val supabase = createSupabaseClient(supabaseUrl, supabaseKey) {
install(Storage)
}
suspend fun uploadData(bytes: ByteArray) {
supabase.storage.from("bucket_name").upload("file_path", bytes)
}
supabase.storage.from("bucket_name").upload("file_path", bytes)
//Or on JVM/Android: (This will stream the data from the file to supabase)
suspend fun uploadFile(file: File) {
supabase.storage.from("bucket_name").upload("file_path", file)
}
supabase.storage.from("bucket_name").upload("file_path", file)
```
</TabPanel>
@@ -147,6 +139,15 @@ try await supabase.storage.from("bucket_name")
)
```
</TabPanel>
<TabPanel id="kotlin" label="Kotlin">
```kotlin
supabase.storage.from("bucket_name").upload("file_path", bytes) {
upsert = true
}
```
</TabPanel>
<TabPanel id="python" label="Python">
@@ -214,6 +215,15 @@ try await supabase.storage.from("bucket_name")
)
```
</TabPanel>
<TabPanel id="kotlin" label="Kotlin">
```kotlin
supabase.storage.from("bucket_name").upload("file_path", bytes) {
contentType = ContentType.Image.JPEG
}
```
</TabPanel>
<TabPanel id="python" label="Python">
+5 -1
View File
@@ -77,9 +77,13 @@ export const REFERENCES = {
name: 'Kotlin',
library: 'supabase-kt',
libPath: 'kotlin',
versions: ['v2', 'v1'],
versions: ['v3', 'v2', 'v1'],
icon: 'reference-kotlin',
meta: {
v3: {
libId: 'reference_kotlin_v3',
specFile: 'supabase_kt_v3',
},
v2: {
libId: 'reference_kotlin_v2',
specFile: 'supabase_kt_v2',
+8 -5
View File
@@ -21,13 +21,15 @@ custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/supab
Add dependency to your build file using the BOM.
The available modules are:
- [**gotrue-kt**](https://github.com/supabase-community/supabase-kt/tree/master/GoTrue)
- [**auth-kt**](https://github.com/supabase-community/supabase-kt/tree/master/Auth)
- [**realtime-kt**](https://github.com/supabase-community/supabase-kt/tree/master/Realtime)
- [**storage-kt**](https://github.com/supabase-community/supabase-kt/tree/master/Storage)
- [**functions-kt**](https://github.com/supabase-community/supabase-kt/tree/master/Functions)
- [**postgrest-kt**](https://github.com/supabase-community/supabase-kt/tree/master/Postgrest)
- Other plugins also available [here](https://github.com/supabase-community/supabase-kt/tree/master/plugins)
Checkout the different READMEs for information about supported Kotlin targets.
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
@@ -41,7 +43,7 @@ custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/supab
```kotlin
implementation(platform("io.github.jan-tennert.supabase:bom:VERSION"))
implementation("io.github.jan-tennert.supabase:postgrest-kt")
implementation("io.github.jan-tennert.supabase:gotrue-kt")
implementation("io.github.jan-tennert.supabase:auth-kt")
implementation("io.github.jan-tennert.supabase:realtime-kt")
```
@@ -51,7 +53,7 @@ custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/supab
```groovy
implementation platform("io.github.jan-tennert.supabase:bom:VERSION")
implementation 'io.github.jan-tennert.supabase:postgrest-kt'
implementation 'io.github.jan-tennert.supabase:gotrue-kt'
implementation 'io.github.jan-tennert.supabase:auth-kt'
implementation 'io.github.jan-tennert.supabase:realtime-kt'
```
@@ -72,7 +74,7 @@ custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/supab
</dependency>
<dependency>
<groupId>io.github.jan-tennert.supabase</groupId>
<artifactId>gotrue-kt</artifactId>
<artifactId>auth-kt</artifactId>
</dependency>
<dependency>
<groupId>io.github.jan-tennert.supabase</groupId>
@@ -92,7 +94,8 @@ custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/supab
<RefSubLayout.Details>
You can find a list of engines [here](https://ktor.io/docs/http-client-engines.html)
Note that not all Ktor engines support Websockets. So if you plan to use the Realtime module, make sure to use an engine that supports Websockets. Checkout the [engine limitations](https://ktor.io/docs/client-engines.html#limitations) for more information.
- Note that not all Ktor engines support Websockets. So if you plan to use the Realtime module, make sure to use an engine that supports Websockets. Checkout the [engine limitations](https://ktor.io/docs/client-engines.html#limitations) for more information.
- If using `supabase-kt` 3.0.0 and above, you need to use Ktor version 3.0.0-rc-1 or later.
</RefSubLayout.Details>
<RefSubLayout.Examples>
+1 -1
View File
@@ -2,7 +2,7 @@ This reference documents every object and method available in Supabase's Kotlin
To see supported Kotlin targets, check the corresponding module README on [GitHub](https://github.com/supabase-community/supabase-kt).
To migrate from version 1.4.X to 2.0.0, see the [migration guide](https://github.com/supabase-community/supabase-kt/blob/master/MIGRATION.md)
To migrate from version 2.X to 3.0.0, see the [migration guide](https://github.com/supabase-community/supabase-kt/blob/master/MIGRATION.md)
<Admonition type="note">
+394
View File
@@ -0,0 +1,394 @@
---
id: installing
title: 'Installing & Initialization'
slug: installing
custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/supabase.yml
---
### Add one or more modules to your project
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
<a href="https://github.com/supabase-community/supabase-kt/releases" style={{ marginRight: '8px' }}>
<img src="https://img.shields.io/github/release/supabase-community/supabase-kt?label=stable" alt="latest stable supabase-kt version"/>
</a>
<a href="https://github.com/supabase-community/supabase-kt/releases">
<img src="https://img.shields.io/maven-central/v/io.github.jan-tennert.supabase/supabase-kt?label=experimental" alt="latest supabase-kt version"/>
</a>
</div>
Add dependency to your build file using the BOM.
The available modules are:
- [**gotrue-kt**](https://github.com/supabase-community/supabase-kt/tree/master/GoTrue)
- [**realtime-kt**](https://github.com/supabase-community/supabase-kt/tree/master/Realtime)
- [**storage-kt**](https://github.com/supabase-community/supabase-kt/tree/master/Storage)
- [**functions-kt**](https://github.com/supabase-community/supabase-kt/tree/master/Functions)
- [**postgrest-kt**](https://github.com/supabase-community/supabase-kt/tree/master/Postgrest)
- Other plugins also available [here](https://github.com/supabase-community/supabase-kt/tree/master/plugins)
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
size="small"
type="underlined"
defaultActiveId="kotlin"
queryGroup="build-file"
>
<TabPanel id="kotlin" label="build.gradle.kts">
```kotlin
implementation(platform("io.github.jan-tennert.supabase:bom:VERSION"))
implementation("io.github.jan-tennert.supabase:postgrest-kt")
implementation("io.github.jan-tennert.supabase:gotrue-kt")
implementation("io.github.jan-tennert.supabase:realtime-kt")
```
</TabPanel>
<TabPanel id="groovy" label="build.gradle">
```groovy
implementation platform("io.github.jan-tennert.supabase:bom:VERSION")
implementation 'io.github.jan-tennert.supabase:postgrest-kt'
implementation 'io.github.jan-tennert.supabase:gotrue-kt'
implementation 'io.github.jan-tennert.supabase:realtime-kt'
```
</TabPanel>
<TabPanel id="xml" label="pom.xml">
```xml
<dependency>
<groupId>io.github.jan-tennert.supabase</groupId>
<artifactId>bom</artifactId>
<version>VERSION</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.github.jan-tennert.supabase</groupId>
<artifactId>postgrest-kt</artifactId>
</dependency>
<dependency>
<groupId>io.github.jan-tennert.supabase</groupId>
<artifactId>gotrue-kt</artifactId>
</dependency>
<dependency>
<groupId>io.github.jan-tennert.supabase</groupId>
<artifactId>realtime-kt</artifactId>
</dependency>
```
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
### Add Ktor Client Engine to each of your Kotlin targets (required)
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
You can find a list of engines [here](https://ktor.io/docs/http-client-engines.html)
Note that not all Ktor engines support Websockets. So if you plan to use the Realtime module, make sure to use an engine that supports Websockets. Checkout the [engine limitations](https://ktor.io/docs/client-engines.html#limitations) for more information.
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
size="small"
type="underlined"
defaultActiveId="kotlin"
queryGroup="build-file"
>
<TabPanel id="kotlin" label="build.gradle.kts">
```kotlin
implementation("io.ktor:ktor-client-[engine]:KTOR_VERSION")
```
</TabPanel>
<TabPanel id="groovy" label="build.gradle">
```groovy
implementation 'io.ktor:ktor-client-[engine]:KTOR_VERSION'
```
</TabPanel>
<TabPanel id="xml" label="pom.xml">
```xml
<dependency>
<groupId>io.ktor</groupId>
<artifactId>ktor-client-[engine]</artifactId>
<version>KTOR_VERSION</version>
</dependency>
```
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
<RefSubLayout.Details>
Multiplatform example:
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
size="small"
type="underlined"
defaultActiveId="kotlin"
queryGroup="build-file"
>
<TabPanel id="kotlin" label="build.gradle.kts">
```kotlin
val commonMain by getting {
dependencies {
//supabase modules
}
}
val jvmMain by getting {
dependencies {
implementation("io.ktor:ktor-client-cio:KTOR_VERSION")
}
}
val androidMain by getting {
dependsOn(jvmMain)
}
val jsMain by getting {
dependencies {
implementation("io.ktor:ktor-client-js:KTOR_VERSION")
}
}
val iosMain by getting {
dependencies {
implementation("io.ktor:ktor-client-darwin:KTOR_VERSION")
}
}
```
</TabPanel>
</Tabs>
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
### Serialization
supabase-kt provides several different ways to encode and decode your custom objects.
By default, [KotlinX Serialization](https://github.com/Kotlin/kotlinx.serialization) is used.
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
Use [KotlinX Serialization](https://github.com/Kotlin/kotlinx.serialization).
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
size="small"
type="underlined"
defaultActiveId="kotlin"
queryGroup="build-file"
>
<TabPanel id="kotlin" label="build.gradle.kts">
```kotlin
plugins {
kotlin("plugin.serialization") version "KOTLIN_VERSION"
}
```
</TabPanel>
<TabPanel id="groovy" label="build.gradle">
```groovy
plugins {
id 'org.jetbrains.kotlin.plugin.serialization' version 'KOTLIN_VERSION'
}
```
</TabPanel>
<TabPanel id="xml" label="pom.xml">
```xml
<build>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
<configuration>
<compilerPlugins>
<plugin>kotlinx-serialization</plugin>
</compilerPlugins>
</configuration>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-serialization</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
```
</TabPanel>
</Tabs>
```kotlin
val supabase = createSupabaseClient(supabaseUrl, supabaseKey) {
//Already the default serializer, but you can provide a custom Json instance (optional):
defaultSerializer = KotlinXSerializer(Json {
//apply your custom config
})
}
```
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
Use [Moshi](https://github.com/square/moshi).
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
size="small"
type="underlined"
defaultActiveId="kotlin"
queryGroup="build-file"
>
<TabPanel id="kotlin" label="build.gradle.kts">
```kotlin
implementation("io.github.jan-tennert.supabase:serializer-moshi:VERSION")
```
</TabPanel>
<TabPanel id="groovy" label="build.gradle">
```groovy
implementation 'io.github.jan-tennert.supabase:serializer-moshi:VERSION'
```
</TabPanel>
<TabPanel id="xml" label="pom.xml">
```xml
<dependency>
<groupId>io.github.jan-tennert.supabase</groupId>
<artifactId>serializer-moshi</artifactId>
<version>VERSION</version>
</dependency>
```
</TabPanel>
</Tabs>
```kotlin
val supabase = createSupabaseClient(supabaseUrl, supabaseKey) {
defaultSerializer = MoshiSerializer()
}
```
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
Use [Jackson](https://github.com/FasterXML/jackson-module-kotlin).
</RefSubLayout.Details>
<RefSubLayout.Examples>
<Tabs
size="small"
type="underlined"
defaultActiveId="kotlin"
queryGroup="build-file"
>
<TabPanel id="kotlin" label="build.gradle.kts">
```kotlin
implementation("io.github.jan-tennert.supabase:serializer-jackson:VERSION")
```
</TabPanel>
<TabPanel id="groovy" label="build.gradle">
```groovy
implementation 'io.github.jan-tennert.supabase:serializer-jackson:VERSION'
```
</TabPanel>
<TabPanel id="xml" label="pom.xml">
```xml
<dependency>
<groupId>io.github.jan-tennert.supabase</groupId>
<artifactId>serializer-jackson</artifactId>
<version>VERSION</version>
</dependency>
```
</TabPanel>
</Tabs>
```kotlin
val supabase = createSupabaseClient(supabaseUrl, supabaseKey) {
defaultSerializer = JacksonSerializer()
}
```
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
<RefSubLayout.EducationRow>
<RefSubLayout.Details>
Use custom serializer.
</RefSubLayout.Details>
<RefSubLayout.Examples>
```kotlin
class CustomSerializer: SupabaseSerializer {
override fun <T : Any> encode(type: KType, value: T): String {
//encode value to string
}
override fun <T : Any> decode(type: KType, value: String): T {
//decode value
}
}
```
```kotlin
val supabase = createSupabaseClient(supabaseUrl, supabaseKey) {
defaultSerializer = CustomSerializer()
}
```
</RefSubLayout.Examples>
</RefSubLayout.EducationRow>
@@ -0,0 +1,13 @@
This reference documents every object and method available in Supabase's Kotlin Multiplatform library, [supabase-kt](https://github.com/supabase-community/supabase-kt). You can use supabase-kt to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.
To see supported Kotlin targets, check the corresponding module README on [GitHub](https://github.com/supabase-community/supabase-kt).
To migrate from version 1.4.X to 2.0.0, see the [migration guide](https://github.com/supabase-community/supabase-kt/blob/master/MIGRATION.md)
<Admonition type="note">
The Kotlin client library is created and maintained by the Supabase community, and is not an official library. Please be tolerant of areas where the library is still being developed, and — as with all the libraries — feel free to contribute wherever you find issues.
Huge thanks to official maintainer, [jan-tennert](https://github.com/jan-tennert).
</Admonition>
+4 -3
View File
@@ -1,9 +1,10 @@
import { ArrowDown, Check, X } from 'lucide-react'
import Link from 'next/link'
import { Button, Image } from 'ui'
import { Admonition } from 'ui-patterns/admonition'
import { GlassPanel } from 'ui-patterns/GlassPanel'
import { IconPanel } from 'ui-patterns/IconPanel'
import SqlToRest from 'ui-patterns/SqlToRest'
import { Admonition, Button, Image } from 'ui'
import { Heading } from 'ui/src/components/CustomHTMLElements'
import { AppleSecretGenerator } from '~/components/AppleSecretGenerator'
import AuthProviders from '~/components/AuthProviders'
@@ -21,8 +22,8 @@ import {
HuggingFaceDeployment,
KotlinProjectSetup,
MigrationWarnings,
ProjectSetup,
OAuthPkceFlow,
ProjectSetup,
QuickstartDbSetup,
QuickstartIntro,
SocialProviderSettingsSupabase,
@@ -37,7 +38,7 @@ import StepHikeCompact from '~/components/StepHikeCompact'
import { CodeSampleWrapper } from '~/features/directives/CodeSample.client'
import { Accordion, AccordionItem } from '~/features/ui/Accordion'
import * as CH from '~/features/ui/CodeHike'
import { Tabs, TabPanel } from '~/features/ui/Tabs'
import { TabPanel, Tabs } from '~/features/ui/Tabs'
const components = {
Accordion,
@@ -11,7 +11,7 @@
import { MDXProvider } from '@mdx-js/react'
import { type PropsWithChildren } from 'react'
import { Admonition } from 'ui'
import { Admonition } from 'ui-patterns/admonition'
const components = { Admonition }
+4
View File
@@ -123,6 +123,10 @@ const levelsData = {
icon: 'reference-kotlin',
name: 'Kotlin Reference v2.0',
},
reference_kotlin_v3: {
icon: 'reference-kotlin',
name: 'Kotlin Reference v3.0',
},
reference_cli: {
icon: 'reference-cli',
name: 'CLI Reference',
@@ -1,6 +1,6 @@
import type { Content, Paragraph, Root } from 'mdast'
import type { MdxJsxFlowElement } from 'mdast-util-mdx'
import type { AdmonitionProps } from 'ui'
import type { AdmonitionProps } from 'ui-patterns/admonition'
import type { Node } from 'unist'
import { visit } from 'unist-util-visit'
+1
View File
@@ -1,5 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference types="next/navigation-types/compat/navigation" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
+15 -2
View File
@@ -36,12 +36,14 @@
"@octokit/graphql": "^7.0.2",
"@octokit/plugin-paginate-graphql": "^4.0.0",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-collapsible": "^1.0.3",
"@supabase/auth-helpers-react": "^0.4.2",
"@supabase/supabase-js": "^2.44.3",
"@tailwindcss/container-queries": "^0.1.1",
"@tanstack/react-query": "^5.13.4",
"acorn": "^8.11.3",
"acorn-typescript": "^1.4.13",
"ai-commands": "*",
"common": "*",
"common-tags": "^1.8.2",
"config": "*",
@@ -50,8 +52,10 @@
"github-slugger": "^2.0.0",
"gray-matter": "^4.0.3",
"hast-util-has-property": "^2.0.1",
"hast-util-to-html": "^9.0.0",
"icons": "*",
"isbot": "^5.1.2",
"js-yaml": "^3.14.1",
"jsrsasign": "^11.0.0",
"katex": "^0.16.10",
"lodash": "^4.17.21",
@@ -61,6 +65,8 @@
"mdast-util-from-markdown": "^1.2.0",
"mdast-util-gfm": "^2.0.2",
"mdast-util-mdx": "^2.0.0",
"mdast-util-mdx-jsx": "^2.0.0",
"mdast-util-to-hast": "^12.3.0",
"mdast-util-to-markdown": "^1.5.0",
"mdast-util-to-string": "^3.1.1",
"micromark-extension-gfm": "^2.0.3",
@@ -73,10 +79,12 @@
"openai": "^4.20.1",
"openapi-fetch": "^0.8.2",
"react": "^18.2.0",
"react-copy-to-clipboard": "^5.1.0",
"react-dom": "^18.2.0",
"react-error-boundary": "^4.0.12",
"react-intersection-observer": "^9.5.3",
"react-markdown": "^8.0.3",
"react-use": "^17.5.0",
"rehype-katex": "^7.0.0",
"rehype-slug": "^5.1.0",
"remark": "^14.0.2",
@@ -91,16 +99,21 @@
"unist-builder": "^3.0.1",
"unist-util-filter": "^4.0.1",
"unist-util-visit": "^4.1.2",
"unist-util-visit-parents": "^5.0.0",
"uuid": "^9.0.1",
"valtio": "^1.12.0",
"yaml": "^2.4.5"
"yaml": "^2.4.5",
"zod": "^3.22.4"
},
"devDependencies": {
"@aws-sdk/client-secrets-manager": "^3.410.0",
"@types/estree": "1.0.5",
"@types/hast": "^2.3.4",
"@types/jsrsasign": "^10.5.12",
"@types/mdast": "^3.0.0",
"@types/node": "^20.11.16",
"@types/react": "^18.2.24",
"@types/react-copy-to-clipboard": "^5.0.4",
"@types/unist": "^2.0.6",
"api-types": "*",
"cheerio": "^1.0.0-rc.12",
@@ -115,7 +128,7 @@
"slugify": "^1.6.6",
"tsconfig": "*",
"tsx": "^4.6.2",
"typescript": "^5.5.0",
"typescript": "~5.5.0",
"vite-tsconfig-paths": "^4.3.2",
"vitest": "^2.0.2"
}
@@ -33,7 +33,8 @@
"reference_swift_v1",
"reference_swift_v2",
"reference_kotlin_v1",
"reference_kotlin_v2"
"reference_kotlin_v2",
"reference_kotlin_v3"
]
},
{
@@ -50,7 +51,8 @@
"reference_swift_v1",
"reference_swift_v2",
"reference_kotlin_v1",
"reference_kotlin_v2"
"reference_kotlin_v2",
"reference_kotlin_v3"
]
},
{
@@ -989,7 +991,8 @@
"reference_kotlin_v2",
"reference_python_v2",
"reference_swift_v1",
"reference_swift_v2"
"reference_swift_v2",
"reference_kotlin_v3"
],
"items": [
{
File diff suppressed because it is too large. Load diff
+5
View File
@@ -0,0 +1,5 @@
// types/mdx.d.ts
declare module '*.mdx' {
let MDXComponent: (props) => JSX.Element
export default MDXComponent
}
@@ -1,3 +1,4 @@
import { describe, test, expect } from 'vitest'
import { formatEstimatedCount } from './Pagination.utils'
describe('formatEstimatedCount', () => {
@@ -184,7 +184,7 @@ const ProviderForm = ({ config, provider }: ProviderFormProps) => {
}
useEffect(() => {
if (urlProvider === provider.title.toLowerCase()) {
if (urlProvider?.toLowerCase() === provider.title.toLowerCase()) {
setOpen(true)
ref.current?.scrollIntoView({ behavior: 'smooth', block: 'start' })
}
@@ -1383,7 +1383,7 @@ const PROVIDER_SAML = {
$schema: JSON_SCHEMA_VERSION,
type: 'object',
title: 'SAML 2.0',
link: 'https://supabase.com/docs/guides/auth/enterprise-sso',
link: 'https://supabase.com/docs/guides/auth/enterprise-sso/auth-sso-saml',
properties: {
SAML_ENABLED: {
title: 'Enable SAML 2.0 Single Sign-on',
@@ -20,7 +20,8 @@ import { useProjectApiQuery } from 'data/config/project-api-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { BASE_PATH } from 'lib/constants'
import { timeout } from 'lib/helpers'
import { Admonition, Button, cn, Separator } from 'ui'
import { Button, cn, Separator } from 'ui'
import { Admonition } from 'ui-patterns/admonition'
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
import { PROVIDERS_SCHEMAS } from '../AuthProvidersFormValidation'
import { BanUserModal } from './BanUserModal'
@@ -45,9 +46,9 @@ export const UserOverview = ({ user, onDeleteSuccess }: UserOverviewProps) => {
const isPhoneAuth = user.phone !== null
const isBanned = user.banned_until !== null
const providers = (user.raw_app_meta_data?.providers ?? []).map((provider) => {
const providers = (user.raw_app_meta_data?.providers ?? []).map((provider: string) => {
return {
name: provider,
name: provider.startsWith('sso') ? 'SAML' : provider,
icon:
provider === 'email'
? `${BASE_PATH}/img/icons/email-icon2.svg`
@@ -235,10 +236,13 @@ export const UserOverview = ({ user, onDeleteSuccess }: UserOverviewProps) => {
<div className="flex-grow mt-0.5">
<p className="capitalize">{provider.name}</p>
<p className="text-xs text-foreground-light">
Signed in with a {providerName} account via OAuth
Signed in with a {providerName} account via{' '}
{providerName === 'SAML' ? 'SSO' : 'OAuth'}
</p>
<Button asChild type="default" className="mt-2">
<Link href={`/project/${projectRef}/auth/providers?provider=${provider.name}`}>
<Link
href={`/project/${projectRef}/auth/providers?provider=${provider.name === 'SAML' ? 'SAML 2.0' : provider.name}`}
>
Configure {providerName} provider
</Link>
</Button>
@@ -21,7 +21,10 @@ export const isAtBottom = ({ currentTarget }: UIEvent<HTMLDivElement>): boolean
export const formatUsersData = (users: User[]) => {
return users.map((user) => {
const provider: string = user.raw_app_meta_data?.provider ?? ''
const providers: string[] = user.raw_app_meta_data?.providers ?? []
const providers: string[] = (user.raw_app_meta_data?.providers ?? []).map((x: string) => {
if (x.startsWith('sso')) return 'SAML'
return x
})
return {
id: user.id,
@@ -34,9 +37,11 @@ export const formatUsersData = (users: User[]) => {
.map((p) => {
return p === 'email'
? `${BASE_PATH}/img/icons/email-icon2.svg`
: providerIconMap[p]
? `${BASE_PATH}/img/icons/${providerIconMap[p]}.svg`
: undefined
: p === 'SAML'
? `${BASE_PATH}/img/icons/saml-icon.svg`
: providerIconMap[p]
? `${BASE_PATH}/img/icons/${providerIconMap[p]}.svg`
: undefined
})
.filter(Boolean),
// I think it's alright to just check via the main provider since email and phone should be mutually exclusive
@@ -207,7 +212,7 @@ export const formatUserColumns = ({
: Array.isArray(value)
? value.join(', ')
: value
const isConfirmed = user?.email_confirmed_at || user?.phone_confirmed_at
const isConfirmed = !!user?.confirmed_at
if (col.id === 'img') {
return (
@@ -93,6 +93,7 @@ export const UsersV2 = () => {
const {
data,
error,
isSuccess,
isLoading,
isRefetching,
isError,
@@ -123,7 +124,7 @@ export const UsersV2 = () => {
})
// eslint-disable-next-line react-hooks/exhaustive-deps
const totalUsers = useMemo(() => countData?.result[0].count, [countData?.result[0].count])
const totalUsers = useMemo(() => countData?.result[0].count ?? 0, [countData?.result[0].count])
const users = useMemo(() => data?.pages.flatMap((page) => page.result), [data?.pages])
const handleScroll = (event: UIEvent<HTMLDivElement>) => {
@@ -171,8 +172,9 @@ export const UsersV2 = () => {
useEffect(() => {
if (
isSuccessStorage ||
(isErrorStorage && (errorStorage as Error).message.includes('data is undefined'))
!isRefetching &&
(isSuccessStorage ||
(isErrorStorage && (errorStorage as Error).message.includes('data is undefined')))
) {
const columns = formatUserColumns({
config: columnConfiguration ?? [],
@@ -186,7 +188,7 @@ export const UsersV2 = () => {
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isSuccessStorage, isErrorStorage, errorStorage])
}, [isSuccess, isRefetching, isSuccessStorage, isErrorStorage, errorStorage])
return (
<div className="h-full flex flex-col">
@@ -1,33 +1,25 @@
import { zodResolver } from '@hookform/resolvers/zod'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { Info, PlusIcon } from 'lucide-react'
import { useRouter } from 'next/router'
import { useEffect } from 'react'
import { useForm } from 'react-hook-form'
import { toast } from 'sonner'
import { z } from 'zod'
import { FormMessage } from '@ui/components/shadcn/ui/form'
import { Input } from '@ui/components/shadcn/ui/input'
import { useParams } from 'common'
import { useCreateCollection } from 'data/analytics/warehouse-collections-create-mutation'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useCurrentOrgPlan } from 'hooks/misc/useCurrentOrgPlan'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import {
Admonition,
AlertDescription_Shadcn_,
AlertTitle_Shadcn_,
Alert_Shadcn_,
Button,
FormControl_Shadcn_,
FormField_Shadcn_,
FormMessage_Shadcn_,
Form_Shadcn_,
Input_Shadcn_,
Modal,
WarningIcon,
} from 'ui'
import { Admonition } from 'ui-patterns/admonition'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { useCurrentOrgPlan } from 'hooks/misc/useCurrentOrgPlan'
import { Alert } from '@ui/components/shadcn/ui/alert'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
export const CreateWarehouseCollectionModal = ({
open,
@@ -133,7 +125,7 @@ export const CreateWarehouseCollectionModal = ({
render={({ field }) => (
<FormItemLayout label="Collection name" layout="horizontal">
<FormControl_Shadcn_>
<Input autoFocus placeholder="Events" {...field} />
<Input_Shadcn_ autoFocus placeholder="Events" {...field} />
</FormControl_Shadcn_>
</FormItemLayout>
)}
@@ -149,7 +141,7 @@ export const CreateWarehouseCollectionModal = ({
>
<FormControl_Shadcn_>
<div className="relative flex items-center gap-2">
<Input type="number" {...field} />
<Input_Shadcn_ type="number" {...field} />
<span className="absolute right-3 text-foreground-lighter text-sm pointer-events-none">
days
</span>
@@ -159,7 +151,7 @@ export const CreateWarehouseCollectionModal = ({
)}
/>
</div>
<FormMessage />
<FormMessage_Shadcn_ />
</Modal.Content>
{plan?.id === 'free' && (
@@ -1,9 +1,10 @@
import { describe, test, expect } from 'vitest'
import { DiskType } from './DiskManagement.constants'
import {
calculateDiskSizePrice,
calculateIOPSPrice,
calculateThroughputPrice,
} from './DiskManagement.utils'
import { DiskType } from './DiskManagement.constants'
describe('DiskManagement.utils.ts:calculateDiskSizePrice', () => {
test('GP3 with 8GB to GP3 with 10GB for pro plan', () => {
@@ -1,3 +1,4 @@
import { describe, test, expect } from 'vitest'
import { deriveRoleChangeActions } from './UpdateRolesPanel.utils'
// [Joshen] This is specifically testing the project scope changes
@@ -5,6 +5,7 @@ import {
isUpdateWithoutWhere,
suffixWithLimit,
} from './SQLEditor.utils'
import { describe, test, expect, it } from 'vitest'
describe('SQLEditor.utils.ts:checkIfAppendLimitRequired', () => {
test('Should return false if limit passed is <= 0', () => {
@@ -1,6 +1,7 @@
import { zodResolver } from '@hookform/resolvers/zod'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { indexOf } from 'lodash'
import { ExternalLink, Lock } from 'lucide-react'
import Link from 'next/link'
import { useEffect, useState } from 'react'
import { useForm } from 'react-hook-form'
@@ -21,9 +22,7 @@ import { useProjectPostgrestConfigUpdateMutation } from 'data/config/project-pos
import { useDatabaseExtensionsQuery } from 'data/database-extensions/database-extensions-query'
import { useSchemasQuery } from 'data/database/schemas-query'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { ExternalLink, Lock } from 'lucide-react'
import {
Admonition,
AlertDescription_Shadcn_,
AlertTitle_Shadcn_,
Alert_Shadcn_,
@@ -40,6 +39,7 @@ import {
Switch,
WarningIcon,
} from 'ui'
import { Admonition } from 'ui-patterns/admonition'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import {
MultiSelector,
@@ -33,8 +33,9 @@ import { useSelectedProject } from 'hooks/misc/useSelectedProject'
import { INSTANCE_MICRO_SPECS, INSTANCE_NANO_SPECS } from 'lib/constants'
import { TIME_PERIODS_BILLING, TIME_PERIODS_REPORTS } from 'lib/constants/metrics'
import { useDatabaseSelectorStateSnapshot } from 'state/database-selector'
import { Button } from 'ui'
import { Admonition } from 'ui-patterns/admonition'
import { INFRA_ACTIVITY_METRICS } from './Infrastructure.constants'
import { Admonition, Button } from 'ui'
const NON_DEDICATED_IO_RESOURCES = [
'ci_micro',
@@ -1,3 +1,4 @@
import { describe, it, expect } from 'vitest'
import { formatTableRowsToSQL } from './TableEntity.utils'
describe('TableEntity.utils: formatTableRowsToSQL', () => {
@@ -192,25 +192,27 @@ export function LogsSidebarMenuV2() {
Create query
</Link>
</DropdownMenuItem>
<Tooltip>
<TooltipTrigger asChild>
<DropdownMenuItem className="gap-x-2" asChild>
<button
onClick={() => setCreateCollectionOpen(true)}
className="w-full flex items-center text-xs px-2 py-1"
disabled={!canCreateCollection}
>
<Plus size={14} />
Create collection
</button>
</DropdownMenuItem>
</TooltipTrigger>
{!canCreateCollection && (
<TooltipContent>
You need additional permissions to create a collection
</TooltipContent>
)}
</Tooltip>
{warehouseEnabled && (
<Tooltip>
<TooltipTrigger asChild>
<DropdownMenuItem className="gap-x-2" asChild>
<button
onClick={() => setCreateCollectionOpen(true)}
className="w-full flex items-center text-xs px-2 py-1"
disabled={!canCreateCollection}
>
<Plus size={14} />
Create collection
</button>
</DropdownMenuItem>
</TooltipTrigger>
{!canCreateCollection && (
<TooltipContent>
You need additional permissions to create a collection
</TooltipContent>
)}
</Tooltip>
)}
</DropdownMenuContent>
</DropdownMenu>
<CreateWarehouseCollectionModal
@@ -247,7 +249,7 @@ export function LogsSidebarMenuV2() {
label={collection?.name ?? ''}
/>
))}
{whCollectionsLoading ? (
{whCollectionsLoading && warehouseEnabled ? (
<div className="p-4">
<GenericSkeletonLoader />
</div>
@@ -194,21 +194,6 @@ const ReportsMenu = () => {
</div>
))}
{/* [Joshen] Temp notice while we shift some pages on 040424 - can probably remove in 3 months perhaps */}
<Alert_Shadcn_>
<AlertTitle_Shadcn_ className="text-sm">
Query Performance and Project Linter reports have been shifted
</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_ className="text-xs">
<p className="mb-2">They can now be found in the menu under the database section.</p>
<Button asChild type="default" size="tiny">
<Link href={`/project/${ref}/database/query-performance`}>
Head over to Database
</Link>
</Button>
</AlertDescription_Shadcn_>
</Alert_Shadcn_>
<UpdateCustomReportModal
onCancel={() => setSelectedReportToUpdate(undefined)}
selectedReport={selectedReportToUpdate}
@@ -1,6 +1,8 @@
function FormsContainer(props: { children: React.ReactNode; header?: string }) {
import { cn } from 'ui'
function FormsContainer(props: { children: React.ReactNode; header?: string; className?: string }) {
return (
<div className="mx-auto max-w-4xl px-5 pt-12 pb-20">
<div className={cn('mx-auto max-w-4xl px-5 pt-12 pb-20', props.className)}>
{props.header && <h1 className="text-foreground mb-8 text-3xl">{props.header}</h1>}
<div className="space-y-20">{props.children}</div>
</div>
+11 -3
View File
@@ -41,9 +41,17 @@ const getUsersCountSQl = ({
}
if (providers && providers.length > 0) {
conditions.push(
`(raw_app_meta_data->>'providers')::jsonb ?| array[${providers.map((p) => `'${p}'`).join(', ')}]`
)
// [Joshen] This is arguarbly not fully optimized, but at the same time not commonly used
// JFYI in case we do eventually run into performance issues here when filtering for SAML provider
if (providers.includes('saml 2.0')) {
conditions.push(
`(select jsonb_agg(case when value ~ '^sso' then 'sso' else value end) from jsonb_array_elements_text((raw_app_meta_data ->> 'providers')::jsonb)) ?| array[${providers.map((p) => (p === 'saml 2.0' ? `'sso'` : `'${p}'`)).join(', ')}]`.trim()
)
} else {
conditions.push(
`(raw_app_meta_data->>'providers')::jsonb ?| array[${providers.map((p) => `'${p}'`).join(', ')}]`
)
}
}
const combinedConditions = conditions.map((x) => `(${x})`).join(' and ')
+12 -4
View File
@@ -43,7 +43,7 @@ const getUsersSQL = ({
if (hasValidKeywords) {
conditions.push(
`id::text ilike '%${keywords}%' or email ilike '%${keywords}%' or phone ilike '%${keywords}%`
`id::text ilike '%${keywords}%' or email ilike '%${keywords}%' or phone ilike '%${keywords}%'`
)
}
@@ -56,9 +56,17 @@ const getUsersSQL = ({
}
if (providers && providers.length > 0) {
conditions.push(
`(raw_app_meta_data->>'providers')::jsonb ?| array[${providers.map((p) => `'${p}'`).join(', ')}]`
)
// [Joshen] This is arguarbly not fully optimized, but at the same time not commonly used
// JFYI in case we do eventually run into performance issues here when filtering for SAML provider
if (providers.includes('saml 2.0')) {
conditions.push(
`(select jsonb_agg(case when value ~ '^sso' then 'sso' else value end) from jsonb_array_elements_text((raw_app_meta_data ->> 'providers')::jsonb)) ?| array[${providers.map((p) => (p === 'saml 2.0' ? `'sso'` : `'${p}'`)).join(', ')}]`.trim()
)
} else {
conditions.push(
`(raw_app_meta_data->>'providers')::jsonb ?| array[${providers.map((p) => `'${p}'`).join(', ')}]`
)
}
}
const combinedConditions = conditions.map((x) => `(${x})`).join(' and ')
+13 -1
View File
@@ -28,6 +28,7 @@
"@hcaptcha/react-hcaptcha": "^1.8.1",
"@headlessui/react": "^1.7.17",
"@heroicons/react": "^2.1.3",
"@hookform/resolvers": "^3.1.1",
"@monaco-editor/react": "^4.6.0",
"@next/bundle-analyzer": "^14.2.3",
"@radix-ui/react-popover": "^1.0.7",
@@ -36,7 +37,9 @@
"@stripe/react-stripe-js": "^2.5.0",
"@stripe/stripe-js": "^3.0.5",
"@supabase/auth-helpers-react": "^0.4.2",
"@supabase/auth-js": "2.64.2",
"@supabase/pg-meta": "*",
"@supabase/realtime-js": "2.10.2",
"@supabase/shared-types": "0.1.70",
"@supabase/supabase-js": "^2.44.3",
"@tanstack/react-query": "4.35.7",
@@ -53,6 +56,7 @@
"config": "*",
"configcat-js": "^7.0.0",
"dayjs": "^1.11.10",
"dnd-core": "^16.0.1",
"file-saver": "^2.0.5",
"framer-motion": "^11.0.3",
"generate-password-browser": "^1.1.0",
@@ -64,6 +68,7 @@
"lodash": "^4.17.21",
"lucide-react": "^0.436.0",
"markdown-table": "^3.0.3",
"memoize-one": "^5.0.1",
"mime-db": "^1.53.0",
"mobx": "^6.10.2",
"mobx-react-lite": "^4.0.5",
@@ -81,6 +86,7 @@
"react": "^18.2.0",
"react-beautiful-dnd": "^13.1.0",
"react-contexify": "^5.0.0",
"react-copy-to-clipboard": "^5.1.0",
"react-csv": "^2.0.3",
"react-data-grid": "7.0.0-beta.41",
"react-datepicker": "^4.18.0",
@@ -89,18 +95,22 @@
"react-dom": "^18.2.0",
"react-error-boundary": "^4.0.13",
"react-grid-layout": "^1.4.2",
"react-hook-form": "^7.45.0",
"react-inlinesvg": "^4.0.4",
"react-intersection-observer": "^9.5.3",
"react-markdown": "^8.0.3",
"react-simple-maps": "4.0.0-beta.6",
"react-tracked": "^1.7.11",
"react-use": "^17.5.0",
"react-virtualized-auto-sizer": "^1.0.20",
"react-window": "^1.8.6",
"react-window-infinite-loader": "^1.0.7",
"reactflow": "^11.10.1",
"recharts": "^2.8.0",
"remark-gfm": "^3.0.1",
"semver": "^6.3.1",
"shared-data": "*",
"sonner": "^1.5.0",
"sql-formatter": "^13.1.0",
"sqlstring": "^2.3.2",
"tus-js-client": "^4.1.0",
@@ -112,6 +122,7 @@
"vitest": "^1.6.0",
"yup": "^1.4.0",
"yup-password": "^0.3.0",
"zod": "^3.22.4",
"zxcvbn": "^4.4.2"
},
"devDependencies": {
@@ -134,6 +145,7 @@
"@types/randombytes": "^2.0.3",
"@types/react": "^18.2.24",
"@types/react-beautiful-dnd": "^13.1.2",
"@types/react-copy-to-clipboard": "^5.0.4",
"@types/react-csv": "^1.1.2",
"@types/react-datepicker": "^4.3.4",
"@types/react-dom": "^18.2.8",
@@ -160,6 +172,6 @@
"prettier": "^4.0.0-alpha.8",
"storybook-dark-mode": "^3.0.1",
"tailwindcss": "^3.4.1",
"typescript": "^5.5.0"
"typescript": "~5.5.0"
}
}
+1 -1
View File
@@ -49,7 +49,6 @@ import passwordStrength from 'lib/password-strength'
import type { CloudProvider } from 'shared-data'
import type { NextPageWithLayout } from 'types'
import {
Admonition,
Badge,
Button,
CollapsibleContent_Shadcn_,
@@ -76,6 +75,7 @@ import {
TableRow,
cn,
} from 'ui'
import { Admonition } from 'ui-patterns/admonition'
import { Input } from 'ui-patterns/DataInputs/Input'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { InfoTooltip } from 'ui-patterns/info-tooltip'
@@ -15,7 +15,7 @@ const PageLayout: NextPageWithLayout = () => {
return <NoPermission isFullPage resourceText="access your project's auth provider settings" />
} else {
return (
<FormsContainer>
<FormsContainer className="w-full">
<AuthProvidersForm />
</FormsContainer>
)
@@ -214,7 +214,10 @@ const DatabaseUsage = () => {
renderer={(props) => {
return (
<div>
<div className="col-span-4 inline-grid grid-cols-12 gap-12 w-full">
<p className="text-sm -mt-4 text-foreground-lighter">
The data refreshes every 24 hours.
</p>
<div className="col-span-4 inline-grid grid-cols-12 gap-12 w-full mt-5">
<div className="grid gap-2 col-span-2">
<h5 className="text-sm">Space used</h5>
<span className="text-lg">{formatBytes(databaseSizeBytes, 2, 'GB')}</span>
@@ -1,4 +1,5 @@
import { sanitizeTaxIdValue } from 'components/interfaces/Organization/BillingSettings/TaxID/TaxID.utils'
import { describe, test, expect } from 'vitest'
/**
* We're sanitizing EU tax ids. Stripe expects a prefixed tax id (ATU12345678),
@@ -1,4 +1,4 @@
import { vi } from 'vitest'
import { expect, test, vi } from 'vitest'
import { screen } from '@testing-library/dom'
import userEvent from '@testing-library/user-event'
@@ -1,4 +1,5 @@
import { removeJSONTrailingComma } from 'lib/helpers'
import { describe, it, expect } from 'vitest'
describe('removeJSONTrailingComma', () => {
it('should handle an empty object', () => {
@@ -1,9 +1,9 @@
import { vi } from 'vitest'
import { RowField } from 'components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.types'
import {
generateRowObjectFromFields,
parseValue,
} from 'components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.utils'
import { RowField } from 'components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.types'
import { describe, expect, it, vi } from 'vitest'
describe('parseValue', () => {
it('should return null when originalValue is null', () => {
@@ -1,4 +1,5 @@
import { inferColumnType } from 'components/interfaces/TableGridEditor/SidePanelEditor/SpreadsheetImport/SpreadsheetImport.utils'
import { describe, test, expect } from 'vitest'
describe('SpreadsheedImport.utils: inferColumnType', () => {
test('should default column type to text if no rows to infer from', () => {
@@ -1,4 +1,5 @@
import { formatFilterURLParams, formatSortURLParams } from 'components/grid/SupabaseGrid.utils'
import { describe, test, expect } from 'vitest'
// Sort URL syntax: `column:order`
describe('SupabaseGrid.utils: formatSortURLParams', () => {
@@ -1,4 +1,5 @@
import { sanitizeRoute } from 'components/layouts/AppLayout/ProjectDropdown'
import { test, expect } from 'vitest'
test('Should sanitize project routes correctly when switching projects by removing project specific parameters', () => {
expect(sanitizeRoute('/project/[ref]', { ref: 'abc' })).toBe('/project/[ref]')
@@ -1,5 +1,6 @@
import { screen } from '@testing-library/react'
import ReportWidget from 'components/interfaces/Reports/ReportWidget'
import { test } from 'vitest'
import { render } from '../../helpers'
test('static elements', async () => {
@@ -3,6 +3,7 @@ import {
convertFromBytes,
convertToBytes,
} from 'components/to-be-cleaned/Storage/StorageSettings/StorageSettings.utils'
import { describe, test, expect } from 'vitest'
describe('StorageSettings.utils: convertFromBytes', () => {
test('should convert 1024 to 1KB', () => {
@@ -1,10 +1,11 @@
import { renderHook } from '@testing-library/react'
import {
isFloat,
numberFormatter,
precisionFormatter,
useStacked,
} from 'components/ui/Charts/Charts.utils'
import { renderHook } from '@testing-library/react'
import { test, expect } from 'vitest'
test('isFloat', () => {
expect(isFloat(123)).toBe(false)
+1
View File
@@ -1,4 +1,5 @@
import { API_URL } from 'lib/constants'
import { test, expect } from 'vitest'
test('MSW works as expected', async () => {
const res = await fetch(`${API_URL}/msw/test`)
+2 -2
View File
@@ -1,7 +1,7 @@
import { render, screen, waitFor } from '@testing-library/react'
import { RouterComponent } from './router'
import { expect, suite, vi } from 'vitest'
import { routerMock } from 'tests/mocks/router'
import { expect, suite, test } from 'vitest'
import { RouterComponent } from './router'
suite('Router Mock', () => {
test('Router mock works as expected', async () => {
@@ -1,7 +1,7 @@
import { vi } from 'vitest'
import { afterEach, beforeEach, test, vi } from 'vitest'
import LogEventChart from 'components/interfaces/Settings/Logs/LogEventChart'
import { screen } from '@testing-library/react'
import LogEventChart from 'components/interfaces/Settings/Logs/LogEventChart'
import { render } from '../../helpers'
const { ResizeObserver } = window
@@ -1,8 +1,8 @@
import { describe, vi, test } from 'vitest'
import LogTable from 'components/interfaces/Settings/Logs/LogTable'
import { waitFor, screen, prettyDOM } from '@testing-library/react'
import { prettyDOM, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import LogTable from 'components/interfaces/Settings/Logs/LogTable'
import dayjs from 'dayjs'
import { beforeAll, expect, test, vi } from 'vitest'
import { render } from '../../helpers'
import customParseFormat from 'dayjs/plugin/customParseFormat'
import utc from 'dayjs/plugin/utc'
@@ -5,7 +5,7 @@ import { PREVIEWER_DATEPICKER_HELPERS } from 'components/interfaces/Settings/Log
import dayjs from 'dayjs'
import timezone from 'dayjs/plugin/timezone'
import utc from 'dayjs/plugin/utc'
import { vi } from 'vitest'
import { expect, test, vi } from 'vitest'
import { render } from '../../helpers'
dayjs.extend(timezone)
@@ -1,8 +1,9 @@
import {
checkForWithClause,
checkForILIKEClause,
checkForWildcard,
checkForWithClause,
} from 'components/interfaces/Settings/Logs/Logs.utils'
import { describe, test, expect } from 'vitest'
describe('checkForWithClause', () => {
test('basic queries', () => {
@@ -1,18 +1,14 @@
import { vi } from 'vitest'
import { act, findByRole, findByText, fireEvent, screen, waitFor } from '@testing-library/react'
import { wait } from '@testing-library/user-event/dist/utils'
import { findByText, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc'
import { useRouter } from 'next/router'
import { expect, test, vi } from 'vitest'
dayjs.extend(utc)
import { useParams } from 'common'
import { LogsTableName } from 'components/interfaces/Settings/Logs/Logs.constants'
import LogsPreviewer from 'components/interfaces/Settings/Logs/LogsPreviewer'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { logDataFixture } from '../../fixtures'
import { render } from '../../helpers'
// [Joshen] There's gotta be a much better way to mock these things so that it applies for ALL tests
@@ -1,8 +1,8 @@
import { vi } from 'vitest'
import { screen } from '@testing-library/react'
import { expect, test, vi } from 'vitest'
import { render } from 'tests/helpers'
import LogsQueryPanel from 'components/interfaces/Settings/Logs/LogsQueryPanel'
import { render } from 'tests/helpers'
test('run and clear', async () => {
const mockRun = vi.fn()
@@ -1,10 +1,6 @@
import { vi } from 'vitest'
import { screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import PreviewFilterPanel from 'components/interfaces/Settings/Logs/PreviewFilterPanel'
import { render } from '../../helpers'
import { clickDropdown } from 'tests/helpers'
import { expect, test, vi } from 'vitest'
test.skip('filter input change and submit', async () => {
const mockFn = vi.fn()
@@ -1,7 +1,5 @@
// mock the fetch function
import { get } from 'lib/common/fetch'
import { LogsSavedPage } from 'pages/project/[ref]/logs/explorer/saved'
import { render } from 'tests/helpers'
import { test } from 'vitest'
test('on load, refresh user content', async () => {
// get.mockResolvedValue({})
@@ -1,10 +1,10 @@
import { vi } from 'vitest'
import { fireEvent, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import dayjs from 'dayjs'
import { LogsExplorerPage } from 'pages/project/[ref]/logs/explorer/index'
import { clickDropdown, render } from 'tests/helpers'
import { routerMock } from 'tests/mocks/router'
import { beforeAll, describe, expect, test, vi } from 'vitest'
// [Joshen] Am temporarily commenting out the breaking tests due to:
// "TypeError: _fetch.get.mockReset is not a function" error from Jest
@@ -21,7 +21,7 @@ beforeAll(() => {
...mod,
IS_PLATFORM: true,
useIsLoggedIn: vi.fn(),
useParams: jest.fn(() => ({ ref: 'projectRef' })),
useParams: vi.fn(() => ({ ref: 'projectRef' })),
}
})
vi.mock('lib/gotrue', () => ({
@@ -1,4 +1,5 @@
import { prettyDOM, screen } from '@testing-library/react'
import { screen } from '@testing-library/react'
import { test } from 'vitest'
import { ApiReport } from 'pages/project/[ref]/reports/api-overview'
import { render } from '../../../helpers'
@@ -1,7 +1,8 @@
import { screen } from '@testing-library/react'
import { test } from 'vitest'
import { render } from '../../../helpers'
import { StorageReport } from 'pages/project/[ref]/reports/storage'
import { render } from '../../../helpers'
// [Joshen] Mock data for ApiReport is in __mocks__/hooks/useStorageReport
// I don't think this is an ideal set up as the mock data is not clear in this file itself
@@ -1,4 +1,5 @@
import { getReturnToPath } from 'lib/gotrue'
import { describe, it, expect } from 'vitest'
describe(`getReturnToPath`, () => {
it(`returns to /projects when no fallback is provided`, () => {
+2 -2
View File
@@ -1,8 +1,8 @@
import { beforeAll, vi } from 'vitest'
import { setupServer } from 'msw/node'
import { createDynamicRouteParser } from 'next-router-mock/dist/dynamic-routes'
import { afterAll, afterEach, beforeAll, vi } from 'vitest'
import { APIMock } from './mocks/api'
import { routerMock } from './mocks/router'
import { createDynamicRouteParser } from 'next-router-mock/dist/dynamic-routes'
export const mswServer = setupServer(...APIMock)
+3 -3
View File
@@ -1,7 +1,7 @@
{
"company": [
{
"number": "85+",
"number": "90+",
"text": "team members\n\nin 25+ countries"
},
{
@@ -9,7 +9,7 @@
"text": "languages spoken"
},
{
"number": "$116M",
"number": "$196M",
"text": "in funding"
},
{
@@ -17,7 +17,7 @@
"text": "community members"
},
{
"number": "5000+",
"number": "5,000+",
"text": "memes posted\n\n(and counting)"
}
],
+24
View File
@@ -0,0 +1,24 @@
export interface JobItemProps {
id: string
title: string
location: any
team: string
employment: string
descriptionHtml: string
jobUrl: string
}
export const PLACEHOLDER_JOB_ID = '64d76968-1fe1-458c-8c6d-8859168c3fb7'
export const filterGenericJob = (job: JobItemProps) => job.id === PLACEHOLDER_JOB_ID
export const groupJobsByTeam = (jobs: JobItemProps[]) => {
return jobs.reduce(
(acc, job) => {
if (!acc[job.team]) {
acc[job.team] = []
}
acc[job.team].push(job)
return acc
},
{} as Record<string, JobItemProps[]>
)
}
+1 -1
View File
@@ -3,7 +3,6 @@ import { PropsWithChildren } from 'react'
import { CH } from '@code-hike/mdx/components'
import { ArrowUpRight, Triangle } from 'lucide-react'
import {
Admonition,
Badge,
cn,
Collapsible_Shadcn_,
@@ -12,6 +11,7 @@ import {
Heading,
Image,
} from 'ui'
import { Admonition } from 'ui-patterns/admonition'
import { type ImageProps } from 'ui/src/components/Image/Image'
import Avatar from '~/components/Avatar'
import Chart from '~/components/Charts/PGCharts'
+9 -1
View File
@@ -25,15 +25,19 @@
"@octokit/graphql": "^7.0.2",
"@octokit/plugin-paginate-graphql": "^4.0.0",
"@octokit/rest": "^20.0.2",
"@radix-ui/react-dialog": "^1.0.5",
"@react-three/fiber": "^7.0.29",
"@supabase/auth-helpers-react": "^0.4.2",
"@supabase/auth-ui-react": "^0.4.5",
"@supabase/supabase-js": "^2.44.3",
"@vercel/og": "^0.6.2",
"ai-commands": "*",
"animejs": "^3.2.2",
"classnames": "^2.3.1",
"clsx": "^1.2.1",
"cobe": "^0.6.2",
"common": "*",
"common-tags": "^1.8.2",
"config": "*",
"contentlayer2": "0.4.6",
"dat.gui": "^0.7.9",
@@ -43,6 +47,7 @@
"globby": "^13.2.2",
"gray-matter": "^4.0.3",
"icons": "*",
"lodash": "^4.0.0",
"lucide-react": "*",
"markdown-toc": "^1.2.0",
"next": "^14.2.10",
@@ -50,6 +55,7 @@
"next-mdx-remote": "^4.4.1",
"next-seo": "^6.5.0",
"next-themes": "^0.3.0",
"openai": "^4.20.1",
"parse-numeric-range": "^1.3.0",
"react": "^18.2.0",
"react-copy-to-clipboard": "^5.1.0",
@@ -69,7 +75,7 @@
"swiper": "^11.0.7",
"three": "^0.136.0",
"typed.js": "^2.0.16",
"typescript": "^5.5.0",
"typescript": "~5.5.0",
"ui": "*",
"ui-patterns": "*",
"use-debounce": "^7.0.1",
@@ -79,9 +85,11 @@
"devDependencies": {
"@types/animejs": "^3.1.12",
"@types/classnames": "^2.3.1",
"@types/common-tags": "^1.8.4",
"@types/dat.gui": "^0.7.10",
"@types/mdx-js__react": "^1.5.6",
"@types/node": "^20.11.16",
"@types/lodash": "^4.0.0",
"@types/parse-numeric-range": "^0.0.1",
"@types/react": "^18.2.24",
"@types/react-copy-to-clipboard": "^5.0.4",
+383 -384
View File
@@ -1,22 +1,26 @@
import { GlobeAltIcon } from '@heroicons/react/outline'
import { Check } from 'lucide-react'
import { NextPage } from 'next'
import { NextSeo } from 'next-seo'
import Image from 'next/image'
import Link from 'next/link'
import { useRouter } from 'next/router'
import ReactMarkdown from 'react-markdown'
import { NextSeo } from 'next-seo'
import { GlobeAltIcon } from '@heroicons/react/outline'
import { Check } from 'lucide-react'
import { Badge, Button, Separator, buttonVariants, cn } from 'ui'
import ReactMarkdown from 'react-markdown'
import Styles from '~/styles/career.module.css'
import Globe from '~/components/Globe'
import DefaultLayout from '~/components/Layouts/Default'
import SectionContainer from '~/components/Layouts/SectionContainer'
import { groupJobsByTeam, filterGenericJob, JobItemProps, PLACEHOLDER_JOB_ID } from '~/lib/careers'
import career from '~/data/career.json'
import Styles from '~/styles/career.module.css'
export async function getStaticProps() {
const job_res = await fetch('https://boards-api.greenhouse.io/v1/boards/supabase/jobs')
const job_res = await fetch('https://api.ashbyhq.com/posting-api/job-board/supabase')
const job_data = await job_res.json()
const jobs = groupJobsByTeam(job_data.jobs.filter((job: JobItemProps) => !filterGenericJob(job)))
const placeholderJob = job_data.jobs.find(filterGenericJob)
const contributor_res = await fetch(
'https://api.github.com/repos/supabase/supabase/contributors?per_page=100'
@@ -70,13 +74,20 @@ export async function getStaticProps() {
return {
props: {
jobs: job_data.jobs,
jobs,
placeholderJob,
contributors: contributors,
},
}
}
const CareerPage: NextPage = ({ jobs, contributors }: any) => {
interface CareersPageProps {
jobs: Record<string, JobItemProps[]>
placeholderJob: JobItemProps
contributors: { login: string; avatar_url: string; html_url: string }[]
}
const CareerPage: NextPage<CareersPageProps> = ({ jobs, placeholderJob, contributors }) => {
const { basePath } = useRouter()
const meta_title = 'Careers | Supabase'
@@ -99,412 +110,400 @@ const CareerPage: NextPage = ({ jobs, contributors }: any) => {
}}
/>
<DefaultLayout>
<div className="text-foreground">
<div className="container relative mx-auto px-6 py-10 lg:pt-12 lg:px-16 xl:px-20 text-center space-y-4">
<h1 className="text-sm text-brand md:text-base">
<span className="sr-only">Supabase </span>Careers
</h1>
<h2 className="text-3xl md:text-4xl xl:text-5xl lg:max-w-2xl xl:max-w-3xl lg:mx-auto tracking-[-1.5px]">
We're on a mission to build the best developer platform
</h2>
<p className="text-sm md:text-base text-foreground-lighter max-w-sm sm:max-w-md md:max-w-lg mx-auto">
Explore remote possibilities and join our team to help us achieve it.
</p>
<div className="mt-4">
<a href="#positions">
<Button type="primary">Open positions</Button>
</a>
</div>
</div>
<header className="container relative mx-auto px-6 pt-12 pb-8 lg:pt-24 lg:px-16 xl:px-20 text-center space-y-4">
<h1 className="text-sm text-brand md:text-base">
<span className="sr-only">Supabase </span>Careers
</h1>
<h2 className="text-3xl md:text-4xl xl:text-5xl lg:max-w-2xl xl:max-w-3xl lg:mx-auto tracking-[-1.5px]">
We're on a mission to build the best developer platform
</h2>
<p className="text-sm md:text-base text-foreground-lighter max-w-sm sm:max-w-md md:max-w-lg mx-auto">
Explore remote possibilities and join our team to help us achieve it.
</p>
<Button asChild type="primary" className="mt-4">
<Link href="#positions">Open positions</Link>
</Button>
</header>
<SectionContainer>
<div className="flex flex-wrap md:flex-nowrap -mt-6 md:mt-0 w-fit md:w-full mx-auto md:flex md:items-start justify-around lg:w-full lg:max-w-5xl">
{career.company.map((company: { number: string; text: string }, i: number) => {
return (
<div
key={i}
className="border-t-[1px] mt-6 mx-2 md:mx-2 md:mt-0 md:border-0 border-brand w-[134px] md:max-w-none"
>
<div className="hidden md:block border-t-[1px] lg:border-t-2 border-brand w-[60px] lg:w-[100px]"></div>
<h2 className="text-3xl lg:text-4xl pt-3 tracking-[-1.5px]">
{company.number}
</h2>
<ReactMarkdown className="text-foreground-light text-sm lg:text-base">
{company.text}
</ReactMarkdown>
</div>
)
})}
</div>
</SectionContainer>
<div className="py-[1.25px] bg-gradient-to-r from-background via-border to-background">
<div className="bg-alternative overflow-clip">
<SectionContainer className="md:!pt-16 md:grid md:max-h-[500px] grid-cols-1 md:grid-cols-5 md:gap-8">
<SectionContainer className="!pt-8">
<div className="flex flex-wrap md:flex-nowrap -mt-6 md:mt-0 w-fit md:w-full mx-auto md:flex md:items-start justify-around lg:w-full lg:max-w-5xl">
{career.company.map((company: { number: string; text: string }, i: number) => {
return (
<div
className="
key={i}
className="border-t-[1px] mt-6 mx-2 md:mx-2 md:mt-0 md:border-0 border-brand w-[134px] md:max-w-none"
>
<div className="hidden md:block border-t-[1px] lg:border-t-2 border-brand w-[60px] lg:w-[100px]"></div>
<h2 className="text-3xl lg:text-4xl pt-3 tracking-[-1.5px]">{company.number}</h2>
<ReactMarkdown className="text-foreground-light text-sm lg:text-base">
{company.text}
</ReactMarkdown>
</div>
)
})}
</div>
</SectionContainer>
<div className="py-[1.25px] bg-gradient-to-r from-background via-border to-background">
<div className="bg-alternative overflow-clip">
<SectionContainer className="md:!pt-16 md:grid md:max-h-[500px] grid-cols-1 md:grid-cols-5 md:gap-8">
<div
className="
relative md:col-span-2 aspect-square
w-[calc(100%+100px)] md:w-[calc(100%+450px)] xl:w-[calc(100%+200px)]
-mb-[160px] md:mb-0
-top-[160px] md:-top-[200px] xl:-top-[210px]
-left-[100px] md:-left-[450px] xl:-left-[200px]
"
>
<Globe />
</div>
<div className="relative md:col-span-3">
<h2 className="text-2xl sm:text-3xl xl:text-4xl max-w-[300px] md:max-w-xs tracking-[-1.5px]">
We work together, wherever we are
</h2>
<p className="text-foreground-light mt-4 text-xs sm:text-sm md:text-base md:w-full">
Working in a globally distributed team is rewarding but has its challenges. We
are across many different timezones, so we use tools like Notion, Slack, and
Discord to stay connected to our team, and our community.
</p>
<div className="max-w-[300px] sm:max-w-md md:max-w-md mt-20">
<div className="border-t-2 border-brand w-4/12"></div>
<h2 className="text-2xl sm:text-3xl md:text-4xl pt-2 tracking-[-1.5px]">
We deeply believe in the efficacy of collaborative open source
</h2>
</div>
</div>
</SectionContainer>
<SectionContainer className="-mt-16 md:mt-16">
<div className="md:flex md:gap-6">
<div className="md:w-1/2">
<div>
<h2 className="text-2xl sm:text-3xl xl:text-4xl tracking-[-1.5px]">
What is Supabase
</h2>
<p className="text-foreground-light text-xs sm:text-sm lg:text-base pt-2 sm:max-w-md xl:max-w-lg">
Supabase is an open source Firebase alternative, built by developers for
developers. Supabase adds auth, realtime, storage, restful APIs, and edge
functions to Postgres without a single line of code. Supabase was
born-remote. Having a globally distributed, open source company is our
secret weapon to hiring top-tier talent.
</p>
</div>
<div className="md:w-full rounded-md mt-10 md:mt-36 lg:mt-40">
<div className="relative w-full aspect-[148/125]">
<Image
src="/images/career/1.jpg"
alt="team photo"
fill
sizes="(min-width: 767px) 45vw, 100vw"
placeholder="blur"
blurDataURL="/images/blur.png"
className="rounded-md object-cover"
draggable={false}
/>
</div>
</div>
<div className="grid justify-items-end">
<div className="w-full md:w-5/6 rounded-md mt-6">
<div className="relative w-full aspect-[29/22]">
<Image
src="/images/career/2.jpg"
alt="team photo"
fill
sizes="(min-width: 767px) 45vw, 100vw"
placeholder="blur"
blurDataURL="/images/blur.png"
className="rounded-md object-cover"
draggable={false}
/>
</div>
</div>
</div>
</div>
<div className="mt-6 md:mt-0 w-full md:w-1/2">
<div className="w-full rounded-md">
<div className="relative w-full aspect-[137/110]">
<Image
src="/images/career/3.jpg"
alt="team photo"
fill
sizes="(min-width: 767px) 45vw, 100vw"
placeholder="blur"
blurDataURL="/images/blur.png"
className="rounded-md object-cover"
draggable={false}
/>
</div>
</div>
<div className="flex gap-6 mt-6">
<div className="w-full rounded-md">
<div className="relative w-full aspect-[142/189]">
<Image
src="/images/career/4.jpg"
alt="team photo"
fill
sizes="(min-width: 767px) 45vw, 100vw"
placeholder="blur"
blurDataURL="/images/blur.png"
className="rounded-md object-cover"
draggable={false}
/>
</div>
</div>
<div className="w-full rounded-md">
<div className="relative w-full aspect-[142/189]">
<Image
src="/images/career/5.jpg"
alt="team photo"
fill
sizes="(min-width: 767px) 45vw, 100vw"
placeholder="blur"
blurDataURL="/images/blur.png"
className="rounded-md object-cover"
draggable={false}
/>
</div>
</div>
</div>
<div className="w-full rounded-md mt-6">
<div className="relative w-full aspect-[16/9]">
<Image
src="/images/career/6.jpg"
alt="team photo"
fill
sizes="(min-width: 767px) 45vw, 100vw"
placeholder="blur"
blurDataURL="/images/blur.png"
className="rounded-md object-cover"
draggable={false}
/>
</div>
</div>
</div>
</div>
</SectionContainer>
<SectionContainer>
<h2 className="text-2xl sm:text-3xl xl:text-4xl tracking-[-1.5px]">
Human powered
>
<Globe />
</div>
<div className="relative md:col-span-3">
<h2 className="text-2xl sm:text-3xl xl:text-4xl max-w-[300px] md:max-w-xs tracking-[-1.5px]">
We work together, wherever we are
</h2>
<p className="text-foreground-lighter text-xs sm:text-sm lg:text-base pt-3 sm:w-3/5 lg:max-w-sm">
As a completely remote and asynchronous team, we focus on these five traits to
keep our team effective:
<p className="text-foreground-light mt-4 text-xs sm:text-sm md:text-base md:w-full">
Working in a globally distributed team is rewarding but has its challenges. We are
across many different timezones, so we use tools like Notion, Slack, and Discord
to stay connected to our team, and our community.
</p>
<div className="grid pt-10 gap-8 grid-cols-2 md:grid-cols-3 lg:gap-16 lg:grid-cols-5">
{career.humanPowered.map(
(human: { icon: string; title: string; text: string }, i: number) => {
return (
<div key={i} className="flex flex-col gap-3">
<div>
<h2 className="text-base">{human.title}</h2>
<p className="text-foreground-light text-xs lg:text-sm">{human.text}</p>
</div>
</div>
)
}
)}
</div>
</SectionContainer>
<SectionContainer className="!pb-0">
<div className="text-center">
<h2 className="text-2xl sm:text-3xl xl:text-4xl max-w-[300px] xl:max-w-none mx-auto tracking-[-1.5px]">
1,000 + Contributors building Supabase
<div className="max-w-[300px] sm:max-w-md md:max-w-md mt-20">
<div className="border-t-2 border-brand w-4/12"></div>
<h2 className="text-2xl sm:text-3xl md:text-4xl pt-2 tracking-[-1.5px]">
We deeply believe in the efficacy of collaborative open source
</h2>
<p className="text-foreground-light text-xs sm:text-sm lg:text-base sm:max-w-lg lg:max-w-2xl mx-auto pt-3">
We're building a community of communities, bringing together developers from
many different backgrounds, as well as new developers looking to get involved
with open source. We love celebrating everyone who contributes their time to the
Supabase mission.
</p>
</div>
<div className="w-[1080px] h-[370px] mx-auto sm:mt-10 md:mt-16 lg:mt-28 2xl:mt-60">
{contributors.map((contributor: any, i: number) => {
return (
<div
className={`${
Styles[`contributors-${i}`]
} absolute w-12 h-12 md:w-14 md:h-14 lg:w-16 lg:h-16 rounded-full border-[1.5px] border-default z-10
transition-all hover:scale-105 drop-shadow-sm hover:drop-shadow-md
`}
key={i}
>
<Link href={contributor.html_url} target="_blank">
<div className="relative w-full h-full">
<Image
src={contributor.avatar_url}
alt={`${contributor.login} github avatar`}
fill
sizes="(min-width: 767px) 45vw, 100vw"
placeholder="blur"
blurDataURL="/images/blur.png"
className="rounded-full object-cover"
draggable={false}
/>
</div>
</Link>
</div>
)
})}
<div
className={`${Styles['contributors-bg-circle']} w-[100%] lg:w-[80%] left-[0%] lg:left-[10%] -bottom-[30%] xs:-bottom-[36%] sm:-bottom-[52%] md:-bottom-[64%] lg:-bottom-[80%] xl:-bottom-[100%]`}
>
<div className="flex flex-col justify-between h-full bg-alternative rounded-full p-4"></div>
</div>
<div
className={`${Styles['contributors-bg-circle']} w-[80%] lg:w-[60%] left-[10%] lg:left-[20%] -bottom-[25%] xs:-bottom-[30%] sm:-bottom-[44%] md:-bottom-[54%] lg:-bottom-[60%] xl:-bottom-[75%]`}
>
<div className="flex flex-col justify-between h-full bg-alternative rounded-full p-4"></div>
</div>
<div
className={`${Styles['contributors-bg-circle']} w-[60%] lg:w-[40%] left-[20%] lg:left-[30%] -bottom-[20%] xs:-bottom-[25%] sm:-bottom-[38%] md:-bottom-[44%] lg:-bottom-[40%] xl:-bottom-[50%]`}
>
<div className="flex flex-col justify-between h-full bg-alternative rounded-full p-4"></div>
</div>
<div
className={`${Styles['contributors-bg-circle']} w-[40%] lg:w-[20%] left-[30%] lg:left-[40%] -bottom-[15%] xs:-bottom-[19%] sm:-bottom-[30%] md:-bottom-[34%] lg:-bottom-[20%] xl:-bottom-[25%]`}
>
<div className="flex flex-col justify-between h-full bg-alternative rounded-full p-4"></div>
</div>
</div>
</SectionContainer>
</div>
</div>
<SectionContainer>
<div className="xl:flex lg:items-start xl:gap-10 justify-between">
<div className="xl:min-w-[300px] xl:max-w-[400px]">
<h2 className="text-2xl sm:text-3xl xl:text-4xl max-w-[280px] sm:max-w-xs xl:max-w-none tracking-[-1.5px]">
Great people deserve great benefits
</h2>
</div>
<div className="mt-12 xl:mt-0 space-y-6 lg:space-y-0 sm:w-fit sm:mx-auto lg:grid lg:grid-cols-2 lg:gap-16">
{career.benefits.map(
(benefits: { icon: string; title: string; text: string }, i: number) => {
return (
<div className="h-full flex items-start space-x-6 w-full" key={i}>
<div className="h-fit text-sm lg:text-base">
<h2 className="text-sm">{benefits.title}</h2>
<ReactMarkdown className="prose pt-1 text-sm">
{benefits.text}
</ReactMarkdown>
</div>
</div>
)
}
)}
</div>
</div>
</SectionContainer>
</SectionContainer>
<SectionContainer>
<div className="text-center sm:max-w-md md:w-3/4 lg:max-w-lg xl:max-w-2xl mx-auto">
<h2 className="text-2xl sm:text-3xl xl:text-4xl tracking-[-1.5px]">How we hire</h2>
<p className="text-xs sm:text-sm lg:text-base text-foreground-light pt-3">
The entire process is fully remote and all communication happens over email or via
video chat in Google Meet. The calls are all 1:1 and usually take between 20-45
minutes. We know you are interviewing us too, so please ask questions. We are happy
to answer.
</p>
</div>
<div className="mt-16 md:ml-36 lg:flex lg:items-start lg:w-fit lg:mx-auto">
{career.hiring.map((hiring: { title: string; text: string }, i: number) => {
return (
<div
key={i + 1}
className="flex lg:block items-start space-x-6 lg:space-x-0 lg:w-full"
>
<div className="lg:flex items-center">
<h3 className="bg-brand-400 border-[1px] border-brand-300 text-brand-600 text-md text-center w-[44px] px-2 py-1.5 rounded-md">
{i + 1}
</h3>
<div className="h-[100px] w-[1px] sm:h-[100px] mx-auto lg:h-[1px] lg:w-full bg-brand-500 lg:pr-6"></div>
</div>
<div className="lg:mt-6">
<h2 className="sm:text-lg max-w-[75%] xl:max-w-none xl:w-11/12 lg:max-w-none">
{hiring.title}
</h2>
<p className="text-foreground-light text-xs sm:text-sm mt-1 md:w-3/4 lg:w-11/12">
{hiring.text}
</p>
<SectionContainer className="-mt-16 md:mt-16">
<div className="md:flex md:gap-6">
<div className="md:w-1/2">
<div>
<h2 className="text-2xl sm:text-3xl xl:text-4xl tracking-[-1.5px]">
What is Supabase
</h2>
<p className="text-foreground-light text-xs sm:text-sm lg:text-base pt-2 sm:max-w-md xl:max-w-lg">
Supabase is an open source Firebase alternative, built by developers for
developers. Supabase adds auth, realtime, storage, restful APIs, and edge
functions to Postgres without a single line of code. Supabase was born-remote.
Having a globally distributed, open source company is our secret weapon to
hiring top-tier talent.
</p>
</div>
<div className="md:w-full rounded-md mt-10 md:mt-36 lg:mt-40">
<div className="relative w-full aspect-[148/125]">
<Image
src="/images/career/1.jpg"
alt="team photo"
fill
sizes="(min-width: 767px) 45vw, 100vw"
placeholder="blur"
blurDataURL="/images/blur.png"
className="rounded-md object-cover"
draggable={false}
/>
</div>
</div>
)
})}
<h3 className="bg-brand-400 border-[1px] border-brand-300 text-brand-600 text-xl w-[44px] lg:min-w-[40px] min-h-[40px] px-2 py-1 rounded-md grid justify-items-center items-center">
<Check />
</h3>
</div>
</SectionContainer>
<div className="grid justify-items-end">
<div className="w-full md:w-5/6 rounded-md mt-6">
<div className="relative w-full aspect-[29/22]">
<Image
src="/images/career/2.jpg"
alt="team photo"
fill
sizes="(min-width: 767px) 45vw, 100vw"
placeholder="blur"
blurDataURL="/images/blur.png"
className="rounded-md object-cover"
draggable={false}
/>
</div>
</div>
</div>
</div>
<div className="mt-6 md:mt-0 w-full md:w-1/2">
<div className="w-full rounded-md">
<div className="relative w-full aspect-[137/110]">
<Image
src="/images/career/3.jpg"
alt="team photo"
fill
sizes="(min-width: 767px) 45vw, 100vw"
placeholder="blur"
blurDataURL="/images/blur.png"
className="rounded-md object-cover"
draggable={false}
/>
</div>
</div>
<div className="flex gap-6 mt-6">
<div className="w-full rounded-md">
<div className="relative w-full aspect-[142/189]">
<Image
src="/images/career/4.jpg"
alt="team photo"
fill
sizes="(min-width: 767px) 45vw, 100vw"
placeholder="blur"
blurDataURL="/images/blur.png"
className="rounded-md object-cover"
draggable={false}
/>
</div>
</div>
<div className="w-full rounded-md">
<div className="relative w-full aspect-[142/189]">
<Image
src="/images/career/5.jpg"
alt="team photo"
fill
sizes="(min-width: 767px) 45vw, 100vw"
placeholder="blur"
blurDataURL="/images/blur.png"
className="rounded-md object-cover"
draggable={false}
/>
</div>
</div>
</div>
<div className="w-full rounded-md mt-6">
<div className="relative w-full aspect-[16/9]">
<Image
src="/images/career/6.jpg"
alt="team photo"
fill
sizes="(min-width: 767px) 45vw, 100vw"
placeholder="blur"
blurDataURL="/images/blur.png"
className="rounded-md object-cover"
draggable={false}
/>
</div>
</div>
</div>
</div>
</SectionContainer>
<Separator />
<div id="positions" className="max-w-6xl mx-auto">
<SectionContainer>
<h2 className="text-xl sm:text-3xl xl:text-3xl tracking-[-1.5px] mb-3">
Open positions
</h2>
<p className="max-w-xl text-foreground-lighter">
Want to build the best developer platform?
<br /> We’d love to talk to you.
<h2 className="text-2xl sm:text-3xl xl:text-4xl tracking-[-1.5px]">Human powered</h2>
<p className="text-foreground-lighter text-xs sm:text-sm lg:text-base pt-3 sm:w-3/5 lg:max-w-sm">
As a completely remote and asynchronous team, we focus on these five traits to keep
our team effective:
</p>
<div className="mt-10 -space-y-px">
{jobs.map(
(
job: {
title: string
location: any
employment: string
description: string
absolute_url: string
},
i: number
) => {
<div className="grid pt-10 gap-8 grid-cols-2 md:grid-cols-3 lg:gap-16 lg:grid-cols-5">
{career.humanPowered.map(
(human: { icon: string; title: string; text: string }, i: number) => {
return (
<Link
href={job.absolute_url}
key={i}
className="
first-of-type:rounded-t-md last-of-type:rounded-b-md
cursor-pointer md:cursor-default bg-surface-75 border border-muted drop-shadow-sm p-4 px-7
flex flex-col md:flex-row
md:items-center
transition hover:bg-surface-100
hover:cursor-pointer"
>
<h2 className="text-base min-w-[240px] lg:min-w-[316px] sm:truncate mr-6">
{job.title}
</h2>
<div className="flex justify-between justify-[normal] pt-2 md:pt-0 w-full items-center">
<div className="flex items-center space-x-4">
<Badge
className="bg-surface-200 bg-opacity-100 border-muted"
variant={'default'}
>
<GlobeAltIcon className="w-3 h-3" />
<span className="ml-1">{job.location.name}</span>
</Badge>
<span className="hidden md:block">{job.employment}</span>
</div>
<p className="hidden lg:block lg:text-sm">{job.description}</p>
<div
className={cn(
buttonVariants({ type: 'default', size: 'tiny' }),
'rounded-full'
)}
>
Apply for position
</div>
<div key={i} className="flex flex-col gap-3">
<div>
<h2 className="text-base">{human.title}</h2>
<p className="text-foreground-light text-xs lg:text-sm">{human.text}</p>
</div>
</Link>
</div>
)
}
)}
</div>
</SectionContainer>
<SectionContainer className="!pb-0">
<div className="text-center">
<h2 className="text-2xl sm:text-3xl xl:text-4xl max-w-[300px] xl:max-w-none mx-auto tracking-[-1.5px]">
1,000 + Contributors building Supabase
</h2>
<p className="text-foreground-light text-xs sm:text-sm lg:text-base sm:max-w-lg lg:max-w-2xl mx-auto pt-3">
We're building a community of communities, bringing together developers from many
different backgrounds, as well as new developers looking to get involved with open
source. We love celebrating everyone who contributes their time to the Supabase
mission.
</p>
</div>
<div className="w-[1080px] h-[370px] mx-auto sm:mt-10 md:mt-16 lg:mt-28 2xl:mt-60">
{contributors.map((contributor: any, i: number) => {
return (
<div
className={`${
Styles[`contributors-${i}`]
} absolute w-12 h-12 md:w-14 md:h-14 lg:w-16 lg:h-16 rounded-full border-[1.5px] border-default z-10
transition-all hover:scale-105 drop-shadow-sm hover:drop-shadow-md
`}
key={i}
>
<Link href={contributor.html_url} target="_blank">
<div className="relative w-full h-full">
<Image
src={contributor.avatar_url}
alt={`${contributor.login} github avatar`}
fill
sizes="(min-width: 767px) 45vw, 100vw"
placeholder="blur"
blurDataURL="/images/blur.png"
className="rounded-full object-cover"
draggable={false}
/>
</div>
</Link>
</div>
)
})}
<div
className={`${Styles['contributors-bg-circle']} w-[100%] lg:w-[80%] left-[0%] lg:left-[10%] -bottom-[30%] xs:-bottom-[36%] sm:-bottom-[52%] md:-bottom-[64%] lg:-bottom-[80%] xl:-bottom-[100%]`}
>
<div className="flex flex-col justify-between h-full bg-alternative rounded-full p-4"></div>
</div>
<div
className={`${Styles['contributors-bg-circle']} w-[80%] lg:w-[60%] left-[10%] lg:left-[20%] -bottom-[25%] xs:-bottom-[30%] sm:-bottom-[44%] md:-bottom-[54%] lg:-bottom-[60%] xl:-bottom-[75%]`}
>
<div className="flex flex-col justify-between h-full bg-alternative rounded-full p-4"></div>
</div>
<div
className={`${Styles['contributors-bg-circle']} w-[60%] lg:w-[40%] left-[20%] lg:left-[30%] -bottom-[20%] xs:-bottom-[25%] sm:-bottom-[38%] md:-bottom-[44%] lg:-bottom-[40%] xl:-bottom-[50%]`}
>
<div className="flex flex-col justify-between h-full bg-alternative rounded-full p-4"></div>
</div>
<div
className={`${Styles['contributors-bg-circle']} w-[40%] lg:w-[20%] left-[30%] lg:left-[40%] -bottom-[15%] xs:-bottom-[19%] sm:-bottom-[30%] md:-bottom-[34%] lg:-bottom-[20%] xl:-bottom-[25%]`}
>
<div className="flex flex-col justify-between h-full bg-alternative rounded-full p-4"></div>
</div>
</div>
</SectionContainer>
</div>
</div>
<SectionContainer>
<div className="xl:flex lg:items-start xl:gap-10 justify-between">
<div className="xl:min-w-[300px] xl:max-w-[400px]">
<h2 className="text-2xl sm:text-3xl xl:text-4xl max-w-[280px] sm:max-w-xs xl:max-w-none tracking-[-1.5px]">
Great people deserve great benefits
</h2>
</div>
<div className="mt-12 xl:mt-0 space-y-6 lg:space-y-0 sm:w-fit sm:mx-auto lg:grid lg:grid-cols-2 lg:gap-16">
{career.benefits.map(
(benefits: { icon: string; title: string; text: string }, i: number) => {
return (
<div className="h-full flex items-start space-x-6 w-full" key={i}>
<div className="h-fit text-sm lg:text-base">
<h2 className="text-sm">{benefits.title}</h2>
<ReactMarkdown className="prose pt-1 text-sm">
{benefits.text}
</ReactMarkdown>
</div>
</div>
)
}
)}
</div>
</div>
</SectionContainer>
<SectionContainer>
<div className="text-center sm:max-w-md md:w-3/4 lg:max-w-lg xl:max-w-2xl mx-auto">
<h2 className="text-2xl sm:text-3xl xl:text-4xl tracking-[-1.5px]">How we hire</h2>
<p className="text-xs sm:text-sm lg:text-base text-foreground-light pt-3">
The entire process is fully remote and all communication happens over email or via
video chat in Google Meet. The calls are all 1:1 and usually take between 20-45
minutes. We know you are interviewing us too, so please ask questions. We are happy to
answer.
</p>
</div>
<div className="mt-16 md:ml-36 lg:flex lg:items-start lg:w-fit lg:mx-auto">
{career.hiring.map((hiring: { title: string; text: string }, i: number) => {
return (
<div
key={i + 1}
className="flex lg:block items-start space-x-6 lg:space-x-0 lg:w-full"
>
<div className="lg:flex items-center">
<h3 className="bg-brand-400 border-[1px] border-brand-300 text-brand-600 text-md text-center w-[44px] px-2 py-1.5 rounded-md">
{i + 1}
</h3>
<div className="h-[100px] w-[1px] sm:h-[100px] mx-auto lg:h-[1px] lg:w-full bg-brand-500 lg:pr-6"></div>
</div>
<div className="lg:mt-6">
<h2 className="sm:text-lg max-w-[75%] xl:max-w-none xl:w-11/12 lg:max-w-none">
{hiring.title}
</h2>
<p className="text-foreground-light text-xs sm:text-sm mt-1 md:w-3/4 lg:w-11/12">
{hiring.text}
</p>
</div>
</div>
)
})}
<h3 className="bg-brand-400 border-[1px] border-brand-300 text-brand-600 text-xl w-[44px] lg:min-w-[40px] min-h-[40px] px-2 py-1 rounded-md grid justify-items-center items-center">
<Check />
</h3>
</div>
</SectionContainer>
<Separator />
<div id="positions" className="max-w-6xl mx-auto">
<SectionContainer>
<h2 className="text-xl sm:text-3xl xl:text-3xl tracking-[-1.5px] mb-3">
Open positions
</h2>
<p className="max-w-xl text-foreground-lighter">
Want to build the best developer platform?
<br /> We’d love to talk to you.
</p>
<div className="mt-10 flex flex-col gap-4">
{Object.entries(jobs).map(([team, teamJobs]) => (
<div key={team}>
<h4 className="text-foreground-lighter text-sm">{team}</h4>
<div className="mt-2 -space-y-px">
{(teamJobs as JobItemProps[])
.filter((job: any) => !filterGenericJob(job))
.map((job: JobItemProps) => (
<JobItem job={job} key={job.id} />
))}
</div>
</div>
))}
</div>
<div className="pt-4 mt-4 border-t border-muted -space-y-px">
{placeholderJob && <JobItem job={placeholderJob} />}
</div>
</SectionContainer>
</div>
</DefaultLayout>
</>
)
}
const JobItem: React.FC<{ job: JobItemProps }> = ({ job }) => {
const isPlaceholderJob = job.id === PLACEHOLDER_JOB_ID
return (
<Link
href={job.jobUrl}
className="
first-of-type:rounded-t-md last-of-type:rounded-b-md
cursor-pointer md:cursor-default bg-surface-75 border border-muted drop-shadow-sm p-4 px-7
flex flex-col md:flex-row
md:items-center
transition hover:bg-surface-100
hover:cursor-pointer
"
target="_blank"
>
<h2 className="text-base min-w-[240px] lg:min-w-[316px] sm:truncate mr-6">{job.title}</h2>
<div className="flex justify-between justify-[normal] pt-2 md:pt-0 w-full items-center">
<div className="flex items-center gap-4">
<Badge>
<GlobeAltIcon className="w-3 h-3" />
<span className="ml-1">{job.location}</span>
</Badge>
<span className="hidden md:block">{job.employment}</span>
</div>
<div className={cn(buttonVariants({ type: 'default', size: 'tiny' }), 'rounded-full')}>
{isPlaceholderJob ? 'Submit resume' : 'Apply for position'}
</div>
</div>
</Link>
)
}
export default CareerPage
@@ -2,19 +2,20 @@ import { type CodeHikeConfig, remarkCodeHike } from '@code-hike/mdx'
import { CH } from '@code-hike/mdx/components'
import { ChevronLeft, ExternalLink } from 'lucide-react'
import { type GetStaticPaths, type GetStaticProps } from 'next'
import Image from 'next/image'
import Link from 'next/link'
import { MDXRemote, type MDXRemoteSerializeResult } from 'next-mdx-remote'
import { serialize } from 'next-mdx-remote/serialize'
import { NextSeo } from 'next-seo'
import Image from 'next/image'
import Link from 'next/link'
import { type Dispatch, type SetStateAction, useState } from 'react'
import remarkGfm from 'remark-gfm'
import { Swiper, SwiperSlide } from 'swiper/react'
import 'swiper/css'
import { Swiper, SwiperSlide } from 'swiper/react'
import { useBreakpoint } from 'common'
import codeHikeTheme from 'config/code-hike.theme.json' assert { type: 'json' }
import { Admonition, Button } from 'ui'
import { Button } from 'ui'
import { Admonition } from 'ui-patterns/admonition'
import { ExpandableVideo } from 'ui-patterns/ExpandableVideo'
import ImageModal from '~/components/ImageModal'
+74 -13
View File
@@ -31,6 +31,7 @@
"apps/design-system": {
"version": "0.1.0",
"dependencies": {
"@hookform/resolvers": "^3.1.1",
"@radix-ui/react-alert-dialog": "^1.0.5",
"contentlayer2": "0.4.6",
"eslint-config-supabase": "*",
@@ -41,20 +42,26 @@
"markdown-wasm": "^1.2.0",
"next": "^14.2.10",
"next-contentlayer2": "0.4.6",
"next-themes": "^0.3.0",
"react": "^18.2.0",
"react-docgen": "^7.0.3",
"react-dom": "^18.2.0",
"react-hook-form": "^7.45.0",
"react-inlinesvg": "^4.0.4",
"react-wrap-balancer": "^1.1.0",
"recharts": "^2.12.7",
"rehype-autolink-headings": "^7.1.0",
"rehype-pretty-code": "^0.6.0",
"rehype-slug": "^6.0.0",
"remark": "^14.0.3",
"remark-code-import": "^1.2.0",
"remark-gfm": "^4.0.0",
"sonner": "^1.5.0",
"ts-morph": "^22.0.0",
"ui": "*",
"unist-util-visit": "^5.0.0"
"ui-patterns": "*",
"unist-util-visit": "^5.0.0",
"zod": "^3.22.4"
},
"devDependencies": {
"@shikijs/compat": "^1.1.7",
@@ -70,7 +77,7 @@
"shiki": "^1.1.7",
"tailwindcss": "^3.3.0",
"tsconfig": "*",
"typescript": "^5.5.0",
"typescript": "~5.5.0",
"unist-builder": "3.0.0"
}
},
@@ -860,12 +867,14 @@
"@octokit/graphql": "^7.0.2",
"@octokit/plugin-paginate-graphql": "^4.0.0",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-collapsible": "^1.0.3",
"@supabase/auth-helpers-react": "^0.4.2",
"@supabase/supabase-js": "^2.44.3",
"@tailwindcss/container-queries": "^0.1.1",
"@tanstack/react-query": "^5.13.4",
"acorn": "^8.11.3",
"acorn-typescript": "^1.4.13",
"ai-commands": "*",
"common": "*",
"common-tags": "^1.8.2",
"config": "*",
@@ -874,8 +883,10 @@
"github-slugger": "^2.0.0",
"gray-matter": "^4.0.3",
"hast-util-has-property": "^2.0.1",
"hast-util-to-html": "^9.0.0",
"icons": "*",
"isbot": "^5.1.2",
"js-yaml": "^3.14.1",
"jsrsasign": "^11.0.0",
"katex": "^0.16.10",
"lodash": "^4.17.21",
@@ -885,6 +896,8 @@
"mdast-util-from-markdown": "^1.2.0",
"mdast-util-gfm": "^2.0.2",
"mdast-util-mdx": "^2.0.0",
"mdast-util-mdx-jsx": "^2.0.0",
"mdast-util-to-hast": "^12.3.0",
"mdast-util-to-markdown": "^1.5.0",
"mdast-util-to-string": "^3.1.1",
"micromark-extension-gfm": "^2.0.3",
@@ -897,10 +910,12 @@
"openai": "^4.20.1",
"openapi-fetch": "^0.8.2",
"react": "^18.2.0",
"react-copy-to-clipboard": "^5.1.0",
"react-dom": "^18.2.0",
"react-error-boundary": "^4.0.12",
"react-intersection-observer": "^9.5.3",
"react-markdown": "^8.0.3",
"react-use": "^17.5.0",
"rehype-katex": "^7.0.0",
"rehype-slug": "^5.1.0",
"remark": "^14.0.2",
@@ -915,16 +930,21 @@
"unist-builder": "^3.0.1",
"unist-util-filter": "^4.0.1",
"unist-util-visit": "^4.1.2",
"unist-util-visit-parents": "^5.0.0",
"uuid": "^9.0.1",
"valtio": "^1.12.0",
"yaml": "^2.4.5"
"yaml": "^2.4.5",
"zod": "^3.22.4"
},
"devDependencies": {
"@aws-sdk/client-secrets-manager": "^3.410.0",
"@types/estree": "1.0.5",
"@types/hast": "^2.3.4",
"@types/jsrsasign": "^10.5.12",
"@types/mdast": "^3.0.0",
"@types/node": "^20.11.16",
"@types/react": "^18.2.24",
"@types/react-copy-to-clipboard": "^5.0.4",
"@types/unist": "^2.0.6",
"api-types": "*",
"cheerio": "^1.0.0-rc.12",
@@ -939,7 +959,7 @@
"slugify": "^1.6.6",
"tsconfig": "*",
"tsx": "^4.6.2",
"typescript": "^5.5.0",
"typescript": "~5.5.0",
"vite-tsconfig-paths": "^4.3.2",
"vitest": "^2.0.2"
}
@@ -1793,6 +1813,7 @@
"@hcaptcha/react-hcaptcha": "^1.8.1",
"@headlessui/react": "^1.7.17",
"@heroicons/react": "^2.1.3",
"@hookform/resolvers": "^3.1.1",
"@monaco-editor/react": "^4.6.0",
"@next/bundle-analyzer": "^14.2.3",
"@radix-ui/react-popover": "^1.0.7",
@@ -1801,7 +1822,9 @@
"@stripe/react-stripe-js": "^2.5.0",
"@stripe/stripe-js": "^3.0.5",
"@supabase/auth-helpers-react": "^0.4.2",
"@supabase/auth-js": "2.64.2",
"@supabase/pg-meta": "*",
"@supabase/realtime-js": "2.10.2",
"@supabase/shared-types": "0.1.70",
"@supabase/supabase-js": "^2.44.3",
"@tanstack/react-query": "4.35.7",
@@ -1818,6 +1841,7 @@
"config": "*",
"configcat-js": "^7.0.0",
"dayjs": "^1.11.10",
"dnd-core": "^16.0.1",
"file-saver": "^2.0.5",
"framer-motion": "^11.0.3",
"generate-password-browser": "^1.1.0",
@@ -1829,6 +1853,7 @@
"lodash": "^4.17.21",
"lucide-react": "^0.436.0",
"markdown-table": "^3.0.3",
"memoize-one": "^5.0.1",
"mime-db": "^1.53.0",
"mobx": "^6.10.2",
"mobx-react-lite": "^4.0.5",
@@ -1846,6 +1871,7 @@
"react": "^18.2.0",
"react-beautiful-dnd": "^13.1.0",
"react-contexify": "^5.0.0",
"react-copy-to-clipboard": "^5.1.0",
"react-csv": "^2.0.3",
"react-data-grid": "7.0.0-beta.41",
"react-datepicker": "^4.18.0",
@@ -1854,18 +1880,22 @@
"react-dom": "^18.2.0",
"react-error-boundary": "^4.0.13",
"react-grid-layout": "^1.4.2",
"react-hook-form": "^7.45.0",
"react-inlinesvg": "^4.0.4",
"react-intersection-observer": "^9.5.3",
"react-markdown": "^8.0.3",
"react-simple-maps": "4.0.0-beta.6",
"react-tracked": "^1.7.11",
"react-use": "^17.5.0",
"react-virtualized-auto-sizer": "^1.0.20",
"react-window": "^1.8.6",
"react-window-infinite-loader": "^1.0.7",
"reactflow": "^11.10.1",
"recharts": "^2.8.0",
"remark-gfm": "^3.0.1",
"semver": "^6.3.1",
"shared-data": "*",
"sonner": "^1.5.0",
"sql-formatter": "^13.1.0",
"sqlstring": "^2.3.2",
"tus-js-client": "^4.1.0",
@@ -1877,6 +1907,7 @@
"vitest": "^1.6.0",
"yup": "^1.4.0",
"yup-password": "^0.3.0",
"zod": "^3.22.4",
"zxcvbn": "^4.4.2"
},
"devDependencies": {
@@ -1899,6 +1930,7 @@
"@types/randombytes": "^2.0.3",
"@types/react": "^18.2.24",
"@types/react-beautiful-dnd": "^13.1.2",
"@types/react-copy-to-clipboard": "^5.0.4",
"@types/react-csv": "^1.1.2",
"@types/react-datepicker": "^4.3.4",
"@types/react-dom": "^18.2.8",
@@ -1925,7 +1957,7 @@
"prettier": "^4.0.0-alpha.8",
"storybook-dark-mode": "^3.0.1",
"tailwindcss": "^3.4.1",
"typescript": "^5.5.0"
"typescript": "~5.5.0"
}
},
"apps/studio/node_modules/@heroicons/react": {
@@ -2971,15 +3003,19 @@
"@octokit/graphql": "^7.0.2",
"@octokit/plugin-paginate-graphql": "^4.0.0",
"@octokit/rest": "^20.0.2",
"@radix-ui/react-dialog": "^1.0.5",
"@react-three/fiber": "^7.0.29",
"@supabase/auth-helpers-react": "^0.4.2",
"@supabase/auth-ui-react": "^0.4.5",
"@supabase/supabase-js": "^2.44.3",
"@vercel/og": "^0.6.2",
"ai-commands": "*",
"animejs": "^3.2.2",
"classnames": "^2.3.1",
"clsx": "^1.2.1",
"cobe": "^0.6.2",
"common": "*",
"common-tags": "^1.8.2",
"config": "*",
"contentlayer2": "0.4.6",
"dat.gui": "^0.7.9",
@@ -2989,6 +3025,7 @@
"globby": "^13.2.2",
"gray-matter": "^4.0.3",
"icons": "*",
"lodash": "^4.0.0",
"lucide-react": "*",
"markdown-toc": "^1.2.0",
"next": "^14.2.10",
@@ -2996,6 +3033,7 @@
"next-mdx-remote": "^4.4.1",
"next-seo": "^6.5.0",
"next-themes": "^0.3.0",
"openai": "^4.20.1",
"parse-numeric-range": "^1.3.0",
"react": "^18.2.0",
"react-copy-to-clipboard": "^5.1.0",
@@ -3015,7 +3053,7 @@
"swiper": "^11.0.7",
"three": "^0.136.0",
"typed.js": "^2.0.16",
"typescript": "^5.5.0",
"typescript": "~5.5.0",
"ui": "*",
"ui-patterns": "*",
"use-debounce": "^7.0.1",
@@ -3025,7 +3063,9 @@
"devDependencies": {
"@types/animejs": "^3.1.12",
"@types/classnames": "^2.3.1",
"@types/common-tags": "^1.8.4",
"@types/dat.gui": "^0.7.10",
"@types/lodash": "^4.0.0",
"@types/mdx-js__react": "^1.5.6",
"@types/node": "^20.11.16",
"@types/parse-numeric-range": "^0.0.1",
@@ -43303,7 +43343,7 @@
"sql-formatter": "^15.0.2",
"ts-jest": "^29.1.1",
"tsconfig": "*",
"typescript": "^5.5.0"
"typescript": "~5.5.0"
}
},
"packages/ai-commands/node_modules/argparse": {
@@ -43408,10 +43448,11 @@
"react-use": "^17.4.0"
},
"devDependencies": {
"@types/lodash": "4.17.5",
"@types/react": "^18.2.24",
"@types/react-dom": "^18.2.8",
"tsconfig": "*",
"typescript": "^5.5.0"
"typescript": "~5.5.0"
},
"peerDependencies": {
"@supabase/auth-js": "^2.63.0",
@@ -43464,7 +43505,13 @@
"packages/icons": {
"dependencies": {
"@supabase/build-icons": "*",
"typescript": "^5.5.0"
"typescript": "~5.5.0"
},
"devDependencies": {
"@types/react": "^18.2.24"
},
"peerDependencies": {
"react": "*"
}
},
"packages/pg-meta": {
@@ -43475,7 +43522,7 @@
"zod": "^3.22.4"
},
"devDependencies": {
"typescript": "^5.5.0"
"typescript": "~5.5.0"
}
},
"packages/shared-data": {
@@ -43498,6 +43545,7 @@
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-collapsible": "^1.0.3",
"@radix-ui/react-context": "^1.0.1",
"@radix-ui/react-context-menu": "^2.1.5",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
@@ -43577,6 +43625,7 @@
"@testing-library/react": "^14.0.0",
"@tokens-studio/sd-transforms": "^0.8.5",
"@types/common-tags": "^1.8.1",
"@types/lodash": "4.17.5",
"@types/react": "^18.2.24",
"@types/react-copy-to-clipboard": "^5.0.4",
"@types/react-dom": "^18.2.8",
@@ -43592,7 +43641,8 @@
"ts-jest": "^29.1.1",
"tsconfig": "*",
"tsconfig-paths-webpack-plugin": "^4.0.1",
"typescript": "^5.5.0"
"typescript": "~5.5.0",
"vitest": "^1.6.0"
},
"peerDependencies": {
"next": "*",
@@ -43603,8 +43653,11 @@
"packages/ui-patterns": {
"license": "MIT",
"dependencies": {
"@hookform/resolvers": "^3.1.1",
"@monaco-editor/react": "^4.6.0",
"@supabase/sql-to-rest": "^0.1.6",
"class-variance-authority": "^0.6.0",
"clsx": "^1.2.1",
"cmdk": "^1.0.0",
"common": "*",
"common-tags": "^1.8.2",
@@ -43614,18 +43667,24 @@
"lucide-react": "*",
"monaco-editor": "*",
"next-themes": "*",
"openai": "^4.20.1",
"prism-react-renderer": "^2.3.1",
"react-error-boundary": "^4.0.12",
"react-hook-form": "^7.45.0",
"react-intersection-observer": "^9.8.2",
"react-markdown": "^9.0.1",
"react-syntax-highlighter": "^15.5.0",
"react-tooltip": "*",
"react-use": "^17.5.0",
"reactflow": "*",
"remark-gfm": "^4.0.0",
"sonner": "^1.5.0",
"sql-formatter": "^15.3.1",
"sse.js": "^2.2.0",
"tsconfig": "*",
"ui": "*",
"valtio": "*"
"valtio": "*",
"zod": "^3.22.4"
},
"devDependencies": {
"@supabase/auth-helpers-react": "^0.4.2",
@@ -43639,6 +43698,8 @@
"@types/react-dom": "^18.3.0",
"api-types": "*",
"next-router-mock": "^0.9.13",
"typescript": "~5.5.0",
"vite": "^5.2.14",
"vitest": "^2.0.3"
},
"peerDependencies": {
@@ -44755,7 +44816,7 @@
"jest-environment-jsdom": "^29.7.0",
"postgres": "^3.0.5",
"ts-jest": "^29.1.1",
"typescript": "^5.5.0"
"typescript": "~5.5.0"
}
},
"tests/node_modules/@testdeck/core": {
+1 -1
View File
@@ -34,6 +34,6 @@
"sql-formatter": "^15.0.2",
"ts-jest": "^29.1.1",
"tsconfig": "*",
"typescript": "^5.5.0"
"typescript": "~5.5.0"
}
}
@@ -136,8 +136,8 @@ export const CLIENT_LIBRARIES = [
url: 'https://github.com/supabase-community/supabase-kt/tree/master/Postgrest',
},
{
name: 'gotrue-kt',
url: 'https://github.com/supabase-community/supabase-kt/tree/master/GoTrue',
name: 'auth-kt',
url: 'https://github.com/supabase-community/supabase-kt/tree/master/Auth',
},
{
name: 'storage-kt',
+2 -1
View File
@@ -17,10 +17,11 @@
"react-use": "^17.4.0"
},
"devDependencies": {
"@types/lodash": "4.17.5",
"@types/react": "^18.2.24",
"@types/react-dom": "^18.2.8",
"tsconfig": "*",
"typescript": "^5.5.0"
"typescript": "~5.5.0"
},
"peerDependencies": {
"@supabase/supabase-js": "*",
+7 -1
View File
@@ -5,7 +5,13 @@
"build:icons": "build-icons --templateSrc=./scripts/exportTemplate.mjs --renderUniqueKey --iconFileExtension=.ts --exportFileName=index.ts"
},
"dependencies": {
"typescript": "^5.5.0",
"typescript": "~5.5.0",
"@supabase/build-icons": "*"
},
"devDependencies": {
"@types/react": "^18.2.24"
},
"peerDependencies": {
"react": "*"
}
}
+1 -1
View File
@@ -9,6 +9,6 @@
"zod": "^3.22.4"
},
"devDependencies": {
"typescript": "^5.5.0"
"typescript": "~5.5.0"
}
}
-3
View File
@@ -1,7 +1,4 @@
import { hover } from '@testing-library/user-event/dist/hover'
import { drop } from 'lodash'
import Image from 'next/image'
import xl from 'react-syntax-highlighter/dist/esm/languages/hljs/xl'
import { cn } from 'ui/src/lib/utils'
interface TweetCard {
+14 -15
View File
@@ -1,7 +1,6 @@
import { cva } from 'class-variance-authority'
import React, { forwardRef } from 'react'
import { cn } from 'ui/src/lib/utils/cn'
import { Alert, AlertDescription, AlertTitle } from 'ui/src/components/shadcn/ui/alert'
import { forwardRef } from 'react'
import { Alert_Shadcn_, AlertDescription_Shadcn_, AlertTitle_Shadcn_, cn } from 'ui'
export interface AdmonitionProps {
type:
@@ -18,8 +17,8 @@ export interface AdmonitionProps {
description?: string | React.ReactNode
showIcon?: boolean
childProps?: {
title?: React.ComponentProps<typeof AlertTitle>
description?: React.ComponentProps<typeof AlertDescription>
title?: React.ComponentProps<typeof AlertTitle_Shadcn_>
description?: React.ComponentProps<typeof AlertDescription_Shadcn_>
}
}
@@ -88,8 +87,8 @@ const admonitionBase = cva('', {
})
export const Admonition = forwardRef<
React.ElementRef<typeof Alert>,
React.ComponentPropsWithoutRef<typeof Alert> & AdmonitionProps
React.ElementRef<typeof Alert_Shadcn_>,
React.ComponentPropsWithoutRef<typeof Alert_Shadcn_> & AdmonitionProps
>(
(
{ type = 'note', variant, showIcon = true, label, title, description, children, ...props },
@@ -98,7 +97,7 @@ export const Admonition = forwardRef<
const typeMapped = variant ? admonitionToAlertMapping[variant] : admonitionToAlertMapping[type]
return (
<Alert
<Alert_Shadcn_
ref={ref}
variant={typeMapped}
{...props}
@@ -116,7 +115,7 @@ export const Admonition = forwardRef<
)}
{label || title ? (
<>
<AlertTitle
<AlertTitle_Shadcn_
{...props.childProps?.title}
className={cn(
'text mt-0.5 flex gap-3 text-sm [&_p]:mb-1.5 [&_p]:mt-0',
@@ -125,15 +124,15 @@ export const Admonition = forwardRef<
)}
>
{label || title}
</AlertTitle>
</AlertTitle_Shadcn_>
{description && (
<AlertDescription className={props.childProps?.description?.className}>
<AlertDescription_Shadcn_ className={props.childProps?.description?.className}>
{description}
</AlertDescription>
</AlertDescription_Shadcn_>
)}
{/* // children is to handle Docs and MDX issues with children and <p> elements */}
{children && (
<AlertDescription
<AlertDescription_Shadcn_
{...props.childProps?.description}
className={cn(
'mt-3 [&_p]:mb-1.5 [&_p]:mt-0',
@@ -141,7 +140,7 @@ export const Admonition = forwardRef<
)}
>
{children}
</AlertDescription>
</AlertDescription_Shadcn_>
)}
</>
) : (
@@ -149,7 +148,7 @@ export const Admonition = forwardRef<
{children}
</div>
)}
</Alert>
</Alert_Shadcn_>
)
}
)
@@ -1,4 +1,5 @@
import { cva } from 'class-variance-authority'
import { motion } from 'framer-motion'
import React from 'react'
import {
FormDescription_Shadcn_,
@@ -8,7 +9,6 @@ import {
cn,
} from 'ui'
import { SIZE } from 'ui/src/lib/constants'
import { AnimatePresence, motion } from 'framer-motion'
type Props = {
align?: 'left' | 'right'
+12 -1
View File
@@ -9,8 +9,11 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@hookform/resolvers": "^3.1.1",
"@monaco-editor/react": "^4.6.0",
"@supabase/sql-to-rest": "^0.1.6",
"class-variance-authority": "^0.6.0",
"clsx": "^1.2.1",
"cmdk": "^1.0.0",
"common": "*",
"common-tags": "^1.8.2",
@@ -21,17 +24,23 @@
"monaco-editor": "*",
"next-themes": "*",
"prism-react-renderer": "^2.3.1",
"openai": "^4.20.1",
"react-error-boundary": "^4.0.12",
"react-hook-form": "^7.45.0",
"react-intersection-observer": "^9.8.2",
"react-markdown": "^9.0.1",
"react-syntax-highlighter": "^15.5.0",
"react-tooltip": "*",
"react-use": "^17.5.0",
"reactflow": "*",
"remark-gfm": "^4.0.0",
"sonner": "^1.5.0",
"sql-formatter": "^15.3.1",
"sse.js": "^2.2.0",
"tsconfig": "*",
"ui": "*",
"valtio": "*"
"valtio": "*",
"zod": "^3.22.4"
},
"devDependencies": {
"@supabase/auth-helpers-react": "^0.4.2",
@@ -45,6 +54,8 @@
"@types/react-dom": "^18.3.0",
"api-types": "*",
"next-router-mock": "^0.9.13",
"typescript": "~5.5.0",
"vite": "^5.2.14",
"vitest": "^2.0.3"
},
"peerDependencies": {
-1
View File
@@ -4,7 +4,6 @@ export * from './src/lib/Markdown'
// GENERAL
export * from 'ui-patterns/admonition'
export * from './src/components/Button'
export * from './src/components/Icon'
export * from './src/components/Icon/IconContext'
-15
View File
@@ -1,15 +0,0 @@
import type { Config } from '@jest/types'
const config: Config.InitialOptions = {
preset: 'ts-jest',
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
testEnvironment: 'jsdom',
// Restricting test file pattern to just tsx files since the js files need a babel preset to run
// and most likely won't pass now anyway - the tests were ported from the UI repo and are out of date
testRegex: '(.*\\.test.tsx$)',
moduleNameMapper: {
'^@ui/(.*)$': '<rootDir>/src/$1',
},
}
export default config
-1
View File
@@ -1 +0,0 @@
import '@testing-library/jest-dom'
+5 -6
View File
@@ -12,7 +12,7 @@
"transform-tokens": "node ./transformTokens.js",
"extract-design-tokens": "node internals/tokens/extract-design-tokens.js",
"generate-styles": "npm run extract-design-tokens && npm run transform-tokens && npm run cleanse-css-for-tailwind && npm run generate-demo-tailwind-classes",
"test": "jest"
"test": "vitest"
},
"dependencies": {
"@headlessui/react": "^1.7.17",
@@ -24,6 +24,7 @@
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-collapsible": "^1.0.3",
"@radix-ui/react-context": "^1.0.1",
"@radix-ui/react-context-menu": "^2.1.5",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
@@ -98,11 +99,11 @@
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.21.4",
"@ctrl/tinycolor": "^3.4.0",
"@jest/types": "^29.6.0",
"@testing-library/jest-dom": "^6.1.3",
"@testing-library/react": "^14.0.0",
"@tokens-studio/sd-transforms": "^0.8.5",
"@types/common-tags": "^1.8.1",
"@types/lodash": "4.17.5",
"@types/react": "^18.2.24",
"@types/react-copy-to-clipboard": "^5.0.4",
"@types/react-dom": "^18.2.8",
@@ -111,14 +112,12 @@
"config": "*",
"copyfiles": "^2.4.1",
"glob": "^8.1.0",
"jest-environment-jsdom": "^29.7.0",
"next": "*",
"sql-formatter": "^12.2.0",
"style-dictionary": "^3.7.1",
"ts-jest": "^29.1.1",
"tsconfig": "*",
"tsconfig-paths-webpack-plugin": "^4.0.1",
"typescript": "^5.5.0"
"typescript": "~5.5.0",
"vitest": "^1.6.0"
},
"peerDependencies": {
"next": "*",
Loaded 100 of 122 files, more files were not shown because too many files have changed in this diff. Show more