Merge branch 'master' into feature/storage-marketing

# Conflicts:
#	www/package.json
#	www/public/rss.xml
This commit is contained in:
Jonathan Summers-Muir committed 2021-03-30 20:41:37 +08:00
commit a8778d79e2
181 files changed
+17654 -1013

No files matched your search

+3 -3
View File
@@ -7861,9 +7861,9 @@
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
},
"y18n": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w=="
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz",
"integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ=="
},
"yaeti": {
"version": "0.0.6",
@@ -0,0 +1,34 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
# vercel
.vercel
@@ -0,0 +1,5 @@
# Supabase Next.js User Management
This is a WIP!
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https%3A%2F%2Fgithub.com%2Fsupabase%2Fsupabase%2Ftree%2Fmaster%2Fexamples%2Fnextjs-ts-user-management&project-name=supabase-user-management&repository-name=supabase-user-management&demo-title=Supabase%20User%20Management&demo-description=An%20example%20web%20app%20using%20Supabase%20and%20Next.js&demo-url=https%3A%2F%2Fsupabase-nextjs-ts-user-management.vercel.app&demo-image=https%3A%2F%2Fi.imgur.com%2FZ3HkQqe.png&integration-ids=oac_jUduyjQgOyzev1fjrW83NYOv&external-id=nextjs-user-management)
@@ -0,0 +1,172 @@
import { useState, useEffect, ChangeEvent } from 'react'
import { supabase } from '../lib/supabaseClient'
import UploadButton from '../components/UploadButton'
import Avatar from './Avatar'
import { AuthSession } from '@supabase/supabase-js'
import { DEFAULT_AVATARS_BUCKET, Profile } from '../lib/constants'
export default function Account({
session,
}: {
session: AuthSession
}) {
const [loading, setLoading] = useState<boolean>(true)
const [uploading, setUploading] = useState<boolean>(false)
const [avatar, setAvatar] = useState<string | null>(null)
const [username, setUsername] = useState<string | null>(null)
const [website, setWebsite] = useState<string | null>(null)
useEffect(() => {
getProfile()
}, [session])
async function signOut() {
const { error } = await supabase.auth.signOut()
if (error) console.log('Error logging out:', error.message)
}
async function uploadAvatar(event: ChangeEvent<HTMLInputElement>) {
try {
setUploading(true)
if (!event.target.files || event.target.files.length == 0) {
throw 'You must select an image to upload.'
}
const user = supabase.auth.user()
const file = event.target.files[0]
const fileExt = file.name.split('.').pop()
const fileName = `${session?.user.id}${Math.random()}.${fileExt}`
const filePath = `${DEFAULT_AVATARS_BUCKET}/${fileName}`
let { error: uploadError } = await supabase.storage.uploadFile(filePath, file)
if (uploadError) {
throw uploadError
}
let { error: updateError } = await supabase.from('profiles').upsert({
id: user!.id,
avatar_url: filePath,
})
if (updateError) {
throw updateError
}
setAvatar(null)
setAvatar(filePath)
} catch (error) {
alert(error.message)
} finally {
setUploading(false)
}
}
function setProfile(profile: Profile) {
setAvatar(profile.avatar_url)
setUsername(profile.username)
setWebsite(profile.website)
}
async function getProfile() {
try {
setLoading(true)
const user = supabase.auth.user()
let { data, error } = await supabase
.from('profiles')
.select(`username, website, avatar_url`)
.eq('id', user!.id)
.single()
if (error) {
throw error
}
setProfile(data)
} catch (error) {
console.log('error', error.message)
} finally {
setLoading(false)
}
}
async function updateProfile() {
try {
setLoading(true)
const user = supabase.auth.user()
const updates = {
id: user!.id,
username,
website,
updated_at: new Date(),
}
let { error } = await supabase.from('profiles').upsert(updates, {
returning: 'minimal', // Don't return the value after inserting
})
if (error) {
throw error
}
} catch (error) {
alert(error.message)
} finally {
setLoading(false)
}
}
return (
<div className="account">
<div>
<label htmlFor="email">Email</label>
<input id="email" type="text" value={session.user.email} disabled />
</div>
<div>
<label htmlFor="username">Name</label>
<input
id="username"
type="text"
value={username || ''}
onChange={(e) => setUsername(e.target.value)}
/>
</div>
<div>
<label htmlFor="website">Website</label>
<input
id="website"
type="website"
value={website || ''}
onChange={(e) => setWebsite(e.target.value)}
/>
</div>
<div>
<label htmlFor="avatar">Avatar image</label>
<div className="avatarField">
<div className="avatarContainer">
{avatar ? (
<Avatar url={avatar} size={40} />
) : (
<div className="avatarPlaceholder">?</div>
)}
</div>
<UploadButton onUpload={uploadAvatar} loading={uploading} />
</div>
</div>
<div>
<button className="button block primary" onClick={() => updateProfile()} disabled={loading}>
{loading ? 'Loading ...' : 'Update'}
</button>
</div>
<div>
<button className="button block" onClick={() => signOut()}>
Sign Out
</button>
</div>
</div>
)
}
@@ -0,0 +1,55 @@
import { useState } from 'react'
import { supabase } from '../lib/supabaseClient'
// import styles from '../styles/Auth.module.css'
export default function Auth({}) {
const [loading, setLoading] = useState(false)
const [email, setEmail] = useState('')
const handleLogin = async (email: string) => {
try {
setLoading(true)
const { error, user } = await supabase.auth.signIn({ email })
if (error) throw error
console.log('user', user)
alert('Check your email for the login link!')
} catch (error) {
console.log('Error thrown:', error.message)
alert(error.error_description || error.message)
} finally {
setLoading(false)
}
}
return (
<div className="authContainer">
<div className="authTitle">
<h1 className="header">Experience our Auth and Storage system</h1>
<p className="description">
Through a simple profile management example. Create a user profile and upload an avatar
image. Fast, simple, secure.
</p>
</div>
<div className="authWidget" style={{ display: 'flex', gap: 20, flexDirection: 'column' }}>
<p className="description">Sign in via magic link with your email below</p>
<input
className="inputField"
type="email"
placeholder="Your email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<button
onClick={(e) => {
e.preventDefault()
handleLogin(email)
}}
className={'button block'}
disabled={loading}
>
{loading ? <img className="loader" src="loader.svg" /> : <span>Send magic link</span>}
</button>
</div>
</div>
)
}
@@ -0,0 +1,29 @@
import { useEffect, useState } from 'react'
import { supabase } from '../lib/supabaseClient'
export default function Avatar({ url, size }: { url: string | null; size: number }) {
const [avatarUrl, setAvatarUrl] = useState<string | null>(null)
useEffect(() => {
if (url) downloadImage(url)
}, [url])
async function downloadImage(path: string) {
try {
const { data, error } = await supabase.storage.downloadFile(path)
if (error) {
throw error
}
const url = URL.createObjectURL(data)
setAvatarUrl(url)
} catch (error) {
console.log('Error downloading image: ', error.message)
}
}
return avatarUrl ? (
<img src={avatarUrl} className="avatar image" style={{ height: size, width: size }} />
) : (
<div className="avatar no-image flex flex-center items-center" style={{ height: size, width: size }}>?</div>
)
}
@@ -0,0 +1,19 @@
export default function Footer({ }) {
return (
<div className="footer">
<div></div>
<div className="flex-center">
<span className="text-sm">Powered by</span>
<img src="logo.png" />
</div>
<div className="flex-end">
<a
target="_blank"
href="https://vercel.com/new/git/external?repository-url=https%3A%2F%2Fgithub.com%2Fsupabase%2Fsupabase%2Ftree%2Fmaster%2Fexamples%2Fnextjs-ts-user-management&project-name=supabase-user-management&repository-name=supabase-user-management&demo-title=Supabase%20User%20Management&demo-description=An%20example%20web%20app%20using%20Supabase%20and%20Next.js&demo-url=https%3A%2F%2Fsupabase-nextjs-ts-user-management.vercel.app&demo-image=https%3A%2F%2Fi.imgur.com%2FZ3HkQqe.png&integration-ids=oac_jUduyjQgOyzev1fjrW83NYOv&external-id=nextjs-user-management"
>
<img src="https://vercel.com/button" />
</a>
</div>
</div>
)
}
@@ -0,0 +1,24 @@
import { Profile } from '../lib/constants'
import Avatar from './Avatar'
export default function ProfileCard({ profile }: { profile: Profile }) {
const lastUpdated = profile.updated_at ? new Date(profile.updated_at) : null
return (
<div className="profileCard">
<Avatar url={profile.avatar_url} size={100} />
<div className="userInfo">
<p className="username">{profile.username}</p>
<a className="website" href={profile.website} target="_blank">{profile.website}</a>
<p>
<small>
Last updated{' '}
{lastUpdated
? `${lastUpdated.toLocaleDateString()} ${lastUpdated.toLocaleTimeString()}`
: 'Never'}
</small>
</p>
</div>
<div />
</div>
)
}
@@ -0,0 +1,70 @@
import ProfileCard from '../components/ProfileCard'
import { Profile } from '../lib/constants'
import { supabase } from '../lib/supabaseClient'
import { useEffect, useReducer } from 'react'
/**
* Since we want this component to update in realtime,
* we should use "useReducer" for sending Realtime events
*/
type State = {
profiles: Profile[]
}
type Action = {
type?: string
payload: any
}
type ProfileListProps = {
profiles: Profile[]
}
const handleDatabaseEvent = (state: State, action: Action) => {
if (action.type === 'upsert') {
const otherProfiles = state.profiles.filter((x) => x.id != action.payload.id)
return {
profiles: [action.payload, ...otherProfiles],
}
} else if (action.type === 'set') {
return {
profiles: action.payload
}
}
return { profiles: [] }
}
export default function ProfileList({ profiles }: ProfileListProps) {
const initialState: State = { profiles }
const [state, dispatch] = useReducer(handleDatabaseEvent, initialState)
useEffect(() => {
const subscription = supabase
.from('profiles')
.on('*', (payload) => {
dispatch({ type: 'upsert', payload: payload.new })
})
.subscribe()
return () => {
supabase.removeSubscription(subscription)
}
}, [])
useEffect(() => {
dispatch({ type: 'set', payload: profiles })
}, [profiles])
return (
<>
{state.profiles.length === 0 ? (
<p className="opacity-half font-light m-0">There are no public profiles created yet</p>
) : (
<div className="profileList">
{state.profiles?.map((profile: any) => (
<ProfileCard profile={profile} key={profile.id} />
))}
</div>
)}
</>
)
}
@@ -0,0 +1,27 @@
import { ChangeEventHandler } from 'react'
export type UploadButtonProps = {
onUpload: ChangeEventHandler<HTMLInputElement>
loading: boolean
}
export default function UploadButton(props: UploadButtonProps) {
return (
<div>
<label className="button primary block" htmlFor="single">
{props.loading ? 'Uploading ...' : 'Upload'}
</label>
<input
style={{
visibility: 'hidden',
position: 'absolute',
}}
type="file"
id="single"
accept="image/*"
onChange={props.onUpload}
disabled={props.loading}
/>
</div>
)
}
@@ -0,0 +1,12 @@
export const NEXT_PUBLIC_SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL
export const NEXT_PUBLIC_SUPABASE_ANON_KEY = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
export const DEFAULT_AVATARS_BUCKET = 'avatars'
export type Profile = {
id: string
avatar_url: string
username: string
website: string
updated_at: string
}
@@ -0,0 +1,7 @@
import { createClient } from '@supabase/supabase-js'
import { NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY } from './constants'
if (!NEXT_PUBLIC_SUPABASE_URL) throw new Error('Missing env.NEXT_PUBLIC_SUPABASE_URL')
if (!NEXT_PUBLIC_SUPABASE_ANON_KEY) throw new Error('Missing env.NEXT_PUBLIC_SUPABASE_ANON_KEY')
export const supabase = createClient(NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY)
+2
View File
@@ -0,0 +1,2 @@
/// <reference types="next" />
/// <reference types="next/types/global" />
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,20 @@
{
"name": "next-storage",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
},
"dependencies": {
"@supabase/supabase-js": "^1.7.1",
"next": "10.0.9",
"react": "17.0.1",
"react-dom": "17.0.1"
},
"devDependencies": {
"@types/react": "^17.0.3",
"typescript": "^4.2.3"
}
}
@@ -0,0 +1,7 @@
import '../styles/globals.css'
import type { AppProps } from 'next/app'
function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
}
export default MyApp
@@ -0,0 +1,71 @@
import Auth from '../components/Auth'
import Account from '../components/Account'
import ProfileList from '../components/ProfileList'
import { useState, useEffect } from 'react'
import { supabase } from '../lib/supabaseClient'
import { AuthSession } from '@supabase/supabase-js'
import { Profile } from '../lib/constants'
import Footer from '../components/Footer'
export default function Home() {
const [session, setSession] = useState<AuthSession | null>(null)
const [profiles, setProfiles] = useState<Profile[]>([])
useEffect(() => {
setSession(supabase.auth.session())
supabase.auth.onAuthStateChange((_event: string, session: AuthSession | null) => {
setSession(session)
})
}, [])
useEffect(() => {
getPublicProfiles()
}, [])
async function getPublicProfiles() {
try {
const { data, error } = await supabase
.from<Profile>('profiles')
.select('id, username, avatar_url, website, updated_at')
.order('updated_at', { ascending: false })
if (error || !data) {
throw error || new Error('No data')
}
console.log('Public profiles:', data)
setProfiles(data)
} catch (error) {
console.log('error', error.message)
}
}
return (
<div
style={{
minHeight: '100vh',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
}}
>
{!session ? (
<Auth />
) : (
<div className="flex" style={{ width: '75%', maxWidth: '850px', gap: 30 }}>
<div className="flex column w-half">
<h3>Account</h3>
<Account key={session.user.id} session={session} />
</div>
<div className="flex column w-half" style={{ gap: 20 }}>
<h3>Public Profiles</h3>
<ProfileList profiles={profiles} />
</div>
</div>
)}
<Footer />
</div>
)
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-loader"><line x1="12" y1="2" x2="12" y2="6"></line><line x1="12" y1="18" x2="12" y2="22"></line><line x1="4.93" y1="4.93" x2="7.76" y2="7.76"></line><line x1="16.24" y1="16.24" x2="19.07" y2="19.07"></line><line x1="2" y1="12" x2="6" y2="12"></line><line x1="18" y1="12" x2="22" y2="12"></line><line x1="4.93" y1="19.07" x2="7.76" y2="16.24"></line><line x1="16.24" y1="7.76" x2="19.07" y2="4.93"></line></svg>

After

Width:  |  Height:  |  Size: 614 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

@@ -0,0 +1,4 @@
<svg width="283" height="64" viewBox="0 0 283 64" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" fill="#000"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,319 @@
html,
body {
--custom-font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu,
Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
--custom-bg-color: #101010;
--custom-box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.8);
--custom-color: #fff;
--custom-color-brand: #24b47e;
--custom-color-secondary: #666;
--custom-border: 1px solid #333;
--custom-border-radius: 5px;
--custom-spacing: 5px;
padding: 0;
margin: 0;
font-family: var(--custom-font-family);
background-color: var(--custom-bg-color);
}
* {
color: var(--custom-color);
font-family: var(--custom-font-family);
box-sizing: border-box;
}
/* Forms */
label {
display: block;
margin: 5px 0;
color: var(--custom-color-secondary);
font-size: 0.8rem;
text-transform: uppercase;
}
input {
width: 100%;
border-radius: 5px;
border: var(--custom-border);
padding: 6px;
font-size: 0.9rem;
background-color: var(--custom-bg-color);
color: var(--custom-color);
}
input[disabled] {
color: var(--custom-color-secondary);
}
/* Utils */
.w-half {
width: 50%;
}
.block {
display: block;
width: 100%;
}
.inline-block {
display: inline-block;
width: 100%;
}
.flex {
display: flex;
}
.flex.column {
flex-direction: column;
}
.flex.row {
flex-direction: row;
}
.flex.flex-1 {
flex: 1 1 0;
}
.flex-end {
justify-content: flex-end;
}
.flex-center {
justify-content: center;
}
.items-center {
align-items: center;
}
.text-sm {
font-size: 0.8rem;
font-weight: 300;
}
.font-light {
font-weight: 300;
}
.opacity-half {
opacity: 50%;
}
.m-0 {
margin: 0;
}
/* Button */
button,
.button {
color: var(--custom-color);
border: var(--custom-border);
background-color: var(--custom-bg-color);
display: inline-block;
text-align: center;
border-radius: var(--custom-border-radius);
padding: 0.5rem 1rem;
cursor: pointer;
text-align: center;
font-size: 0.9rem;
text-transform: uppercase;
}
button.primary,
.button.primary {
background-color: var(--custom-color-brand);
border: 1px solid var(--custom-color-brand);
}
/* Widgets */
.card {
width: 100%;
display: block;
border: var(--custom-border);
border-radius: var(--custom-border-radius);
padding: var(--custom-spacing);
}
.avatar {
border-radius: var(--custom-border-radius);
overflow: hidden;
max-width: 100%;
}
.avatar.image {
object-fit: cover;
}
.avatar.no-image {
background-color: #333;
border: 1px solid rgb(200, 200, 200);
border-radius: 5px;
}
.footer {
position: absolute;
bottom: 0;
left: 0;
right: 0;
display: flex;
flex-flow: row;
border-top: var(--custom-border);
}
.footer div {
padding: var(--custom-spacing);
display: flex;
align-items: center;
width: 100%;
}
.footer div > img {
height: 20px;
margin-left: 10px;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.mainHeader {
width: 100%;
font-size: 1.3rem;
margin-bottom: 20px;
}
.avatarPlaceholder {
border: var(--custom-border);
border-radius: var(--custom-border-radius);
width: 40px;
height: 40px;
background-color: rgba(255, 255, 255, 0.2);
display: flex;
align-items: center;
justify-content: center;
}
/* Auth */
.authContainer {
display: flex;
/* flex-direction: column; */
align-items: center;
justify-items: center;
}
.authContainer > .authTitle {
width: 22rem;
margin-right: 40px;
}
.authContainer > .authTitle > .header {
font-weight: 400;
}
.authContainer > .authTitle > .description {
opacity: 50%;
font-weight: 300;
line-height: 1.6em;
}
.authWidget {
background-color: #181818;
border: 1px solid #2a2a2a;
padding: 30px 30px;
border-radius: 5px;
width: 28rem;
}
.authWidget > .description {
margin: 0;
font-size: 0.9rem;
}
.authWidget > .inputField {
border: none;
padding: 10px 10px;
}
.authWidget > .button {
display: flex;
align-items: center;
justify-content: center;
border: none;
background-color: #444444;
text-transform: none !important;
transition: all 0.2s ease;
}
.authWidget > .button:hover {
background-color: #2a2a2a;
}
.authWidget > .button > .loader {
width: 17px;
animation: spin 1s linear infinite;
filter: invert(1);
}
/* Account */
.account {
width: 100%;
max-width: 400px;
display: flex;
flex-direction: column;
gap: 20px;
}
.account > * > .avatarField {
display: flex;
align-items: center;
margin-bottom: 30px;
}
.account > * > .avatarField > .avatarContainer {
margin-right: 20px;
}
/* Profile Card */
.profileList {
max-height: 420px;
overflow-y: auto;
}
.profileCard {
border-radius: 5px;
display: flex;
background-color: #2a2a2a;
padding: 20px 20px;
margin-bottom: 20px;
}
.profileCard:last-child {
margin-bottom: 0px;
}
.profileCard > .avatar {
border-radius: 50%;
}
.profileCard > .userInfo {
margin-left: 20px;
font-weight: 300;
display: flex;
flex-direction: column;
justify-content: center;
}
.profileCard > .userInfo > p {
margin: 0;
}
.profileCard > .userInfo > .username {
font-size: 1.3rem;
font-weight: 500;
margin-bottom: 5px;
}
.profileCard > .userInfo > .website {
font-size: 0.9rem;
color: var(--custom-color-brand);
margin-bottom: 10px;
text-decoration: none;
}
@@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve"
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}
@@ -0,0 +1,13 @@
# editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
@@ -0,0 +1,90 @@
# Created by .ignore support plugin (hsz.mobi)
### Node template
# Logs
/logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# Nuxt generate
dist
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless
# IDE / Editor
.idea
# Service worker
sw.*
# macOS
.DS_Store
# Vim swap files
*.swp
+23
View File
@@ -0,0 +1,23 @@
# Supabase Nuxt.js User Management
This is a WIP!
## Build Setup
```bash
# install dependencies
npm install
# serve with hot reload at localhost:3000
npm run dev
# build for production and launch server
npm run build
npm run start
# generate static project
npm run generate
```
For detailed explanation on how things work, check out [Nuxt.js docs](https://nuxtjs.org).
@@ -0,0 +1,7 @@
# ASSETS
**This directory is not required, you can delete it if you don't want to use it.**
This directory contains your un-compiled assets such as LESS, SASS, or JavaScript.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/assets#webpacked).
@@ -0,0 +1,322 @@
html,
body {
--custom-font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu,
Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
--custom-bg-color: #101010;
--custom-box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.8);
--custom-color: #fff;
--custom-color-brand: #24b47e;
--custom-color-secondary: #666;
--custom-border: 1px solid #333;
--custom-border-radius: 5px;
--custom-spacing: 5px;
padding: 0;
margin: 0;
font-family: var(--custom-font-family);
background-color: var(--custom-bg-color);
}
* {
color: var(--custom-color);
font-family: var(--custom-font-family);
box-sizing: border-box;
}
/* Forms */
label {
display: block;
margin: 5px 0;
color: var(--custom-color-secondary);
font-size: 0.8rem;
text-transform: uppercase;
}
input {
width: 100%;
border-radius: 5px;
border: var(--custom-border);
padding: 6px;
font-size: 0.9rem;
background-color: var(--custom-bg-color);
color: var(--custom-color);
}
input[disabled] {
color: var(--custom-color-secondary);
}
/* Utils */
.w-half {
width: 50%;
}
.block {
display: block;
width: 100%;
}
.inline-block {
display: inline-block;
width: 100%;
}
.flex {
display: flex;
}
.flex.column {
flex-direction: column;
}
.flex.row {
flex-direction: row;
}
.flex.flex-1 {
flex: 1 1 0;
}
.flex-end {
justify-content: flex-end;
}
.flex-center {
justify-content: center;
}
.text-sm {
font-size: 0.8rem;
font-weight: 300;
}
/* Button */
button,
.button {
color: var(--custom-color);
border: var(--custom-border);
background-color: var(--custom-bg-color);
display: inline-block;
text-align: center;
border-radius: var(--custom-border-radius);
padding: 0.5rem 1rem;
cursor: pointer;
text-align: center;
font-size: 0.9rem;
text-transform: uppercase;
}
button.primary,
.button.primary {
background-color: var(--custom-color-brand);
border: 1px solid var(--custom-color-brand);
}
/* Widgets */
.card {
width: 100%;
display: block;
border: var(--custom-border);
border-radius: var(--custom-border-radius);
padding: var(--custom-spacing);
}
.avatar {
border-radius: var(--custom-border-radius);
overflow: hidden;
max-width: 100%;
}
.avatar.image {
object-fit: cover;
}
.avatar.noImage {
background-color: rgb(230, 230, 230);
border: 2px dotted rgb(200, 200, 200);
border-radius: 5px;
}
.avatar.noImage:after {
font-family: var(--custom-font-family);
content: 'No avatar photo';
display: block;
font-size: 16px;
color: rgb(100, 100, 100);
position: absolute;
top: 50%;
left: 0;
width: 100%;
-webkit-transform: translateY(-50%);
transform: translateY(-50%);
text-align: center;
}
.footer {
position: absolute;
bottom: 0;
left: 0;
right: 0;
display: flex;
flex-flow: row;
border-top: var(--custom-border);
}
.footer div {
padding: var(--custom-spacing);
display: flex;
align-items: center;
width: 100%;
}
.footer div > img {
height: 20px;
margin-left: 10px;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.mainHeader {
width: 100%;
font-size: 1.3rem;
margin-bottom: 20px;
}
.avatarPlaceholder {
border: var(--custom-border);
border-radius: var(--custom-border-radius);
width: 40px;
height: 40px;
background-color: rgba(255, 255, 255, 0.2);
display: flex;
align-items: center;
justify-content: center;
}
/* Auth */
.authContainer {
display: flex;
/* flex-direction: column; */
align-items: center;
justify-items: center;
}
.authContainer > .authTitle {
width: 22rem;
margin-right: 40px;
}
.authContainer > .authTitle > .header {
font-weight: 400;
}
.authContainer > .authTitle > .description {
opacity: 50%;
font-weight: 300;
line-height: 1.6em;
}
.authWidget {
background-color: #181818;
border: 1px solid #2a2a2a;
padding: 30px 30px;
border-radius: 5px;
width: 28rem;
}
.authWidget > .description {
margin: 0;
font-size: 0.9rem;
}
.authWidget > .inputField {
border: none;
padding: 10px 10px;
}
.authWidget > .button {
display: flex;
align-items: center;
justify-content: center;
border: none;
background-color: #444444;
text-transform: none !important;
transition: all 0.2s ease;
}
.authWidget > .button:hover {
background-color: #2a2a2a;
}
.authWidget > .button > .loader {
width: 17px;
animation: spin 1s linear infinite;
filter: invert(1);
}
/* Account */
.account {
width: 100%;
max-width: 400px;
display: flex;
flex-direction: column;
gap: 20px;
}
.account > * > .avatarField {
display: flex;
align-items: center;
margin-bottom: 30px;
}
.account > * > .avatarField > .avatarContainer {
margin-right: 20px;
}
/* Profile Card */
.profileCard {
border-radius: 5px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #2a2a2a;
padding: 30px;
}
.profileCard > .avatarContainer {
border-radius: 50%;
overflow: hidden;
margin-bottom: 30px;
}
.profileCard > .avatar {
border-top-right-radius: 0 !important;
border-bottom-right-radius: 0 !important;
}
.profileCard > .userInfo {
width: 100%;
font-weight: 300;
display: flex;
flex-direction: column;
justify-content: center;
}
.profileCard > .userInfo > p {
margin: 0;
}
.profileCard > .userInfo > .username {
font-size: 1.3rem;
font-weight: 500;
margin-bottom: 10px;
}
.profileCard > .userInfo > .website {
font-size: 0.9rem;
margin-bottom: 10px;
color: var(--custom-color-brand);
text-decoration: none;
}
@@ -0,0 +1,104 @@
<template>
<div class="account-container">
<div>
<label for="email">Email</label>
<input id="email" type="text" v-model="state.propSession.user.email" disabled />
</div>
<div>
<label for="username">Username</label>
<input id="username" type="text" v-model="state.username" />
</div>
<div>
<label for="website">Website</label>
<input id="website" type="website" v-model="state.website" />
</div>
<div>
<button class="button block primary" @click="updateProfile" :disabled="state.loading">
{{ state.loading ? 'Loading ...' : 'Update' }}
</button>
</div>
<div>
<button @click="signOut" className="button block">Sign Out</button>
</div>
</div>
</template>
<script>
import { supabase } from '../lib/supabaseClient'
export default {
props: ['session'],
data() {
return {
state: {
loading: true,
username: '',
website: '',
propSession: this.session,
},
}
},
mounted() {
this.getProfile()
},
methods: {
async signOut() {
const { error } = await supabase.auth.signOut()
if (error) console.log('Error logging out:', error.message)
},
setProfile(profile) {
this.state.username = profile.username
this.state.website = profile.website
},
async getProfile() {
try {
this.state.loading = true
let { data, error } = await supabase
.from('profiles')
.select(`username, website, avatar_url`)
.eq('id', this.stete.propSession.user.id)
.single()
if (error || !data) {
throw error || new Error('Profile not found')
}
setProfile(data)
} catch (error) {
console.log('error', error.message)
} finally {
this.state.loading = false
}
},
async updateProfile() {
try {
this.state.loading = true
const updates = {
id: this.state.propSession.user.id,
username: this.state.username,
website: this.state.website,
updated_at: new Date(),
}
let { error } = await supabase.from('profiles').upsert(updates, {
returning: 'minimal', // Don't return the value after inserting
})
if (error) {
throw error
}
} catch (error) {
alert(error.message)
} finally {
this.state.loading = false
}
},
},
}
</script>
@@ -0,0 +1,57 @@
<template>
<div>
<form @submit.prevent="handleSubmit" class="auth-container">
<div>
<label for="email-register">Email</label>
<input
v-model="formValues.email"
id="email-register"
type="email"
placeholder="Your email"
/>
</div>
<div style="margin-top: 50px">
<button class="button block primary" :disabled="formValues.loader">
{{ formValues.loader ? "Loading ..." : "Sign up with magic link" }}
</button>
</div>
</form>
</div>
</template>
<script>
import { supabase } from "../lib/supabaseClient";
export default {
data() {
return {
formValues: {
email: "",
loader: false,
},
};
},
methods: {
async handleSubmit() {
console.log("ENV", process.env.NUXT_PUBLIC_SUPABASE_URL);
try {
this.formValues.loader = true;
const { error, user } = await supabase.auth.signIn({
email: this.formValues.email,
});
if (error) {
throw error;
}
alert("Check your email for the login link!");
} catch (error) {
alert(error.error_description || error.message);
} finally {
this.formValues.loader = false;
}
},
},
};
</script>
@@ -0,0 +1,33 @@
<template>
<div className="card">
<p>Username: {{ state.propProfile.username }}</p>
<p>Website: {{ state.propProfile.website }}</p>
<p>
<small>
Last updated
{{
state.lastUpdated
? `${state.lastUpdated.toLocaleDateString()}
${state.lastUpdated.toLocaleTimeString()}`
: "Never"
}}
</small>
</p>
</div>
</template>
<script>
export default {
props: ["profile"],
data() {
return {
state: {
propProfile: this.profile,
lastUpdated: this.profile.updated_at
? new Date(this.profile.updated_at)
: null,
},
};
},
};
</script>
@@ -0,0 +1,21 @@
<template>
<div>
<div v-for="profile in state.propProfiles" :key="profile.id">
<profile-card :profile="profile" />
</div>
</div>
</template>
<script>
import ProfileCard from "./ProfileCard.vue";
export default {
components: { ProfileCard },
props: ["profiles"],
data() {
return {
state: {
propProfiles: this.profiles,
},
};
},
};
</script>
@@ -0,0 +1,146 @@
<template>
<div>
<Nuxt />
</div>
</template>
<style>
html,
body {
--custom-font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
--custom-bg-color: #1f1f1f;
--custom-box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.8);
--custom-color: #fff;
--custom-color-brand: #24b47e;
--custom-color-secondary: #666;
--custom-border: 1px solid #666;
--custom-border-radius: 5px;
--custom-spacing: 5px;
padding: 0;
margin: 0;
font-family: var(--custom-font-family);
background-color: var(--custom-bg-color);
}
* {
color: var(--custom-color);
font-family: var(--custom-font-family);
box-sizing: border-box;
}
/* Forms */
label {
display: block;
margin: 5px 0;
color: var(--custom-color-secondary);
font-size: 0.8rem;
text-transform: uppercase;
}
input {
width: 100%;
border-radius: 5px;
border: var(--custom-border);
padding: 6px;
font-size: 0.9rem;
background-color: var(--custom-bg-color);
color: var(--custom-color);
}
input[disabled] {
color: var(--custom-color-secondary);
}
/* Utils */
.w-half {
width: 50%;
}
.block {
display: block;
width: 100%;
}
.inline-block {
display: inline-block;
width: 100%;
}
.flex {
display: flex;
}
.flex.column {
flex-direction: column;
}
.flex.row {
flex-direction: row;
}
.flex.flex-1 {
flex: 1 1 0;
}
/* Button */
button,
.button {
color: var(--custom-color);
border: var(--custom-border);
background-color: var(--custom-bg-color);
display: inline-block;
text-align: center;
border-radius: var(--custom-border-radius);
padding: 0.5rem 1rem;
cursor: pointer;
text-align: center;
font-size: 0.9rem;
text-transform: uppercase;
}
button.primary,
.button.primary {
background-color: var(--custom-color-brand);
border: 1px solid var(--custom-color-brand);
}
/* Widgets */
.card {
width: 100%;
display: block;
border: var(--custom-border);
border-radius: var(--custom-border-radius);
padding: var(--custom-spacing);
}
.avatar {
border-radius: var(--custom-border-radius);
overflow: hidden;
max-width: 100%;
}
.avatar.image {
object-fit: cover;
}
.avatar.noImage {
background-color: rgb(230, 230, 230);
border: 2px dotted rgb(200, 200, 200);
border-radius: 5px;
}
.avatar.noImage:after {
font-family: var(--custom-font-family);
content: "No avatar photo";
display: block;
font-size: 16px;
color: rgb(100, 100, 100);
position: absolute;
top: 50%;
left: 0;
width: 100%;
-webkit-transform: translateY(-50%);
transform: translateY(-50%);
text-align: center;
}
</style>
@@ -0,0 +1,10 @@
export const NUXT_PUBLIC_SUPABASE_URL = process.env.NUXT_PUBLIC_SUPABASE_URL
export const NUXT_PUBLIC_SUPABASE_ANON_KEY = process.env.NUXT_PUBLIC_SUPABASE_ANON_KEY
// export type Profile = {
// id: string
// avatar_url: string
// username: string
// website: string
// updated_at: string
// }
@@ -0,0 +1,7 @@
import { createClient } from '@supabase/supabase-js'
import { NUXT_PUBLIC_SUPABASE_URL, NUXT_PUBLIC_SUPABASE_ANON_KEY } from './constants'
if (!NUXT_PUBLIC_SUPABASE_URL) throw new Error('Missing env.NUXT_PUBLIC_SUPABASE_URL')
if (!NUXT_PUBLIC_SUPABASE_ANON_KEY) throw new Error('Missing env.NUXT_PUBLIC_SUPABASE_KEY')
export const supabase = createClient(NUXT_PUBLIC_SUPABASE_URL, NUXT_PUBLIC_SUPABASE_ANON_KEY)
@@ -0,0 +1,8 @@
# MIDDLEWARE
**This directory is not required, you can delete it if you don't want to use it.**
This directory contains your application middleware.
Middleware let you define custom functions that can be run before rendering either a page or a group of pages.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/routing#middleware).
@@ -0,0 +1,41 @@
export default {
// publicRuntimeConfig: {
// URL: process.env.SUPABASE_URL,
// KEY: process.env.SUPABASE_KEY
// },
// Disable server-side rendering: https://go.nuxtjs.dev/ssr-mode
ssr: false,
// Global page headers: https://go.nuxtjs.dev/config-head
head: {
title: 'nuxtjs-user-management',
htmlAttrs: {
lang: 'en',
},
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: '' },
],
link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }],
},
// Global CSS: https://go.nuxtjs.dev/config-css
css: ['@/assets/global.css'],
// Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
plugins: [],
// Auto import components: https://go.nuxtjs.dev/config-components
components: true,
// Modules for dev and build (recommended): https://go.nuxtjs.dev/config-modules
buildModules: ['@nuxtjs/dotenv'],
// Modules: https://go.nuxtjs.dev/config-modules
modules: [],
// Build Configuration: https://go.nuxtjs.dev/config-build
build: {},
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,18 @@
{
"name": "nuxtjs-user-management",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "nuxt",
"build": "nuxt build",
"start": "nuxt start",
"generate": "nuxt generate"
},
"dependencies": {
"@nuxtjs/dotenv": "^1.4.1",
"@supabase/supabase-js": "^1.7.1",
"core-js": "^3.9.1",
"nuxt": "^2.15.3"
},
"devDependencies": {}
}
@@ -0,0 +1,81 @@
<template>
<div
style="
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
"
>
<div class="flex" style="gap: 30">
<div class="flex column w-half">
<h3>Account</h3>
<div v-if="!state.session">
<Auth />
</div>
<div v-else>
<Account :session="state.session" />
</div>
</div>
<div class="flex column w-half" style="gap: 20">
<h3>Public Profiles</h3>
<div v-if="state.profiles.length > 0">
<ProfileList :profiles="state.profiles" />
</div>
</div>
</div>
</div>
</template>
<script>
import { supabase } from "../lib/supabaseClient";
export default {
data() {
return {
state: {
session: null,
profiles: [],
},
};
},
mounted() {
this.state.session = supabase.auth.session();
supabase.auth.onAuthStateChange((_event, session) => {
this.state.session = session;
});
this.getPublicProfiles();
},
methods: {
async getPublicProfiles() {
try {
const { data, error } = await supabase
.from("profiles")
.select("id, username, avatar_url, website, updated_at")
.order("updated_at", { ascending: false });
if (error || !data) {
throw error || new Error("No data");
}
console.log("data", data);
this.state.profiles = data;
} catch (error) {
console.log("error", error.message);
}
},
},
};
</script>
<style>
.container {
margin: 0 auto;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
}
</style>
@@ -0,0 +1,7 @@
# PLUGINS
**This directory is not required, you can delete it if you don't want to use it.**
This directory contains Javascript plugins that you want to run before mounting the root Vue.js application.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/plugins).
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

+2
View File
@@ -15,6 +15,8 @@
{ "source": "/", "destination": "http://localhost:3000/new" },
{ "source": "/beta", "destination": "http://localhost:3000/new/beta" },
{ "source": "/blog", "destination": "http://localhost:3000/new/blog" },
{ "source": "/database", "destination": "http://localhost:3000/new/database" },
{ "source": "/auth", "destination": "http://localhost:3000/new/auth" },
{
"source": "/blog/:match*",
"destination": "http://localhost:3000/new/blog/:match*"
+6 -1
View File
@@ -27,6 +27,9 @@
{ "source": "/blog/rss.xml", "destination": "https://www-internal.supabase.io/new/rss.xml" },
{ "source": "/blog/rss", "destination": "https://www-internal.supabase.io/new/rss.xml" },
{ "source": "/beta", "destination": "https://www-internal.supabase.io/new/beta" },
{ "source": "/pricing", "destination": "https://www-internal.supabase.io/new/pricing" },
{ "source": "/database", "destination": "https://www-internal.supabase.io/new/database" },
{ "source": "/auth", "destination": "https://www-internal.supabase.io/new/auth" },
{ "source": "/:match*", "destination": "https://docs-rose.vercel.app/:match*" }
],
"redirects": [
@@ -139,7 +142,9 @@
{ "source": "/docs/client/fts", "destination": "/docs/reference/javascript/textSearch" },
{ "source": "/docs/client/plfts", "destination": "/docs/reference/javascript/textSearch" },
{ "source": "/docs/client/phfts", "destination": "/docs/reference/javascript/textSearch" },
{ "source": "/docs/client/wfts", "destination": "/docs/reference/javascript/textSearch" }
{ "source": "/docs/client/wfts", "destination": "/docs/reference/javascript/textSearch" },
{ "source": "/docs/pricing", "destination": "/pricing" }
],
"trailingSlash": false
}
+295
View File
@@ -0,0 +1,295 @@
---
id: privacy
title: Privacy Policy
---
`Last modified: 27 March 2021`
Thank you for your interest in Supabase, Inc., ("**_Supabase_**," "**_we_**", "**_our_**" or "**_us_**"). Supabase provides a suite of open source tools, stitched together to build a seamless developer experience. This Privacy Notice explains how information about you, that directly identifies you, or that makes you identifiable ("**_personal information_**") is collected, used and disclosed by Supabase in connection with our website at [supabase.io](https://supabase.io) (the "**_Site_**") and our services offered in connection with the Site (collectively with the Site, the "**_Service_**").
We may also provide you with additional privacy notices or disclosures where the scope of the inquiry, request, or personal information we require falls outside the scope of this Privacy Notice. In that case, the additional Privacy Notice or disclosures will govern how we may process the information you provide at that time. Please note that this Privacy Notice does not cover or apply to our processing of information about our employees or contractors.
This Policy explains how we use your personal information when we act as a data controller. As far as you use our Service as a natural person, we are the controller of your personal information. We are responsible for, and control, the processing of your personal information.
Wherever our customers use our Service to submit, manage, or otherwise use content relating to our customers’ end users ("**_Customer Data_**") during the provision of our Service, we have contractually committed ourselves to only process such information on behalf and under the instruction of the respective customer, who is the data controller. This Privacy Notice does not apply to such processing and we recommend you read the Privacy Notice of the respective customer, if their processing concerns your personal information.
## Region-specific Disclosures
- **California - Your California Privacy Rights:** If you are a California resident, California Civil Code Section 1798.83 permits you to request information regarding the disclosure of personal information to third parties for their direct marketing purposes during the immediately preceding calendar year. Note we do not share your personal information with third parties for their own marketing purposes.
- **Nevada:** Chapter 603A of the Nevada Revised Statutes permits a Nevada resident to opt out of future sales of certain covered information that a website operator has collected or will collect about the resident. Note we do not sell your personal information within the meaning of Chapter 603A. However, if you would still like to submit such a request, please contact us at support@supabase.io.
- **European Economic Area, United Kingdom or Switzerland:** If you are located in the European Economic Area ("**_EEA_**"), United Kingdom or Switzerland, or otherwise engage with Supabase’s European operations, please see the **Privacy Disclosures for the European Economic Area, United Kingdom and Switzerland** for additional European-specific privacy disclosures, including what constitutes your personal information, the lawful bases we rely on to process your personal information, how we use cookies when you access our Sites from the EEA, UK or Switzerland and your rights in respect of your personal information.
**Note for International Visitors:** Personal information may be transferred to, stored and processed in a country other than the one in which it was collected. For example, the Sites are primarily hosted in and provided from the United States. Please note the country to which personal data is transferred may not provide the same level of protection for personal information as the country from which it was transferred.
## 1. Information we collect and our use
We collect personal information in connection with your visits to and use of the Service. This collection includes information that you provide in connection with the Service, information from third parties, and information that is collected automatically such as through the use of cookies and other technologies.
### Information That You Provide
We collect personal information from you. The categories of information we collect can include:
- **_Registration information._** We collect personal and/or business information that you provide when you register for an account at the Site. This information may include your name, email address, GitHub username. We use this information to administer your account, provide you with the relevant services and information, communicate with you regarding your account, the Site and for customer support purposes.
- **_Information collected through the Use of the Service._** After registration, you may create, upload or transmit files, documents, videos, images, data or information as part of your use of the Service (collectively, "**_User Content_**"). User Content and any information contained in the User Content, including personal information you may have included, is stored and collected as part of the Service. You have full control of the information included in the User Content.
- **_Payment information._** If you make a purchase or payment on the Site, such as for a subscription, we collect transactional information provided in connection with your purchase or payment. Please note that we use third party payment processors, including Stripe, to process payments made to us. As such, we do not retain any personally identifiable financial information such as credit card numbers. Rather, all such information is provided directly by you to our third-party processor. The payment processor’s use of your personal information is governed by their privacy notice. To view Stripe’s privacy notice, please visit: <https://stripe.com/privacy>.
- **_Communications._** If you communicate with us through any paper or electronic form, we may collect your name, email address, mailing address, phone number, or any other personal information you choose to provide to us. We use this information to investigate and respond to your inquiries, and to communicate with you, to enhance the services we offer to our users and to manage and grow our organization. If you register for our newsletters or updates, we may communicate with you by email. To unsubscribe from promotional messages, please follow the instructions within our messages and review the **Control Over Your Information** section below. If you become a contributor, we may also collect your GitHub name and feature you on our website.
- **_Inquiries and Feedback._** If you contact us, we will collect the information that you provide us, such as your contact information and the contents of your communication with us.
You are free to choose which personal information you want to provide to us or whether you want to provide us with personal information at all. However, some information, such as your name, address, payment transaction information, and information on your requested Services may be necessary for the performance of our contractual obligations.
### Information from Third Party Sources
We may receive personal information about you from our business partners and service providers and combine this information with other data we collect from you. The third-parties may include website and service operators, payment processors, marketing partners, and shipping providers. The information may include contact information, demographic information, information about your communications and related activities, and information about your orders. We may use this information to administer and facilitate our services, your orders and our marketing activities.
- **_Single Sign-On._** We use single sign-on ("**SSO**") such as Github to allow a user to authenticate their account using one set of login information. We will have access to certain information from those third parties in accordance with the authorization procedures determined by those third parties, including, for example, your name, username, email address, language preference, and profile picture. We use this information to operate, maintain, and provide to you the features and functionality of the Service. We may also send you service-related emails or messages (e.g., account verification, purchase confirmation, customer support, changes or updates to features of the Site, technical and security notices).
- **_Social Media._** When you interact with our Site through various social media, such as when you click on the social media icon on the Site, follow us on a social media site, or post a comment to one of our pages, we may receive information from the social network such as your profile information, profile picture, gender, user name, user ID associated with your social media account, age range, language, country, and any other information you permit the social network to share with third parties. The data we receive is dependent upon your privacy settings with the social network. We use this information to operate, maintain, and provide to you the features and functionality of the Service, as well as to communicate directly with you, such as to send you email messages about products and services that may be of interest to you.
- **_Employment Applications._** If you apply for employment, we collect your contact and demographic information, educational and work history, employment interests, information obtained during interviews and any other information you choose to provide. We use the information provided to evaluate your candidacy for employment, to communicate with you during the application process and to facilitate the onboarding process.
- **_Information from Other Sources._** We may obtain information from other sources, including through third-party information providers, our shareholders, customers, or through transactions such as mergers and acquisitions. We may combine this information with other information we collect from or about you. In these cases, our Privacy Notice governs the handling of the combined personal information. We use this information to operate, maintain, and provide to you the features and functionality of the Service, as well as to communicate directly with you, such as to send you email messages about products and services that may be of interest to you.
### Other Uses of Personal Information
In addition to the uses described above, we may collect and use personal information for the following purposes:
- For our business activities, including to operate the Service and to provide you with the features and functionality of the Service;
- To communicate with you and respond to your requests, such as to respond to your questions, contact you about changes to the Service, and communicate about account related matters;
- For marketing and advertising purposes, such as to market to you or offer you with information and updates on our products or services we think that you may be interested in. While we may use your personal information in this manner, please note that we do not use User Content to serve you ads, and we will never share User Content with any third parties for marketing or advertising purposes, unless you have explicitly submitted it to us for that purpose;
- For analytics and research purposes;
- To enforce our **Terms of Service**, to resolve disputes, to carry out our obligations and enforce our rights, and to protect our business interests and the interests and rights of third parties;
- To comply with contractual and legal obligations and requirements;
- To fulfill any other purpose for which you provide personal information; and
- For any other lawful purpose, or other purpose that you consent to.
## 2. How we share personal information
We may share your personal information in the instances described below. For further information on your choices regarding your information, see **Control Over Your Information**.
- We may share your personal information with third-party service providers or business partners who help us deliver or improve our Site or services, or who perform services on our behalf, which are subject to reasonable confidentiality terms, and may include processing payments, providing web hosting services, or providing analytics.
- Third parties as required by law or subpoena or if we reasonably believe that such action is necessary to (a) comply with the law and the reasonable requests of law enforcement; (b) to enforce our **Terms of Service** or other agreements or to protect the security or integrity of the Supabase services, including to prevent harm or financial loss, or in connection with preventing fraud or illegal activity; and/or (c) to exercise or protect the rights, property, or personal safety of Supabase, our Customers, visitors, or others.
- We may share with other companies and brands owned or controlled by Supabase, and other companies owned by or under common ownership as Supabase. These companies will use your personal information in the same way as we can under this Privacy Notice.
- We may transfer any information we collect in the event we sell or transfer all or a portion of our business or assets (including any shares in the company) or any portion or combination of our products, services, businesses and/or assets. Should such a transaction occur (whether a divestiture, merger, acquisition, bankruptcy, dissolution, reorganization, liquidation, or similar transaction or proceeding), we will use reasonable efforts to ensure that any transferred information is treated in a manner consistent with this Privacy Notice.
- We may disclose your information publicly or with another third party with your prior authorization.
- With others in an aggregated or otherwise anonymized form that does not reasonably identify you directly as an individual.
## 3. Control over your information
### Email Communications
From time to time, we may send you emails regarding updates to our Service, products or services, notices about our organization, or information about products/services we offer (or promotional offers from third parties) that we think may be of interest to you. If you wish to unsubscribe from such emails, simply click the "unsubscribe link" provided at the bottom of the email communication. Note that you cannot unsubscribe from certain services-related email communications (e.g., account verification, confirmations of transactions, technical or legal notices).
### Modifying Account Information
If you have an online account with us, you have the ability to modify certain information in your account (e.g., your contact information) through the account options provided on the Site. If there is personal information in your User Content, you can use the features and functionality of the Service to edit or delete the personal information or User Content. Not all personal information is maintained in a format that you can access or change. If you would like to request access to, or correction or deletion of personal information, you may send your request to us at the email provided below. We will review your request and may require you to provide additional information to identify yourself, but we do not promise that we will be able to satisfy your request.
## 4. How We Use Cookies and Other Tracking Technology to Collect Information
We, and our third-party partners, automatically collect certain types of usage information when you visit our Site, read our emails, or otherwise engage with us.  We typically collect this information through a variety of tracking technologies, including cookies, web beacons, embedded scripts, location-identifying technologies, file information, and similar technology (collectively, "**tracking technologies**").
We, and our third-party partners, use tracking technologies to automatically collect usage and device information, such as:
- Information about your device and its software, such as your IP address, browser type, Internet service provider, device type/model/manufacturer, operating system, date and time stamp, and a unique ID that allows us to uniquely identify your browser or your account (including, for example, a persistent device identifier), and other such information.
- When you access our sites from a mobile device, we may collect unique identification numbers associated with your device or our mobile application mobile carrier, device type, model and manufacturer, mobile device operating system brand and model, and depending on your mobile device settings, we may be able to approximate a device’s location by analyzing other information, like an IP address.
- Information about the way you access and use our services, for example, the site from which you came and the site to which you are going when you leave our services, the pages you visit, the links you click, whether you open emails or click the links contained in emails, whether you access the services from multiple devices, and other actions you take on the Sites.
We use the data collected through tracking technologies to:  (a) remember information so that you will not have to re-enter it during your visit or the next time you visit the site; (b) provide custom content and information; (c) identify you across multiple devices; (d) provide and monitor the effectiveness of our services; (e) monitor aggregate metrics such as total number of visitors, traffic, usage, and demographic patterns on our Site; (f) diagnose or fix technology problems; and (g) to provide, plan for, and enhance our services.
**Note we do not engage in online targeted advertising.**
**Cookies and Other Tracking Technologies Opt-Out.** Depending on your browser or mobile device, you may be able to set your browser to delete or notify you of cookies and other tracking technology by actively managing the settings on your browser or mobile device.
If you would prefer not to accept cookies, most browsers will allow you to: (i) change your browser settings to notify you when you receive a cookie, which lets you choose whether or not to accept it; (ii) disable existing cookies; or (iii) set your browser to automatically reject cookies. Please note that doing so may negatively impact your experience using the sites, as some features and services on our sites may not work properly. Depending on your mobile device and operating system, you may not be able to delete or block all cookies. You may also set your e-mail options to prevent the automatic downloading of images that may contain technologies that would allow us to know whether you have accessed our e-mail and performed certain functions with it.
## 5. Data Retention and Security
We will retain your personal information for the length of time needed to fulfill the purposes outlined in this Privacy Notice, unless a longer retention period is required or permitted by law. We store data on servers in the U.S. or any other country in which Supabase or its affiliates, subsidiaries, agents or contractors maintain facilities. If you are located in the European Union or other regions with laws governing data collection and use that may differ from U.S. law, please note that your personal information may be transferred to a country and jurisdiction that does not have the same data protection laws as your jurisdiction. When you register for use with Supabase you have the option of where you store your information and we will not transfer it without providing information to you in advance.
Supabase cares about the security of your information and uses commercially reasonable physical, technical and organizational measures designed to preserve the integrity and security of all information we collect. However, no security system is impenetrable, and we cannot guarantee the security of our systems 100%. In the event that any information under our control is compromised as a result of a breach of security, we will take reasonable steps to investigate the situation and where appropriate, notify those individuals whose information may have been compromised and take other steps, in accordance with any applicable laws and regulations.
## 6. Links to Third-Party Websites and Services
For your convenience, our Site may provide links to third-party websites or services that we do not own or operate. We are not responsible for the practices employed by any websites or services linked to or from the services, including the information or content contained within them. Your browsing and interaction on any other website or service are subject to the applicable third party’s rules and policies, not ours. If you are using a third-party website or service, you do so at your own risk. We encourage you to review the privacy policies of any site or service before providing any personal information.
## 7. Children’s Privacy
Our services are not intended for children under the age of 13. We do not knowingly solicit or collect personal information from children under the age of 13. If we learn that any personal information has been collected inadvertently from a child under 13, we will delete the information as soon as possible. If you believe that we might have collected information from a child under 13, please contact us at privacy@supabase.io.
## 8. Changes to Privacy Notice
We reserve the right to change this Privacy Notice from time to time in our sole discretion. We will notify you about material changes in the way we treat personal data by sending a notice to the primary email address specified in your Supabase account and/or by placing a prominent notice on our Site. It is your responsibility to review this Privacy Notice periodically. When we do change the Privacy Notice, we will also revise the "last modified" date.
## 9. Contact Us
For additional inquiries about this Privacy Notice, please send us an email at privacy@supabase.io.
This Privacy Notice was last modified on 27th March 2021
## Privacy disclosures for the European economic area, United Kingdom, and Switzerland.
While we are primarily based in the United States, Supabase maintains operations in Europe and may direct our services to individuals located in the European Economic Area ("**_EEA_**"), United Kingdom and Switzerland, including through our Site [supabase.io](https://supabase.io/) (collectively, our "**_European Services_**"). The following disclosures ("**_Privacy Disclosures_**") apply to our processing of personal data in connection with our European Services.
Supabase, Inc. is the data controller responsible for the processing of personal data in connection with our European Services. This means that we determine and are responsible for how your personal information is used.
**Personal Data:** When we use the term "personal data" in this section, we mean information relating to an identified or identifiable natural person.
### 1. Personal data we collect from you when you use the Supabase European Services, and how we use it.
We collect the categories of personal data that you voluntarily submit directly to us when you use the European Services, as set forth in our Privacy Notice under the section entitled **Information We Collect and Our Use**. The table at **Annex 1** sets out in detail the categories of personal data we collect about you and how we use that information when you use the European Services, as well as the legal basis which we rely on to process the personal information and recipients of that personal information.
### 2. Information we collect about you automatically.
We also automatically collect personal information indirectly about how you access and use the European Services, and information about the device you use to access the European Services. For example, we may collect:
(a) information about the features you use and the pages you view on the European Services;
(b) information about your device (such as your IP address, device identifier, device type, model and manufacturer); and
(c) information about your usage patterns (such as how often you use the Supabase European Services and your language settings).
We use this information to provide you the features and functionality of the European Services, to monitor and improve the European Services and to develop new services.
The table at **Annex 2** sets out further information about the categories of personal information we collect about you automatically and how we use that information. The table also lists the legal basis which we rely on to process the personal information and recipients of that personal information.
We may link or combine the personal information we collect about you and the information we collect automatically.
We may anonymise and aggregate any of the personal information we collect (so that it does not directly identify you). We may use anonymised information for purposes that include testing our IT systems, research, data analysis, improving the Supabase European Services. We may also share such anonymised and aggregated information with others.
### 3. How long will we store your personal information
We will usually store the personal information we collect about you for no longer than necessary for the purposes set out in Annex 1 and Annex 2, in accordance with our legal obligations and legitimate business interests.
The criteria used to determine the period for which personal information about you will be retained varies depending on the legal basis under which we process the personal information:
1. **Legitimate Interests.** Where we are processing personal information based on our legitimate interests, we generally will retain such information for a reasonable period of time based on the particular interest, taking into account the fundamental interests and the rights and freedoms of data subjects.
2. **Consent.** Where we are processing personal information based on your consent, we generally will retain the information until you withdraw your consent, or otherwise for the period of time necessary to fulfil the underlying agreement with you or provide you with the applicable service for which we process that personal information.
3. **Contract.** Where we are processing personal information based on contract, we generally will retain the information for the duration of the contract plus some additional limited period of time that is necessary to comply with law or that represents the statute of limitations for legal claims that could arise from the contractual relationship.
4. **Legal Obligation.** Where we are processing personal information based on a legal obligation, we generally will retain the information for the period of time necessary to fulfil the legal obligation.
5. **Legal Claim.** We may need to apply a "legal hold" that retains information beyond our typical retention period where we face threat of legal claim.  In that case, we will retain the information until the hold is removed, which typically means the claim or threat of claim has been resolved.
In all cases, in addition to the purposes and legal bases, we consider the amount, nature and sensitivity of the personal information, as well as the potential risk of harm from unauthorised use or disclosure of your personal information.
### 4. Recipients of Personal Information
In addition to the recipients listed in Annexes 1 and 2, we may also share your personal information with the following (as required in accordance with the uses set out in Annexes 1 and 2):
1. **Service providers and advisors**: we may share your personal information with third party vendors and other service providers that perform services for us or on our behalf, which may include providing professional services, such as legal and accounting services, mailing, email or chat services, fraud prevention, web hosting, or providing analytic services.
2. **Affiliates**. Other companies owned by or under common ownership as Supabase, including our subsidiaries (i.e., any organisation we own or control) and our ultimate holding company (i.e., any organisation that owns or controls us) and any subsidiaries it owns. These companies will use your personal information in the same way as we can under these Privacy Disclosures.
3. **Purchasers and third parties in connection with a business transaction**: your personal information may be disclosed to third parties in connection with a transaction, such as a merger, sale of assets or shares, reorganisation, financing, change of control or acquisition of all or a portion of our business.
4. **Law enforcement, regulators and other parties for legal reasons**: we may share your personal information with third parties as required by law or if we reasonably believe that such action is necessary to (i) comply with the law and the reasonable requests of law enforcement; (ii) detect and investigate illegal activities and breaches of agreements, including our Terms; and/or (iii) exercise or protect the rights, property, or personal safety of Supabase, its users or others.
### 5. Marketing and Advertising
From time to time we may contact you with information about our services, including sending you marketing messages and asking for your feedback on our services. Most marketing messages we send will be by email. For some marketing messages, we may use personal information we collect about you to help us determine the most relevant marketing information to share with you.
We will only send you marketing messages if you have given us your consent to do so. You can withdraw your consent at a later date by clicking on the unsubscribe link at the bottom of our marketing emails or by updating your preferences via your account on the Site.
### 6. Storing and transferring your personal information
**Security**. We implement appropriate technical and organisational measures to protect your personal information against accidental or unlawful destruction, loss, change or damage. All personal information we collect will be stored by our cloud hosting provider on secure servers. We will never send you unsolicited emails or contact you by phone requesting credit or debit card information or national identification numbers.
**International Transfers of your Personal Information**. The personal information we collect may be transferred to and stored in countries outside of the jurisdiction you are in where we and our third party service providers have operations. If you are located in the EEA, United Kingdom or Switzerland, your personal information may be processed outside of those regions, including in the United States.
In the event of such a transfer, we ensure that: (i) the personal information is transferred to countries recognised as offering an equivalent level of protection; or (ii) the transfer is made pursuant to appropriate safeguards, such as standard data protection clauses adopted by the European Commission.
If you wish to enquire further about these safeguards used, please contact us using the details set out at the end of these Privacy Disclosures.
### 7. Profiling
We may analyze personal data we have collected about you to create a profile of your interests and send product updates. We may also use personal data about you to detect and reduce fraud.
### 8. Your rights in respect of your personal information
In accordance with applicable privacy law, you have the following rights in respect of your personal information that we hold:
1. **Right of access**. You have the right to obtain:
1. confirmation of whether, and where, we are processing your personal information;
2. information about the categories of personal information we are processing, the purposes for which we process your personal information and information as to how we determine applicable retention periods;
3. information about the categories of recipients with whom we may share your personal information; and
4. a copy of the personal information we hold about you.
2. **Right of portability**. You have the right, in certain circumstances, to receive a copy of the personal information you have provided to us in a structured, commonly used, machine-readable format that supports re-use, or to request the transfer of your personal data to another person.
3. **Right to rectification**. You have the right to obtain rectification of any inaccurate or incomplete personal information we hold about you without undue delay.
4. **Right to erasure**. You have the right, in some circumstances, to require us to erase your personal information without undue delay if the continued processing of that personal information is not justified.
5. **Right to restriction**. You have the right, in some circumstances, to require us to limit the purposes for which we process your personal information if the continued processing of the personal information in this way is not justified, such as where the accuracy of the personal information is contested by you.
6. **Right to withdraw consent**. There are certain circumstances where we require your consent to process your personal information. In these instances, and if you have provided consent, you have the right to withdraw your consent. If you withdraw your consent, this will not affect the lawfulness of our use of your personal information before your withdrawal.
**You also have the right to object to any processing based on our legitimate interests where there are grounds relating to your particular situation. There may be compelling reasons for continuing to process your personal information, and we will assess and inform you if that is the case. You can object to marketing activities for any reason.**
You also have the right to lodge a complaint to your local data protection authority. If you are based in the European Union, information about how to contact your local data protection authority is available [here](http://ec.europa.eu/justice/data-protection/bodies/authorities/index_en.htm). If you are based in the UK or Switzerland, your local data protection authorities are the UK Information Commissioner's Office (<https://ico.org.uk/global/contact-us/>) and the Swiss Federal Data Protection and Information Commissioner (<https://www.edoeb.admin.ch/edoeb/en/home/the-fdpic/contact/address.html>).
If you wish to exercise one of these rights, please contact us using the contact details at the end of these Privacy Disclosures.
Due to the confidential nature of data processing we may ask you to provide proof of identity when exercising the above rights. This can be done by providing a scanned copy of a valid identity document or a signed photocopy of a valid identity document.
### 9. Cookies and similar technologies used on our European Services
Our European Services uses cookies and similar technologies such as pixels and Local Storage Objects (LSOs) like HTML5 (together "**_cookies_**") to distinguish you from other users of our European Services. This helps us to provide you with a good experience when you browse our European Services and also allows us to monitor and analyse how you use and interact with our European Services so that we can continue to improve our European Services.
Cookies are pieces of code that allow for personalisation of our European Services experience by saving your information such as user ID and other preferences. A cookie is a small data file that we transfer to your computer's hard disk for record-keeping purposes.
We use the following types of cookies:
1. **Strictly necessary cookies**. These are cookies that are required for the operation of our European Services. They include, for example, cookies that enable you to log into secure areas of our European Services.
Please see **Annex 3** for more information about the cookies we use on the European Services.
Most browsers also allow you to change your cookie settings to block certain cookies. Depending on your mobile device and operating system, you may not be able to delete or block all cookies. Please note that if you choose to refuse all cookies you may not be able to use the full functionality of our European Services. These settings will typically be found in the "options" or "preferences" menu of your browser. In order to understand these settings, the following links may be helpful, otherwise you should use the "Help" option in your browser for more details.
- [Cookie settings in Internet Explorer](https://support.microsoft.com/en-gb/help/278835/how-to-delete-cookie-files-in-internet-explorer)
- [Cookie settings in Firefox](http://support.mozilla.org/en-US/kb/cookies)
- [Cookie settings in Chrome](https://support.google.com/chrome/answer/95647?hl=en)
- [Cookies settings in Safari web](https://support.apple.com/en-gb/guide/safari/manage-cookies-and-website-data-sfri11471/mac) and [iOS](https://support.apple.com/en-gb/HT201265).
If you would like to find out more about cookies and other similar technologies, please visit [allaboutcookies.org](http://www.allaboutcookies.org).
Please note that deleting or blocking cookies may not be effective for all types of tracking technologies, such as Local Storage Objects (LSOs) like HTML5.
### 10. Tracking technologies used in our emails
Our emails may contain tracking pixels that identify if and when you have opened an email that we have sent you, how many times you have read it and whether you have clicked on any links in that email. This helps us measure the effectiveness of our marketing email campaigns, make the emails we send to you more relevant to your interests and to understand if you have opened and read any important administrative emails we might send you.
Most popular email clients will allow you to block these pixels by disabling certain external images in emails. You can do this through the settings on your email client – these generally give you the option of choosing whether emails will display "remote images", "remote content" or "images" by default.
Some browsers also give you the option of downloading and installing extensions that block pixels and other tracking technologies.
## Annex 1 – Personal information you provide to us
| **Category of Personal Information** | **How we may use the Personal Information** | **Legal Bases for Processing** | **Recipients of Personal Information** |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Contact information**, such as first name, last name and email address. | We may use this information to set up and authenticate your account on the Service. | The processing is necessary for the performance of a contract with you and to take steps prior to entering into a contract with you, namely our Terms of Service. | <p>We may share this information with the following service providers through the provision of the Service: Segment, Auth0, Stripe, Intercom, Mixpanel, Notion, Slack, Amazon Web Services, and BigQuery (Google Cloud). </p><p> </p> |
| | We may use this information to communicate with you, including sending service-related communications. | The processing is necessary for the performance of a contract with you, namely our Terms of Service. | |
| | We may use this information to deal with enquiries and complaints made by or about you relating to the Service. | The processing is necessary for our legitimate interests, namely administering the Service, and for communicating with you effectively to respond to your queries or complaints. | |
| | We may use this information in connection with providing you with marketing communications in accordance with your preferences. | We will only use your personal information in this way to the extent you have given us consent to do so. | |
| **Your registration / account information** such as your full name, email, and password. | <p>We may use this information to create your account on the Service.</p><p></p><p></p> | <p>The processing is necessary for the performance of a contract with you.</p><p></p><p></p> | <p>We may share this information with the following service providers through the provision of the Service: Segment, Auth0, Stripe, Amazon Web Services.</p><p></p> |
| | <p>We use this information to deal with enquiries and complaints made by or about you relating to the Service.</p><p></p> | The processing is necessary for our legitimate interests, namely for communicating with our members effectively to respond to any queries or complaints. | |
| **Payment transaction information.** When you make a purchase, we may collect information such as your billing address and other information such as date and time of your transaction. | <p>We may use this information to process your orders through the Service.</p><p></p> | The processing is necessary for the performance of a contract. | <p>We may share this information with the following service providers through the provision of the Service: Stripe, and Amazon Web Services.</p><p></p> |
| | We may use this information to verify your identity in connection with the detection and prevention of fraud or financial crime. | The processing is necessary for our and third partiers' legitimate interests, namely the detection and prevention of fraud and financial crime. | |
| **Approximate Location information.** When you visit our Service, we may collect information about your location. This information may be derived from WiFi positioning or your IP address. | We may use information to present the Service to you on your device. | The processing is necessary for performance of a contract with you. | <p>We may share this information with the following service providers through the provision of the Service: Sentry, BigQuery (Google Cloud), and Amazon Web Services.</p><p></p> |
| | We may use this information to localise features of the Service. | The processing is necessary for our legitimate interest, namely localising features of the Service and tailoring the Service so that it is more relevant to our users. | |
| | We may use this information to determine content that may be of interest to you. | The processing is necessary for our legitimate interests, namely tailoring the Service so that it is more relevant to you. | |
| **Chat, comments and opinions.** When you contact us directly, e.g. by email or phone we will record your comments and opinions. | We may use this information to address your questions, issues and concerns. | The processing is necessary for our legitimate interests, namely communicating with you and responding to queries, complaints and concerns. | <p>We may share this information with the following service providers through the provision of the Service: Intercom, Google Gsuite, and Slack.</p><p></p> |
| | We may use this information to improve the Service. | The processing is necessary for our legitimate interests (to develop and improve our service). | |
| **Information received from third parties, such as social networks.** If you interact with us through a social network, we may receive information from the social network such as your name, profile information, and any other information you permit the social network to share with third parties. We use single sign-on ("**_SSO_**") such as Github to allow a user to authenticate their account using one set of login information. The data we receive is dependent on your privacy settings with the social network. | We may use this information to reshare content created through the use of the Service | The processing is necessary for our legitimate interests (to develop our service and inform our marketing strategy) | We may share this information with the following service providers through the provision of the Service: Auth0 and Slack. |
| | <p>We may use this information to authenticate you and allow you to access the Service.</p><p></p> | The processing is necessary for the performance of a contract with you. | |
| **Your preferences**, such as preferences set for notifications, marketing communications, how the Service is displayed and the active functionalities on the Service. | <p>We use this information to provide notifications, send news, alerts and marketing communications and provide the Service in accordance with your choices.</p><p></p> | <p>The processing is necessary for our legitimate interest, namely ensuring the user receives the correct marketing and other communications, and that this is displayed in accordance with the user's preferences.</p><p></p> | We may share this information with the following service providers through the provision of the Service: Segment, Intercom. |
| | <p>We use this information to ensure that we comply with our legal obligation to send only those marketing communications to which you have consented. </p><p></p> | The processing is necessary for compliance with a legal obligation to which we are subject. | |
## Annex 2 – Personal information collected automatically
| **Category of personal information** | **How we may use it** | **Legal basis for the processing** | **Recipients of Personal Data** |
| :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Approximate location information.** Other than information you choose to provide to us, we do not collect information about your precise location. Your device’s IP address may however help us determine an approximate location. | We may use information you provide to us about your location to monitor and detect fraud or suspicious activity in relation to your Supabase account. | The processing is necessary for our legitimate interests, namely to protect our business and your account from fraud and other illegal activities. | <p>**Approximate location information:** We may share this information with the following service providers through the provision of the Service: Sentry, BigQuery (Google Cloud), and Amazon Web Services.</p><p></p><p></p><p></p><p>**Information about how you access and use the Service.** We may share this information with the following service providers through the provision of the Service: Segment, Mixpanel, Intercom, BigQuery (Google Cloud), and Amazon Web Services.</p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p></p><p>**Log files and information about your device.** We may share this information with the following service providers through the provision of the Service: Segment, Mixpanel, Intercom, Sentry, BigQuery (Google Cloud), and Amazon Web Services.</p> |
| | We may use this information to tailor how the Service is displayed to you (such as the language in which it is provided to you). | The processing is necessary for our legitimate interest, namely tailoring our service so that it is more relevant to our users. | |
| **Information about how you access and use the Service.** For example, how frequently you access the Service, the time you access the Service and how long you use it for, the approximate location that you access the Service from, the site from which you came and the site to which you are going when you leave our website, the website pages you visit, the links you click, whether you open emails or click the links contained in emails, whether you access the Service from multiple devices, and other actions you take on the Service. | <p>We may use information about how you use and connect to the Service to present the Service to you on your device.</p><p></p> | The processing is necessary for our legitimate interests, namely to tailor the Service to the user. | |
| | We may use this information to determine products and services that may be of interest to you for marketing purposes. | The processing is necessary for our legitimate interests, namely to inform our direct marketing. | |
| | <p>We may use this information to monitor and improve the Service and business, resolve issues and to inform the development of new products and services.</p><p></p> | The processing is necessary for our legitimate interests, namely to monitor and resolve issues with the Service and to improve the Service generally. | |
| **Log files and information about your device.** We also collect information about the tablet, smartphone or other electronic device you use to connect to the Service. This information can include details about the, operating systems, browsers and applications connected to the Service through the device and your IP address. | <p>We may use information about how you use and connect to the Service to present the Service to you on your device.</p><p></p> | The processing is necessary for our legitimate interests, namely to tailor the Service to the user. | |
| | <p>We may use this information to monitor and improve the Service and business, resolve issues and to inform the development of new products and services.</p><p></p> | The processing is necessary for our legitimate interests, namely to monitor and resolve issues with the Service and to improve the Service generally. | |
## Annex 3 - Cookies
| **Cookie Name** | **Type of cookie** | **How long does the cookie stay on my device?** | **Purpose of the cookie** |
| :----------------------- | :----------------- | :---------------------------------------------- | :------------------------ |
| <p>a0:session</p><p></p> | Strictly necessary | 1 Day | Authentication Purposes. |
| <p>a0:state</p><p></p> | Strictly necessary | 2 Hours | Authentication Purposes. |
| <p>youtube-nocookie</p><p></p> | Strictly necessary | Persistent | <p>Privacy-enhanced cookie, essential for embedded videos. [Link.](https://support.google.com/youtube/answer/171780)</p><p><blockquote><small>Privacy Enhanced Mode allows you to embed YouTube videos without using cookies that track viewing behavior. This means no activity is collected to personalize the viewing experience.</small></blockquote></p>|
+1 -1
View File
@@ -4,7 +4,7 @@ title: Terms of Service
---
Last Modified: 20 November 2020
`Last Modified: 20 November 2020`
These Customer Terms and Conditions (this "**Agreement**"), effective as of the date on which you click a button or check a box (or something similar) acknowledging your acceptance of this Agreement (the "**Effective Date**"), is by and between Supabase, Inc., a Delaware corporation with offices located at 970 Toa Payoh North #07-04, Singapore 318992 ("**Supabase**") and the entity on whose behalf the individual accepting this Agreement accepts this Agreement ("**Customer**"). The individual accepting this Agreement hereby represents and warrants that it is duly authorized by the entity on whose behalf it accepts this Agreement to so accept this Agreement. Supabase and Customer may be referred to herein collectively as the "**Parties**" or individually as a "**Party**." The Parties agree as follows:
+1 -11
View File
@@ -4,17 +4,7 @@ title: Pricing
description: Supabase Pricing
---
Supabase is an **open source** company.
While we're in Beta we are free to use. In the future, we will charge for hosting. However, we are offering the following initiatives:
- All Alpha Users will receive credits equivalent of 2 years of base tier usage. These will automatically be credited to your account if you signed up prior to December 2020.
- All Beta Users (new users from December 2020) will receive 1 year of base tier usage for free.
- University (and participating code school) Students will be eligible for 2 years of base tier usage (Code Schools can contact rory@supabase.io)
- Early stage startups participating in selected incubator programs can claim additional credits which can be applied to products outside of the base tier.
The Supabase Base Tier constitutes a Supabase Instance running in a single region, on 2 vCPUs, 1 GiB Memory, with 2 GB of storage, and daily backups.
See [pricing](https://supabase.io/pricing).
## Sponsor
+8 -1
View File
@@ -69,7 +69,14 @@ module.exports = {
type: 'category',
label: 'See Also',
collapsed: false,
items: ['guides/examples', 'pricing', 'support', 'handbook/contributing', 'company/terms'],
items: [
'guides/examples',
'pricing',
'support',
'handbook/contributing',
'company/terms',
'company/privacy',
],
},
],
resources: [
@@ -10,7 +10,7 @@ image: supabase-strive-school.png
thumb: supabase-strive-school.png
tags:
- supabase
- Striveschool
- striveschool
date: '12-02-2020'
---
@@ -9,8 +9,8 @@ authorURL: https://github.com/awalias
image: elephants.png
thumb: elephants.png
tags:
- SQL
- Postgres
- sql
- postgres
date: '02-27-2021'
---
@@ -9,9 +9,9 @@ authorURL: https://github.com/awalias
image: replit-og.jpg
thumb: replit-og.jpg
tags:
- Replit
- replit
- node-js
- Postgres
- postgres
date: '03-11-2021'
---
+8 -7
View File
@@ -5,15 +5,16 @@ author: rory_wilding
image: intheloop-supabase.jpg
thumb: intheloop-supabase.jpg
tags:
- Supabase
- Postgres
- Open-source
- supabase
- postgres
- open-source
date: '03-22-2021'
---
Kevin Grüneberg is a freelance engineer who has been programming since he was 12 years old. His latest project, intheloop.dev, provides a single gateway for developers to stay up to date.
## At the leading edge of Developer updates
## At the leading edge of Developer updates
As a developer, Kevin loves to stay up to date with the latest technology. Within his network, he also finds himself the go-to person to help his colleagues stay up to speed by searching through Twitter, staying active in forums, and searching out different tech communities. He knows this insight will help others stay up to date at scale, so created intheloop.dev.
Intheloop.dev is a single point of entry for developers to stay up to date on the topics they care about by aggregating feeds from various sources. It serves as an on-ramp to finding the communities you care about, monitor releases and change logs, critical tweets, and blogs of interest.
@@ -31,11 +32,11 @@ With over seven years of experience working with relational databases, Kevin kno
Kevin loves building with Supabase because of SQL and other Postgres features like constraints, views, and materialised views. Using all these features quickly and efficiently is amazing for Kevin when he knows Supabase is just as easy to integrate as Firebase.
<Quote img="intheloop-kevin.png" caption="Kevin Grüneberg - Creator of intheloop.dev">
Supabase building on top of Postgres is a true superpower for developers. Postgres is already insanely powerful, and having much more functionality out of the box means I can build quickly and continue to scale
Supabase building on top of Postgres is a true superpower for developers. Postgres is already
insanely powerful, and having much more functionality out of the box means I can build quickly and
continue to scale
</Quote>
## Launch fast and have confidence in adding more functionality with Supabase
With Supabase, Kevin can focus on his idea's core rather than worrying about his tech stack. Intheloop is just getting started with additional features coming like notifications, following topics, and developers to build a personal digest which is where the real time-saver begins. With Postgres under the hood, Kevin can add functionality to delight his users without worrying about his data model. You can sign up to stay in the loop [here](https://intheloop.dev/).
+330
View File
@@ -0,0 +1,330 @@
---
title: Angels of Supabase
description: Meet the investors of Supabase.
author: paul_copplestone
author_title: Supabase
author_url: https://github.com/kiwicopple
author_image_url: https://github.com/kiwicopple.png
authorURL: https://github.com/kiwicopple
image: angels-of-supabase.png
thumb: angels-of-supabase.png
tags:
- fundraising
date: '2021-03-25'
---
Supabase is an open source Firebase alternative. We're on a mission to build an incredible developer experience around
the world's best open source tools.
To build an amazing developer experience, we need developers to guide us. So after finishing Y Combinator, we took a
deliberate approach to fundraising. We loaded our Seed round with developers, technical founders, and developer advocates:
CTOs who have seen scale, community managers, and hackers. We amassed over 20 angel investors - almost all technical.
The Supabase Team is at full capacity preparing for [Launch Week](/blog/2021/03/25/launch-week) and since we're all short on time we decided -
why not ask our Angels to write a fundraising post with us?
So here is our unconventional fundraising announcement, written with a few of the Angels of Supabase.
## Mike Krieger (Instagram)
<img className="border dark:border-gray-600 rounded" src="/new/images/blog/angels/mike-krieger-supabase.png" alt="Mike Krieger Supabase"/>
Cofounder & CTO of Instagram. [@mikeyk](https://twitter.com/mikeyk)
#### How many hours per week do you spend coding?
~30 these days
#### Why did you invest in Supabase?
Supabase is building tools that are foundational to almost any interactive, modern app, and doing so in a way that's
really aligned with how I think of building tech stacks. For example, choosing Postgres (and deeply understanding
Postgres' features) as the underlying datastore.
#### Do you write SQL in uppercase or lowercase?
Uppercase but I'm coming around to lowercase!
#### What was the first program you wrote and what was it written with?
A website that I built so I could skip out on writing a school paper instead — HTML and Image `<map>`s for interactivity.
#### What's the worst programming error you've ever made?
On launch day for Instagram, the site was down for a good chunk of time because we hadn't set a favicon.ico *and*
we had Django set up to email us on all errors. Cue storm of email sends every time someone loaded our homepage…and a very unhappy web server.
#### If you could only use one programming language for the rest of your life, what would it be?
Python — though I am very intrigued by Rust.
#### If someone wanted to do what you do, what's a piece of advice you'd give them?
Stay curious and don't feel like everything you build has to be "the next big thing"; so many times I've worked on a
fun side project or exploration and ended up learning something I applied to more "serious" work.
## Cassidy Williams (Netlify)
<img className="border dark:border-gray-600 rounded" src="/new/images/blog/angels/cassidy-williams-supabase.png" alt="Cassidy Williams Supabase"/>
Principal Developer Experience Engineer at Netlify. Outside of that, makes memes, classes, talks, and content for
developers. [@cassidoo](https://twitter.com/cassidoo)
#### How many hours per week do you spend coding?
Probably around 20, depending on the week. Sometimes more!
#### Why did you invest in Supabase?
It's a good tech stack and a tool that I think fills a need for a lot of folks.
#### Do you write SQL in uppercase or lowercase?
Uppercase!
#### What was the first program you wrote and what was it written with?
I started making websites as a teenager, and programmed my graphing calculator with good ol' BASIC!
#### When was the first time you used a computer and what did you do with it?
Oh I was a kid, and definitely used it only for CD-ROM games in those dial-up days.
#### Would you rather fight 100 duck-sized horses or 1 horse-sized duck?
100 duck-sized horses, because I'm a weakling and could probably do better with the smaller things as long as
they aren't too organized.
#### If you could only use one programming language for the rest of your life, what would it be?
Probably JavaScript. It's gotten to that level of power that I could apply it to almost anything.
#### If someone wanted to do what you do, what's a piece of advice you'd give them?
Get a calendar you'll actually use and stick to your to-do lists!
## Thorsten Schaeff (Stripe)
<img className="border dark:border-gray-600 rounded" src="/new/images/blog/angels/thor-supabase.png" alt="Thorsten Schaeff Supabase"/>
Helps developers grow the GDP of the Internet as a Developer Advocate at Stripe. [@thorwebdev](https://twitter.com/thorwebdev)
#### How many hours per week do you spend coding?
Really depends on the projects I'm working on. Sometimes I spend a good amount of time making videos.
#### Why did you invest in Supabase?
The team | The Culture | The DX
#### Do you write SQL in uppercase or lowercase?
lowercase - I prefer not to scream at my DB.
#### What was the first program you wrote and what was it written with?
My first proper project was probably at uni, building an app to alert wind surfers of great wind conditions.
Was built with Python on a Raspberry Pi, jQuery and PhoneGap
#### When was the first time you used a computer and what did you do with it?
When I was 9 years old my dad signed me up for an HTML summer camp. That's how it all started :D
#### Would you rather fight 100 duck-sized horses or 1 horse-sized duck?
I think there's a market for duck-sized horses. Would capture and sell them. Also duck's have some nasty claws,
wouldn't be a good idea to make them bigger!
#### If you could only use one programming language for the rest of your life, what would it be?
I'd do anything and everything in TypeScript if I could!
#### If someone wanted to do what you do, what's a piece of advice you'd give them?
Get involved with and start contributing to open-source. Start creating content about your open-source contributions,
and I'm certain that it will pay off!
## Brian Douglas (GitHub)
<img className="border dark:border-gray-600 rounded" src="/new/images/blog/angels/brian-douglas-supabase.png" alt="Brian Douglas Supabase"/>
Staff Developer Advocate at GitHub. [@bdougieyo](https://twitter.com/bdougieyo)
#### How many hours per week do you spend coding?
~10 hours a week.
#### Why did you invest in Supabase?
Supabase is scratching an itch for me as a developer. I see the value of having a manageable database based on SQL,
but I don't only have limited time to code during the week and I don't want that to get sucked into the maintenance
and management of that data. I also was a big Firebase fan, and love the developer ergonomics if offered to me as an early user.
#### Do you write SQL in uppercase or lowercase?
Always uppercase, but don't ask me what the acronym stands for.
#### What was the first program you wrote and what was it written with?
The first function programming was an Android app in Java that was just a picture of a cat. When you pet the cat it purred,
it was part of an Android tutorial when I thought I wanted to be an Android dev. I later got an iPhone and decided to never
write Java again when I found Ruby on Rails. I built a Yelp clone for churches, but bailed on that when I took a full time dev role.
#### When was the first time you used a computer and what did you do with it?
My first time using a computer was in second grade and it was to play wolfenstein. We had a computer that we could use in
the office of the apartment complex I grew up in. One of the older kids installed the game and showed the other kids how
to run in from powershell. Probably not the best game to introduce to a 7 year old, but here we are.
#### If someone wanted to do what you do, what's a piece of advice you'd give them?
I would tell them to start with creating short form content every day. It easier to make content on things you just
learned, so do that. It is also easier to get hired when you already have content.
## Justin Gage (Retool)
<img className="border dark:border-gray-600 rounded" src="/new/images/blog/angels/justin-gauge-supabase.png" alt="Justin Gage Supabase"/>
Growth at Retool. Writes [a newsletter](https://technically.dev/) that helps people understand software engineering and tech.
[@itunpredictable](https://twitter.com/itunpredictable)
#### How many hours per week do you spend coding?
It depends – some weeks I’m mostly writing and coordinating, other weeks I’m writing a lot of code. Lets say 20 hours on a good week :)
#### Why did you invest in Supabase?
I had been working on the Firebase integration at Retool for a bit, and kept wondering why it was (a) closed source
and (b) NoSQL only. Over the span of a week, something like 5 or 6 of my developer friends asked me what I thought
about Supabase, so I checked it out – after using the product for a side project, investing was an easy decision.
#### Do you write SQL in uppercase or lowercase?
Lowercase, obviously. I wrote thousands of queries when I worked at DigitalOcean, and we didn’t have time to capitalize things.
#### If you could only use one programming language for the rest of your life, what would it be?
SQL (particularly standard Postgres syntax) is the best language on the planet. Fight me.
#### If someone wanted to do what you do, what's a piece of advice you'd give them?
Eh, I can’t really give advice, I haven’t figured anything out myself yet!
## Iheanyi Ekechukwu (PlanetScale)
<img className="border dark:border-gray-600 rounded" src="/new/images/blog/angels/iheanyi-ekechukwu-supabase.png" alt="Iheanyi Ekechukwu Supabase"/>
Senior Software Engineer at PlanetScale. [@kwuchu](https://twitter.com/kwuchu)
#### How do you pronounce your name?
<video width="99%" controls={true}>
<source src="/new/images/blog/angels/kwuchu.mp4" type="video/mp4" playsInline />
</video>
#### How many hours per week do you spend coding?
Pretty much, the majority of my week is spent coding. I'd say realistically, around 30-ish hours a week or so.
#### Why did you invest in Supabase?
The tagline about Supabase being an "open-source Firebase alternative" had me intrigued. As I looked more deeply into
the product, I was impressed by the well-thought out design and also how easy it is to use the product as well.
#### Do you write SQL in uppercase or lowercase?
Uppercase.
#### When was the first time you used a computer and what did you do with it?
During summers as a child, my dad would take me to work with him at the community college he taught at. I'd be sitting
in his office while he was in lectures and actually just use his computer to play that 3D Space Pinball game. I think
I was 5 or 6 years old at the time.
#### If you could only use one programming language for the rest of your life, what would it be?
Honestly, I would probably choose Go. It's a pretty dope language for building out systems.
#### If someone wanted to do what you do, what's a piece of advice you'd give them?
Always be learning. There's so many things to learn as an engineer and the job is constantly about learning new things.
Never settle, never get comfortable, learn as much as you can consistently.
## Zach Waterfield (On Deck)
<img className="border dark:border-gray-600 rounded overflow-hidden" src="/new/images/blog/angels/zach-waterfield-supabase.png" alt="Zach Waterfield Supabase"/>
Engineer at OnDeck, Network Leader at Village Global, Founder of Kopa (W19). [@zlwaterfield](https://twitter.com/zlwaterfield)
#### How many hours per week do you spend coding?
Anywhere from 10 to 50 depending on the week
#### Why did you invest in Supabase?
I have many ideas, and spinning up a backend, db, etc., is so daunting, so I started using Firebase, but it was very
frustrating. When I saw Supabase I instantly knew that was the solution. The ability to use Postgres directly from the
client with authentication built-in was a game-changer. I think it could become the go-to that early-stage companies
build on to simplify their MVPs.
#### Do you write SQL in uppercase or lowercase?
UPPERCASE
#### When was the first time you used a computer and what did you do with it?
Weirdly enough, I didn't own a computer until I was in grade 12 and didn't learn to code until I was in University.
I was the only one in a class of 400+ freshman engineers that had never coded before.
#### If someone wanted to do what you do, what's a piece of advice you'd give them?
Network, network, network. Everything I do and all my successes are based on my network. Whether that be from YC, my
university peers, OnDeck, Twitter, etc. Focus on building your network, and good opportunities will start to pop up.
## Shawn Wang (Temporal)
<img className="border dark:border-gray-600 rounded overflow-hidden object-cover m-0 p-0 " src="/new/images/blog/angels/swyx-supabase.png" alt="Shawn Wang Supabase" />
Head of Developer Experience at Temporal. [@swyx](https://twitter.com/swyx)
#### How many hours per week do you spend coding?
10
#### Why did you invest in Supabase?
I was extremely impressed by the founders and love the idea of offering a beautiful, integrated developer experience atop
of proven, scalable, open source foundations.
#### Do you write SQL in uppercase or lowercase?
Uppercase if formal, lowercase if lazy!
#### What was the first program you wrote and what was it written with?
We had a computer class in middle school and we were assigned to write a pancake stacking program in BASIC. I was really
obsessed by it and was the only person to submit this assignment complete with a graphical user interface (in BASIC!).
Sadly I didn't see myself as a programmer until 20 years later.
#### If someone wanted to do what you do, what's a piece of advice you'd give them?
[Learn in Public](https://www.swyx.io/LIP) :)
## By the way
We [raised](https://techcrunch.com/2020/12/15/supabase-raises-6m-for-its-open-source-firebase-alternative/) a $6M seed
round led by Coatue, Mozilla, and YC.
Next week is [Launch Week](/blog/2021/03/25/launch-week) and we'll be launching one new thing every day for a week.
[Check it out](/blog/2021/03/25/launch-week)!
+95
View File
@@ -0,0 +1,95 @@
---
title: Launch week
description: Five days of Supabase.
author: ant_wilson
author_title: Supabase
author_url: https://github.com/kiwicopple
author_image_url: https://github.com/kiwicopple.png
authorURL: https://github.com/kiwicopple
image: supabase-launch-week.png
thumb: supabase-launch-week.png
tags:
- storage
- pricing
- developers
date: '2021-03-25'
---
When we joined Y Combinator last summer, we spent the entire 3-month program building one (major) feature: Auth.
Demo Day gave our team something to rally around. The looming deadline forced us to deliver a lot of complex functionality in a short amount of time.
After we finished YC, we recreated Demo Day conditions internally: we gave ourselves a 3-month timeline to move from Alpha to Beta.
It worked wonders. We made [giant leaps forward](https://supabase.io/beta) in the performance, security, and
stability of the platform.
The day after our Beta announcement (still high on the adrenaline of the launch) we asked ourselves:
what's the most ambitious thing we can hope to launch 3 months from now?
We decided not to settle for just one major feature. We wanted to fill an entire week with launches and announcements.
Looking at the list of features we planned, and with the relatively small team we have, this felt like a hugely ambitious idea.
But after another 3 months of building, we're happy to announce that...
Starting on Monday 29th March, we'll be commencing our Launch Week!
## What to expect
We don't want to spoil the surprise, so for now we'll give you a teaser of what to expect. Come back here each day to see what we launch,
or follow us on twitter to keep up to date: [@supabase_io](https://twitter.com/supabase_io) and follow the hashtag [#supalaunchweek](https://twitter.com/hashtag/supalaunchweek).
## Monday: Pricing
- 29 March 2021
We're announcing pricing today. If you don’t care for the details, [here is the pricing](https://supabase.io/pricing).
The [blog post here](https://supabase.io/blog/2021/03/29/pricing) goes into depth around our decision-making process.
The key takeaway is that we will stick with (what we feel is) a generous free tier. This was critical for many of our users who are still in the “build” phase of their project/business.
Another key takeaway is predictability. We’ve talked to too many devs who have been caught out by Firebase’s usage billing.
Usually this is due to rogue API requests, which have a habit of spiking without any good debugging tools. To circumvent this, our usage pricing is centered on more “predictable” mechanisms (like storage), avoiding usage-billing on items like API requests.
Even on our free tier you’re able to make millions of requests per day (we have developers doing this already!). In the future we will introduce “usage caps” on our top tier, which will allow you to specify maximum spends. Finally, we’re starting with soft-tiers, so nobody will be “unexpectedly” upgraded.
We’re a startup ourselves, so we want to build a sustainable business. Even though we are offering a free tier, we’re confident in our ability to support it long-term with the enterprise features we are building (SSO, multi-cloud).
If you aren’t confident in our business model though, we’re open source. You can [host it yourself](https://github.com/supabase/supabase/tree/master/docker). Even better, everything we build is centered around Postgres, so you can pretty much `pgdump` your database and take it to your favourite Postgres platform.
![Supabase Pricing](/new/images/blog/launch-week/bernie-1.png)
## Tuesday: Storage
- 30 March 2021
- Details: [Coming soon]
- Get Notified: Follow us on [Twitter](https://twitter.com/supabase_io), or visit this page on Tuesday.
![Supabase Storage](/new/images/blog/launch-week/bernie-2.png)
## Wednesday: CLI
- 31 March 2021
- Details: [Coming soon]
- Get Notified: Follow us on [Twitter](https://twitter.com/supabase_io), or visit this page on Wednesday.
![Supabase CLI](/new/images/blog/launch-week/bernie-3.png)
## Thursday: UI (not an April Fool's joke, we promise)
- 1 April 2021
- Details: [Coming soon]
- Get Notified: Follow us on [Twitter](https://twitter.com/supabase_io), or visit this page on Thursday.
![Supabase UI](/new/images/blog/launch-week/bernie-4.png)
## Friday: One more thing
- 2 April 2021
- Details: [Coming soon]
- Get Notified: Follow us on [Twitter](https://twitter.com/supabase_io), or visit this page on Friday.
![moar](/new/images/blog/launch-week/bernie-5.png)
+82
View File
@@ -0,0 +1,82 @@
---
title: Supabase Beta Pricing
description: Supabase launches Beta pricing structure
author: rory_wilding
image: pricing-og.jpg
thumb: launch-week-pricing.jpg
tags:
- Supabase
date: '03-29-2021'
---
## Pricing is hard
Many developers have been waiting for [pricing](https://supabase.io/pricing) before building on Supabase. It has taken this long to announce pricing for one simple reason: pricing is hard. We've spent countless hours discussing and testing different models. We consulted users, OSS veterans, and SaaS Gurus. We explored and ruled out a number of options including: no free tier, pure usage based, pay-per-seat, pay-per-row, and "bolt-on" feature based pricing.
Supabase is a suite of integrated products - should we compare our Postgres databases with Amazon RDS? Do we benchmark our Auth pricing against Auth0 or Okta?
So we decided to do what we always do: ship it, share it, and then listen to our users' feedback.
## TLDR
We are launching [pricing](https://supabase.io/pricing) with three tiers:
1. Free - for up to four hobby-level projects
2. Pro - $25 per project per month, for more serious projects
3. Usage based - for those who need more resource than what's on the Pro tier
## Goals for our pricing structure
Our goals for pricing are simple:
- Continue offering free Supabase projects for Students, Hobbyists, and Early Adopters
- Price based on predictable metrics (no shock bills at the end of the month)
- Enable and grow with our most successful users, providing a pricing model that supports their growth
We came up with a 3-tier model to offer predictability, and then iterated through numerous user interviews to answer the questions: "how much do we charge in each tier, and what features go in each tier?". To answer these questions we considered:
1. How much value does Supabase create compared to alternatives including Firebase, authentication providers like Okta and Auth0, API, and storage providers?
2. How much time does Supabase save developers?
3. How can we make Supabase as accessible as possible?
4. As developers ourselves, how do we feel about our proposed pricing?
5. The underlying cost of development and maintenance of Supabase.
As a result, we've arrived at a pricing model that brings predictability, is sustainable, and enables us to continue to offer a free tier.
Our model allows anybody to come in and experience the full power of the Supabase stack without first needing to put down a credit card.
## Launching with a free tier
Many of our early users are building or migrating their passion projects to Supabase. Offering a free tier enables this. We also support a large number of students learning SQL for the first time, and fledgling startups which haven't started earning any revenue yet.
Developers need time to build their side projects, and they don't want to pay for a database while they do it. At the same time, hosting Postgres databases is expensive. So we decided to take the same strategy that we always have: ship, share it, and iterate.
No company gets the pricing right first time. It would be arrogant to believe that we will. What we can promise you is this: as we learn and grow we plan to offer more value for less, not the other way around.
Our success depends on the success of developers using Supabase. In short, if our users' projects succeed then Supabase adoption will continue. That is exactly what we want.
We will issue credits for those who signed up during alpha, beta, and those who sign up through to the end of Launch Week. If you are not on board yet, [you can sign up here.](http://www.supabase.io) Projects on select incubators will also get credits, as founders we know the importance of credits when getting something new off the ground. We will continue to support code schools and their students. If you need Supabase credits to support your project, reach out to us here and we will do our best to help you.
## Making pricing predictable
We know that Firebase pricing can be [problematic](https://medium.com/madhash/how-not-to-get-a-30k-bill-from-firebase-37a6cb3abaca).
That's why we made our pricing as predictable as possible. You can quickly understand which pricing tier you fit into, without having to forecast how many API requests you're going to make this quarter.
What does this mean for you as a developer?
- You can make as many API calls as you need to (within the constraints of your database). We already have users making millions of requests per day.
- We have been deliberately generous with authentication and storage on the free tier so that you get as much of the Supabase experience as possible.
- We're starting with soft-limits for usage billing to prevent unexpected charges.
- We'll reach out to you before changing your billing plan to see if it makes sense for your project.
- On our "Pay as you go" tier we will work with you to forecast billing before making a commitment.
## Next steps
In the next few days, you will see your usage appear in the dashboard with an upgrade button.
Check back throughout the week to see what else we're launching [here](https://supabase.io/new/blog/2021/03/25/launch-week).
Sign up for the Supabase Beta [here](https://app.supabase.io).
And check out the full pricing details [here](https://supabase.io/pricing). And we'd love to hear your opnions on our Pricing! You can let us know via beta@supabase.io.
@@ -0,0 +1,140 @@
import { useState } from 'react'
import { createClient } from '@supabase/supabase-js'
import { Button, Card, Auth, Space, Tabs, Typography, IconCode, IconSearch } from '@supabase/ui'
import CodeBlock from '../CodeBlock/CodeBlock'
// Import Swiper React components
import { Swiper, SwiperSlide } from 'swiper/react'
const supabase = createClient(
'https://rsnibhkhsbfnncjmwnkj.supabase.co',
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYxNTIxNDE1MywiZXhwIjoxOTMwNzkwMTUzfQ.OQEbAaTfgDdLCCht251P2JRD3QDnui6nsU8N-tZA_Mc'
)
function AuthComponentExample() {
// store API swiper instance
const [imageSwiper, setImageSwiper] = useState(undefined)
const [imageSwiperActiveIndex, setImageSwiperActiveIndex] = useState(0)
function handleNavChange(e: number) {
console.log(e)
setImageSwiperActiveIndex(e)
// @ts-ignore
imageSwiper.slideTo(e)
}
const AuthContainer = (props: any) => {
const { user } = Auth.useUser()
if (user) {
return (
<>
<Typography.Text>Signed in: {user.email}</Typography.Text>
<Button block onClick={() => props.supabaseClient.auth.signOut()}>
Sign out
</Button>
</>
)
}
return props.children
}
return (
<div className="sbui-tabs--alt">
{/* <Tabs
activeId={imageSwiperActiveIndex.toString()}
onChange={(id: string) => handleNavChange(Number(id))}
>
<Tabs.Panel label="Preview" id="0" icon={<IconSearch />}>
<span></span>
</Tabs.Panel>
<Tabs.Panel label="Code" icon={<IconCode />} id="1">
<span></span>
</Tabs.Panel>
</Tabs> */}
<div
className={`relative bg-white dark:bg-gray-800 py-2 pb-16 border dark:border-gray-600 auth-container`}
>
<Swiper
// @ts-ignore
onSwiper={setImageSwiper}
style={{ zIndex: 0 }}
initialSlide={0}
spaceBetween={0}
slidesPerView={1}
speed={300}
autoHeight={true}
allowTouchMove={false}
>
<SwiperSlide key={1}>
<div className="pt-8" style={{ maxWidth: '420px', margin: '0 auto' }}>
<Card>
<Space size={8} direction="vertical">
<div>
<Space size={3} direction="vertical">
<img src="https://app.supabase.io/img/supabase-dark.svg" width="96" />
<Typography.Title level={3}>{'Acme Company'}</Typography.Title>
</Space>
</div>
<Auth.UserContextProvider supabaseClient={supabase}>
<AuthContainer supabaseClient={supabase}>
<Auth
providers={['facebook', 'google', 'github', 'bitbucket', 'gitlab']}
socialButtonSize={'medium'}
socialLayout={'horizontal'}
supabaseClient={supabase}
socialColors={false}
/>
</AuthContainer>
</Auth.UserContextProvider>
</Space>
</Card>
</div>
</SwiperSlide>
<SwiperSlide key={2}>
<div className="p-8">
<CodeBlock
children={`import React, { useState } from 'react'
import { Auth, Typography, Button } from '@supabase/ui'
import { createClient } from '@supabase/supabase-js'
export default function app() {
const supabase = createClient(
"https://YOUR-PROJECT-ID.supabase.co",
"YOUR-PUBLIC-ANON-KEY"
);
function Container(props: any) {
const { user } = Auth.useUser();
if (user) {
return (
<div>
<Typography.Text>Signed in: {user.email}</Typography.Text>
<Button block onClick={() => props.supabaseClient.auth.signOut()}>
Sign out
</Button>
</div>
)
}
return props.children;
};
return (
<Auth.UserContextProvider supabaseClient={supabase}>
<Container supabaseClient={supabase}>
<Auth supabaseClient={supabase} />
</Container>
</Auth.UserContextProvider>
);
}
`}
lang="js"
/>
</div>
</SwiperSlide>
</Swiper>
</div>
</div>
)
}
export default AuthComponentExample
+1 -1
View File
@@ -142,7 +142,7 @@ function AuthPage() {
<Space size={12} direction="vertical">
<div>
<Typography.Title level={3}>Customise your login form</Typography.Title>
<Typography.Text level={3}>
<Typography.Text>
Our ReactJS Auth form will help you get going even quicker{' '}
</Typography.Text>
</div>
+24 -35
View File
@@ -6,61 +6,50 @@ import SectionContainer from '../Layouts/SectionContainer'
import ExampleCard from '../ExampleCard'
import Examples from 'data/Examples.json'
import Link from 'next/link'
// import Button from '../Button'
const BuiltExamples = () => {
const { basePath } = useRouter()
return (
<SectionContainer className="pb-16">
<SectionContainer className="xl:pt-32">
<div className="text-center">
<Typography.Title level={2}>What can you build with supabase?</Typography.Title>
<Typography.Text>
<p className="text-lg">There's many example apps and starter projects to get going</p>
<p className="lg:text-lg">There's many example apps and starter projects to get going</p>
<Space className="justify-center">
<Button type="outline" href="https://github.com/supabase/supabase/tree/master/examples">
View all examples
</Button>
<Button
type="outline"
icon={<IconGitHub />}
href="https://github.com/supabase/supabase/tree/master/examples"
<Link
href="https://supabase.io/docs/guides/examples"
as="https://supabase.io/docs/guides/examples"
>
Official github library
</Button>
<a>
<Button type="default">View all examples</Button>
</a>
</Link>
<Link
href="https://github.com/supabase/supabase/tree/master/examples"
as="https://github.com/supabase/supabase/tree/master/examples"
>
<a>
<Button type="default" icon={<IconGitHub />}>
Official github library
</Button>
</a>
</Link>
</Space>
</Typography.Text>
</div>
<div class="grid grid-cols-12 gap-5 mt-16">
<div className="grid grid-cols-12 gap-5 mt-16">
{Examples.slice(0, 6).map((example, i) => {
return (
<div className="col-span-12 lg:col-span-6 xl:col-span-4">
<div
className={`col-span-12 lg:col-span-6 xl:col-span-4 ${i > 2 && `sm:hidden lg:block`}`} key={i}
>
<ExampleCard {...example} />
</div>
)
})}
</div>
{/* <div className="mt-12 max-w-lg mx-auto grid gap-5 lg:grid-cols-3 lg:max-w-none">
{ProjectExamples.map((example: any, idx: number) => (
<a href={example.url}>
<Card
key={`example_${idx}`}
hoverable
cover={<img src={`${basePath}/${example.imgUrl}`} className="h-64 object-cover" />}
>
<Space className="justify-between h-40" direction="vertical">
<div>
<Typography.Text small type="secondary">
Project example
</Typography.Text>
<Typography.Title level={3}>{example.title}</Typography.Title>
</div>
<Typography.Text type="default">{example.description}</Typography.Text>
</Space>
</Card>
</a>
))}
</div> */}
</SectionContainer>
)
}
+1 -1
View File
@@ -67,7 +67,7 @@ const Card = (props: Props) => {
</div>
)} */}
<p className="text-sm font-base capitalize text-gray-600 dark:text-dark-300">{type}</p>
<div className="block mt-2 flex flex-col justify-between h-32">
<div className="mt-2 flex flex-col justify-between h-32">
{/* {logoUrl && <img src={logoUrl} className="h-9 my-4" />} */}
{title && <p className="text-xl text-gray-900 dark:text-white">{title}</p>}
<p className="mt-3 text-base text-gray-500 dark:text-dark-400">
+39 -32
View File
@@ -4,33 +4,30 @@ import { useState } from 'react'
// Import Swiper React components
import { Swiper, SwiperSlide } from 'swiper/react'
import SwiperCore, { EffectFade } from 'swiper'
// Import Swiper styles
import 'swiper/swiper.min.css'
import 'swiper/components/effect-fade/effect-fade.min.css'
import CodeBlock from '../CodeBlock/CodeBlock'
import ImageCarouselStyles from './ImageCarousel.module.css'
import TweetCard from '../TweetCard'
SwiperCore.use([EffectFade])
import Link from 'next/link'
interface Content {
title: string
label?: string
img_url: string
text: string
img_url?: string
video_url?: string
text?: string
cta?: string
url?: string
}
interface ImageCaoursel {
content: Content
interface ImageCarouselProps {
content: Content[]
footer?: React.ReactNode
altTabView?: boolean
}
function ImageCarousel(props: ImageCaoursel) {
function ImageCarousel(props: ImageCarouselProps) {
// base path for images
const { basePath } = useRouter()
@@ -80,9 +77,10 @@ function ImageCarousel(props: ImageCaoursel) {
// @ts-ignore
onChange={(id: string) => handleImageSwiperNav(Number(id))}
>
{props.content.map((content, i) => {
{props.content.map((content: Content, i) => {
return (
<Tabs.Panel
key={i}
label={content.label ? content.label : content.title}
id={i.toString()}
>
@@ -93,20 +91,19 @@ function ImageCarousel(props: ImageCaoursel) {
</Tabs>
</div>
<div
className={`border border-gray-100 dark:border-gray-600 rounded-md bg-gray-800 overflow-hidden ${ImageCarouselStyles['gradient-bg']}`}
className={`overflow-hidden border border-gray-100 dark:border-gray-600 rounded-md bg-gray-800 ${ImageCarouselStyles['gradient-bg']}`}
>
<Swiper
// @ts-ignore
onSwiper={setImageSwiper}
style={{ zIndex: 0 }}
style={{ zIndex: 0, overflow: 'auto' }}
initialSlide={0}
spaceBetween={0}
slidesPerView={1}
speed={400}
autoHeight={true}
speed={300}
allowTouchMove={false}
>
{props.content.map((content, i) => {
{props.content.map((content: Content, i: number) => {
return (
<SwiperSlide key={i}>
{content.img_url && <img src={`${basePath}${content.img_url}`} />}
@@ -140,9 +137,13 @@ function ImageCarousel(props: ImageCaoursel) {
onChange={(id: string) => handleImageSwiperNav(Number(id))}
block
>
{props.content.map((content, i) => {
{props.content.map((content: Content, i: number) => {
return (
<Tabs.Panel label={content.label ? content.label : content.title} id={i.toString()}>
<Tabs.Panel
label={content.label ? content.label : content.title}
id={i.toString()}
key={i}
>
<span></span>
</Tabs.Panel>
)
@@ -153,7 +154,7 @@ function ImageCarousel(props: ImageCaoursel) {
// @ts-ignore
onSwiper={setSwiperDetails}
initialSlide={0}
speed={400}
speed={300}
allowTouchMove={false}
autoHeight={true}
>
@@ -162,17 +163,23 @@ function ImageCarousel(props: ImageCaoursel) {
<SwiperSlide key={i}>
<div className="bg-white dark:bg-gray-800">
<Typography.Title level={4}>{content.title}</Typography.Title>
<Typography.Text>
<p className="text-base">{content.text}</p>
<Button
className="mb-8"
type="outline"
size="small"
icon={<IconArrowUpRight />}
>
View documentation
</Button>
</Typography.Text>
<p className="text-base">{content.text}</p>
{content.url && (
<Typography.Text>
<Link href={content.url} as={content.url}>
<a>
<Button
className="mb-8"
type="outline"
size="small"
icon={<IconArrowUpRight />}
>
{content.cta ? content.cta : 'View documentation'}
</Button>
</a>
</Link>
</Typography.Text>
)}
</div>
</SwiperSlide>
)
@@ -8,9 +8,23 @@ import CodeBlock from '../CodeBlock/CodeBlock'
// Import Swiper styles
import 'swiper/swiper.min.css'
import Badge from '../Badge'
import Link from 'next/link'
interface Content {
lang: 'js' | 'py' | 'sql'
title: string
code: string
detail_title?: string
detail_text?: string
badges_label?: string
badges?: string[]
url?: string
}
interface SplitCodeBlockCarousel {
content: any
content: Content[]
footer?: React.ReactNode
altTabView?: boolean
}
function SplitCodeBlockCarousel(props: SplitCodeBlockCarousel) {
@@ -48,7 +62,6 @@ function SplitCodeBlockCarousel(props: SplitCodeBlockCarousel) {
return (
<div className="col-span-12 lg:col-span-6 lg:col-start-7 sbui-tabs--alt">
<Tabs
className="alt-tabs"
scrollable
// @ts-ignore
activeId={apiSwiperActiveIndex.toString()}
@@ -71,13 +84,13 @@ function SplitCodeBlockCarousel(props: SplitCodeBlockCarousel) {
initialSlide={apiSwiperActiveIndex}
spaceBetween={0}
slidesPerView={1}
speed={400}
speed={300}
autoHeight={true}
allowTouchMove={false}
>
{props.content.map((extension, i) => {
return (
<SwiperSlide>
<SwiperSlide key={i}>
<CodeBlock key={i} lang={extension.lang}>
{extension.code}
</CodeBlock>
@@ -96,7 +109,7 @@ function SplitCodeBlockCarousel(props: SplitCodeBlockCarousel) {
slidesPerView={1}
direction="horizontal"
// style={{ overflow: 'hidden' }}
speed={400}
speed={300}
allowTouchMove={false}
>
{props.content.map((extension, i) => {
@@ -112,9 +125,15 @@ function SplitCodeBlockCarousel(props: SplitCodeBlockCarousel) {
<p>{extension.detail_text}</p>
</Typography.Text>
<Space className="justify-between">
<Typography.Text>
<Button type="outline">View documentation</Button>
</Typography.Text>
{extension.url && (
<Link href={extension.url} as={extension.url}>
<a>
<Typography.Text>
<Button type="outline">View documentation</Button>
</Typography.Text>
</a>
</Link>
)}
<div>
<Typography.Text type="secondary" className="mr-4">
{extension.badges_label}
+1 -1
View File
@@ -22,7 +22,7 @@ const CaseStudies = () => {
</div>
<div className="mt-5 max-w-lg mx-auto grid gap-5 lg:grid-cols-3 lg:max-w-none">
{CaseStudiesData.map((caseStudy: any, idx: number) => (
<a href={caseStudy.url}>
<a href={caseStudy.url} key={idx}>
<Card
key={`caseStudy_${idx}`}
hoverable
+2 -1
View File
@@ -30,7 +30,8 @@ function CodeBlock(props: Props) {
SyntaxHighlighter.registerLanguage('py', py)
SyntaxHighlighter.registerLanguage('sql', sql)
const large = props.size === 'large' ? true : false
// const large = props.size === 'large' ? true : false
const large = false
return (
<div className="relative">
+3 -14
View File
@@ -13,12 +13,12 @@ function ExampleCard(props: any) {
return (
<>
<div
className="
bg-white dark:bg-gray-700
className="h-40 bg-white dark:bg-gray-700
border-t border-r border-l border-gray-100 dark:border-gray-600
p-5
flex flex-col justify-between
rounded rounded-b-none"
rounded rounded-b-none
"
>
<div className="mb-4">
<Typography.Title level={5} className="mb-1">
@@ -41,7 +41,6 @@ function ExampleCard(props: any) {
<div>
<div
className="
bg-white dark:bg-gray-800
border-b border-r border-l border-gray-100 dark:border-gray-600
p-5
@@ -82,16 +81,6 @@ function ExampleCard(props: any) {
</a>
</Link>
)}
{/* <Link href={props.demo_url} as={props.demo_url}>
<a target="_blank">
<Button size="tiny" type="text" iconRight={<IconGitHub />}>
Github
</Button>
</a>
</Link> */}
{/* <Button size="tiny" type="default">
Github
</Button> */}
</Space>
</div>
</div>
+6 -10
View File
@@ -1,14 +1,16 @@
import { Button, Badge, Typography } from '@supabase/ui'
import { Button, Badge, Typography, IconArrowRight } from '@supabase/ui'
import SectionHeader from 'components/UI/SectionHeader'
import Solutions from 'data/Solutions.json'
import Link from 'next/link'
import SectionContainer from '../Layouts/SectionContainer'
import ProductIcon from '../ProductIcon'
import TextLink from '../TextLink'
const Features = () => {
const IconSections = Object.values(Solutions).map((solution: any) => {
const { name, description, icon, label, url } = solution
return (
<div key={name} className="mb-16">
<div key={name} className="mb-10 md:mb-0">
<div className="flex items-center">
<ProductIcon icon={icon} />
<dt className="flex flex-row xl:flex-col ml-4">
@@ -25,13 +27,7 @@ const Features = () => {
<Badge dot>{label}</Badge>
</div>
)}
{url && (
<a href={url} className="block mt-3">
<Typography.Link style={{ textDecoration: 'underline' }} className="mt-5">
Learn more
</Typography.Link>
</a>
)}
{url && <TextLink label={label ? 'Get notified' : 'Learn more'} url={url} />}
</div>
)
})
@@ -41,7 +37,7 @@ const Features = () => {
<Typography.Title level={2} className="mb-16">
Build faster and focus on your products
</Typography.Title>
<dl className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 md:gap-16 lg:gap-x-8 xl:gap-x-24">
<dl className="grid grid-cols-1 sm:grid-cols-2 gap-y-4 md:grid-cols-2 lg:grid-cols-4 md:gap-16 lg:gap-x-8 xl:gap-x-24">
{IconSections}
</dl>
</SectionContainer>
@@ -7,7 +7,7 @@ function FloatingIcons() {
const { basePath } = useRouter()
return (
<div className="absolute w-full h-full">
<div className="absolute w-full h-full hidden lg:block">
<div className="container max-w-screen-2xl h-full relative mx-auto">
<img
className={`${Styles['icon-react-one']}`}
+1 -3
View File
@@ -1,9 +1,7 @@
import { useState, useEffect } from 'react'
import FooterLinks from 'data/Footer.json'
import { useRouter } from 'next/router'
import FooterLinks from 'data/Footer.json'
import SectionContainer from '../Layouts/SectionContainer'
import DarkModeToggle from '../DarkModeToggle'
import SupabaseStatus from './SupabaseStatus'
type Props = {
darkMode: boolean
+25 -17
View File
@@ -1,4 +1,5 @@
import { Button, IconBookOpen, Space, Typography } from '@supabase/ui'
import Link from 'next/link'
import { useRouter } from 'next/router'
import SectionContainer from './Layouts/SectionContainer'
@@ -6,7 +7,7 @@ const Hero = () => {
const { basePath } = useRouter()
return (
<div className="">
<div className="overflow-hidden">
<SectionContainer className="pb-0 pt-24">
<div className="relative">
<main className="">
@@ -26,21 +27,28 @@ const Hero = () => {
<p className="mt-3 text-base">Serverless functions coming soon</p>
</Typography.Text>
<Space>
<a href="https://app.supabase.io/api/login">
<Button className="mt-10 py-3" size="medium">
Start your project
</Button>
</a>
<a href="https://app.supabase.io/api/login">
<Button
className="mt-10 py-3"
size="medium"
type="text"
icon={<IconBookOpen />}
>
Documentation
</Button>
</a>
<Link
href="https://app.supabase.io/api/login"
as="https://app.supabase.io/api/login"
>
<a>
<Button className="mt-10 py-3" size="medium">
Start your project
</Button>
</a>
</Link>
<Link href="https://app.supabase.io/docs" as="https://supabase.io/docs">
<a>
<Button
className="mt-10 py-3"
size="medium"
type="text"
icon={<IconBookOpen />}
>
Documentation
</Button>
</a>
</Link>
</Space>
<p className="mt-8 text-sm text-dark-400 tracking-wide sm:mt-10">backed by</p>
<div className="mt-5 w-full sm:max-w-lg lg:ml-0">
@@ -64,7 +72,7 @@ const Hero = () => {
</div>
</div>
</div>
<div className="flex content-center mt-16 sm:mt-24 lg:absolute lg:-right-80 lg:mt-4 lg:w-9/12 lg:mt-0 lg:col-span-6 xl:relative xl:right-0 xl:w-full">
<div className="flex content-center mt-16 sm:mt-24 lg:absolute lg:-right-80 lg:w-9/12 lg:mt-0 lg:col-span-6 xl:relative xl:right-0 xl:w-full">
<div className="flex flex-col items-center justify-center relative w-full rounded-md">
<div className="w-full rounded-t-md h-5 bg-dark-700 flex items-center justify-start px-2">
<div className="h-2 w-2 mr-2 rounded-full bg-dark-500" />
+1 -1
View File
@@ -27,7 +27,7 @@ const DefaultLayout = (props: Props) => {
return (
<>
{!hideHeader && <Nav darkMode={darkMode} updateTheme={updateTheme} />}
{!hideHeader && <Nav darkMode={darkMode} />}
<div className="min-h-screen bg-white dark:bg-gray-800">
<main>{children}</main>
</div>
+3 -1
View File
@@ -4,7 +4,9 @@ interface Props {
}
const SectionContainer = ({ children, className }: Props) => (
<div className={`container mx-auto px-8 sm:px-16 xl:px-20 relative py-32 ${className}`}>
<div
className={`container mx-auto px-6 lg:px-16 xl:px-20 relative py-16 sm:py-18 md:py-24 lg:py-24 ${className}`}
>
{children}
</div>
)
+16 -39
View File
@@ -1,50 +1,32 @@
// import Button from 'components/Button'
// import Badge from 'components/Badge'
import {
Badge,
Button,
IconAlignJustify,
IconArrowUpRight,
IconCode,
IconMail,
IconTerminal,
IconType,
Typography,
} from '@supabase/ui'
import SectionHeader from 'components/UI/SectionHeader'
// import CodeExamples from 'components/MadeForDevelopers/CodeExamples'
import CodeExamples from 'data/home/api-examples'
import DeveloperFeatures from 'data/DeveloperFeatures.json'
import { Badge, Button, IconArrowUpRight, IconMail } from '@supabase/ui'
import APISection from '../Sections/APISection'
import SectionContainer from '../Layouts/SectionContainer'
import FeatureColumn from '../FeatureColumn'
import CodeExamples from 'data/home/api-examples'
import DeveloperFeatures from 'data/DeveloperFeatures.json'
import TextLink from '../TextLink'
const MadeForDevelopers = () => {
return (
<SectionContainer className="pb-0">
<SectionContainer>
<APISection
title={[
<span>
Instant APIs
<br /> that do the hard work for you
</span>,
]}
autoHeight={true}
size="large"
title={"Instant APIs that do the hard work for you"}
text={[
<p className="text-lg">
<p className="lg:text-lg" key={'madefordeveloper-para-1'}>
We introspect your database to provide APIs instantly. Stop building repetitive CRUD
endpoints and focus on your product.
</p>,
]}
// @ts-ignore
content={CodeExamples}
size="small"
footer={[
// <div className="grid grid-cols-12 gap-8 lg:gap-16">
<dl className="grid grid-cols-12 gap-8">
{DeveloperFeatures.map((feature: any, idx: number) => {
<dl className="grid gap-y-4 grid-cols-12 md:gap-8" key={'madefordeveloper-footer'}>
{DeveloperFeatures.map((feature: any, i: number) => {
return (
<div className={'col-span-6 lg:col-span-6'} key={`dev_feature_${idx}`}>
<div className="col-span-12 md:col-span-6 lg:col-span-6" key={i}>
<div className="lg:mt-5">
<dt>
<FeatureColumn title={feature.name} text={feature.description} />
@@ -56,13 +38,9 @@ const MadeForDevelopers = () => {
</div>
)}
{feature.badge ? (
<Button href={feature.url} type="default" icon={<IconMail />}>
{'Get notified'}
</Button>
<TextLink url={feature.url} label="Get notified" />
) : (
<Button href={feature.url} type="default" icon={<IconArrowUpRight />}>
{'Explore more'}
</Button>
<TextLink url={feature.url} label="Explore more" />
)}
</dt>
</div>
@@ -70,7 +48,6 @@ const MadeForDevelopers = () => {
)
})}
</dl>,
// </div>,
]}
/>
</SectionContainer>
+5 -5
View File
@@ -226,7 +226,7 @@ const Nav = (props: Props) => {
Beta
</a>
<a
href="/docs/pricing"
href="/pricing"
className={`
inline-flex items-center px-1 border-b-2 border-transparent text-sm font-medium
text-gray-500 hover:text-gray-700 hover:border-gray-500 p-5
@@ -237,14 +237,14 @@ const Nav = (props: Props) => {
</a>
</div>
</div>
<div className="flex items-center sm:space-x-4">
<div className="hidden lg:flex items-center sm:space-x-4">
<a href="https://app.supabase.io/api/login">
<Button>Start your project</Button>
</a>
<a
href="https://app.supabase.io/api/login"
className={`
inline-flex items-center px-1 border-b-2 border-transparent text-sm font-medium
items-center px-1 border-b-2 border-transparent text-sm font-medium
text-gray-500 hover:text-gray-700 hidden lg:block dark:text-dark-100 dark:hover:text-white
`}
>
@@ -267,7 +267,7 @@ const Nav = (props: Props) => {
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<div className="p-4 md:p-8 h-full h-screen fixed bg-white transform lg:hidden overflow-y-scroll -inset-y-0 z-50 dark:bg-dark-700">
<div className="p-4 md:p-8 h-screen w-screen fixed bg-white transform overflow-y-scroll -inset-y-0 z-50 dark:bg-dark-700">
<div className="absolute right-4 top-4 items-center justify-between">
<div className="-mr-2">
<button
@@ -325,7 +325,7 @@ const Nav = (props: Props) => {
Company
</a>
<a
href="/docs/pricing"
href="/pricing"
className="block pl-3 pr-4 py-2 text-base font-medium text-gray-600 hover:text-gray-800 hover:bg-gray-50 dark:hover:bg-dark-600 hover:border-gray-300 dark:text-white"
>
Pricing
+162
View File
@@ -0,0 +1,162 @@
import React from 'react'
import ReactDOM from 'react-dom'
import { IconHelpCircle, IconWatch, Space, Typography } from '@supabase/ui'
import ReactTooltip from 'react-tooltip'
import ProductIcon from 'components/ProductIcon'
const Chevron = (props: any) => (
<>
<svg
className={`h-5 w-5 text-green-500`}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fillRule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clipRule="evenodd"
/>
</svg>
<span className="sr-only">Included in {props.tier}</span>
</>
)
const Minus = (props: any) => (
<>
<svg
className="h-5 w-5 text-gray-400"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fillRule="evenodd"
d="M3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z"
clipRule="evenodd"
/>
</svg>
<span className="sr-only">Not included in {props.tier}</span>
</>
)
export const PricingTableRowDesktop = (props: any) => {
const category = props.category
return (
<>
<tr className="divide-x dark:divide-gray-600" style={{ borderTop: 'none' }}>
<th
className="pricing-table__product-row bg-gray-50 dark:bg-gray-700 py-3 pl-6 text-sm font-medium text-gray-900 dark:text-white text-left"
scope="colgroup"
>
<Space size={4}>
{props.icon && (
<ProductIcon icon={props.icon}/>
)}
<Typography.Title className="m-0" level={4}>{category.title}</Typography.Title>
</Space>
</th>
<td className="pricing-table__product-row bg-gray-50 dark:bg-dark-700 py-5 px-6"></td>
<td className="pricing-table__product-row bg-gray-50 dark:bg-dark-700 py-5 px-6"></td>
<td className="pricing-table__product-row bg-gray-50 dark:bg-dark-700 py-5 px-6"></td>
</tr>
{category.features.map((feat: any) => {
return (
<tr className="divide-x dark:divide-gray-600">
<th
className="flex items-center py-5 px-6 text-sm font-normal text-gray-500 dark:text-gray-300 text-left"
scope="row"
>
<span>{feat.title} </span>
{feat.tooltip && (
<span
className="ml-2 cursor-pointer hover:text-gray-800 dark:hover:text-white"
data-tip={feat.tooltip}
>
<IconHelpCircle size="small" />
</span>
)}
</th>
{Object.values(feat.tiers).map((tier: any) => {
return (
<td className="py-5 px-6">
{typeof tier === 'boolean' && tier === true ? (
<Chevron tier={tier} />
) : typeof tier === 'boolean' && tier === false ? (
<Minus tier={tier} />
) : (
<span className="block text-sm text-gray-700 dark:text-white">{tier}</span>
)}
</td>
)
})}
</tr>
)
})}
<ReactTooltip effect={'solid'} />
</>
)
}
export const PricingTableRowMobile = (props: any) => {
const category = props.category
const tier = props.tier
return (
<>
<table className="mt-8 w-full">
<caption className="bg-gray-50 dark:bg-dark-900 border-t border-gray-200 dark:border-gray-600 py-3 px-4 text-sm font-medium text-gray-900 dark:text-white text-left">
<Space size={4}>
{category.icon ? (
<ProductIcon icon={props.icon}/>
) : null}
<Typography.Title className="m-0" level={4}>{category.title}</Typography.Title>
</Space>
</caption>
<thead>
<tr>
<th className="sr-only" scope="col">
Feature
</th>
<th className="sr-only" scope="col">
Included
</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200 dark:divide-gray-600 ">
{category.features.map((feat: any) => {
return (
<tr className="border-t border-gray-200 dark:border-gray-600 ">
<th className="py-5 px-4 text-sm font-normal text-gray-500 text-left" scope="row">
<span>
<Typography.Text type="secondary">{feat.title}</Typography.Text>
</span>
</th>
<td className="py-5 pr-4 text-right">
{typeof feat.tiers[tier] === 'boolean' && feat.tiers[tier] === true ? (
<div className="inline-block">
<Chevron tier={tier} />
</div>
) : typeof feat.tiers[tier] === 'boolean' && feat.tiers[tier] === false ? (
<div className="inline-block">
<Minus tier={tier} />
</div>
) : (
<span className="block text-sm text-gray-700 dark:text-white">
{feat.tiers[tier]}
</span>
)}
</td>
</tr>
)
})}
</tbody>
</table>
<ReactTooltip effect={'solid'} />
</>
)
}
+1 -1
View File
@@ -4,7 +4,7 @@ interface ProductIcon {
function ProductIcon({ icon }: ProductIcon) {
return (
<div className="inline-flex items-center justify-center rounded-md bg-gray-800 dark:bg-white text-white sm:h-8 sm:w-8">
<div className="inline-flex items-center justify-center rounded-md bg-gray-800 dark:bg-white text-white h-8 w-8">
<svg
className="h-4 w-4 stroke-white dark:stroke-black"
xmlns="http://www.w3.org/2000/svg"
+24 -15
View File
@@ -3,18 +3,22 @@ import { useState } from 'react'
import { Button, IconArrowUpRight, Tabs, Typography } from '@supabase/ui'
import { Swiper, SwiperSlide } from 'swiper/react'
import CodeBlock from '../CodeBlock/CodeBlock'
import Link from 'next/link'
interface APIExample {
interface Example {
lang: 'js' | 'py' | 'sql'
code: string
title: string
code: string
}
interface Props {
content?: APIExample
size?: 'small' | 'large'
content: Example[]
title: string | React.ReactNode
footer?: React.ReactNode
text?: React.ReactNode
autoHeight?: boolean
documentation_link?: string
}
function APISection(props: Props) {
@@ -33,23 +37,27 @@ function APISection(props: Props) {
<div className="col-span-12 lg:col-span-5 xl:col-span-5 pb-8">
<Typography.Title level={2}>{props.title}</Typography.Title>
{props.text}
<Button size="small" className="mt-4" type="default" icon={<IconArrowUpRight />}>
Expore documentation
</Button>
{props.documentation_link && (
<Link href={props.documentation_link} as={props.documentation_link}>
<a>
<Button size="small" className="mt-4" type="default" icon={<IconArrowUpRight />}>
Expore documentation
</Button>
</a>
</Link>
)}
{props.footer && <div className="py-8">{props.footer}</div>}
</div>
<div className="col-span-12 lg:col-span-7 xl:col-span-6 xl:col-start-7 sbui-tabs--alt">
<Tabs
scrollable
// @ts-ignore
activeId={apiSwiperActiveIndex.toString()}
// @ts-ignore
onChange={(id: string) => handleApiSwiperNavChange(Number(id))}
>
{props.content &&
props.content.map((content, i) => (
<Tabs.Panel label={content.title} id={i.toString()}>
<span></span>
props.content.map((content: Example, i) => (
<Tabs.Panel label={content.title} id={i.toString()} key={i}>
<span key={i}></span>
</Tabs.Panel>
))}
</Tabs>
@@ -61,13 +69,14 @@ function APISection(props: Props) {
initialSlide={apiSwiperActiveIndex}
spaceBetween={0}
slidesPerView={1}
speed={400}
speed={300}
allowTouchMove={false}
autoHeight={props.autoHeight ? props.autoHeight : false}
>
{props.content &&
props.content.map((content, i) => (
<SwiperSlide>
<CodeBlock key={i} lang={content.lang} size={'large'}>
props.content.map((content: Example, i) => (
<SwiperSlide key={i}>
<CodeBlock key={i} lang={content.lang} size={props.size ? props.size : 'small'}>
{content.code}
</CodeBlock>
</SwiperSlide>
+3 -3
View File
@@ -9,7 +9,7 @@ const FeatureSection = (props: any) => {
</p>
<Typography.Title level={4}>Write less code</Typography.Title>
<Typography.Text>
<p className="text-lg">
<p className="lg:text-lg">
Use media in your project without needing to reply on external services or learn new
frameworks.
</p>
@@ -25,7 +25,7 @@ const FeatureSection = (props: any) => {
</p>
<Typography.Title level={4}>Blazing fast</Typography.Title>
<Typography.Text>
<p className="text-lg">
<p className="lg:text-lg">
Use media in your project without needing to reply on external services or learn new
frameworks.
</p>
@@ -41,7 +41,7 @@ const FeatureSection = (props: any) => {
</p>
<Typography.Title level={4}>Dependable</Typography.Title>
<Typography.Text>
<p className="text-lg">
<p className="lg:text-lg">
Use media in your project without needing to reply on external services or learn new
frameworks.
</p>
+99 -11
View File
@@ -1,15 +1,35 @@
import { useRouter } from 'next/router'
import { Swiper, SwiperSlide } from 'swiper/react'
import { useRef } from 'react'
import SectionContainer from '../Layouts/SectionContainer'
import { Typography, IconGitHub } from '@supabase/ui'
import { useRouter } from 'next/router'
import { Swiper, SwiperSlide } from 'swiper/react'
// import Swiper core and required modules
import SwiperCore, { Navigation, Pagination } from 'swiper'
import Link from 'next/link'
import {
Button,
Typography,
IconGitHub,
IconArrowLeft,
IconArrowRight,
Space,
IconBookOpen,
} from '@supabase/ui'
import Examples from '../../data/Examples.json'
import ExampleCard from '../ExampleCard'
// install Swiper modules
SwiperCore.use([Navigation, Pagination])
function GithubExamples() {
// base path for images
const { basePath } = useRouter()
const prevRef = useRef(null)
const nextRef = useRef(null)
return (
<>
<div className="grid grid-cols-12">
@@ -17,14 +37,46 @@ function GithubExamples() {
<Typography.Title level={2}>
Community driven examples, libaries and guides
</Typography.Title>
<Typography.Text>
<p className="text-lg">Type definitions for both server side and client side </p>
<Typography.Text className="block mb-6">
<p className="text-base lg:text-lg">
Supported by a network of early advocates, contributors, and champions.
</p>
</Typography.Text>
<div className="flex space-y-1.5 flex-col lg:space-y-0 lg:space-x-1.5 lg:flex-row justify-center">
<Link
href="https://supabase.io/docs/guides/examples#guides"
as="https://supabase.io/docs/guides/examples#guides"
>
<a>
<Button type="default" icon={<IconBookOpen />}>
View guides
</Button>
</a>
</Link>
<Link
href="https://supabase.io/docs/guides/examples"
as="https://supabase.io/docs/guides/examples"
>
<a>
<Button type="default">View all examples</Button>
</a>
</Link>
<Link
href="https://github.com/supabase/supabase/tree/master/examples"
as="https://github.com/supabase/supabase/tree/master/examples"
>
<a>
<Button type="default" icon={<IconGitHub />}>
Official GitHub library
</Button>
</a>
</Link>
</div>
</div>
</div>
<div className="mt-16">
<div className={'-mr-32 -ml-32'}>
<div className="mt-4">
<div className={'lg:-mr-32 lg:-ml-32'}>
<Swiper
style={{ overflow: 'visible' }}
initialSlide={3}
@@ -33,18 +85,54 @@ function GithubExamples() {
autoplay={{
delay: 2400,
}}
loop={true}
speed={2400}
speed={300}
navigation={{
prevEl: prevRef.current,
nextEl: nextRef.current,
// prevEl: prevRef.current ? prevRef.current : undefined,
// nextEl: nextRef.current ? nextRef.current : undefined,
}}
onInit={(swiper: any) => {
swiper.params.navigation.prevEl = prevRef.current
swiper.params.navigation.nextEl = nextRef.current
swiper.navigation.update()
}}
breakpoints={{
320: {
slidesPerView: 1,
},
720: {
slidesPerView: 2,
},
920: {
slidesPerView: 3,
},
1024: {
slidesPerView: 4,
},
}}
>
{Object.values(Examples).map((example: any, i: number) => {
return (
<SwiperSlide>
<SwiperSlide key={i}>
<div className="my-8 mr-3 ml-3">
<ExampleCard i={i} {...example} />
</div>
</SwiperSlide>
)
})}
<div className="container mx-auto hidden md:flex flex-row justify-between mt-3">
<div ref={prevRef} className="cursor-pointer">
<Typography.Text>
<IconArrowLeft />
</Typography.Text>
</div>
<div ref={nextRef} className="cursor-pointer">
<Typography.Text>
<IconArrowRight />
</Typography.Text>
</div>
</div>
</Swiper>
</div>
</div>
+23 -11
View File
@@ -1,4 +1,5 @@
import { Button, IconBookOpen, IconKey, Space, Typography } from '@supabase/ui'
import Link from 'next/link'
import ProductIcon from '../ProductIcon'
type subheader = string
@@ -9,36 +10,47 @@ interface Types {
title?: string
image?: React.ReactNode
footer?: React.ReactNode
documentation_url?: string
}
const ProductHeader = (props: Types) => (
<div className="container mx-auto px-8 sm:px-16 xl:px-20 relative pt-32 pb-0">
<div className="container mx-auto px-6 sm:px-16 xl:px-20 relative pt-16 lg:pt-32 pb-0">
<div className="grid grid-cols-12">
<div className="col-span-12 lg:col-span-5">
{props.icon || props.title ? (
<div className="flex flex-row mb-4 item-center">
{props.icon && <ProductIcon icon={props.icon} />}
{props.title && (
<Typography.Title level={4} className="ml-3">
<Typography.Title level={4} className="ml-3" key={`product-name-${props.title}`}>
{props.title}
</Typography.Title>
)}
</div>
) : null}
<Typography.Title level={1}>{props.h1}</Typography.Title>
<Typography.Title level={1} key={`h1`}>{props.h1}</Typography.Title>
{props.subheader && (
<Typography.Text>
{props.subheader.map((subheader) => {
return <p className="text-lg">{subheader}</p>
{props.subheader.map((subheader, i) => {
return <p className="lg:text-lg" key={i}>{subheader}</p>
})}
</Typography.Text>
)}
<Space className="mt-12">
<Button size="medium">Start a project</Button>
<Button type="text" size="medium" icon={<IconBookOpen />}>
See documentation
</Button>
</Space>
<div className="mt-12 flex flex-row md:flex-row md:items-center">
<Link href="https://app.supabase.io/api/login" as="https://app.supabase.io/api/login">
<a>
<Button size="medium">Start a project</Button>
</a>
</Link>
{props.documentation_url && (
<Link href={props.documentation_url} as={props.documentation_url}>
<a>
<Button type="text" size="medium" icon={<IconBookOpen />}>
See documentation
</Button>
</a>
</Link>
)}
</div>
{props.footer && <div className="mb-4">{props.footer}</div>}
</div>
{props.image && (
+78 -32
View File
@@ -1,80 +1,126 @@
import { useRef } from 'react'
import { useRouter } from 'next/router'
import { Swiper, SwiperSlide } from 'swiper/react'
// import Swiper core and required modules
import SwiperCore, { Navigation, Pagination } from 'swiper'
import SectionContainer from '../Layouts/SectionContainer'
import {
Button,
Typography,
IconGitHub,
IconSpeaker,
IconMessageCircle,
IconMail,
Space,
IconSlack,
IconAlignLeft,
IconArrowLeft,
IconArrowRight,
} from '@supabase/ui'
import Examples from '../../data/tweets/Tweets.json'
import ExampleCard from '../ExampleCard'
import TweetCard from '../TweetCard'
// Import Swiper styles
import 'swiper/swiper.min.css'
import 'swiper/components/navigation/navigation.min.css'
import 'swiper/components/pagination/pagination.min.css'
import Link from 'next/link'
// install Swiper modules
SwiperCore.use([Navigation, Pagination])
function TwitterSocialProof() {
// base path for images
const { basePath } = useRouter()
const prevRef = useRef(null)
const nextRef = useRef(null)
return (
<>
<div className="grid grid-cols-12">
<div className="col-span-12 text-center">
<Typography.Title level={2}>Join the community</Typography.Title>
<Typography.Text>
<p className="text-lg">
The Supabase team is overwhelmed with the support from early advocates, contributors,
and champions.
<br />
Supabase would not be possible without them.
<p className="lg:text-lg">
Supported by a network of early advocates, contributors, and champions.
</p>
<Space className="justify-center mt-8">
<Button size="small" iconRight={<IconMessageCircle size="tiny" />} type="default">
Github discussion
</Button>
<Button
size="small"
iconRight={<img src={`${basePath}/images/index/slack-mark.svg`} width="16" />}
type="default"
<div className="my-4">
<Link
href={'https://github.com/supabase/supabase/discussions'}
as={'https://github.com/supabase/supabase/discussions'}
>
Request Slack invite
</Button>
</Space>
<a className="block text-sm text-gray-400 dark:text-gray-400 mt-3" target="_blank">
<Button size="small" iconRight={<IconMessageCircle size="tiny" />} type="default">
GitHub discussions
</Button>
</a>
</Link>
</div>
</Typography.Text>
</div>
</div>
<div className="mt-6">
<div className={'-mr-32 -ml-32'}>
<div className={'lg:-mr-32 lg:-ml-32'}>
<Swiper
style={{ overflow: 'visible' }}
initialSlide={3}
spaceBetween={0}
slidesPerView={4}
autoplay={{
delay: 2400,
speed={300}
navigation={{
prevEl: prevRef.current,
nextEl: nextRef.current,
// prevEl: prevRef.current ? prevRef.current : undefined,
// nextEl: nextRef.current ? nextRef.current : undefined,
}}
onInit={(swiper: any) => {
swiper.params.navigation.prevEl = prevRef.current
swiper.params.navigation.nextEl = nextRef.current
// swiper.navigation.update()
}}
breakpoints={{
320: {
slidesPerView: 1,
},
720: {
slidesPerView: 2,
},
920: {
slidesPerView: 3,
},
1024: {
slidesPerView: 4,
},
1208: {
slidesPerView: 5,
},
}}
loop={true}
speed={2400}
>
{Examples.map((tweet: any, i: number) => {
return (
<SwiperSlide>
<div className="mr-3 ml-3 my-8 ">
<SwiperSlide key={i}>
<div className="mr-3 ml-3 cursor-move">
<TweetCard
key={i}
handle={`@${tweet.handle}`}
quote={tweet.text}
img_url={tweet.img_url}
img_url={`${basePath}${tweet.img_url}`}
/>
</div>
</SwiperSlide>
)
})}
<div className="container mx-auto hidden md:flex flex-row justify-between mt-3">
<div ref={prevRef} className="cursor-pointer">
<Typography.Text>
<IconArrowLeft />
</Typography.Text>
</div>
<div ref={nextRef} className="cursor-pointer">
<Typography.Text>
<IconArrowRight />
</Typography.Text>
</div>
</div>
</Swiper>
</div>
</div>
+19
View File
@@ -0,0 +1,19 @@
import { IconArrowRight } from '@supabase/ui'
import Link from 'next/link'
function TextLink({ url, label }: any) {
return (
<Link href={url} as={url}>
<a className="block text-sm text-gray-400 dark:text-gray-400 mt-3">
<div className="flex items-center">
<span>{label}</span>
<span className="ml-2">
<IconArrowRight size="tiny" />
</span>
</div>
</a>
</Link>
)
}
export default TextLink
+1 -1
View File
@@ -10,7 +10,7 @@ function TweetCard(props: TweetCard) {
return (
<div className="bg-white dark:bg-dark-800 rounded-lg border border-gray-100 dark:border-gray-600 p-6">
<div className="relative">
<img className="rounded-md w-12" src={props.img_url} />
<img className="rounded-md w-12 border dark:border-gray-600" src={props.img_url} />
<div
className="absolute -top-2 -left-2 w-6 h-6 flex justify-center items-center rounded-md"
style={{ background: '#00acee' }}
+1 -1
View File
@@ -92,7 +92,7 @@ const FlyOut = (props: Props) => {
<div
className="fixed inset-0 t-63 transition-opacity bg-red"
style={{
zIndex: 10,
zIndex: 100,
top: '63px',
marginLeft: 0,
pointerEvents: 'visiblePainted',
+4 -4
View File
@@ -2,14 +2,14 @@
{
"name": "TypeScript Support",
"description": "Type definitions built directly from your database schema",
"badge": null,
"url": "/docs/client/generating-types"
"badge": "",
"url": "https://supabase.io/docs/client/generating-types"
},
{
"name": "Install from CDN",
"description": "Use Supabase in the browser without a build process",
"badge": null,
"url": "/docs/client/initializing"
"badge": "",
"url": "https://supabase.io/docs/client/initializing"
},
{
"name": "Local emulator",
+1 -1
View File
@@ -20,7 +20,7 @@
},
{
"text": "Pricing",
"url": "/docs/pricing"
"url": "/pricing"
}
]
},
+243
View File
@@ -0,0 +1,243 @@
{
"database": {
"title": "Database",
"icon": "M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4",
"features": [
{
"title": "Full Postgres Database",
"tiers": {
"free": true,
"pro": true,
"enterprise": true
}
},
{
"title": "Unlimited API requests",
"tiers": {
"free": true,
"pro": true,
"enterprise": true
}
},
{
"title": "Realtime functionality",
"tiers": {
"free": true,
"pro": true,
"enterprise": true
}
},
{
"title": "Optimised database instances",
"tooltip": "Why? Unique workload requirements, heavy reads and writes, compliance",
"tiers": {
"free": false,
"pro": false,
"enterprise": true
}
},
{
"title": "Database space",
"tiers": {
"free": "Up to 500 MB",
"pro": "Up to 8 GB",
"enterprise": "0.125 per GB"
}
},
{
"title": "Automatic backups",
"tooltip": "Backups are entire copies of your database that can be restored in the future.",
"tiers": {
"free": false,
"pro": "7 days of backup",
"enterprise": "30 days Point in Time backup"
}
},
{
"title": "Pausing",
"tooltip": "Unused projects that have no activity will be shut down. They can be reactivated by logging into the dashboard",
"tiers": {
"free": "Paused after 1 week inactivity",
"pro": "Never",
"enterprise": "Never"
}
}
]
},
"auth": {
"title": "Auth",
"icon": "M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4",
"features": [
{
"title": "Users",
"tiers": {
"free": "10,000 Users",
"pro": "100,000 Users",
"enterprise": "Unlimited"
}
},
{
"title": "Social OAuth providers",
"tiers": {
"free": true,
"pro": true,
"enterprise": true
}
},
{
"title": "Custom SMTP server",
"tiers": {
"free": false,
"pro": true,
"enterprise": true
}
},
{
"title": "Remove Supabase branding from emails",
"tiers": {
"free": false,
"pro": true,
"enterprise": true
}
},
{
"title": "Enterprise OAuth providers",
"tiers": {
"free": false,
"pro": false,
"enterprise": true
}
},
{
"title": "Audit trails",
"tiers": {
"free": "1 hour",
"pro": "7 days",
"enterprise": true
}
}
]
},
"storage": {
"title": "File storage",
"icon": "M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4",
"features": [
{
"title": "Storage total",
"tiers": {
"free": "Up to 1 GB",
"pro": "Up to 100 GB",
"enterprise": "$0.021 per GB"
}
},
{
"title": "Transfer limits",
"tiers": {
"free": "Up to 2 GB",
"pro": "Up to 200 GB",
"enterprise": "$0.07 per GB"
}
},
{
"title": "Custom access controls",
"tiers": {
"free": true,
"pro": true,
"enterprise": true
}
}
]
},
"dashboard": {
"title": "Dashboard",
"icon": "",
"features": [
{
"title": "Team members",
"tiers": {
"free": "Unlimited",
"pro": "Unlimited",
"enterprise": "Unlimited"
}
},
{
"title": "Access controls",
"tiers": {
"free": false,
"pro": "Coming soon",
"enterprise": true
}
},
{
"title": "Audit trails",
"tiers": {
"free": false,
"pro": false,
"enterprise": true
}
}
]
},
"support": {
"title": "Support and Compliance",
"icon": "",
"features": [
{
"title": "Community support",
"tiers": {
"free": true,
"pro": true,
"enterprise": true
}
},
{
"title": "Email support",
"tiers": {
"free": false,
"pro": true,
"enterprise": true
}
},
{
"title": "Dedicated support",
"tiers": {
"free": false,
"pro": false,
"enterprise": true
}
},
{
"title": "On Premises / BYO cloud",
"tiers": {
"free": false,
"pro": false,
"enterprise": true
}
},
{
"title": "Log retention (API & Database)",
"tiers": {
"free": "1 day",
"pro": "7 days",
"enterprise": "3 months"
}
},
{
"title": "Log drain",
"tiers": {
"free": false,
"pro": false,
"enterprise": "Coming soon"
}
},
{
"title": "SOC2",
"tiers": {
"free": false,
"pro": false,
"enterprise": "Coming soon"
}
}
]
}
}
+122
View File
@@ -0,0 +1,122 @@
[
{
"question": "Is there a cap on the number of projects for the Free Tier?",
"answer": "Yes, on the Free Tier you can have a maximum of two organizations, each with up to two projects. This means that you can have a maximum of four free projects in your account."
},
{
"question": "When will I be billed?",
"answer": "Billing will go live from the 7th of April. Once live, we will give you 2 weeks notice to confirm you are happy to start paying for projects. For subsequent new projects on a paid tier you will be charged immediately."
},
{
"question": "How can I monitor my usage and billing?",
"answer": "You can view your usage and billing in your project settings on the Dashboard. (This will go live in a few days)"
},
{
"question": "Can I cap my usage so my bill doesn't run over?",
"answer": "Not yet, however we do not \"automatically\" upgrade you. This is to prevent surprise bill spikes. We have soft-limits which means that if you do go over we will not automatically bill you. We will reach out first to discuss moving up a tier."
},
{
"question": "What are soft limits? ",
"answer": "Soft limits mean if you cross your usage tier we will let you know and give you a grace period without charging you for exceeding your limits. We will email you to discuss moving up a tier and you will not be charged until you agree to move up a tier. If you do not respond, or do want to move up a tier and continue to exceed your limits then we may have to limit your projects consumption."
},
{
"question": "How do I know which tier I need before I even finish building my project?",
"answer": "Start on the free tier and see what happens. We have soft limits and will reach out to you to let you know if you need to upgrade to a higher tier."
},
{
"question": "I'm worried I could end up with a huge bill at the end of the month.",
"answer": "Your bill will not automatically run over the tier you have selected. We will reach out first to discuss moving up a tier."
},
{
"question": "Is there a way for me to learn about Supabase without being billed?",
"answer": "Yes, you can use the free tier."
},
{
"question": "How do I upgrade or downgrade my tier?",
"answer": "Email us at <support@supabase.io>[](mailto:support@supabase.io)"
},
{
"question": "Will I hit any kind of API limits?",
"answer": "There are no API limits. For Free and Pro tiers we have allocated you a database which you can use as much as you need to within the resources listed for that tier."
},
{
"question": "What happens if I run over my limits for a feature?",
"answer": "We have a soft limits policy so we will reach out to you with a grace period for you to make a decision on your project and upgrading a tier. This means your application will remain live and you will not automatically be billed more."
},
{
"question": "Are you going to change your pricing in the future?",
"answer": "Our pricing is in Beta. You can read more about our decisions in our blog on pricing [here](https://supabase.io/blog/2021/03/29/pricing). Pricing may change in the future, however as a team of developers we are committed to pricing being as developer friendly as possible."
},
{
"question": "I read that you are giving credits to users who sign up during beta, do I get those?",
"answer": "If you sign up before the end of launch week we will issue credits for the Pro tier. You can claim them by getting in touch via <support@supabase.io>[](mailto:support@supabase.io)."
},
{
"question": "What if I want to cancel my monthly subscription?",
"answer": "Simply delete your project within the Dashboard and your subscription will be cancelled."
},
{
"question": "Do I get a notification if I am approaching my usage limits?",
"answer": "Yes, we will email you when you are within 20% of your tier limits."
},
{
"question": "If I hit the resource limits for my tier what happens to my project? Does it shut down?",
"answer": "We have a soft limits policy so if you do run over we will reach out to you with a grace period for you to make a decision on your project and upgrading a tier. This means your project will remain live and you will not automatically be billed more."
},
{
"question": "If I exceed my limits, do I just get charged \"Overage\" for the items I use?",
"answer": "We have soft limits so we will reach out if you exceed resource limits on the Pro Tier. Your project plan will need to move to the Usage based pricing before we charge any usage pricing. We will work with you in advance to understand your project cost forecasts."
},
{
"question": "I need more than the Pro tier, what do I need to do?",
"answer": "Contact us via <support@supabase.io>[](mailto:support@supabase.io) and we will be happy to help you upgrade."
},
{
"question": "I have special database requirements, can I request a larger server?",
"answer": "Contact us via <support@supabase.io>[](mailto:support@supabase.io) and we will be happy to help you upgrade."
},
{
"question": "What happens if I need to transfer my project to someone else in future?",
"answer": "Contact us via <support@supabase.io>[](mailto:support@supabase.io) and we will be happy to help migrate your project to a new / another account."
},
{
"question": "Do you offer a free trial?",
"answer": "We offer a Free Tier, which has no time limit."
},
{
"question": "What kind of support will I receive?",
"answer": "Support depends on your tier. Free Tier: community support. Pro Tier: email support. Pay As You Go: dedicated support."
},
{
"question": "Do you offer discounts or deals for non-profits and educational institutions or students?",
"answer": "We have the free tier which you are welcome to take advantage of. If you need more than the free tier you can reach out to us [here](mailto:support@supabase.io) for our consideration of merit on a case by case basis."
},
{
"question": "Do you cater to enterprise contracts, support, or dedicated infrastructure hosting?",
"answer": "Yes. Contact us via <support@supabase.io>[](mailto:support@supabase.io) and we will be happy to discuss your needs."
},
{
"question": "Where can I find Privacy and Terms of Use information for Supabase?",
"answer": "You can check out our privacy policy here [here](https://supabase.io/docs/company/privacy) and terms of service [here](https://supabase.io/docs/company/terms)."
},
{
"question": "What is the largest sized file I can deploy to storage?",
"answer": "A big cat like a Lion or Siberian tiger, also Ligers are fine. If you are uploading via the dashboard there is a 50MB limit."
},
{
"question": "Are there any limits for data egress?",
"answer": "For Free Tier the limit is 2GB, for Pro Tier it is 200GB. There are no limits on Pay as you Go."
},
{
"question": "What is the largest database I can deploy to the Free Tier? Is there a limit on the number of rows?",
"answer": "You can make a project with a 500 Mb Database and 1 GB File Storage on the Free Tier. You can fit as many rows as possible into the space provided."
},
{
"question": "What if I need one project for development and one for production?",
"answer": "We are working on multi-environment projects. For now, you can create a project for your development backend and production backend. We give you four free projects as part of our free tier. This means you could have two development and two production servers as part of your free tier."
},
{
"question": "Can I self host Supabase for free?",
"answer": "Yes, our Docker setup can be found [here](https://github.com/supabase/supabase), or in our [CLI](https://github.com/supabase/cli). Note that our Dashboard is not available in the Docker set up."
}
]
+4 -4
View File
@@ -5,7 +5,7 @@
"text": "You don’t have to be a database expert to use Supabase. Our table editor makes Postgres easy to use, even for non-techies. You can do everything right in our dashboard.",
"video_url": "videos/tabTableEditor.mp4",
"cta": "Explore Table View",
"url": "/docs/guides/platform"
"url": "/database"
},
{
"label": "SQL Editor",
@@ -13,14 +13,14 @@
"text": "Write, save, and execute SQL queries directly on our dashboard, with templates to save you time. Run common queries and even build applications using our growing list of templates.",
"video_url": "videos/tabSqlEditor.mp4",
"cta": "Explore SQL Editor",
"url": "/docs/guides/platform"
"url": "/database"
},
{
"label": "Auth rules",
"title": "User management as straight-forward as it can be",
"text": "Easily manage your users with Supabase Auth, with email logins, magic links, and third-party logins. Create complex access policies with SQL rules to fit your unique business needs.",
"video_url": "videos/tabAuthRules.mp4",
"cta": "Explore Auth Policies",
"url": "/docs/guides/auth"
"cta": "Explore Auth",
"url": "/auth"
}
]
+6 -6
View File
@@ -1,8 +1,8 @@
export default [
{
lang: 'js',
title: 'Create user',
description: 'Sign up a new user in an example chat room',
lang: 'js',
code: `import { createClient } from '@supabase/supabase-js'
// Initialize
@@ -18,9 +18,9 @@ const { user, error } = await supabase.auth.signUp({
`,
},
{
lang: 'js',
title: 'Realtime subscriptions',
description: 'Receive realtime messages in an example chat room',
lang: 'js',
code: `import { createClient } from '@supabase/supabase-js'
// Initialize
@@ -38,10 +38,10 @@ const realtime = supabase
`,
},
{
lang: 'js',
title: 'Read a record',
description: 'Get all public rooms and their messages',
lang: 'js',
code: `m '@supabase/supabase-js'
code: `import '@supabase/supabase-js'
// Initialize
const supabaseUrl = 'https://chat-room.supabase.co'
@@ -59,9 +59,9 @@ const publicRooms = await supabase
`,
},
{
lang: 'js',
title: 'Create a record',
description: 'Create a new chat room',
lang: 'js',
code: `import { createClient } from '@supabase/supabase-js'
// Initialize
@@ -76,9 +76,9 @@ const newRoom = await supabase
`,
},
{
lang: 'js',
title: 'Update a record',
description: 'Update a user',
lang: 'js',
code: `import { createClient } from '@supabase/supabase-js'
// Initialize
+102
View File
@@ -0,0 +1,102 @@
export default [
{
lang: 'js',
title: 'Sign up',
description: '',
code: `
// Sign up with email
const { user, error } = await supabase.auth.signUp({
email: 'example@email.com',
password: 'example-password',
})
`,
},
{
lang: 'js',
title: 'Sign in',
description: '',
code: `
// Sign in with email
const { user, error } = await supabase.auth.signIn({
email: 'example@email.com',
password: 'example-password',
})
`,
},
{
lang: 'js',
title: 'Magic Links',
description: '',
code: `
// Sign in with magic links
const { user, error } = await supabase.auth.signIn({
email: 'example@email.com'
})
`,
},
{
lang: 'js',
title: 'OAuth logins',
description: '',
code: `
// Sign in with GitHub
// And request extra permissions!
const { user, error } = await supabase.auth.signIn({
provider: 'github',
}, {
scopes: 'repo gist notifications'
})
`,
},
]
@@ -1,81 +1,84 @@
export default [
{
lang: 'sql',
title: 'Allow anyone to view',
detail_title: 'Spatial and Geographic objects for PostgreSQL',
detail_text:
'PostGIS is a spatial database extender for PostgreSQL object-relational database. It adds support for geographic objects allowing location queries to be run in SQL.',
badges_label: 'Extensions used:',
badges: ['PostGIS'],
url: '',
code: `create table profiles (
title: 'Allow read access',
detail_title: 'Public profiles are viewable by everyone',
detail_text: 'Create a policy that allows public access to a table',
badges_label: '',
badges: [],
url: 'https://supabase.io/docs/guides/auth#allow-read-access',
code: `
-- 1. Create table
create table profiles (
id serial primary key,
username text unique,
avatar_url
name text
);
alter table profiles
enable row level security;
-- 2. Enable RLS
alter table profiles enable row level security;
-- 3. Create Policy
create policy "Public profiles are viewable by everyone."
on profiles for select using (
true
);`,
on profiles for select
using ( true );
`.trim(),
},
{
lang: 'sql',
title: 'Users can only update their own profiles',
detail_title: 'Spatial and Geographic objects for PostgreSQL',
title: 'Restrict updates',
detail_title: 'Users can update their own profiles',
detail_text:
'PostGIS is a spatial database extender for PostgreSQL object-relational database. It adds support for geographic objects allowing location queries to be run in SQL.',
badges_label: 'Extensions used:',
badges: ['PostGIS'],
url: '',
code: `create table profiles (
'Create a policy that only allows a user to update rows that match their unique ID',
badges_label: '',
badges: [],
url: 'https://supabase.io/docs/guides/auth#restrict-updates',
code: `
-- 1. Create table
create table profiles (
id serial primary key,
username text unique,
avatar_url
name text
);
alter table profiles
enable row level security;
-- 2. Enable RLS
alter table profiles enable row level security;
-- 3. Create Policy
create policy "Users can update their own profiles."
on profiles for update using (
auth.uid() = id
);`,
on profiles for update
using ( auth.uid() = id );
`.trim(),
},
{
lang: 'sql',
title: 'Detailed joins',
detail_title:
'Advance joins with many to many tables Allow any team member to update the team name',
title: 'Advanced rules',
detail_title: 'Team members can update team details',
detail_text:
'PostGIS is a spatial database extender for PostgreSQL object-relational database. It adds support for geographic objects allowing location queries to be run in SQL.',
badges_label: 'Extensions used:',
badges: ['PostGIS'],
url: '',
code: `create table teams (
'Create a policy that allows for team members to update only rows which match their team ID.',
badges_label: '',
badges: [],
url: 'https://supabase.io/docs/guides/auth#policies-with-joins',
code: `
create table teams (
id serial primary key,
name
name text
);
-- Many to many joins
create table members (
team_id references team.id,
user_id referenced auth.users.id
);
alter table teams
enable row level security;
alter table teams enable row level security;
-- Create Advanced Policies
create policy "Team members can update team details"
on teams
for update using (
auth.uid() in (
select user_id
from memebers
where team_id = id
)
);`,
on teams
for update using (
auth.uid() in (
select user_id from members
where team_id = id
)
);
`.trim(),
},
]
+30 -16
View File
@@ -1,11 +1,15 @@
/*
* description
*/
export default [
{
lang: 'js',
title: 'Create a record',
code: `//
// Create a record in a table
// Insert new record into a table called \`rooms\`
size: 'large',
code: `// Create a record
// Insert new record into a table called \`rooms\`
const { data, error } = await supabase
.from('rooms')
.insert({
@@ -13,8 +17,10 @@ const { data, error } = await supabase
public: true
})
// \`data\` returns data
// \`error\` for handling errors
@@ -26,19 +32,27 @@ const { data, error } = await supabase
{
lang: 'js',
title: 'Read a record',
code: `//
// reading a record from a table
// with column \`public\` equals true
size: 'large',
code: `// Read a record
// Retrieve all of the \`rooms\`,
// and get all the messages for each room.
const { data, error } = await supabase
.from('rooms').select(\`
name,
messages ( text )
\`)
.eq('public', true)
.from('rooms').select(\`
name,
messages ( text )
\`)
.eq('public', true)
// \`data\` returns data
// \`error\` for handling errors
`,
},
]
@@ -1,32 +1,30 @@
[
{
"title": "Fast column navigation",
"label": "Fast column navigation",
"img_url": "/images/product/database/table-view/test.png",
"text": "Write, save, and execute SQL queries directly on our"
"title": "Full SQL",
"label": "SQL Editor built in",
"img_url": "/images/product/database/sql-view/sql-editor.png",
"text": "Create tables, functions, join tables - A full SQL editor built into the dashboard",
"url": ""
},
{
"title": "Fast column navigation",
"label": "Fast column navigation",
"img_url": "/images/product/database/table-view/test.png",
"text": "Write, save, and execute SQL queries directly on our"
"title": "Monaco editor",
"label": "Monaco editor",
"img_url": "/images/product/database/sql-view/manaco-editor.png",
"text": "Built in manaco editor, with rich validation and autocomplete.",
"url": ""
},
{
"title": "Fast column navigation",
"label": "Fast column navigation",
"img_url": "/images/product/database/table-view/test.png",
"text": "Write, save, and execute SQL queries directly on our"
"title": "Favourite queries",
"label": "Favourite queries",
"img_url": "/images/product/database/sql-view/favourites.png",
"text": "Save your favourite queries to use again and again.",
"url": ""
},
{
"title": "Fast column navigation",
"label": "Fast column navigation",
"img_url": "/images/product/database/table-view/test.png",
"text": "Write, save, and execute SQL queries directly on our"
},
{
"title": "Fast column navigation",
"label": "Fast column navigation",
"img_url": "/images/product/database/table-view/test.png",
"text": "Write, save, and execute SQL queries directly on our"
"title": "Export to CSV",
"label": "Select and Export",
"img_url": "/images/product/database/sql-view/export.png",
"text": "Any output can be exported into a CSV",
"url": ""
}
]
@@ -1,32 +1,30 @@
[
{
"title": "Fast column navigation",
"label": "Fast column navigation",
"img_url": "/images/product/database/table-view/test.png",
"text": "Write, save, and execute SQL queries directly on our"
"title": "The simplicity of a spreadsheet",
"label": "Spreadsheet like editing",
"img_url": "/images/product/database/table-view/spreadsheet-interface.png",
"text": "Add, edit, and update your data with the simplicity of a no-code tool.",
"url": ""
},
{
"title": "Fast column navigation",
"label": "Fast column navigation",
"img_url": "/images/product/database/table-view/test.png",
"text": "Write, save, and execute SQL queries directly on our"
"title": "Create tables",
"label": "Create tables",
"img_url": "/images/product/database/table-view/create-table.png",
"text": "Add tables, columns and rows right in the dashboard. Without a single line SQL.",
"url": ""
},
{
"title": "Fast column navigation",
"label": "Fast column navigation",
"img_url": "/images/product/database/table-view/test.png",
"text": "Write, save, and execute SQL queries directly on our"
"title": "Set up foreign keys",
"label": "Set up foreign keys",
"img_url": "/images/product/database/table-view/foreign-keys.png",
"text": "Build connections throughout your data with the full power of a Relational Database.",
"url": ""
},
{
"title": "Fast column navigation",
"label": "Fast column navigation",
"img_url": "/images/product/database/table-view/test.png",
"text": "Write, save, and execute SQL queries directly on our"
},
{
"title": "Fast column navigation",
"label": "Fast column navigation",
"img_url": "/images/product/database/table-view/test.png",
"text": "Write, save, and execute SQL queries directly on our"
"title": "Select and Export",
"label": "Select and Export",
"img_url": "/images/product/database/table-view/export.png",
"text": "Pick the rows you want and export them into a CSV. ",
"url": ""
}
]
+43 -61
View File
@@ -3,276 +3,258 @@
"text": "Been playing with @supabase_io and so far I'm liking it. I ❤️ you get full access to the database. It's very quick to get up and running, seems a nice fit for prototypes or smaller apps",
"url": "https://twitter.com/mattegreer/status/1341765395133415425",
"handle": "mattegreer",
"img_url": "https://pbs.twimg.com/profile_images/1362197960663080960/IgXniSVk_400x400.jpg"
"img_url": "/images/twitter-profiles/IgXniSVk_400x400.jpg"
},
{
"text": "Reply enjoying building on @supabase_io for a project with @lasnindrek - allows incredible development speed when bashing together an MVP",
"url": "https://twitter.com/geordidearns/status/1344433863804788737",
"handle": "geordidearns",
"img_url": "https://pbs.twimg.com/profile_images/1346222912714518535/vppimY3M_400x400.jpg"
},
{
"text": "We started migrating @primo_af from @Firebase to @supabase_io for better auth/data management, but stumbled on a missing critical feature. 2 weeks later our PR got merged and now everyone has it. Open source is amazing.",
"url": "https://twitter.com/_mateomorris/status/1369661317729124353",
"handle": "_mateomorris",
"img_url": "https://pbs.twimg.com/profile_images/1342201402727215104/1FyEWRE8_400x400.jpg"
"img_url": "/images/twitter-profiles/vppimY3M_400x400.jpg"
},
{
"text": "OKAY OKAY FINE I'LL LOOK AT SUPABASE PROPERLY",
"url": "https://twitter.com/benbarbersmith/status/1369758528764776453",
"handle": "benbarbersmith",
"img_url": "https://pbs.twimg.com/profile_images/1332690145099862016/Jqle_tE6_400x400.jpg"
},
{
"text": "We started migrating @primo_af from @Firebase to @supabase_io for better auth/data management, but stumbled on a missing critical feature. 2 weeks later our PR got merged and now everyone has it. Open source is amazing.",
"url": "https://twitter.com/_mateomorris/status/1369661317729124353",
"handle": "_mateomorris",
"img_url": "https://pbs.twimg.com/profile_images/1342201402727215104/1FyEWRE8_400x400.jpg"
"img_url": "/images/twitter-profiles/Jqle_tE6_400x400.jpg"
},
{
"text": "Been trying out this new stack. Loving it so far. Next.js (Frontend) + Stitches.js (Styling) + Radix (Primitives & Icons) + Supabase (Backend) + Vercel (Deployment) 😍 Thank you @vercel @Modulz @supabase_io for this amazing DX.",
"url": "https://twitter.com/VaibhavAcharya_/status/1369499616794660864",
"handle": "VaibhavAcharya_",
"img_url": "https://pbs.twimg.com/profile_images/1238375573388275713/mZAIoZM1_400x400.jpg"
"img_url": "/images/twitter-profiles/mZAIoZM1_400x400.jpg"
},
{
"text": "DB/Backend: @supabase_io Reason: Takes care of Auth, DB hosting, API. Open source, easy to eject into something else. I love SQL. Cloud IDE is incredible for productivity.",
"url": "https://twitter.com/JessetheSibley/status/1369678185508593673",
"handle": "JessetheSibley",
"img_url": "https://pbs.twimg.com/profile_images/1355558918177624074/aNx9O0lH_400x400.jpg"
"img_url": "/images/twitter-profiles/aNx9O0lH_400x400.jpg"
},
{
"text": "Looking for a Firebase alternative? Consider using the open-source @supabase_io. It's as simple as Firebase but I found the dashboard UX more intuitive and hey it's OSS! It's in beta so you can use it for free rn 🤌",
"url": "https://twitter.com/mneelansh15/status/1368171468153118721",
"handle": "mneelansh15",
"img_url": "https://pbs.twimg.com/profile_images/1329674768430206979/6XKH3Rm6_400x400.jpg"
"img_url": "/images/twitter-profiles/6XKH3Rm6_400x400.jpg"
},
{
"text": "big props to the @supabase_io team - open source is a two way street and they’re super responsive & community oriented",
"url": "https://twitter.com/_mateomorris/status/1369661833557196800",
"handle": "_mateomorris",
"img_url": ""
"img_url": "/images/twitter-profiles/1FyEWRE8_400x400.jpg"
},
{
"text": "Been actively working on Quill. It's been rapid, thanks to @supabase_io and @tailwindui",
"url": "https://twitter.com/quill_web/status/1369061125157961734",
"handle": "quill_web",
"img_url": "https://pbs.twimg.com/profile_images/1366133044185333762/F_8WdZbZ_400x400.jpg"
"img_url": "/images/twitter-profiles/F_8WdZbZ_400x400.jpg"
},
{
"text": "@supabase_io not only is it Firebase alternative, but it has SQL and it uses #elixirlang for handling realtime Smiling face with heart-shaped eyesSmiling face with heart-shaped eyesSmiling face with heart-shaped eyes @josevalim did you know that? That's power of your creation Smiling face with sunglasses",
"url": "https://twitter.com/MartinOrszulik/status/1369252918570983424",
"handle": "MartinOrszulik",
"img_url": "https://pbs.twimg.com/profile_images/1296536233208930305/ext7ggBo_400x400.jpg"
"img_url": "/images/twitter-profiles/ext7ggBo_400x400.jpg"
},
{
"text": "Using @supabase_io with SWR by @vercel is something else Fire",
"url": "https://twitter.com/eunjae_lee/status/1369788285317419009",
"handle": "eunjae_lee",
"img_url": "https://pbs.twimg.com/profile_images/1188191474401320965/eGjSYbQd_400x400.jpg"
"img_url": "/images/twitter-profiles/eGjSYbQd_400x400.jpg"
},
{
"text": "Another month, another impressive @supabase_io release. It’s rare to see a startup ship in public so consistently.",
"url": "https://twitter.com/yuris/status/1366770633401491456",
"handle": "yuris",
"img_url": "https://pbs.twimg.com/profile_images/798707219520835585/2ilFPL2Z_400x400.jpg"
"img_url": "/images/twitter-profiles/2ilFPL2Z_400x400.jpg"
},
{
"text": "There are a lot of indie hackers building in public, but it’s rare to see a startup shipping as consistently and transparently as Supabase. Their upcoming March releases look to be 🔥 Def worth a follow! also opened my eyes as to how to value add in open source.",
"url": "https://twitter.com/swyx/status/1366685025047994373",
"handle": "swyx",
"img_url": "https://pbs.twimg.com/profile_images/1360406256842797056/qhvO9V6x_400x400.jpg"
"img_url": "/images/twitter-profiles/qhvO9V6x_400x400.jpg"
},
{
"text": "@supabase_io is amazing, super easy to get up and running quickly + they have multiple examples with various frameworks (chat app, auth, etc)",
"url": "https://twitter.com/ssw1ss/status/1366515168260337671",
"handle": "ssw1ss",
"img_url": "https://pbs.twimg.com/profile_images/1258050979901014019/4mD0C5Wk_400x400.jpg"
"img_url": "/images/twitter-profiles/4mD0C5Wk_400x400.jpg"
},
{
"text": "This weekend I made a personal record 🥇 on the less time spent creating an application with social login / permissions, database, cdn, infinite scaling, git push to deploy and for free. Thanks to @supabase_io and @vercel",
"url": "https://twitter.com/jperelli/status/1366195769657720834",
"handle": "jperelli",
"img_url": "https://pbs.twimg.com/profile_images/1373967833495973889/YH_VX6we_400x400.jpg"
"img_url": "/images/twitter-profiles/_ki30kYo_400x400.jpg"
},
{
"text": "I'm excited to know that there's a Firebase alternative OSS project, @supabase_io. It's not a mere copy, you can use Postgre. I like Firebase but always miss the power of SQL.",
"url": "https://twitter.com/kouariga/status/1366192045648347138",
"handle": "kouariga",
"img_url": "https://pbs.twimg.com/profile_images/1081935821224128517/IhXPN_CQ_400x400.jpg"
"img_url": "/images/twitter-profiles/IhXPN_CQ_400x400.jpg"
},
{
"text": "That’s the best part of @supabase_io! No vendor lock 😎",
"url": "https://twitter.com/jim_bisenius/status/1366094388816928770",
"handle": "jim_bisenius",
"img_url": "https://pbs.twimg.com/profile_images/1372987165869760513/rLgwUZSB_400x400.jpg"
"img_url": "/images/twitter-profiles/rLgwUZSB_400x400.jpg"
},
{
"text": "Just span up a @supabase_io app for my new side project. Honestly thinking the tagline \"Firebase Alternative\" should really be \"A Far Superior Firebase Alternative\". Love writing SQL in the cloud! Big love ❤️",
"url": "https://twitter.com/JessetheSibley/status/1365634006608457729",
"handle": "JessetheSibley",
"img_url": "https://pbs.twimg.com/profile_images/1355558918177624074/aNx9O0lH_400x400.jpg"
"img_url": "/images/twitter-profiles/aNx9O0lH_400x400.jpg"
},
{
"text": "Lack of count in firebase/firestore is such a nightmare and workaround solutions are crappy. I experienced it while implementing http://HelloEle.com good job Supabase 👍",
"url": "https://twitter.com/prakis/status/1365201855849009155",
"handle": "prakis",
"img_url": "https://pbs.twimg.com/profile_images/1313453251509932032/eRvqp-t0_400x400.jpg"
"img_url": "/images/twitter-profiles/eRvqp-t0_400x400.jpg"
},
{
"text": "Me using @supabase_io for the first time right now 🤯",
"url": "https://twitter.com/nasiscoe/status/1365140856035024902",
"handle": "nasiscoe",
"img_url": "https://pbs.twimg.com/profile_images/1332049061621485568/nc2Ms5hH_400x400.jpg"
"img_url": "/images/twitter-profiles/nc2Ms5hH_400x400.jpg"
},
{
"text": "I'm trying @supabase_io, Firebase alternative that uses PostgreSQL (and you can use GraphQL too) in the cloud. It's incredible Smiling face with heart-shaped eyes",
"url": "https://twitter.com/JP__Gallegos/status/1365699468109242374",
"handle": "JP__Gallegos",
"img_url": "https://pbs.twimg.com/profile_images/1304486009888018435/1PH2mt6v_400x400.jpg"
"img_url": "/images/twitter-profiles/1PH2mt6v_400x400.jpg"
},
{
"text": "Check out this amazing product @supabase_io. A must give try #newidea #opportunity",
"url": "https://twitter.com/digitaldaswani/status/1364447219642814464",
"handle": "digitaldaswani",
"img_url": "https://pbs.twimg.com/profile_images/1354865630738935814/w8HLdlC7_400x400.jpg"
"img_url": "/images/twitter-profiles/w8HLdlC7_400x400.jpg"
},
{
"text": "I gave @supabase_io a try this weekend and I was able to create a quick dashboard to visualize the data from the PostgreSQL instance. It's super easy to use Supabase's API or the direct DB connection. Check out the tutorial 📖",
"url": "https://twitter.com/razvanilin/status/1363770020581412867",
"handle": "razvanilin",
"img_url": "https://pbs.twimg.com/profile_images/1343810205184770049/AiaH9vJ2_400x400.jpg"
"img_url": "/images/twitter-profiles/AiaH9vJ2_400x400.jpg"
},
{
"text": "Tried @supabase_io for the first time yesterday. Amazing tool! I was able to get my Posgres DB up in no time and their documentation on operating on the DB is super easy! 👏 Can't wait for Cloud functions to arrive! It's gonna be a great Firebase alternative!",
"url": "https://twitter.com/chinchang457/status/1363347740793524227",
"handle": "chinchang457",
"img_url": "https://pbs.twimg.com/profile_images/1370236201081434113/LTw5OCnv_400x400.jpg"
"img_url": "/images/twitter-profiles/LTw5OCnv_400x400.jpg"
},
{
"text": "10/100 All day i was migrating my project from firebase to @supabase_io Because it is perfect and simple!!! I like design and API for understandable. There are in BETA now. Just try!🧪",
"url": "https://twitter.com/roomahhka/status/1363155396391763971",
"handle": "roomahhka",
"img_url": "https://pbs.twimg.com/profile_images/1358741973239234560/e_2eQt6C_400x400.jpg"
"img_url": "/images/twitter-profiles/e_2eQt6C_400x400.jpg"
},
{
"text": "I gave @supabase_io a try today and I was positively impressed! Very quick setup to get a working remote database with API access and documentation generated automatically for you 👌 10/10 will play more",
"url": "https://twitter.com/razvanilin/status/1363002398738800640",
"handle": "razvanilin",
"img_url": "https://pbs.twimg.com/profile_images/1343810205184770049/AiaH9vJ2_400x400.jpg"
"img_url": "/images/twitter-profiles/AiaH9vJ2_400x400.jpg"
},
{
"text": "Wait. Is it so easy to write queries for @supabase_io ? It's like simple SQL stuff!",
"url": "https://twitter.com/T0ny_Boy/status/1362911838908911617",
"handle": "T0ny_Boy",
"img_url": "https://pbs.twimg.com/profile_images/1368251332948279301/UCBhUBZl_400x400.jpg"
"img_url": "/images/twitter-profiles/UCBhUBZl_400x400.jpg"
},
{
"text": "Jeez, and @supabase_io have native support for magic link login?! I was going to use http://magic.link for this But if I can get my whole DB + auth + magic link support in one... Awesome",
"url": "https://twitter.com/louisbarclay/status/1362016666868154371",
"handle": "louisbarclay",
"img_url": "https://pbs.twimg.com/profile_images/1334856185888067588/6f1O8ZOW_400x400.jpg"
"img_url": "/images/twitter-profiles/6f1O8ZOW_400x400.jpg"
},
{
"text": "@MongoDB or @MySQL?!?! Please, let me introduce you to @supabase_io and the wonderful world of @PostgreSQL before it's too late!!",
"url": "https://twitter.com/jim_bisenius/status/1361772978841788416",
"handle": "jim_bisenius",
"img_url": "https://pbs.twimg.com/profile_images/1372987165869760513/rLgwUZSB_400x400.jpg"
"img_url": "/images/twitter-profiles/rLgwUZSB_400x400.jpg"
},
{
"text": "Where has @supabase_io been all my life? 😍",
"url": "https://twitter.com/Elsolo244/status/1360257201911320579",
"handle": "Elsolo244",
"img_url": "https://pbs.twimg.com/profile_images/2486328968/v6citnk33y2wpeyzrq05_400x400.jpeg"
"img_url": "/images/twitter-profiles/v6citnk33y2wpeyzrq05_400x400.jpeg"
},
{
"text": "Honestly Supabase is such a killer Firebase alternative.",
"url": "https://twitter.com/XPCheese/status/1360229397735895043",
"handle": "XPCheese",
"img_url": "https://pbs.twimg.com/profile_images/1362369605453418497/eYP6YXr7_400x400.jpg"
"img_url": "/images/twitter-profiles/eYP6YXr7_400x400.jpg"
},
{
"text": "I think you'll love @supabase_io :-) Open-source, PostgreSQL-based & zero magic.",
"url": "https://twitter.com/zippoxer/status/1360021315852328961",
"handle": "zippoxer",
"img_url": "https://pbs.twimg.com/profile_images/1295778726261727252/6rd3xub9_400x400.png"
"img_url": "/images/twitter-profiles/6rd3xub9_400x400.png"
},
{
"text": "@supabase_io is the answer to all of firebase’s problems imo",
"url": "https://twitter.com/jim_bisenius/status/1358590362953142278",
"handle": "jim_bisenius",
"img_url": "https://pbs.twimg.com/profile_images/1372987165869760513/rLgwUZSB_400x400.jpg"
"img_url": "/images/twitter-profiles/rLgwUZSB_400x400.jpg"
},
{
"text": "@supabase_io is insane.",
"url": "https://twitter.com/codewithbhargav/status/1357647840911126528",
"handle": "codewithbhargav",
"img_url": "https://pbs.twimg.com/profile_images/1352128808283848704/LQYfHXBp_400x400.jpg"
"img_url": "/images/twitter-profiles/LQYfHXBp_400x400.jpg"
},
{
"text": "It’s fun, feels lightweight, and really quick to spin up user auth and a few tables. Almost too easy! Highly recommend.",
"url": "https://twitter.com/nerdburn/status/1356857261495214085",
"handle": "nerdburn",
"img_url": "https://pbs.twimg.com/profile_images/1138638265337040896/66VSV9Mm_400x400.png"
"img_url": "/images/twitter-profiles/66VSV9Mm_400x400.png"
},
{
"text": "I’m probably the wrong person to ask because I pick tools based on UX. Supabase was immediately approachable: instant setup, fast web app, auth, and easy APIs. Same reason I liked Firebase when I first discovered. Fauna didn’t do that for me and I unfortunately lost interest.",
"url": "https://twitter.com/jasonbarone/status/1357015483619422210",
"handle": "jasonbarone",
"img_url": "https://pbs.twimg.com/profile_images/1242700943755489280/6zCnwpvi_400x400.jpg"
"img_url": "/images/twitter-profiles/6zCnwpvi_400x400.jpg"
},
{
"text": "Now things are starting to get interesting! Firebase has long been the obvious choice for many #flutter devs for the ease of use. But their databases are NoSQL, which has its downsides... Seems like @supabase_io is working on something interesting here!",
"url": "https://twitter.com/RobertBrunhage/status/1356973695865085953",
"handle": "RobertBrunhage",
"img_url": "https://pbs.twimg.com/profile_images/1364915715350888449/5LMWEACf_400x400.jpg"
"img_url": "/images/twitter-profiles/5LMWEACf_400x400.jpg"
},
{
"text": "Honestly, I really love what @supabase_io is doing, you don't need to own a complete backend, just write your logic within your app and you'll get a powerful Postgresql at your disposal.",
"url": "https://twitter.com/NavicsteinR/status/1356927229217959941",
"handle": "NavicsteinR",
"img_url": "https://pbs.twimg.com/profile_images/1342254554025517058/w_zNZAs7_400x400.jpg"
"img_url": "/images/twitter-profiles/w_zNZAs7_400x400.jpg"
},
{
"text": "Next.js, @supabase_io, @stripe, and @vercel. Supastack™",
"url": "https://twitter.com/jasonbarone/status/1356765411832922115",
"handle": "jasonbarone",
"img_url": "https://pbs.twimg.com/profile_images/1242700943755489280/6zCnwpvi_400x400.jpg"
},
{
"text": "> So they just help me use @PostgreSQL better. They don't try to invent a wheel and trap me in it. Whereas they provide a good abstraction overall, they also provide a raw access to the database.",
"url": "https://twitter.com/supabase_io/status/1370652139152797698",
"handle": "supabase_io",
"img_url": "https://pbs.twimg.com/profile_images/1265930492752261120/Z_q3YLKR_400x400.jpg"
"img_url": "/images/twitter-profiles/6zCnwpvi_400x400.jpg"
},
{
"text": "I've really enjoyed the DX! Extremely fun to use, which is odd to say about a database at least for me.",
"url": "https://twitter.com/Soham_Asmi/status/1373086068132745217",
"handle": "Soham_Asmi",
"img_url": "https://pbs.twimg.com/profile_images/1371671270581010435/Os4nhKIr_400x400.jpg"
"img_url": "/images/twitter-profiles/Os4nhKIr_400x400.jpg"
},
{
"text": "Supabase team is doing some awesome stuff Heart suit #supabase #facts @supabase_io",
"url": "https://twitter.com/_strawbird/status/1372607500499841025",
"handle": "_strawbird",
"img_url": "https://pbs.twimg.com/profile_images/1359293071301644290/iMBvvQdn_400x400.jpg"
"img_url": "/images/twitter-profiles/iMBvvQdn_400x400.jpg"
},
{
"text": "Did a website with @supabase_io last week with no prior experience with it. Up and running in 20 minutes. It's awesome to use. Thumbs up",
"url": "https://twitter.com/michael_webdev/status/1352885366928404481?s=20",
"handle": "michael_webdev",
"img_url": "https://pbs.twimg.com/profile_images/1285187864083550208/SvAyLaWV_400x400.jpg"
"img_url": "/images/twitter-profiles/SvAyLaWV_400x400.jpg"
},
{
"text": "Next.js, @supabase_io, @stripe, and @vercel. Supastack™",
"url": "https://twitter.com/jasonbarone/status/1356765411832922115?s=20",
"handle": "jasonbarone",
"img_url": "https://pbs.twimg.com/profile_images/1242700943755489280/6zCnwpvi_400x400.jpg"
"img_url": "/images/twitter-profiles/6zCnwpvi_400x400.jpg"
},
{
"text": "I just learned about @supabase_io and im in love Smiling face with heart-shaped eyes Supabase is an open source Firebase alternative! EarListen (& react) to database changes Person tipping handManage users & permissions WrenchSimple UI for database interaction",
"url": "https://twitter.com/0xBanana/status/1373677301905362948",
"handle": "0xBanana",
"img_url": "https://pbs.twimg.com/profile_images/1345829348939591683/pgHIGqZ0_400x400.jpg"
"img_url": "/images/twitter-profiles/pgHIGqZ0_400x400.jpg"
}
]
+7
View File
@@ -6,6 +6,13 @@
"authorURL": "https://github.com/roryw10",
"position": "Head of Growth"
},
"ant_wilson": {
"author": "Ant Wilson",
"author_url": "https://github.com/awalias",
"author_image_url": "https://github.com/awalias.png",
"authorURL": "https://github.com/awalias",
"position": "CTO and Co-Founder"
},
"paul_copplestone": {
"author": "Paul Copplestone",
"author_url": "https://github.com/kiwicopple",
+19 -3
View File
@@ -1721,9 +1721,9 @@
}
},
"@supabase/ui": {
"version": "0.21.4",
"resolved": "https://registry.npmjs.org/@supabase/ui/-/ui-0.21.4.tgz",
"integrity": "sha512-NbsPBWiTCWHjzJpQKNOrt+hR0t4bOiw3b9Gb0mCt6UJ0YuOV7kemEo29Ufz3PVkw54AdJz87KxLQ6V6+rUpdZg==",
"version": "0.22.0",
"resolved": "https://registry.npmjs.org/@supabase/ui/-/ui-0.22.0.tgz",
"integrity": "sha512-lgsmDJkPduq1KVxFgbWz90Fu+OO3I8a1zM5QO5SKP8JYaiwzwaqGmqpEig+O5ZjzbaObOZtoiHmPTHHcvaEpGw==",
"requires": {
"lodash": "^4.17.20",
"prop-types": "^15.7.2",
@@ -6146,6 +6146,22 @@
"refractor": "^3.2.0"
}
},
"react-tooltip": {
"version": "4.2.17",
"resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-4.2.17.tgz",
"integrity": "sha512-LzwUbQYzeRyrjbuuCbYUB0dlJpFPGPwigWS052umr1QulcF5N4czabDiWJ+Y585Q7ijvMFuBbeOvnI/azoTusw==",
"requires": {
"prop-types": "^15.7.2",
"uuid": "^7.0.3"
},
"dependencies": {
"uuid": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz",
"integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg=="
}
}
},
"react-transition-group": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.1.tgz",
+1
View File
@@ -30,6 +30,7 @@
"react-ga": "^3.3.0",
"react-markdown": "^5.0.3",
"react-syntax-highlighter": "^15.3.1",
"react-tooltip": "^4.2.17",
"react-transition-group": "^4.4.1",
"rehype-slug": "^4.0.1",
"remark": "^13.0.0",
+251 -278
View File
@@ -32,12 +32,15 @@ import GithubExamples from '~/components/Sections/GithubExamples'
import FeatureColumn from '~/components/FeatureColumn'
import FloatingIcons from '~/components/FloatingIcons'
import ApiExamples from 'data/products/database/api-examples'
import ApiExamples from 'data/products/auth/auth-api-examples'
import AuthSqlRulesExamples from 'data/products/auth/auth-sql-rules-examples'
import CTABanner from '~/components/CTABanner'
import Solutions from 'data/Solutions.json'
import AuthComponentExample from '~/components/AuthWidget/AuthComponentExample'
import Link from 'next/link'
import { NextSeo } from 'next-seo'
function AuthPage() {
// base path for images
@@ -49,298 +52,268 @@ function AuthPage() {
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlhdCI6MTYxNTIxNDE1MywiZXhwIjoxOTMwNzkwMTUzfQ.OQEbAaTfgDdLCCht251P2JRD3QDnui6nsU8N-tZA_Mc'
)
const meta_title = 'Auth | Built-in user mangement'
const meta_description =
'Authentication that you can afford that is built in to your supabase project.'
return (
<DefaultLayout>
<ProductHeader
icon={Solutions['authentication'].icon}
title={Solutions['authentication'].name}
h1={[
<span>
Built-in user mangement
<br /> (that you can afford)
</span>,
]}
subheader={[
'An open source scalable object store, capable of holding any file and file size you like.',
'With custom policies and permissions that are familair, easy to implement and unlimited scalable storage.',
]}
image={[
<img
className="w-full header--light block"
src={`${basePath}/images/product/auth/header--light.png`}
/>,
<img
className="w-full header--dark mr-0 dark:block"
src={`${basePath}/images/product/auth/header--dark.png`}
/>,
]}
<>
<NextSeo
title={meta_title}
description={meta_description}
openGraph={{
title: meta_title,
description: meta_description,
url: `${basePath}/auth`,
images: [
{
url: `https://supabase.io/${basePath}/images/product/auth/auth-og.jpg`,
},
],
}}
/>
<SectionContainer>
<div className="grid grid-cols-12">
<div className="col-span-3">
<p className="mb-4">
<Space>
<img src={`${basePath}/images/product/auth/google-icon.svg`} width={21} />
<img src={`${basePath}/images/product/auth/facebook-icon.svg`} width={21} />
<div>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 32.58 31.77"
width={21}
className="text-gray-800 dark:text-white"
>
<path
fill="currentColor"
d="M16.29,0a16.29,16.29,0,0,0-5.15,31.75c.82.15,1.11-.36,1.11-.79s0-1.41,0-2.77C7.7,29.18,6.74,26,6.74,26a4.36,4.36,0,0,0-1.81-2.39c-1.47-1,.12-1,.12-1a3.43,3.43,0,0,1,2.49,1.68,3.48,3.48,0,0,0,4.74,1.36,3.46,3.46,0,0,1,1-2.18c-3.62-.41-7.42-1.81-7.42-8a6.3,6.3,0,0,1,1.67-4.37,5.94,5.94,0,0,1,.16-4.31s1.37-.44,4.48,1.67a15.41,15.41,0,0,1,8.16,0c3.11-2.11,4.47-1.67,4.47-1.67A5.91,5.91,0,0,1,25,11.07a6.3,6.3,0,0,1,1.67,4.37c0,6.26-3.81,7.63-7.44,8a3.85,3.85,0,0,1,1.11,3c0,2.18,0,3.94,0,4.47s.29.94,1.12.78A16.29,16.29,0,0,0,16.29,0Z"
/>
</svg>
</div>
<img src={`${basePath}/images/product/auth/gitlab-icon.svg`} width={21} />
<img src={`${basePath}/images/product/auth/bitbucket-icon.svg`} width={21} />
</Space>
</p>
<Typography.Title level={4}>All the social providers</Typography.Title>
<Typography.Text>
<p className="text-lg">
We provide all the popular social logins you expect. Google, Facebook, Github,
Azure, Gitlab and Bitbucket.
</p>
<Space>
<img src={`${basePath}/images/product/auth/twitter-icon.svg`} width={21} />
<p className="m-0">
<span className="text-gray-800 dark:text-white">Twitter</span> coming soon
</p>
</Space>
</Typography.Text>
</div>
<div className="col-span-3 col-start-5">
<p className="mb-4">
<IconLink />
</p>
<Typography.Title level={4}>Fully integrated</Typography.Title>
<Typography.Text>
<p className="text-lg">
Auth built into your project, with no need for external authentication services.
</p>
<p>
Use your own custom domains with SMTP settings.
</p>
</Typography.Text>
</div>
<div className="col-span-3 col-start-9">
<p className="mb-4">
<IconShield />
</p>
<Typography.Title level={4}>Own your data</Typography.Title>
<Typography.Text>
<p className="text-lg">
Keep all your user data in your supabase project so you never have to worry about 3rd party privacy issues.
</p>
<p>Supabase is GDPR / CPA Compliant.</p>
</Typography.Text>
</div>
</div>
</SectionContainer>
<SectionContainer className="-mb-48">
<APISection
title="Simple and convenient APIs"
content={ApiExamples}
text={[
<p>
An API built from the groud up for server and client side authentication that is fast
to implement.
</p>,
<p>
With powerful library clients coming soon that allow for asset optimasation and image
transformation
</p>,
<DefaultLayout>
<ProductHeader
icon={Solutions['authentication'].icon}
title={Solutions['authentication'].name}
h1={[
<span key={'authentication-h1'}>
Open Source Auth
<br /> (without the integrations)
</span>,
]}
footer={[
<div className="grid grid-cols-12 gap-8 lg:gap-0 xl:gap-16 mt-8">
<div className="col-span-6 lg:col-span-12 xl:col-span-4">
<FeatureColumn
icon={<IconBriefcase />}
title="Enterprise logins"
text="Type definitions for both server side and client side"
/>
</div>
<div className="col-span-6 lg:col-span-12 xl:col-span-4">
<FeatureColumn
icon={<IconEye />}
title="Social login scopes"
text="Request additonal user data permissions when using social logins"
/>
</div>
</div>,
subheader={[
'Every Supabase project comes with a complete User Management system that works without any additional tools.',
"Including PostgreSQL's policy engine, for fine-grained access rules.",
]}
image={[
<img
key={'authentication-header-img--light'}
className="w-full header--light block"
src={`${basePath}/images/product/auth/header--light.png`}
/>,
<img
key={'authentication-header-img--dark'}
className="w-full header--dark mr-0 dark:block"
src={`${basePath}/images/product/auth/header--dark.png`}
/>,
]}
documentation_url={'https://supabase.io/docs/guides/auth'}
/>
</SectionContainer>
<div className="relative">
<div className="section--masked">
<div className="section--bg-masked">
<div className="section--bg border-t border-b border-gray-100 dark:border-gray-600"></div>
</div>
<div className="section-container pt-12 pb-0">
<FloatingIcons />
<div className="overflow-x-hidden">
<SectionContainer className="mb-0 pb-8">
<GithubExamples />
</SectionContainer>
</div>
</div>
</div>
</div>
<SectionContainer>
<div className="grid grid-cols-12 lg:gap-16">
<div className="col-span-12 lg:col-span-5 mb-8">
<Typography.Title level={2}>No middleware user permission</Typography.Title>
<p className="text-lg">
Restrict user access with row level security, even without prior knowledge of SQL.
Control who can create, edit and delete specific rows in your database.
</p>
<p>
Set up user profiles in your app using methods such as auth.user() so you can spend
more time building an app, rather than user profile forms.
</p>
<Button size="small" type="default" className="mt-4" icon={<IconArrowUpRight />}>
Expore documentation
</Button>
</div>
<div className="col-span-12 lg:col-span-6 lg:col-start-7">
<SplitCodeBlockCarousel content={AuthSqlRulesExamples} />
</div>
</div>
</SectionContainer>
<div className="container mx-auto px-8 sm:px-16 xl:px-20 relative py-32">
<div className="grid grid-cols-12 lg:gap-16">
<div className="order-last col-span-12 lg:order-first lg:col-span-6 mt-8 lg:mt-0">
<div
className={`relative bg-white dark:bg-gray-800 py-2 pb-16 border dark:border-gray-600 auth-container`}
>
<div className="w-full h-full left-0 top-2">
<Tabs
size="medium"
defaultActiveId="panel-1"
type="underlined"
tabBarStyle={{ padding: '0 16px' }}
addOnAfter={[
<Space>
<Button type="default" icon={<IconCopy />}>
Copy code
</Button>
</Space>,
]}
>
<Tabs.Panel label="Preview" id="panel-1" icon={<IconZoomIn />}>
<div style={{ maxWidth: '420px', margin: '0 auto' }}>
<Card>
<Space size={8} direction="vertical">
<div>
<Space size={3} direction="vertical">
<img src="https://app.supabase.io/img/supabase-dark.svg" width="96" />
<Typography.Title level={3}>{'Acme Company'}</Typography.Title>
</Space>
</div>
<Auth
providers={['facebook', 'google', 'github', 'bitbucket', 'gitlab']}
socialButtonSize={'medium'}
socialLayout={'horizontal'}
supabaseClient={supabase}
socialColors={false}
/>
</Space>
</Card>
</div>
</Tabs.Panel>
<Tabs.Panel label="Code" icon={<IconCode />} id="panel-2">
<div className="h-full">
<CodeBlock
hideCopy
children={`// Auth example
import React, { useState } from 'react'
import { Auth } from './'
import { createClient } from '@supabase/supabase-js'
// @ts-ignore
import { Typography, Button, Space } from '../../index'
export default function app() {
const supabase = createClient(
'https://YOUR-PROJECT-ID.supabase.co',
'YOUR-PUBLIC-ANON-KEY'
)
return (
<Auth.UserContextProvider {...args}>
<Container {...args}>
<Auth {...args} />
</Container>
</Auth.UserContextProvider>
)
}
`}
lang="js"
<SectionContainer>
<div className="grid grid-cols-12">
<div className="mb-10 lg:mb-0 col-span-12 lg:col-span-3">
<p className="mb-4">
<Space>
<img src={`${basePath}/images/product/auth/google-icon.svg`} width={21} />
<img src={`${basePath}/images/product/auth/facebook-icon.svg`} width={21} />
<div>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 32.58 31.77"
width={21}
className="text-gray-800 dark:text-white"
>
<path
fill="currentColor"
d="M16.29,0a16.29,16.29,0,0,0-5.15,31.75c.82.15,1.11-.36,1.11-.79s0-1.41,0-2.77C7.7,29.18,6.74,26,6.74,26a4.36,4.36,0,0,0-1.81-2.39c-1.47-1,.12-1,.12-1a3.43,3.43,0,0,1,2.49,1.68,3.48,3.48,0,0,0,4.74,1.36,3.46,3.46,0,0,1,1-2.18c-3.62-.41-7.42-1.81-7.42-8a6.3,6.3,0,0,1,1.67-4.37,5.94,5.94,0,0,1,.16-4.31s1.37-.44,4.48,1.67a15.41,15.41,0,0,1,8.16,0c3.11-2.11,4.47-1.67,4.47-1.67A5.91,5.91,0,0,1,25,11.07a6.3,6.3,0,0,1,1.67,4.37c0,6.26-3.81,7.63-7.44,8a3.85,3.85,0,0,1,1.11,3c0,2.18,0,3.94,0,4.47s.29.94,1.12.78A16.29,16.29,0,0,0,16.29,0Z"
/>
</div>
</Tabs.Panel>
</Tabs>
</div>
</svg>
</div>
<img src={`${basePath}/images/product/auth/gitlab-icon.svg`} width={21} />
<img src={`${basePath}/images/product/auth/bitbucket-icon.svg`} width={21} />
</Space>
</p>
<Typography.Title level={4}>All the social providers</Typography.Title>
<Typography.Text>
<p className="text-base lg:text-lg">
Enable social logins with the click of a button. Google, Facebook, Github, Azure,
Gitlab and Bitbucket.
</p>
<Space>
<img src={`${basePath}/images/product/auth/twitter-icon.svg`} width={21} />
<p className="m-0">
<span className="text-gray-800 dark:text-white">Twitter</span> coming soon
</p>
</Space>
</Typography.Text>
</div>
<div className="mb-10 lg:mb-0 col-span-12 lg:col-span-3 lg:col-start-5">
<p className="mb-4">
<IconLink />
</p>
<Typography.Title level={4}>Fully integrated</Typography.Title>
<Typography.Text>
<p className="text-base lg:text-lg">
Incredibly simple Auth, without a single external authentication service.
</p>
<p>Built-in Authentication, Authorization, and User Management.</p>
</Typography.Text>
</div>
<div className="col-span-12 lg:col-span-3 lg:col-start-9">
<p className="mb-4">
<IconShield />
</p>
<Typography.Title level={4}>Own your data</Typography.Title>
<Typography.Text>
<p className="text-base lg:text-lg">
User data stored in your Supabase database so you never have to worry about 3rd
party privacy issues.
</p>
<p>Host your data in 8 different regions.</p>
</Typography.Text>
</div>
</div>
<div className="col-span-12 lg:col-span-6 lg:col-start-7 xl:col-span-4 xl:col-start-8">
<Space className="mb-4">
<div className="w-8 h-8 rounded-md bg-gray-900 dark:bg-white text-white dark:text-gray-900 flex justify-center items-center">
<IconKey size="small" strokeWidth={1.5} />
</div>
<Typography.Text type="secondary">
<IconX />
</Typography.Text>
<img className="w-8" src={`${basePath}/images/product/auth/react-icon.svg`} />
</Space>
</SectionContainer>
<Typography.Title level={2} className="mb-4">
ReactJS Auth Widget
</Typography.Title>
<Typography.Text>
<p className="text-lg">
Bring authentication to your ReactJS app without coding a single form.
</p>
<p className="text-base">
We provide a ReactJS component that handles all the usual needs for logging in,
signing up, magic link and forgot password forms.
</p>
</Typography.Text>
<Button size="small" type="default" className="mt-4" icon={<IconArrowUpRight />}>
Explore documentation
</Button>
<SectionContainer className="-mb-48">
<APISection
title="Simple APIs"
// @ts-ignore
content={ApiExamples}
size="large"
text={[
<Typography.Text>
<p className="text-base lg:text-lg">
APIs that you can understand. With powerful libraries that work on client and
server-side applications.
</p>
{/* I think less is more here ... */}
{/* <p>
With powerful client libraries that work on both client and server-side
applications.
</p> */}
</Typography.Text>,
]}
footer={[
<div className="grid grid-cols-12 md:gap-8 lg:gap-0 xl:gap-16 mt-8">
<div className="col-span-12 sm:col-span-6 lg:col-span-12 xl:col-span-4">
<FeatureColumn
icon={<IconBriefcase />}
title="Enterprise logins"
text="Support for SAML, Azure. More enterprise providers and SSO coming soon."
/>
</div>
<div className="col-span-12 sm:col-span-6 lg:col-span-12 xl:col-span-4">
<FeatureColumn
icon={<IconEye />}
title="Social login scopes"
text="Request additonal user data permissions when using social logins."
/>
</div>
</div>,
]}
documentation_link={'https://supabase.io/docs/guides/auth'}
/>
</SectionContainer>
<div className="grid grid-cols-12 gap-8 lg:gap-0 xl:gap-16 mt-8">
<div className="col-span-6 lg:col-span-12 xl:col-span-4">
<FeatureColumn
icon={<IconBriefcase />}
title="Social login support"
text="Support for Social logins are built in and the component "
/>
</div>
<div className="col-span-6 lg:col-span-12 xl:col-span-4">
<FeatureColumn
icon={<IconEye />}
title="User context"
text="Includes a React hook that provides a User Session context provider so you can display the right parts of your app for logged in users."
/>
<div className="relative">
<div className="section--masked">
<div className="section--bg-masked">
<div className="section--bg border-t border-b border-gray-100 dark:border-gray-600"></div>
</div>
<div className="section-container pt-12 pb-0">
<FloatingIcons />
<div className="overflow-x-hidden">
<SectionContainer className="mb-0 pb-8">
<GithubExamples />
</SectionContainer>
</div>
</div>
</div>
</div>
</div>
<CTABanner />
</DefaultLayout>
<SectionContainer>
<div className="grid grid-cols-12 lg:gap-16">
<div className="col-span-12 lg:col-span-5 mb-8">
<Typography.Title level={2}>User permissions without the middleware</Typography.Title>
<Typography.Text>
<p className="text-base lg:text-lg">
Supabase Auth works without any additional servers. Build Authorization rules with
Postgres' Row Level Security, controlling who can create, edit
and delete specific rows in your database.
</p>
<p>Policies can be written in SQL or using the dashboard online.</p>
</Typography.Text>
<Link href="https://supabase.io/docs/guides/auth#policy-examples">
<a>
<Button size="small" type="default" className="mt-4" icon={<IconArrowUpRight />}>
Expore documentation
</Button>
</a>
</Link>
</div>
<div className="col-span-12 lg:col-span-6 lg:col-start-7">
<SplitCodeBlockCarousel
// @ts-ignore
content={AuthSqlRulesExamples}
/>
</div>
</div>
</SectionContainer>
<SectionContainer>
<div className="grid grid-cols-12 lg:gap-16">
<div className="order-last col-span-12 lg:order-first lg:col-span-6 mt-8 lg:mt-0">
<AuthComponentExample />
</div>
<div className="col-span-12 lg:col-span-6 lg:col-start-7 xl:col-span-4 xl:col-start-8">
<Space className="mb-4">
<div className="w-8 h-8 rounded-md bg-gray-900 dark:bg-white text-white dark:text-gray-900 flex justify-center items-center">
<IconKey size="small" strokeWidth={1.5} />
</div>
<Typography.Text type="secondary">
<IconX />
</Typography.Text>
<img className="w-8" src={`${basePath}/images/product/auth/react-icon.svg`} />
</Space>
<Typography.Title level={2} className="mb-4">
React Auth
</Typography.Title>
<Typography.Text>
<p className="text-base lg:text-lg">
Pre-built auth widgets to get started in minutes.
</p>
<p>
Supabase provides React libraries which handle common scenarios, including logging in,
signing up, magic link and forgot password forms.
</p>
</Typography.Text>
<Link
href="https://github.com/supabase/ui#using-supabase-ui-auth"
as="https://github.com/supabase/ui#using-supabase-ui-auth"
>
<a>
<Button size="small" type="default" className="mt-4" icon={<IconArrowUpRight />}>
Explore documentation
</Button>
</a>
</Link>
<div className="grid grid-cols-12 md:gap-8 lg:gap-0 xl:gap-16 mt-8">
<div className="col-span-12 lg:col-span-12 xl:col-span-4">
<FeatureColumn
icon={<IconBriefcase />}
title="Social login support"
text="Support for social logins are built in and the component "
/>
</div>
<div className="col-span-12 lg:col-span-12 xl:col-span-4">
<FeatureColumn
icon={<IconEye />}
title="User context hooks"
text="Access the auth status from any component."
/>
</div>
</div>
</div>
</div>
</SectionContainer>
<CTABanner />
</DefaultLayout>
</>
)
}
+34 -5
View File
@@ -12,6 +12,9 @@ import { render } from 'react-dom'
import Link from 'next/link'
import { addBasePath } from 'next/dist/next-server/lib/router/router'
import { NextSeo } from 'next-seo'
import authors from 'lib/authors.json'
const site_title = `${APP_NAME} | We are now in Beta`
// Dark text: text-dark-400
@@ -660,6 +663,9 @@ const BetaPricing = () => (
<div className="col-span-12 grid grid-cols-12 gap-x-2 lg:gap-x-8 mb-10">
<div className="col-span-12 sm:col-span-9 xl:col-span-7 text-base">
<p className="mb-10">
For up to date pricing info see: https://supabase.io/pricing
</p>
<p className="mb-10">
We're working closely with many open source projects, infrastructure providers, and of
course our Alpha Users, to provide a predictable and sustainable pricing model.
@@ -680,8 +686,7 @@ const BetaPricing = () => (
</li>
</ul>
<p className="mb-10">
Whilst we're not yet ready to publish a standardized pricing model, we are committing to
the following initiatives:
We are committing to the following initiatives:
</p>
<ul className="list-decimal list-outside ml-6 mb-10">
<li className="mb-5">
@@ -711,8 +716,7 @@ const BetaPricing = () => (
</li>
</ul>
<p>
The Supabase Base Tier constitutes a Supabase Instance running in a single region, on 2
vCPUs, 1 GiB Memory, with 2 GB of storage, and daily backups.
The Supabase Base Tier is now called the Supabase Pro tier as per the pricing page: https://supabase.io/pricing
</p>
</div>
</div>
@@ -960,8 +964,33 @@ const Beta = () => {
<meta name="twitter:site" content={site_title} />
<meta name="twitter:text:title" content={site_title} />
</Head>
<NextSeo
title={site_title}
openGraph={{
title: site_title,
description: DESCRIPTION,
url: `https://supabase.io/beta`,
type: 'article',
article: {
//
// to do: add expiration and modified dates
// https://github.com/garmeeh/next-seo#article
publishedTime: '2020-12-03T00:00:00Z',
//
// to do: author urls should be internal in future
// currently we have external links to github profiles
authors: [authors['supabase'].authorURL],
tags: ['beta'],
},
images: [
{
url: 'https://supabase.io/new/og/og-image.jpg',
},
],
}}
/>
<Container>
<div className="relative shadow-lg py-5 px-5 xl:px-20 sticky inset-0 bg-dark-800 z-50 flex items-center justify-between">
<div className="shadow-lg py-5 px-5 xl:px-20 sticky inset-0 bg-dark-800 z-50 flex items-center justify-between">
<a href="/">
<img className="h-5" src={`${basePath}/images/logo-dark.png`} />
</a>
+1 -1
View File
@@ -123,7 +123,7 @@ function FeaturedThumb(blog: PostTypes) {
<Link href={`/blog/${blog.url}`} as={`/blog/${blog.url}`}>
<a className="inline-block">
<img
className="h-96 w-full object-cover"
className="h-96 w-full object-cover border dark:border-dark-600"
src={`/new/images/blog/` + (blog.thumb ? blog.thumb : blog.image)}
/>
<Space direction="vertical" size={5} className="mt-4">
Loaded 100 of 181 files, more files were not shown because too many files have changed in this diff. Show more