Loading schemas...
diff --git a/studio/components/to-be-cleaned/Charts/ChartHandler.tsx b/studio/components/to-be-cleaned/Charts/ChartHandler.tsx
index ccfcbfa6bf9..eb57b34a3e6 100644
--- a/studio/components/to-be-cleaned/Charts/ChartHandler.tsx
+++ b/studio/components/to-be-cleaned/Charts/ChartHandler.tsx
@@ -15,6 +15,7 @@ import { API_URL } from 'lib/constants'
import { get } from 'lib/common/fetch'
import { BarChart, AreaChart } from './ChartRenderer'
import { ChartData } from './ChartHandler.types'
+import { TooltipProps } from 'recharts'
interface Props {
label: string
@@ -30,6 +31,7 @@ interface Props {
hideChartType?: boolean
data?: ChartData
isLoading?: boolean
+ onBarClick?: (v: any) => void
}
/**
@@ -55,6 +57,7 @@ const ChartHandler: FC = ({
hideChartType = false,
data,
isLoading,
+ onBarClick,
}) => {
const router = useRouter()
const { ref } = router.query
@@ -169,6 +172,7 @@ const ChartHandler: FC = ({
highlightedValue={highlightedValue}
label={label}
customDateFormat={customDateFormat}
+ onBarClick={onBarClick}
/>
) : (
(
No data to show
-
-
- May take 24 hours for data to appear
-
-
)
@@ -146,6 +143,7 @@ export function BarChart({
highlightedValue,
customDateFormat,
label,
+ onBarClick,
}: any) {
const hasData = data ? dataCheck(data, attribute) : true
@@ -190,9 +188,13 @@ export function BarChart({
left: 0,
bottom: 0,
}}
- className="overflow-visible"
+ className="overflow-visible cursor-pointer"
onMouseMove={onMouseMove}
onMouseLeave={onMouseLeave}
+ onClick={(tooltipData: any) => {
+ // receives tooltip data https://github.com/recharts/recharts/blob/2a3405ff64a0c050d2cf94c36f0beef738d9e9c2/src/chart/generateCategoricalChart.tsx
+ if (onBarClick) onBarClick(tooltipData)
+ }}
>
(
{
// Get the API service
const apiService = (data?.services ?? []).find((x: any) => x.app.id == API_SERVICE_ID)
- const apiKeys = apiService.service_api_keys
+ const apiKeys = apiService?.service_api_keys ?? []
return (
diff --git a/studio/pages/project/[ref]/building.tsx b/studio/pages/project/[ref]/building.tsx
index 5217b5aad7d..75e2e84f99c 100644
--- a/studio/pages/project/[ref]/building.tsx
+++ b/studio/pages/project/[ref]/building.tsx
@@ -3,16 +3,16 @@ import { useRouter } from 'next/router'
import { observer } from 'mobx-react-lite'
import { useStore, withAuth } from 'hooks'
-import BaseLayout from 'components/layouts'
+import ProjectLayout from 'components/layouts'
type ProjectBuildingPageState = {} & any
const ProjectBuildingPage: React.FC = () => {
const { ui } = useStore()
const project: any = ui.selectedProject
return (
-
+
-
+
)
}
export default withAuth(observer(ProjectBuildingPage))
@@ -24,7 +24,10 @@ const RedirectToDashboard: React.FC = ({ projectRef })
const router = useRouter()
React.useEffect(() => {
- router.push(`/project/${projectRef}`)
+ // Use redirect to reload store data properly after project has been
+ // been created or unpaused, this is necessarily especially for unpausing
+ // so that the dashboard fetches the updated connection strings
+ window.location.replace(`/project/${projectRef}`)
}, [])
return null
}
diff --git a/studio/pages/project/[ref]/settings/logs/[type].tsx b/studio/pages/project/[ref]/settings/logs/[type].tsx
index 659825f605f..6154a981d2c 100644
--- a/studio/pages/project/[ref]/settings/logs/[type].tsx
+++ b/studio/pages/project/[ref]/settings/logs/[type].tsx
@@ -26,12 +26,14 @@ import {
LogTemplate,
TEMPLATES,
LogData,
+ LogSearchCallback,
} from 'components/interfaces/Settings/Logs'
import { uuidv4 } from 'lib/helpers'
import useSWRInfinite from 'swr/infinite'
import { isUndefined } from 'lodash'
import Flag from 'components/ui/Flag/Flag'
import { useFlag } from 'hooks'
+import dayjs from 'dayjs'
/**
* Acts as a container component for the entire log display
@@ -48,7 +50,7 @@ export const LogPage: NextPage = () => {
const logsQueryParamsSyncing = useFlag('logsQueryParamsSyncing')
const logsCustomSql = useFlag('logsCustomSql')
const router = useRouter()
- const { ref, type, q, s, ts } = router.query
+ const { ref, type, q, s, te } = router.query
const [editorId, setEditorId] = useState(uuidv4())
const [editorValue, setEditorValue] = useState('')
const [mode, setMode] = useState<'simple' | 'custom'>('simple')
@@ -82,8 +84,10 @@ export const LogPage: NextPage = () => {
searchString: s as string,
})
}
- if (ts) {
- setParams({ ...params, timestamp_start: ts as string })
+ if (te) {
+ setParams((prev) => ({ ...prev, timestamp_end: te as string }))
+ } else {
+ setParams((prev) => ({ ...prev, timestamp_end: '' }))
}
}, [logsQueryParamsSyncing])
@@ -105,7 +109,7 @@ export const LogPage: NextPage = () => {
if (prevPageData === null) {
// reduce interval window limit by using the timestamp of the last log
queryParams = genQueryParams(params)
- } else if (prevPageData.data.length === 0) {
+ } else if ((prevPageData?.data ?? []).length === 0) {
// no rows returned, indicates that no more data to retrieve and append.
return null
} else {
@@ -147,7 +151,14 @@ export const LogPage: NextPage = () => {
const handleRefresh = () => {
setLatestRefresh(new Date().toISOString())
- setParams({ ...params, timestamp_start: '' })
+ setParams({ ...params, timestamp_end: '' })
+ router.push({
+ pathname: router.pathname,
+ query: {
+ ...router.query,
+ te: undefined,
+ },
+ })
setSize(1)
}
@@ -171,6 +182,7 @@ export const LogPage: NextPage = () => {
where: isSelectQuery ? '' : template.searchString,
sql: isSelectQuery ? template.searchString : '',
search_query: '',
+ timestamp_end: '',
}))
setEditorId(uuidv4())
}
@@ -189,18 +201,27 @@ export const LogPage: NextPage = () => {
...router.query,
q: editorValue,
s: undefined,
+ te: undefined,
},
})
}
- const handleSearch = (v: string) => {
- setParams((prev) => ({ ...prev, search_query: v || '', where: '', sql: '' }))
+ const handleSearch: LogSearchCallback = ({ query, from }) => {
+ const unixMicro = dayjs(from).valueOf() * 1000
+ setParams((prev) => ({
+ ...prev,
+ search_query: query || '',
+ timestamp_end: from ? String(unixMicro) : '',
+ where: '',
+ sql: '',
+ }))
if (!logsQueryParamsSyncing) return
router.push({
pathname: router.pathname,
query: {
...router.query,
q: undefined,
- s: v || '',
+ s: query || '',
+ te: unixMicro,
},
})
setEditorValue('')
@@ -217,6 +238,9 @@ export const LogPage: NextPage = () => {
onRefresh={handleRefresh}
onSearch={handleSearch}
defaultSearchValue={params.search_query}
+ defaultFromValue={
+ params.timestamp_end ? dayjs(Number(params.timestamp_end) / 1000).toISOString() : ''
+ }
onCustomClick={handleModeToggle}
onSelectTemplate={onSelectTemplate}
/>
diff --git a/studio/stores/common/PostgresMetaInterface.ts b/studio/stores/common/PostgresMetaInterface.ts
index f2be74940b7..3314521b081 100644
--- a/studio/stores/common/PostgresMetaInterface.ts
+++ b/studio/stores/common/PostgresMetaInterface.ts
@@ -23,6 +23,12 @@ export interface IPostgresMetaInterface {
byId: (id: number | string) => T | undefined
initialDataArray: (value: T[]) => void
}
+
+// [TODO] Need to refactor the logic for 'isInitialized'
+// Should avoid page level "loaded" state like TableEditorLayout
+// The logic should be - state should be INITIAL at first, and only
+// taken as initialized after the first trigger of the load method.
+// https://github.com/supabase/supabase/pull/5128#pullrequestreview-860706726
export default class PostgresMetaInterface implements IPostgresMetaInterface {
STATES = {
INITIAL: 'initial',
diff --git a/studio/stores/content/ProjectContentStore.ts b/studio/stores/content/ProjectContentStore.ts
index 4ad1f5f6da6..2952aaa42a0 100644
--- a/studio/stores/content/ProjectContentStore.ts
+++ b/studio/stores/content/ProjectContentStore.ts
@@ -11,6 +11,7 @@ import { API_URL } from 'lib/constants'
export interface IProjectContentStore {
isLoading: boolean
+ isInitialized: boolean
error: any
load: () => void
@@ -47,6 +48,10 @@ export default class ProjectContentStore implements IProjectContentStore {
return this.state === this.STATES.INITIAL || this.state === this.STATES.LOADING
}
+ get isInitialized() {
+ return this.state !== this.STATES.INITIAL
+ }
+
async fetchData() {
const headers = {
'Content-Type': 'application/json',
diff --git a/studio/tests/components/Home/ProjectUsage.test.js b/studio/tests/components/Home/ProjectUsage.test.js
index 9864a387cd1..b226b21f4b9 100644
--- a/studio/tests/components/Home/ProjectUsage.test.js
+++ b/studio/tests/components/Home/ProjectUsage.test.js
@@ -7,12 +7,13 @@ import { get } from 'lib/common/fetch'
jest.mock('next/router')
import { useRouter } from 'next/router'
import { clickDropdown } from 'tests/helpers'
-useRouter.mockReturnValue({ query: { ref: '123' } })
+const mockPush = jest.fn()
+useRouter.mockReturnValue({ query: { ref: '123' }, push: mockPush })
// need to wrap component with SWRConfig in order to clear cache between tests
// TODO: abstract out to global setup
import { SWRConfig } from 'swr'
-const ProjectUsage = jest.fn()
+const ProjectUsage = jest.fn()
ProjectUsage.mockImplementation((props) => {
const Original = jest.requireActual('components/interfaces/Home/ProjectUsage').default
// wrap with SWR to reset the cache each time
@@ -30,10 +31,9 @@ jest.mock('hooks')
import { useFlag } from 'hooks'
useFlag.mockReturnValue(true)
-
-
beforeEach(() => {
get.mockReset()
+ mockPush.mockReset()
})
const MOCK_CHART_DATA = {
@@ -71,7 +71,6 @@ test('mounts correctly', async () => {
test('dropdown options changes chart query', async () => {
get.mockImplementation((url) => {
- console.log(url)
if (url.includes('usage')) return {}
return { data: MOCK_CHART_DATA }
})
@@ -79,9 +78,8 @@ test('dropdown options changes chart query', async () => {
await waitFor(() => screen.getByText(/Statistics for past 60 minutes/))
await waitFor(() => screen.getAllByRole('button', { name: '60 minutes' }))
console.log(get.mock.calls)
- await waitFor(()=>{
+ await waitFor(() => {
expect(get).toHaveBeenCalledWith(expect.stringContaining('interval=minutely'))
-
})
// find button that has radix id
const [btn] = screen.getAllByRole('button', { name: '60 minutes' }).filter((e) => e.id)
@@ -93,5 +91,4 @@ test('dropdown options changes chart query', async () => {
userEvent.click(screen.getByText(/24 hours/))
await waitFor(() => screen.getByText(/Statistics for past 24 hours/))
expect(get).toHaveBeenCalledWith(expect.stringContaining('interval=hourly'))
-
-})
\ No newline at end of file
+})
diff --git a/studio/tests/pages/projects/LogPanel.test.js b/studio/tests/pages/projects/LogPanel.test.js
index 021a842500d..b707a7303c0 100644
--- a/studio/tests/pages/projects/LogPanel.test.js
+++ b/studio/tests/pages/projects/LogPanel.test.js
@@ -1,6 +1,14 @@
import LogPanel from 'components/interfaces/Settings/Logs/LogPanel'
import { render, waitFor, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
+import { clickDropdown } from 'tests/helpers'
+
+jest.mock('components/ui/Flag/Flag')
+import Flag from 'components/ui/Flag/Flag'
+Flag.mockImplementation(({ children }) => <>{children}>)
+jest.mock('hooks')
+import { useFlag } from 'hooks'
+useFlag.mockReturnValue(true)
test('templates', async () => {
const mockFn = jest.fn()
@@ -59,3 +67,56 @@ test('reset search filter', async () => {
expect(() => screen.getByTitle(/Clear search/)).toThrow()
expect(() => screen.getByDisplayValue(/something123/)).toThrow()
})
+
+test('timestamp from filter default value', async () => {
+ render()
+ userEvent.click(await screen.findByText('Custom'))
+ await screen.findByDisplayValue('2022-01-18T10:43:39+0000')
+})
+test('timestamp from filter', async () => {
+ const mockFn = jest.fn()
+ render()
+ const dropdown = await screen.findByText(/Now/)
+
+ // click the dropdown
+ userEvent.click(dropdown)
+
+ // TODO: use screen.findByLabelText when https://github.com/supabase/ui/issues/310 is resolved
+ await screen.findByText('From')
+ // display iso timestamp
+ const year = new Date().getFullYear()
+ const input = await screen.findByDisplayValue(RegExp(year))
+
+ // replace the input's value
+ userEvent.clear(input)
+
+ // type some random input to check for error handling
+ userEvent.type(input, '123456')
+ await screen.findByText(/[iI]nvalid ISO 8601 timestamp/)
+ userEvent.clear(input)
+ await expect(screen.findByText(/[iI]nvalid ISO 8601 timestamp/)).rejects.toThrow()
+
+ // get time 20 mins before
+ const newDate = new Date()
+ newDate.setMinutes(new Date().getMinutes() - 20)
+ userEvent.type(input, newDate.toISOString())
+
+ // input actions
+ await screen.findByTitle('Reset')
+ const set = await screen.findByRole('button', { name: 'Set' })
+
+ userEvent.click(set)
+ expect(mockFn).toBeCalled()
+ await screen.findByText('Custom')
+ await screen.findByTitle(/Clear timestamp filter/)
+})
+
+test('custom query mode hides elements', async () => {
+ const { rerender } = render()
+ await screen.findByPlaceholderText(/Search/)
+ await screen.findByText('Now')
+ rerender()
+ await expect(screen.findByPlaceholderText(/Search/)).rejects.toThrow()
+ await expect(screen.findByText('Now')).rejects.toThrow()
+ await expect(screen.findByText('Custom')).rejects.toThrow()
+})
diff --git a/studio/tests/pages/projects/logs.test.js b/studio/tests/pages/projects/logs.test.js
index 9319fc1395f..5a1d73a4db5 100644
--- a/studio/tests/pages/projects/logs.test.js
+++ b/studio/tests/pages/projects/logs.test.js
@@ -60,8 +60,9 @@ LogPage.mockImplementation((props) => {
import { render, fireEvent, waitFor, screen, act } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
-import { getToggleByText } from '../../helpers'
+import { clickDropdown, getToggleByText } from '../../helpers'
import { wait } from '@testing-library/user-event/dist/utils'
+import { find } from 'lodash'
beforeEach(() => {
// reset mocks between tests
@@ -273,16 +274,26 @@ test('q= query param will populate the query input', async () => {
expect(() => !screen.queryByDisplayValue(/someSearch/))
})
-test('ts= query param will set the timestamp_start param', async () => {
+test('te= query param will populate the timestamp from input', async () => {
+ // get time 20 mins before
+ const newDate = new Date()
+ newDate.setMinutes(new Date().getMinutes() - 20)
+ const isoString = newDate.toISOString()
+ const unixMicro = newDate.getTime() * 1000 //microseconds
+
useRouter.mockReturnValueOnce({
- query: { ref: '123', type: 'api', ts: 123456 },
+ query: { ref: '123', type: 'api', te: unixMicro },
push: jest.fn(),
})
render()
await waitFor(() => {
- expect(get).toHaveBeenCalledWith(expect.stringContaining('timestamp_start=123456'))
+ expect(get).toHaveBeenCalledWith(
+ expect.stringContaining(`timestamp_end=${encodeURIComponent(unixMicro)}`)
+ )
})
+ userEvent.click(await screen.findByText('Custom'))
+ await screen.findByDisplayValue(isoString)
})
test('custom sql querying', async () => {
get.mockImplementation((url) => {
@@ -332,10 +343,10 @@ test('custom sql querying', async () => {
// clicking on the row value should not show log selection panel
userEvent.click(screen.getByText(/12345/))
- await waitFor(() => expect(() => screen.getByText(/Metadata/)).toThrow())
+ await expect(screen.findByText(/Metadata/)).rejects.toThrow()
// should not see chronological features
- await waitFor(() => screen.queryByText(/Load older/)) //column header
+ await expect(screen.findByText(/Load older/)).rejects.toThrow()
})
test('load older btn will fetch older logs', async () => {
diff --git a/web/docs/about.mdx b/web/docs/about.mdx
index bb7b7bba574..67a2be47967 100755
--- a/web/docs/about.mdx
+++ b/web/docs/about.mdx
@@ -114,22 +114,13 @@ Supabase is just Postgres, which makes it compatible with a large number of tool
## How to use these docs
-The documentation is divided into 3 sections.
+The documentation is divided into 2 sections.
- {/* Overview */}
-
-
-
-
Overview
-
A summary of the tools and how to get started.
-
-
-
{/* Auth */}
-
-
+
+
Guides
In-depth explanations for each tool.
@@ -137,7 +128,7 @@ The documentation is divided into 3 sections.
{/* Ref */}
-
+
Reference Docs
diff --git a/web/docs/guides.md b/web/docs/guides.md
deleted file mode 100644
index 07cb02a102e..00000000000
--- a/web/docs/guides.md
+++ /dev/null
@@ -1,31 +0,0 @@
----
-id: guides
-title: Overview
-description: Common task
-hide_title: true
----
-
-## Guides
-
-### Database
-
-These guides help you unleash the power of PostgreSQL, the world's most advanced open source database.
-
-### Auth
-
-Authenticating users isn't the most exciting job for most developers, so Supabase makes it quick and simple, while maintaining security and performance.
-
-
diff --git a/web/docs/guides/api.mdx b/web/docs/guides/api.mdx
index 77c845e1814..a7fb8dfc992 100644
--- a/web/docs/guides/api.mdx
+++ b/web/docs/guides/api.mdx
@@ -9,7 +9,13 @@ import TabItem from '@theme/TabItem';
## Overview
-Supabase generates APIs directly from your database schema. The API is:
+Supabase generates three types of API directly from your database schema.
+
+- REST - interact with your database through a restful interface.
+- Realtime - listen to database changes.
+- GraphQL - [coming soon](https://supabase.com/blog/2021/12/03/pg-graphql).
+
+The APIs are:
- Instant and auto-generated: as you update your database the changes are immediately accessible through your API.
- Self documenting: Supabase generates documentation in the Dashboard which updates as you make database changes.
@@ -32,7 +38,7 @@ It provides everything you need from a CRUD API:
-## User Management
+## Overview
-Supabase makes it simple to manage your users.
+There are two parts to every Auth system:
-
+- **Authentication:** should this person be allowed in? If yes, who are they?
+- **Authorization:** once they are in, what are they allowed to do?
-When users sign up, Supabase assigns them a unique ID. You can reference this ID anywhere in your database. For example, you might create a `profiles` table referencing `id` in the `auth.users` table using a `user_id` field.
+Supabase Auth is designed to work either as a standalone product, or deeply integrated with the other Supabase products.
+Postgres is at the heart of everything we do, and the Auth system follows this principle. We leverage Postgres' build-in Auth functionality wherever possible.
-Supabase provides the routes to [sign up](/docs/reference/javascript/auth-signup), [log in](/docs/reference/javascript/auth-signin),
-[log out](/docs/reference/javascript/auth-signout), and manage users in your apps and websites.
+## Authentication
-## Third Party Logins
+You can authenticate your users in several ways:
-We currently support the following OAuth providers:
+- Email & password.
+- Magic links (one-click logins).
+- Social providers.
+- Phone logins.
-
+### Providers
+
+We provide a suite of Providers and login methods.
+
+
@@ -51,11 +78,25 @@ We currently support the following OAuth providers:
-You can enable providers by navigating to Authentication > Settings > External OAuth Providers and inputting your `Client ID` and `Secret` for each.
+
+### Simple interface
+
+You can enable third-providers with the click of a button by navigating to Authentication > Settings > External OAuth Providers and inputting your `Client ID` and `Secret` for each.

-## Row Level Security
+
+## Authorization
+
+
+When you need granular authorization rules, nothing beats PostgreSQL's Row Level Security (RLS).
+
+Policies are PostgreSQL's rule engine. They are incredibly powerful and flexible, allowing you to write complex SQL rules which fit your unique business needs.
+
+Get started with our [Row Level Security Guides](/docs/guides/auth/row-level-security).
+
+
+### Row Level Security
Authentication only gets you so far. When you need granular authorization rules, nothing beats PostgreSQL's [Row Level Security (RLS)](https://www.postgresql.org/docs/current/ddl-rowsecurity.html). Supabase makes it simple to turn RLS on and off.
@@ -63,7 +104,7 @@ Authentication only gets you so far. When you need granular authorization rules,
-## Policies
+### Policies
[Policies](https://www.postgresql.org/docs/current/sql-createpolicy.html) are PostgreSQL's rule engine. They are incredibly powerful and flexible, allowing you to write complex SQL rules which fit your unique business needs.
@@ -94,7 +135,7 @@ let user = await supabase.from('users').select('user_id, name')
```
-## How It Works
+### How It Works
1. A user signs up. Supabase creates a new user in the `auth.users` table.
2. Supabase returns a new JWT, which contains the user's `UUID`.
@@ -104,37 +145,22 @@ let user = await supabase.from('users').select('user_id, name')
Supabase provides a special function in Postgres, `auth.uid()`, which extracts the user's UID from the JWT. This is especially useful when creating policies.
-## Tips
-
-#### Enable Realtime for database tables
-
-Realtime server broadcasts database changes to authorized users depending on your Row Level Security (RLS) policies.
-We recommend that you enable row level security and set row security policies on tables that you add to the publication.
-However, you may choose to disable RLS on a table and have changes broadcast to all connected clients.
-```sql
-/**
- * REALTIME SUBSCRIPTIONS
- * Realtime enables listening to any table in your public schema.
- */
+## User Management
-begin;
- -- remove the realtime publication
- drop publication if exists supabase_realtime;
+Supabase makes it simple to manage your users.
- -- re-create the publication but don't enable it for any tables
- create publication supabase_realtime;
-commit;
+
--- add a table to the publication
-alter publication supabase_realtime add table products;
+When users sign up, Supabase assigns them a unique ID. You can reference this ID anywhere in your database. For example, you might create a `profiles` table referencing `id` in the `auth.users` table using a `user_id` field.
+
+Supabase provides the routes to [sign up](/docs/reference/javascript/auth-signup), [log in](/docs/reference/javascript/auth-signin),
+[log out](/docs/reference/javascript/auth-signout), and manage users in your apps and websites.
--- add other tables to the publication
-alter publication supabase_realtime add table posts;
-```
## Next Steps
-- Read more about Auth in the [Guides](/docs/guides/auth/intro).
- Sign in: [app.supabase.io](https://app.supabase.io)
diff --git a/web/docs/guides/auth/auth-apple.mdx b/web/docs/guides/auth/auth-apple.mdx
index 4c29a3e3f11..7e7de7767f8 100644
--- a/web/docs/guides/auth/auth-apple.mdx
+++ b/web/docs/guides/auth/auth-apple.mdx
@@ -27,7 +27,7 @@ Apple OAuth consists of six broad steps:
- Go to [developer.apple.com](https://developer.apple.com).
- Click on `Account` at the top right to log in.
-
+
### Obtain an App ID
diff --git a/web/docs/guides/auth/auth-azure.mdx b/web/docs/guides/auth/auth-azure.mdx
index 6b5b84c9ce6..c623bbc2237 100644
--- a/web/docs/guides/auth/auth-azure.mdx
+++ b/web/docs/guides/auth/auth-azure.mdx
@@ -34,7 +34,7 @@ Azure OAuth consists of six broad steps:
- The redirect / callback URI should look like this: `https://.supabase.co/auth/v1/callback`
- Click "Register" at the bottom of the form.
-
+
### Obtain a Client ID
@@ -42,7 +42,7 @@ This will serve as the `client_id` when you make API calls to authenticate the u
- Once your app has been registered, the client id can be found under the [list of app registrations](https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredApps) under the column titled "Application (client) ID".
-
+
### Obtain a Secret ID
@@ -54,7 +54,7 @@ This will serve as the `client_secret` when you make API calls to authenticate t
- Enter a description and choose your preferred expiry for the secret.
- Once the secret is generated, save the value (not the secret ID).
-
+
### Add login code to your client app
@@ -62,7 +62,7 @@ The JavaScript client code is documented in the [Supabase OAuth Reference](/docs
```js
const { user, session, error } = await supabase.auth.signIn({
- provider: 'Azure',
+ provider: 'azure',
})
```
@@ -71,7 +71,7 @@ Add a function which you can call from a button, link, or UI element.
```js
async function signInWithAzure() {
const { user, session, error } = await supabase.auth.signIn({
- provider: 'Azure',
+ provider: 'azure',
}, {
scopes: 'email',
})
diff --git a/web/docs/guides/auth/auth-bitbucket.mdx b/web/docs/guides/auth/auth-bitbucket.mdx
index 7959269a5e6..f442be7e800 100644
--- a/web/docs/guides/auth/auth-bitbucket.mdx
+++ b/web/docs/guides/auth/auth-bitbucket.mdx
@@ -24,7 +24,7 @@ Setting up Bitbucket logins for your application consists of 3 parts:
- Go to [bitbucket.org](https://bitbucket.org/).
- Click on `Login` at the top right to log in.
-
+
### Find your callback URL
diff --git a/web/docs/guides/auth/auth-discord.mdx b/web/docs/guides/auth/auth-discord.mdx
index 84660b3f731..ca5f313634f 100644
--- a/web/docs/guides/auth/auth-discord.mdx
+++ b/web/docs/guides/auth/auth-discord.mdx
@@ -24,11 +24,11 @@ Setting up Discord logins for your application consists of 3 parts:
- Go to [discord.com](https://discord.com/).
- Click on `Login` at the top right to log in.
-
+
- Once logged in, go to [discord.com/developers](https://discord.com/developers).
-
+
### Find your callback URL
diff --git a/web/docs/guides/auth/auth-facebook.mdx b/web/docs/guides/auth/auth-facebook.mdx
index c7fc632ae1b..425d120d00f 100644
--- a/web/docs/guides/auth/auth-facebook.mdx
+++ b/web/docs/guides/auth/auth-facebook.mdx
@@ -24,7 +24,7 @@ Setting up Facebook logins for your application consists of 3 parts:
- Go to [developers.facebook.com](https://developers.facebook.com).
- Click on `Log In` at the top right to log in.
-
+
### Create a Facebook App
diff --git a/web/docs/guides/auth/auth-github.mdx b/web/docs/guides/auth/auth-github.mdx
index b20f747f700..4b45e4d0899 100644
--- a/web/docs/guides/auth/auth-github.mdx
+++ b/web/docs/guides/auth/auth-github.mdx
@@ -24,7 +24,7 @@ Setting up GitHub logins for your application consists of 3 parts:
- Go to [github.com](https://github.com).
- Click on `Sign In` at the top right to log in.
-
+
### Create a GitHub Oauth App
diff --git a/web/docs/guides/auth/auth-gitlab.mdx b/web/docs/guides/auth/auth-gitlab.mdx
index f18051366b9..00854a6fb30 100644
--- a/web/docs/guides/auth/auth-gitlab.mdx
+++ b/web/docs/guides/auth/auth-gitlab.mdx
@@ -24,7 +24,7 @@ Setting up GitLab logins for your application consists of 3 parts:
- Go to [gitlab.com](https://gitlab.com).
- Click on `Login` at the top right to log in.
-
+
### Find your callback URL
diff --git a/web/docs/guides/auth/auth-google.mdx b/web/docs/guides/auth/auth-google.mdx
index 3e7c0d68400..798c043cfad 100644
--- a/web/docs/guides/auth/auth-google.mdx
+++ b/web/docs/guides/auth/auth-google.mdx
@@ -24,7 +24,7 @@ Setting up Google logins for your application consists of 3 parts:
- Go to [cloud.google.com](https://cloud.google.com).
- Click on `Sign in` at the top right to log in.
-
+
### Create a Google Cloud Platform Project
diff --git a/web/docs/guides/auth/auth-slack.mdx b/web/docs/guides/auth/auth-slack.mdx
index 5350262dd4b..86a7ac2bd67 100644
--- a/web/docs/guides/auth/auth-slack.mdx
+++ b/web/docs/guides/auth/auth-slack.mdx
@@ -24,7 +24,7 @@ Setting up Slack logins for your application consists of 3 parts:
- Go to [api.slack.com](https://api.slack.com/apps).
- Click on `Your Apps` at the top right to log in.
-
+
### Find your callback URL
diff --git a/web/docs/guides/auth/auth-spotify.mdx b/web/docs/guides/auth/auth-spotify.mdx
index b98422b2e1e..b29288ebbd3 100644
--- a/web/docs/guides/auth/auth-spotify.mdx
+++ b/web/docs/guides/auth/auth-spotify.mdx
@@ -24,7 +24,7 @@ Setting up Spotify logins for your application consists of 3 parts:
- Log into [Spotify](https://spotify.com)
- Access the [Spotify Developer Dashboard](https://developer.spotify.com/dashboard)
-
+
### Find your callback URL
diff --git a/web/docs/guides/auth/auth-twitch.mdx b/web/docs/guides/auth/auth-twitch.mdx
index d395957500f..19e099f605b 100644
--- a/web/docs/guides/auth/auth-twitch.mdx
+++ b/web/docs/guides/auth/auth-twitch.mdx
@@ -25,11 +25,11 @@ Setting up Twitch logins for your application consists of 3 parts:
- Click on `Log in with Twitch` at the top right to log in.
- If you have not already enabled 2-Factor Authentication for your Twitch Account, you will need to do that at [Twitch Security Settings](https://www.twitch.tv/settings/security) before you can continue.
-
+
- Once logged in, go to the [Twitch Developer Console](https://dev.twitch.tv/console).
-
+
### Find your callback URL
@@ -49,11 +49,11 @@ In the next step you require a callback URL, which looks like this:
### Create a Twitch Application
-
+
- Click on `+ Register Your Application` at the top right.
-
+
- Enter the name of your application.
- Type or paste your `OAuth Redirect URL` (the callback URL from the previous step.)
@@ -64,13 +64,13 @@ In the next step you require a callback URL, which looks like this:
- Click `Manage` at the right of your application entry in the list.
-
+
- Copy your Client ID.
- Click `New Secret` to create a new Client Secret.
- Copy your Client Secret.
-
+
### Add your Twitch credentials into your Supabase Project
diff --git a/web/docs/guides/auth/auth-twitter.mdx b/web/docs/guides/auth/auth-twitter.mdx
index ebf95d5d155..da34d8f5394 100644
--- a/web/docs/guides/auth/auth-twitter.mdx
+++ b/web/docs/guides/auth/auth-twitter.mdx
@@ -24,7 +24,7 @@ Setting up Twitter logins for your application consists of 3 parts:
- Go to [developer.twitter.com](https://developer.twitter.com).
- Click on `Sign in` at the top right to log in.
-
+
### Find your callback URL
diff --git a/web/docs/guides/database.mdx b/web/docs/guides/database.mdx
index a4cc857341c..ba024cf68fa 100644
--- a/web/docs/guides/database.mdx
+++ b/web/docs/guides/database.mdx
@@ -4,9 +4,22 @@ title: Database
description: Use Supabase to manage your data.
---
-import ExtensionsComponent from '@site/src/components/Extensions'
-Supabase is built on top of [Postgres](/docs/postgres/server/about), an extremely scalable Relational Database.
+Every Supabase project comes with a full [Postgres](https://www.postgresql.org/) database, a free and open source
+database which is considered one of the world's most stable and advanced databases.
+
+
+## Postgres or PostgreSQL?
+
+PostgreSQL the database was derived from the POSTGRES Project, a package written at the University of California at Berkeley in 1986.
+This package included a query language called "PostQUEL".
+
+In 1994, Postgres95 was built on top of POSTGRES code, adding an SQL language interpreter as a replacement for PostQUEL.
+Eventually, Postgres95 was renamed to PostgreSQL to reflect the SQL query capability.
+
+After this, many people referred to it as Postgres since it's less prone to confusion. Supabase is all about
+simplicity, so we also refer to it as Postgres.
+
## Features
diff --git a/web/docs/guides/database/functions.mdx b/web/docs/guides/database/functions.mdx
index e84afc13fd7..b0b4d5771b8 100644
--- a/web/docs/guides/database/functions.mdx
+++ b/web/docs/guides/database/functions.mdx
@@ -125,7 +125,7 @@ At it's most basic a function has the following parts:
3. `language sql`: The language used inside the function body. This can also be a procedural language: `plpgsql`, `plv8`, `plpython`, etc.
4. `as $$`: The function wrapper. Anything enclosed inside the `$$` symbols will be part of the function body.
5. `select 'hello world';`: A simple function body. The final `select` statement inside a function body will be returned if there are no statements following it.
-6. `$$;`: The closing symbols of the funciton wrapper.
+6. `$$;`: The closing symbols of the function wrapper.
diff --git a/web/docs/guides/database/intro.mdx b/web/docs/guides/database/intro.mdx
deleted file mode 100644
index 812dcf52200..00000000000
--- a/web/docs/guides/database/intro.mdx
+++ /dev/null
@@ -1,37 +0,0 @@
----
-id: introduction
-title: "Introduction"
-slug: introduction
----
-
-import Tabs from '@theme/Tabs';
-import TabItem from '@theme/TabItem';
-
-Every Supabase project comes with a full Postgres database, a free and open source
-database which is considered one of the world's most stable and advanced databases.
-
-
-## Postgres or PostgreSQL?
-
-
-The database was derived from the POSTGRES Project, a package written at the University of California at Berkeley in 1986.
-This package included a query language called "PostQUEL".
-
-In 1994, Postgres95 was built on top of POSTGRES code, adding an SQL language interpreter as a replacement for PostQUEL.
-Eventually, Postgres95 was renamed to PostgreSQL to reflect the SQL query capability.
-
-After this, many people referred to it as Postgres since it's less prone to confusion. Supabase is all about
-simplicity, so we also refer to it as Postgres.
-
-
\ No newline at end of file
diff --git a/web/docs/guides/examples.mdx b/web/docs/guides/examples.mdx
index f57f0cf7334..d5231dc6d51 100644
--- a/web/docs/guides/examples.mdx
+++ b/web/docs/guides/examples.mdx
@@ -6,7 +6,55 @@ description: 'Examples you can use to get started with Supabase'
We have a [set of examples](https://github.com/supabase/supabase/tree/master/examples) in our [main repository](https://github.com/supabase/supabase) to help you get started.
-## Todo List
+## Featured
+
+
+### Supabase Crash Course
+
+By [Traversy Media](https://www.youtube.com/watch?v=7uKQBl9uZ00).
+
+
+
+### Build an App With Supabase and NextJS
+
+By [@jlengstorf](https://twitter.com/jlengstorf) and [@jonmeyers_io](https://twitter.com/jonmeyers_io).
+
+
+
+
+### Is Supabase Legit
+
+By [Fireship](https://www.youtube.com/watch?v=WiwfiVdfRIc).
+
+
+
+## Official Examples
+
+### Todo List
Build a basic Todo List with Supabase and your favorite frontend framework:
@@ -17,13 +65,13 @@ Build a basic Todo List with Supabase and your favorite frontend framework:
- [Vue 3 Todo List (Typescript).](https://github.com/supabase/supabase/tree/master/examples/vue3-ts-todo-list)
- [Angular Todo List.](https://github.com/supabase/supabase/tree/master/examples/angular-todo-list)
-## Auth examples
+### Auth examples
- [Supabase Auth with vanilla Javascript.](https://github.com/supabase/supabase/tree/master/examples/javascript-auth). Use Supabase without any frontend frameworks.
- [Supabase Auth with Next.js SSR.](https://github.com/supabase/supabase/tree/master/examples/nextjs-with-supabase-auth) Uses cookies to persist auth between the server and the client.
- [Supabase Auth with RedwoodJS.](https://redwood-playground-auth.netlify.app/supabase) Try out Supabase authentication in the [RedwoodJS](https://redwoodjs.com) Authentication Playground complete with OAuth support and code samples.
-## Collaborative
+### Collaborative
- [Next.js Slack Clone.](https://github.com/supabase/supabase/tree/master/examples/nextjs-slack-clone)
@@ -31,6 +79,7 @@ Build a basic Todo List with Supabase and your favorite frontend framework:
### Libraries
+- D (in development): [GitHub](https://github.com/csharpdf/dupabase)
- Dart (in development): [GitHub](https://github.com/supabase/supabase-dart)
- Python (in development): [GitHub](https://github.com/supabase/supabase-py)
- C# (in development): [GitHub](https://github.com/supabase/supabase-csharp)
diff --git a/web/docs/guides/hosting/platform.mdx b/web/docs/guides/hosting/platform.mdx
index c74e5abe601..a0e988fa082 100644
--- a/web/docs/guides/hosting/platform.mdx
+++ b/web/docs/guides/hosting/platform.mdx
@@ -26,8 +26,8 @@ You can invite your Team members into your Organizations so that you can collabo
Each project on Supabase comes with these features:
- A dedicated Postgres database. [Learn more](/docs/guides/database)
-- Auto-generated APIs.
-- User management. [Learn more](/docs/guides/auth)
+- Auto-generated APIs. [Learn more](/docs/guides/api)
+- Auth and User management. [Learn more](/docs/guides/auth)
- Storage. [Learn more](/docs/guides/storage)
## Next Steps
diff --git a/web/docs/guides/local-development.mdx b/web/docs/guides/local-development.mdx
index 01a76e7262a..7ed04778e09 100644
--- a/web/docs/guides/local-development.mdx
+++ b/web/docs/guides/local-development.mdx
@@ -9,7 +9,9 @@ import TabItem from '@theme/TabItem';
Supabase provides a CLI so that you can develop your application locally, rather than connecting to a live project.
-## Prerequisites
+## Getting started
+
+### Prerequisites
You will need to have these in your environment:
@@ -17,7 +19,13 @@ You will need to have these in your environment:
- Docker (make sure the daemon is up and running)
- Supabase CLI (instructions [here](https://github.com/supabase/cli))
-## Initialize your project
+### Initialize git
+
+```bash
+git init
+```
+
+### Initialize your project
```bash
supabase init
@@ -25,7 +33,7 @@ supabase init
This command will create a `supabase` folder which holds all the configuration for developing your project locally.
-## Start
+### Start
```bash
supabase start
@@ -45,7 +53,7 @@ Started local development setup.
service_role key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoic2VydmljZV9yb2xlIn0.M2d2z4SFn5C7HlJlaSLfrzuYim9nbY_XI40uWFN3hEE
```
-#### Accessing Services Directly
+### Accessing Services Directly
-## Example application
+### Example application
Now that we've learned how to install and start Supabase locally, let's see how you can use it with a frontend application.
This quick-start guide will show you, how you can configure a React app which uses the Supabase stack.
@@ -139,7 +147,7 @@ npm start # Start the React app
If everything is working, you should have a React app running on `http://localhost:3000` and Supabase services running on `http://localhost:54321`
-## Stop
+### Stop
When you're finished with Supabase, send Ctrl-C to stop the Docker services.
diff --git a/web/docs/guides/storage.mdx b/web/docs/guides/storage.mdx
index 042f40f661a..e845b3661bf 100644
--- a/web/docs/guides/storage.mdx
+++ b/web/docs/guides/storage.mdx
@@ -349,13 +349,6 @@ on storage.objects for select
using ( auth.uid() = owner );
```
-## Tips
-
-### Go Easy
-
-Supabase Storage is in Beta. If you're experiencing any issues,
-let us know on our [GitHub](https://github.com/supabase/supabase/discussions) and we will fix it as fast as we can.
-
## Next steps
- Got a question? [Ask here](https://github.com/supabase/supabase/discussions).
diff --git a/web/docs/guides/with-expo.mdx b/web/docs/guides/with-expo.mdx
new file mode 100644
index 00000000000..62bab155081
--- /dev/null
+++ b/web/docs/guides/with-expo.mdx
@@ -0,0 +1,519 @@
+---
+id: with-expo
+title: "Quickstart: Expo"
+description: Learn how to use Supabase in your React Native App.
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+## Intro
+
+This example provides the steps to build a simple user management app (from scratch!) using Supabase and React Native. It includes:
+
+- Supabase [Database](/docs/guides/database): a Postgres database for storing your user data.
+- Supabase [Auth](/docs/guides/auth): users can sign in with magic links (no passwords, only email).
+- Supabase [Storage](/docs/guides/storage): users can upload a photo.
+- [Row Level Security](/docs/guides/auth#row-level-security): data is protected so that individuals can only access their own data.
+- Instant [APIs](/docs/guides/api): APIs will be automatically generated when you create your database tables.
+
+By the end of this guide you'll have an app which allows users to login and update some basic profile details:
+
+## Project set up
+
+Before we start building we're going to set up our Database and API. This is as simple as starting a new Project in Supabase
+and then creating a "schema" inside the database.
+
+### Create a project
+
+1. Go to [app.supabase.io](https://app.supabase.io).
+1. Click on "New Project".
+1. Enter your project details.
+1. Wait for the new database to launch.
+
+
+### Set up the database schema
+
+Now we are going to set up the database schema. We can use the "User Management Starter" quickstart in the SQL Editor,
+or you can just copy/paste the SQL from below and run it yourself.
+
+
+
+
+```sh
+1. Go to the "SQL" section.
+2. Click "User Management Starter".
+3. Click "Run".
+```
+
+
+
+
+
+
+```sql
+-- Create a table for public "profiles"
+create table profiles (
+ id uuid references auth.users not null,
+ updated_at timestamp with time zone,
+ username text unique,
+ avatar_url text,
+ website text,
+
+ primary key (id),
+ unique(username),
+ constraint username_length check (char_length(username) >= 3)
+);
+
+alter table profiles enable row level security;
+
+create policy "Public profiles are viewable by everyone."
+ on profiles for select
+ using ( true );
+
+create policy "Users can insert their own profile."
+ on profiles for insert
+ with check ( auth.uid() = id );
+
+create policy "Users can update own profile."
+ on profiles for update
+ using ( auth.uid() = id );
+
+-- Set up Realtime!
+begin;
+ drop publication if exists supabase_realtime;
+ create publication supabase_realtime;
+commit;
+alter publication supabase_realtime add table profiles;
+
+-- Set up Storage!
+insert into storage.buckets (id, name)
+values ('avatars', 'avatars');
+
+create policy "Avatar images are publicly accessible."
+ on storage.objects for select
+ using ( bucket_id = 'avatars' );
+
+create policy "Anyone can upload an avatar."
+ on storage.objects for insert
+ with check ( bucket_id = 'avatars' );
+
+```
+
+
+
+
+
+### Get the API Keys
+
+
+Now that you've created some database tables, you are ready to insert data using the auto-generated API.
+We just need to get the URL and `anon` key from the API settings.
+
+
+
+
+```sh
+1. Go to the "Settings" section.
+2. Click "API" in the sidebar.
+3. Find your API URL in this page.
+4. Find your "anon" and "service_role" keys on this page.
+```
+
+
+
+
+
+
+## Building the App
+
+Let's start building the React Native app from scratch.
+
+### Initialize a React Native app
+
+We can use [`expo`](https://docs.expo.dev/get-started/create-a-new-app/) to initialize
+an app called `supabaseReactNative`:
+
+```bash
+#select the "Blank" template when prompted
+expo init supabaseReactNative
+
+cd supabaseReactNative
+```
+
+Then let's install the only additional dependency: [supabase-js](https://github.com/supabase/supabase-js)
+
+```bash
+yarn add @supabase/supabase-js
+```
+
+Now let's create a helper file to initialize the Supabase client.
+We need the API URL and the `anon` key that you copied [earlier](#get-the-api-keys).
+These variables will be exposed on the browser, and that's completely fine since we have
+[Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database.
+
+```js title="lib/supabase.js"
+import { createClient } from '@supabase/supabase-js'
+
+const supabaseUrl = YOUR_REACT_NATIVE_SUPABASE_URL
+const supabaseAnonKey = YOUR_REACT_NATIVE_SUPABASE_ANON_KEY
+
+export const supabase = createClient(supabaseUrl, supabaseAnonKey)
+```
+
+### Set up a Login component
+
+Let's set up a React Native component to manage logins and sign ups.
+We'll use Magic Links, so users can sign in with their email without using passwords.
+
+```jsx title="components/Auth.js"
+import React, {useState} from 'react'
+import {Alert, StyleSheet, View} from 'react-native'
+import {supabaseClient} from '../lib/initSupabase'
+import {Button, Input} from 'react-native-elements'
+
+export default function Auth() {
+ const [email, setEmail] = useState('')
+ const [password, setPassword] = useState('')
+ const [loading, setLoading] = useState('')
+
+ async function signInWithEmail() {
+ const { user, error } = await supabase.auth.signIn({
+ email: email,
+ password: password,
+ })
+ }
+
+ async function signUpWithEmail() {
+ const { user, error } = await supabase.auth.signUp({
+ email: email,
+ password: password,
+ })
+ }
+
+ return (
+
+
+ setEmail(text)}
+ value={email}
+ placeholder="email@address.com"
+ autoCapitalize={'none'}
+ />
+
+
+ setPassword(text)}
+ value={password}
+ secureTextEntry={true}
+ placeholder="Password"
+ autoCapitalize={'none'}
+ />
+
+
+
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ marginTop: 40,
+ padding: 12,
+ },
+ verticallySpaced: {
+ paddingTop: 4,
+ paddingBottom: 4,
+ alignSelf: 'stretch',
+ },
+ mt20: {
+ marginTop: 20,
+ },
+});
+```
+
+### Account page
+
+After a user is signed in we can allow them to edit their profile details and manage their account.
+
+Let's create a new component for that called `Account.js`.
+
+```jsx title="components/Account.js"
+import { useState, useEffect } from 'react'
+import { supabase } from '../utils/supabaseClient'
+import {StyleSheet, View} from 'react-native'
+
+import {Button, Input} from 'react-native-elements'
+
+export default function Account({ session }) {
+ const [loading, setLoading] = useState(true)
+ const [username, setUsername] = useState(null)
+ const [website, setWebsite] = useState(null)
+ const [avatar_url, setAvatarUrl] = useState(null)
+
+ useEffect(() => {
+ getProfile()
+ }, [session])
+
+ async function getProfile() {
+ try {
+ setLoading(true)
+ const user = supabase.auth.user()
+
+ let { data, error, status } = await supabase
+ .from('profiles')
+ .select(`username, website, avatar_url`)
+ .eq('id', user.id)
+ .single()
+
+ if (error && status !== 406) {
+ throw error
+ }
+
+ if (data) {
+ setUsername(data.username)
+ setWebsite(data.website)
+ setAvatarUrl(data.avatar_url)
+ }
+ } catch (error) {
+ alert(error.message)
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ async function updateProfile({ username, website, avatar_url }) {
+ try {
+ setLoading(true)
+ const user = supabase.auth.user()
+
+ const updates = {
+ id: user.id,
+ username,
+ website,
+ avatar_url,
+ 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 (
+
+
+
+
+
+ setUsername(e.target.value)}
+ />
+
+
+
+ setWebsite(e.target.value)}
+ />
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+```
+
+### Launch!
+
+Now that we have all the components in place, let's update `app.js`:
+
+```jsx title="app.js"
+import { useState, useEffect } from 'react'
+import { supabase } from './lib/supabaseClient'
+import Auth from './components/Auth'
+import Account from './components/Account'
+import View from 'react-native'
+
+export default function App() {
+ const [session, setSession] = useState(null)
+
+ useEffect(() => {
+ setSession(supabase.auth.session())
+
+ supabase.auth.onAuthStateChange((_event, session) => {
+ setSession(session)
+ })
+ }, [])
+
+ return (
+
+ {!session ? : }
+
+ )
+}
+```
+
+Once that's done, run this in a terminal window:
+
+```bash
+yarn start
+```
+
+And then open the browser to [localhost:3000](http://localhost:3000) and you should see the completed app.
+
+
+## Bonus: Profile photos
+
+Every Supabase project is configured with [Storage](/docs/guides/storage) for managing large files like
+photos and videos.
+
+### Create an upload widget
+
+Let's create an avatar for the user so that they can upload a profile photo.
+We can start by creating a new component:
+
+```jsx title="components/Avatar.js"
+import { useEffect, useState } from 'react'
+import { supabase } from '../lib/supabase'
+import {Alert, StyleSheet, View, Image} from 'react-native'
+import {Button, Input} from 'react-native-elements'
+
+export default function Avatar({ url, size, onUpload }) {
+ const [avatarUrl, setAvatarUrl] = useState(null)
+ const [uploading, setUploading] = useState(false)
+
+ useEffect(() => {
+ if (url) downloadImage(url)
+ }, [url])
+
+ async function downloadImage(path) {
+ try {
+ const { data, error } = await supabase.storage.from('avatars').download(path)
+ if (error) {
+ throw error
+ }
+ const url = URL.createObjectURL(data)
+ setAvatarUrl(url)
+ } catch (error) {
+ console.log('Error downloading image: ', error.message)
+ }
+ }
+
+
+ async function uploadAvatar(event) {
+ try {
+ setUploading(true)
+
+ if (!event.target.files || event.target.files.length === 0) {
+ throw new Error('You must select an image to upload.')
+ }
+
+ const file = event.target.files[0]
+ const fileExt = file.name.split('.').pop()
+ const fileName = `${Math.random()}.${fileExt}`
+ const filePath = `${fileName}`
+
+ let { error: uploadError } = await supabase.storage
+ .from('avatars')
+ .upload(filePath, file)
+
+ if (uploadError) {
+ throw uploadError
+ }
+
+ onUpload(filePath)
+ } catch (error) {
+ alert(error.message)
+ } finally {
+ setUploading(false)
+ }
+ }
+
+ return (
+
+ {avatarUrl ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ )
+}
+```
+
+## Next steps
+
+At this stage you have a fully functional application!
+
+- Got a question? [Ask here](https://github.com/supabase/supabase/discussions).
+- Sign in: [app.supabase.io](https://app.supabase.io)
diff --git a/web/docs/handbook/contributing.mdx b/web/docs/handbook/contributing.mdx
index befc7809563..48d0da689ff 100644
--- a/web/docs/handbook/contributing.mdx
+++ b/web/docs/handbook/contributing.mdx
@@ -8,13 +8,20 @@ import Sponsors from '@site/src/components/Sponsors'
## How to contribute
+Supabase has many [open source repos](https://github.com/orgs/supabase/repositories). Some of the more popular ones are:
+ - [Supabase](https://github.com/supabase/supabase) - Our Dashboard, Websites, and example apps
+ - [Supabase-js](https://github.com/supabase/supabase-js) - A clientside library written in Typescript
+ - [Gotrue](https://github.com/supabase/gotrue) - Our auth server, written in Go
+ - [Realtime](https://github.com/supabase/realtime) - A server for streaming changes from Postgres, written in Elixir
+ - [browse all]((https://github.com/orgs/supabase/repositories))
+
Want to contribute? Why not jump into our GitHub repo and:
- [Sponsor Supabase](https://github.com/sponsors/supabase).
- [Answer Discussions](https://github.com/supabase/supabase/discussions).
-- Submit an issue.
+- Submit an issue to one of our [repos](https://github.com/orgs/supabase/repositories)
- Report a performance issue or a part of the documentation that you find confusing.
-- Create a pull request.
+- Create a pull request in one of our [repos](https://github.com/orgs/supabase/repositories)
- [Translate our Readme](https://github.com/supabase/supabase/issues/1341).
- Try our products and give feedback.
- Spread the word if you like what we are doing.
diff --git a/web/docusaurus.config.js b/web/docusaurus.config.js
index dec7083cb5c..29dfa601602 100755
--- a/web/docusaurus.config.js
+++ b/web/docusaurus.config.js
@@ -56,19 +56,12 @@ module.exports = {
},
items: [
{
- label: 'Overview',
+ label: 'Guides',
to: '/',
activeBaseRegex:
'.*.docs/$|^/docs/architecture|(^/docs/guides/(database|auth|storage|api|examples!))|(^/docs/guides/(examples|with-angular|with-flutter|with-nextjs|with-react|with-redwoodjs|with-svelte|with-vue-3)|^/docs/faq|^/docs/going-into-prod|^/docs/handbook|^/docs/company)',
position: 'left',
},
- {
- label: 'Guides',
- to: '/docs/guides',
- activeBaseRegex:
- '(^/docs/guides/database/|^/docs/guides/auth/)|(/docs/guides$)|docs/(resources|learn)',
- position: 'left',
- },
{
label: 'Reference',
to: '/docs/reference/javascript/supabase-client',
diff --git a/web/package-lock.json b/web/package-lock.json
index 7d2761e54ed..ce865eb1d70 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -7547,9 +7547,9 @@
"integrity": "sha512-2nMHqg1x5PU+unxX7PGY7AuYxl2qDx7PSrTRjizr8sxdd3l/3hBuWWaki62qmtYm2U5i4Z5E7GbjlyDFhs9/EQ=="
},
"nanoid": {
- "version": "3.1.25",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.25.tgz",
- "integrity": "sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q=="
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz",
+ "integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA=="
},
"nanomatch": {
"version": "1.2.13",
diff --git a/web/sidebars.js b/web/sidebars.js
index 4406d585699..6e11581c2cf 100755
--- a/web/sidebars.js
+++ b/web/sidebars.js
@@ -43,20 +43,19 @@ module.exports = {
items: [
'about',
'architecture',
- 'guides/database',
- 'guides/auth',
- 'guides/storage',
- 'guides/api',
+ 'guides/hosting/platform',
+ 'guides/local-development',
'guides/examples',
],
collapsed: false,
},
{
type: 'category',
- label: 'Tutorials',
+ label: 'Quickstarts',
collapsed: false,
items: [
'guides/with-angular',
+ 'guides/with-expo',
'guides/with-flutter',
'guides/with-nextjs',
'guides/with-react',
@@ -65,34 +64,12 @@ module.exports = {
'guides/with-vue-3',
],
},
- {
- type: 'category',
- label: 'See Also',
- collapsed: false,
- items: [
- 'faq',
- 'going-into-prod',
- 'handbook/contributing',
- 'handbook/supasquad',
- 'company/terms',
- 'company/privacy',
- 'company/aup',
- ],
- },
- ],
- guides: [
- {
- type: 'category',
- label: 'Guides',
- items: ['guides', 'guides/local-development'],
- collapsed: false,
- },
{
type: 'category',
label: 'Database',
collapsed: false,
items: [
- 'guides/database/introduction',
+ 'guides/database',
'guides/database/connecting-to-postgres',
'guides/database/tables',
'guides/database/functions',
@@ -125,12 +102,18 @@ module.exports = {
},
],
},
+ {
+ type: 'category',
+ label: 'APIs',
+ collapsed: false,
+ items: ['guides/api'],
+ },
{
type: 'category',
label: 'Auth',
collapsed: false,
items: [
- 'guides/auth/intro',
+ 'guides/auth',
{
type: 'category',
label: 'Authentication',
@@ -174,6 +157,12 @@ module.exports = {
},
],
},
+ {
+ type: 'category',
+ label: 'Storage',
+ collapsed: false,
+ items: ['guides/storage'],
+ },
{
type: 'category',
label: 'Integrations',
@@ -195,7 +184,21 @@ module.exports = {
type: 'category',
label: 'Self Hosting',
collapsed: false,
- items: ['guides/hosting/overview', 'guides/hosting/platform', 'guides/hosting/docker'],
+ items: ['guides/hosting/overview', 'guides/hosting/docker'],
+ },
+ {
+ type: 'category',
+ label: 'See Also',
+ collapsed: false,
+ items: [
+ 'faq',
+ 'going-into-prod',
+ 'handbook/contributing',
+ 'handbook/supasquad',
+ 'company/terms',
+ 'company/privacy',
+ 'company/aup',
+ ],
},
],
postgresServer: [
diff --git a/web/spec/dart.yml b/web/spec/dart.yml
index 86880ddc94c..24e0f9b6517 100644
--- a/web/spec/dart.yml
+++ b/web/spec/dart.yml
@@ -772,6 +772,7 @@ pages:
title: 'stream()'
notes: |
- `stream()` will emit the initial data as well as any further change on the database as `Stream` of `List