From 0271ab45caeac2be3999f8947f5fb53754ed714e Mon Sep 17 00:00:00 2001 From: Benjamin Dobell Date: Sun, 2 Oct 2022 03:41:01 +1100 Subject: [PATCH 001/157] Redirect URLs, not Redirect Domains --- .../interfaces/Auth/Auth.constants.ts | 6 +- .../RedirectUrlList.tsx} | 14 +-- .../RedirectUrls.tsx} | 95 +++++++++++-------- .../ValueContainer.tsx | 0 .../interfaces/Auth/SmtpForm/SmtpForm.tsx | 4 +- studio/pages/project/[ref]/auth/settings.tsx | 4 +- .../AuthProvidersFormValidation.tsx | 12 +-- .../components/Auth/Auth.constants.test.js | 40 ++++---- 8 files changed, 93 insertions(+), 82 deletions(-) rename studio/components/interfaces/Auth/{RedirectDomains/DomainList.tsx => RedirectUrls/RedirectUrlList.tsx} (82%) rename studio/components/interfaces/Auth/{RedirectDomains/RedirectDomains.tsx => RedirectUrls/RedirectUrls.tsx} (64%) rename studio/components/interfaces/Auth/{RedirectDomains => RedirectUrls}/ValueContainer.tsx (100%) diff --git a/studio/components/interfaces/Auth/Auth.constants.ts b/studio/components/interfaces/Auth/Auth.constants.ts index 32f80bc5cb2..aac17a564ec 100644 --- a/studio/components/interfaces/Auth/Auth.constants.ts +++ b/studio/components/interfaces/Auth/Auth.constants.ts @@ -7,7 +7,7 @@ // "https://uptime-monitor-fe.vercel.app/" // Supports wildcards, port numbers at the end, paths at the end and query params -const baseDomainRegex = +const baseUrlRegex = /^((ftp|http|https):\/\/)?(www.)?(?!.*(ftp|http|https|www.))[a-zA-Z0-9_*-]+(\.[a-zA-Z0-9_*-]+)+((\/)[\w#]+)*(\/\w+\?[a-zA-Z0-9_]+=\w+(&[a-zA-Z0-9_]+=\w+)*)+(?:\.[a-z]+)*(?::\d+)?(?![^<]*(?:<\/\w+>|\/?>))(.*)?\/?(.)*?$/gm // iOS deep linking scheme https://benoitpasquier.com/deep-linking-url-scheme-ios/ @@ -21,7 +21,7 @@ const localhostRegex = /^(?:^|\s)((https?:\/\/)?(?:localhost|[\w-]+(?:\.[\w-]+)+ const chromeExtensionRegex = /chrome-extension:\/\/([a-zA-Z]*)/gm // combine the above regexes -export const domainRegex = new RegExp( - `(${baseDomainRegex.source})|(${localhostRegex.source})|(${appRegex.source})|(${chromeExtensionRegex.source})`, +export const urlRegex = new RegExp( + `(${baseUrlRegex.source})|(${localhostRegex.source})|(${appRegex.source})|(${chromeExtensionRegex.source})`, 'i' ) diff --git a/studio/components/interfaces/Auth/RedirectDomains/DomainList.tsx b/studio/components/interfaces/Auth/RedirectUrls/RedirectUrlList.tsx similarity index 82% rename from studio/components/interfaces/Auth/RedirectDomains/DomainList.tsx rename to studio/components/interfaces/Auth/RedirectUrls/RedirectUrlList.tsx index 08929879c79..72eeab1194e 100644 --- a/studio/components/interfaces/Auth/RedirectDomains/DomainList.tsx +++ b/studio/components/interfaces/Auth/RedirectUrls/RedirectUrlList.tsx @@ -9,10 +9,10 @@ import { HorizontalShimmerWithIcon } from 'components/ui/Shimmers' interface Props { canUpdate: boolean - onSelectDomainToDelete: (domain: string) => void + onSelectUrlToDelete: (url: string) => void } -const DomainList: FC = ({ canUpdate, onSelectDomainToDelete }) => { +const RedirectUrlList: FC = ({ canUpdate, onSelectUrlToDelete }) => { const { authConfig } = useStore() const URI_ALLOW_LIST_ARRAY = authConfig.config.URI_ALLOW_LIST @@ -31,20 +31,20 @@ const DomainList: FC = ({ canUpdate, onSelectDomainToDelete }) => { ) : URI_ALLOW_LIST_ARRAY.length > 0 ? ( - URI_ALLOW_LIST_ARRAY.map((domain: string) => { + URI_ALLOW_LIST_ARRAY.map((url: string) => { return ( - +
- {domain} + {url}
{canUpdate && ( @@ -69,4 +69,4 @@ const DomainList: FC = ({ canUpdate, onSelectDomainToDelete }) => { ) } -export default observer(DomainList) +export default observer(RedirectUrlList) diff --git a/studio/components/interfaces/Auth/RedirectDomains/RedirectDomains.tsx b/studio/components/interfaces/Auth/RedirectUrls/RedirectUrls.tsx similarity index 64% rename from studio/components/interfaces/Auth/RedirectDomains/RedirectDomains.tsx rename to studio/components/interfaces/Auth/RedirectUrls/RedirectUrls.tsx index 5eede56fce7..ad8e9a80e8f 100644 --- a/studio/components/interfaces/Auth/RedirectDomains/RedirectDomains.tsx +++ b/studio/components/interfaces/Auth/RedirectUrls/RedirectUrls.tsx @@ -7,10 +7,10 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' import { checkPermissions, useStore } from 'hooks' import { FormHeader } from 'components/ui/Forms' -import { domainRegex } from '../Auth.constants' -import DomainList from './DomainList' +import { urlRegex } from '../Auth.constants' +import RedirectUrlList from './RedirectUrlList' -const RedirectDomains = () => { +const RedirectUrls = () => { const { authConfig, ui } = useStore() const URI_ALLOW_LIST_ARRAY = authConfig.config.URI_ALLOW_LIST @@ -19,24 +19,24 @@ const RedirectDomains = () => { const [open, setOpen] = useState(false) const [isDeleting, setIsDeleting] = useState(false) - const [selectedDomainToDelete, setSelectedDomainToDelete] = useState() + const [selectedUrlToDelete, setSelectedUrlToDelete] = useState() const canUpdateConfig = checkPermissions(PermissionAction.UPDATE, 'custom_config_gotrue') - const newDomainSchema = object({ - domain: string().matches(domainRegex, 'URL is not valid').required(), + const newUrlSchema = object({ + url: string().matches(urlRegex, 'URL is not valid').required(), }) - const onAddNewDomain = async (values: any, { setSubmitting }: any) => { + const onAddNewUrl = async (values: any, { setSubmitting }: any) => { setSubmitting(true) const payload = URI_ALLOW_LIST_ARRAY - payload.push(values.domain) + payload.push(values.url) const payloadString = payload.toString() if (payloadString.length > 2 * 1024) { ui.setNotification({ - message: 'Too many redirect domains, please remove some or try to use wildcards', + message: 'Too many redirect URLs, please remove some or try to use wildcards', category: 'error', }) @@ -47,36 +47,36 @@ const RedirectDomains = () => { const { error } = await authConfig.update({ URI_ALLOW_LIST: payloadString }) if (!error) { setOpen(false) - ui.setNotification({ category: 'success', message: 'Successfully added domain' }) + ui.setNotification({ category: 'success', message: 'Successfully added URL' }) } else { ui.setNotification({ error, category: 'error', - message: `Failed to update domain: ${error?.message}`, + message: `Failed to update URL: ${error?.message}`, }) } setSubmitting(false) } - const onConfirmDeleteDomain = async (domain?: string) => { - if (!domain) return + const onConfirmDeleteUrl = async (url?: string) => { + if (!url) return setIsDeleting(true) - // Remove selectedDomain from array and update - const payload = URI_ALLOW_LIST_ARRAY.filter((e: string) => e !== domain) + // Remove selectedUrl from array and update + const payload = URI_ALLOW_LIST_ARRAY.filter((e: string) => e !== url) const { error } = await authConfig.update({ URI_ALLOW_LIST: payload.toString() }) if (!error) { - setSelectedDomainToDelete(undefined) - ui.setNotification({ category: 'success', message: 'Successfully removed domain' }) + setSelectedUrlToDelete(undefined) + ui.setNotification({ category: 'success', message: 'Successfully removed URL' }) } else { ui.setNotification({ error, category: 'error', - message: `Failed to remove domain: ${error?.message}`, + message: `Failed to remove URL: ${error?.message}`, }) } @@ -88,12 +88,12 @@ const RedirectDomains = () => {
{!canUpdateConfig && ( @@ -113,36 +113,47 @@ const RedirectDomains = () => { )}
- + setOpen(!open)} - header={

Add a new domain

} + header={

Add a new URL

} >
{({ isSubmitting }: { isSubmitting: boolean }) => { return (

- This will add a domain to a list of allowed domains that can interact with your - Authenticaton services for this project. + This will add a URL to a list of allowed URLs that can interact with your + Authentication services for this project. +

+

+ Supports{' '} + + globs + + {' '}where . and / are used as delimiters.

@@ -150,13 +161,13 @@ const RedirectDomains = () => {
@@ -167,18 +178,18 @@ const RedirectDomains = () => { Remove domain} - onCancel={() => setSelectedDomainToDelete(undefined)} + visible={selectedUrlToDelete !== undefined} + header={

Remove URL

} + onCancel={() => setSelectedUrlToDelete(undefined)} >

Are you sure you want to remove{' '} - {selectedDomainToDelete}? + {selectedUrlToDelete}?

- This domain will no longer work with your authentication configuration. + This URL will no longer work with your authentication configuration.

@@ -187,7 +198,7 @@ const RedirectDomains = () => { block type="default" size="medium" - onClick={() => setSelectedDomainToDelete(undefined)} + onClick={() => setSelectedUrlToDelete(undefined)} > Cancel @@ -196,9 +207,9 @@ const RedirectDomains = () => { size="medium" type="warning" loading={isDeleting} - onClick={() => onConfirmDeleteDomain(selectedDomainToDelete)} + onClick={() => onConfirmDeleteUrl(selectedUrlToDelete)} > - {isDeleting ? 'Removing...' : 'Remove domain'} + {isDeleting ? 'Removing...' : 'Remove URL'}
@@ -207,4 +218,4 @@ const RedirectDomains = () => { ) } -export default observer(RedirectDomains) +export default observer(RedirectUrls) diff --git a/studio/components/interfaces/Auth/RedirectDomains/ValueContainer.tsx b/studio/components/interfaces/Auth/RedirectUrls/ValueContainer.tsx similarity index 100% rename from studio/components/interfaces/Auth/RedirectDomains/ValueContainer.tsx rename to studio/components/interfaces/Auth/RedirectUrls/ValueContainer.tsx diff --git a/studio/components/interfaces/Auth/SmtpForm/SmtpForm.tsx b/studio/components/interfaces/Auth/SmtpForm/SmtpForm.tsx index 888e2c4d161..f6eb10046a4 100644 --- a/studio/components/interfaces/Auth/SmtpForm/SmtpForm.tsx +++ b/studio/components/interfaces/Auth/SmtpForm/SmtpForm.tsx @@ -13,7 +13,7 @@ import { FormSectionLabel, } from 'components/ui/Forms' import { useStore, checkPermissions } from 'hooks' -import { domainRegex } from './../Auth.constants' +import { urlRegex } from './../Auth.constants' import { defaultDisabledSmtpFormValues } from './SmtpForm.constants' import { generateFormValues, isSmtpEnabled } from './SmtpForm.utils' @@ -55,7 +55,7 @@ const SmtpForm = () => { }, then: (schema) => schema - .matches(domainRegex, 'Must be a valid URL or IP address') + .matches(urlRegex, 'Must be a valid URL or IP address') .required('Host URL is required.'), otherwise: (schema) => schema, }), diff --git a/studio/pages/project/[ref]/auth/settings.tsx b/studio/pages/project/[ref]/auth/settings.tsx index ea14e36bc0d..0bc8ed2ca78 100644 --- a/studio/pages/project/[ref]/auth/settings.tsx +++ b/studio/pages/project/[ref]/auth/settings.tsx @@ -6,7 +6,7 @@ import { NextPageWithLayout } from 'types' import { checkPermissions, useStore } from 'hooks' import { AuthProvidersForm } from 'components/interfaces' import AutoSchemaForm from 'components/interfaces/Auth/AutoSchemaForm' -import RedirectDomains from 'components/interfaces/Auth/RedirectDomains/RedirectDomains' +import RedirectUrls from 'components/interfaces/Auth/RedirectUrls/RedirectUrls' import { AuthLayout } from 'components/layouts' import { FormsContainer } from 'components/ui/Forms' import NoPermission from 'components/ui/NoPermission' @@ -26,7 +26,7 @@ const PageLayout: NextPageWithLayout = () => { return ( - + ) diff --git a/studio/stores/authConfig/schema/AuthProviders/AuthProvidersFormValidation.tsx b/studio/stores/authConfig/schema/AuthProviders/AuthProvidersFormValidation.tsx index 3ceb9e46087..7d10bf80748 100644 --- a/studio/stores/authConfig/schema/AuthProviders/AuthProvidersFormValidation.tsx +++ b/studio/stores/authConfig/schema/AuthProviders/AuthProvidersFormValidation.tsx @@ -1,5 +1,5 @@ import { boolean, number, object, string } from 'yup' -import { domainRegex } from 'components/interfaces/Auth/Auth.constants' +import { urlRegex } from 'components/interfaces/Auth/Auth.constants' const JSON_SCHEMA_VERSION = 'http://json-schema.org/draft-07/schema#' @@ -379,7 +379,7 @@ const EXTERNAL_PROVIDER_AZURE = { then: (schema) => schema.required('Secret ID is required'), otherwise: (schema) => schema, }), - EXTERNAL_AZURE_URL: string().matches(domainRegex, 'Must be a valid URL').optional(), + EXTERNAL_AZURE_URL: string().matches(urlRegex, 'Must be a valid URL').optional(), }), misc: { iconKey: 'microsoft-icon', @@ -577,7 +577,7 @@ const EXTERNAL_PROVIDER_GITLAB = { then: (schema) => schema.required('Client Secret is required'), otherwise: (schema) => schema, }), - EXTERNAL_GITLAB_URL: string().matches(domainRegex, 'Must be a valid URL').optional(), + EXTERNAL_GITLAB_URL: string().matches(urlRegex, 'Must be a valid URL').optional(), }), misc: { iconKey: 'gitlab-icon', @@ -665,8 +665,8 @@ const EXTERNAL_PROVIDER_KEYCLOAK = { EXTERNAL_KEYCLOAK_URL: string().when('EXTERNAL_KEYCLOAK_ENABLED', { is: true, then: (schema) => - schema.matches(domainRegex, 'Must be a valid URL').required('Realm URL is required'), - otherwise: (schema) => schema.matches(domainRegex, 'Must be a valid URL'), + schema.matches(urlRegex, 'Must be a valid URL').required('Realm URL is required'), + otherwise: (schema) => schema.matches(urlRegex, 'Must be a valid URL'), }), }), misc: { @@ -931,7 +931,7 @@ const EXTERNAL_PROVIDER_WORKOS = { validationSchema: object().shape({ EXTERNAL_WORKOS_ENABLED: boolean().required(), EXTERNAL_WORKOS_URL: string() - .matches(domainRegex, 'Must be a valid URL') + .matches(urlRegex, 'Must be a valid URL') .when('EXTERNAL_WORKOS_ENABLED', { is: true, then: (schema) => schema.required('WorkOS URL is required'), diff --git a/studio/tests/components/Auth/Auth.constants.test.js b/studio/tests/components/Auth/Auth.constants.test.js index 4c8a3fe62f3..ab7504014dc 100644 --- a/studio/tests/components/Auth/Auth.constants.test.js +++ b/studio/tests/components/Auth/Auth.constants.test.js @@ -1,87 +1,87 @@ -import { domainRegex } from 'components/interfaces/Auth/Auth.constants' +import { urlRegex } from 'components/interfaces/Auth/Auth.constants' describe('Auth.constants: domainRegex', () => { test('should validate typical URLs', () => { const mockInput1 = 'http://domain.com' - const output1 = domainRegex.test(mockInput1) + const output1 = urlRegex.test(mockInput1) expect(output1).toBe(true) const mockInput2 = 'https://supabase.io' - const output2 = domainRegex.test(mockInput2) + const output2 = urlRegex.test(mockInput2) expect(output2).toBe(true) const mockInput3 = 'https://new-domain-vercel.com' - const output3 = domainRegex.test(mockInput3) + const output3 = urlRegex.test(mockInput3) expect(output3).toBe(true) const mockInput4 = 'www.test-domain.com' - const output4 = domainRegex.test(mockInput4) + const output4 = urlRegex.test(mockInput4) expect(output4).toBe(true) }) test('should validate app-based domains', () => { const mockInput1 = 'exp://exp.host/some-app' - const output1 = domainRegex.test(mockInput1) + const output1 = urlRegex.test(mockInput1) expect(output1).toBe(true) const mockInput2 = 'exp://exp.host/some-app?release-channel=default' - const output2 = domainRegex.test(mockInput2) + const output2 = urlRegex.test(mockInput2) expect(output2).toBe(true) }) test('should validate subdomains', () => { const mockInput1 = 'https://app.supabase.com' - const output1 = domainRegex.test(mockInput1) + const output1 = urlRegex.test(mockInput1) expect(output1).toBe(true) }) test('should validate localhost URLs', () => { const mockInput1 = 'http://localhost:3000' - const output1 = domainRegex.test(mockInput1) + const output1 = urlRegex.test(mockInput1) expect(output1).toBe(true) }) test('should validate URLs with query params', () => { const mockInput1 = 'https://supabase.com?name=test' - const output1 = domainRegex.test(mockInput1) + const output1 = urlRegex.test(mockInput1) expect(output1).toBe(true) const mockInput2 = 'https://supabase.com?name=test&description=hello&page=2' - const output2 = domainRegex.test(mockInput2) + const output2 = urlRegex.test(mockInput2) expect(output2).toBe(true) }) test('should validate URLs with wildcards', () => { const mockInput1 = 'https://supabase*.com' - const output1 = domainRegex.test(mockInput1) + const output1 = urlRegex.test(mockInput1) expect(output1).toBe(true) const mockInput2 = 'https://supabase.com/*' - const output2 = domainRegex.test(mockInput2) + const output2 = urlRegex.test(mockInput2) expect(output2).toBe(true) const mockInput3 = 'https://new-*-domain.com/*' - const output3 = domainRegex.test(mockInput3) + const output3 = urlRegex.test(mockInput3) expect(output3).toBe(true) const mockInput4 = 'https://new-*-domain.com/*/*/*' - const output4 = domainRegex.test(mockInput4) + const output4 = urlRegex.test(mockInput4) expect(output4).toBe(true) const mockInput5 = 'https://sub-*-domain.new-*-domain.com/*/*/*' - const output5 = domainRegex.test(mockInput5) + const output5 = urlRegex.test(mockInput5) expect(output5).toBe(true) }) test('should invalidate invalid URLs', () => { const mockInput1 = 'supabase' - const output1 = domainRegex.test(mockInput1) + const output1 = urlRegex.test(mockInput1) expect(output1).toBe(false) const mockInput2 = 'mailto:test@gmail.com' - const output2 = domainRegex.test(mockInput2) + const output2 = urlRegex.test(mockInput2) expect(output2).toBe(false) const mockInput4 = 'hello world.com' - const output4 = domainRegex.test(mockInput4) + const output4 = urlRegex.test(mockInput4) expect(output4).toBe(false) const mockInput5 = 'email@domain.com' - const output5 = domainRegex.test(mockInput5) + const output5 = urlRegex.test(mockInput5) expect(output5).toBe(false) }) }) From f83bd85f1d2c8b31d99062e21a699880540be186 Mon Sep 17 00:00:00 2001 From: gitstart Date: Fri, 25 Nov 2022 16:12:56 +0000 Subject: [PATCH 002/157] fix: rename/remove table filter/sort rules on column rename --- .../SidePanelEditor/SidePanelEditor.tsx | 82 ++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx index 8a0d3ee57d3..c04a98fff2a 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx @@ -9,11 +9,13 @@ import { PostgresType, } from '@supabase/postgres-meta' -import { useStore } from 'hooks' +import { useStore, useUrlState } from 'hooks' import { RowEditor, ColumnEditor, TableEditor } from '.' import { ImportContent } from './TableEditor/TableEditor.types' import { ColumnField, CreateColumnPayload, UpdateColumnPayload } from './SidePanelEditor.types' import ConfirmationModal from 'components/ui/ConfirmationModal' +import { onLoadStorage, saveStorageDebounced } from 'components/grid/SupabaseGrid.utils' +import { InitialStateType } from 'components/grid/store/reducers' interface Props { selectedSchema: string @@ -48,9 +50,11 @@ const SidePanelEditor: FC = ({ onColumnSaved = () => {}, }) => { const { meta, ui } = useStore() + const projectRef = ui.selectedProject?.ref const [isEdited, setIsEdited] = useState(false) const [isClosingPanel, setIsClosingPanel] = useState(false) + const [_, setParams] = useUrlState({ arrayKeys: ['filter', 'sort'] }) const tables = meta.tables.list() const enumTypes = meta.types.list( @@ -121,6 +125,65 @@ const SidePanelEditor: FC = ({ } } + /** + * Renames a column's filter and/or sort rules if the related column has been renamed. + */ + const updateSortsAndFiltersAfterColumnRename = (oldColumnName: string, newColumnName: string) => { + setParams((prevParams) => { + const existingFilters = (prevParams?.filter ?? []) as string[] + const existingSorts = (prevParams?.sort ?? []) as string[] + + return { + ...prevParams, + filter: existingFilters.map((filter: string) => { + const [column] = filter.split(':') + return column === oldColumnName ? filter.replace(column, newColumnName) : filter + }), + sort: existingSorts.map((sort: string) => { + const [column] = sort.split(':') + return column === oldColumnName ? sort.replace(column, newColumnName) : sort + }), + } + }) + } + + /** + * Removes tables's column filter and/or sort rules if the related column unavailable + * (resulting from name change or deleted column) + */ + const updateSavedStateAfterTableUpdate = ( + storageRef: string, + tableName: string, + schema: string | null, + tableColumns: ColumnField[] + ) => { + const savedState = onLoadStorage(storageRef, tableName, schema) + const columnNames = tableColumns.map((columnInstance) => columnInstance.name) + const { filters, sorts }: { filters?: string[]; sorts?: string[] } = savedState + const state = { + table: { name: tableName, schema }, + gridColumns: savedState?.gridColumns, + } as InitialStateType + + const newSorts = sorts?.filter((filterQuery) => { + const [column] = filterQuery.split(':') + + return columnNames.includes(column) + }) + const newFilters = filters?.filter((sortrQuery) => { + const [column] = sortrQuery.split(':') + + return columnNames.includes(column) + }) + + saveStorageDebounced( + state, + storageRef, + newSorts?.length ? newSorts : undefined, + newFilters?.length ? newFilters : undefined + ) + } + const saveColumn = async ( payload: CreateColumnPayload | UpdateColumnPayload, foreignKey: Partial | undefined, @@ -144,6 +207,15 @@ const SidePanelEditor: FC = ({ if (response?.error) { ui.setNotification({ category: 'error', message: response.error.message }) } else { + if ( + !isNewRecord && + payload.name && + selectedColumnToEdit && + selectedColumnToEdit.name !== payload.name + ) { + updateSortsAndFiltersAfterColumnRename(selectedColumnToEdit.name, payload.name) + } + await meta.tables.loadById(selectedTable!.id) onColumnSaved() setIsEdited(false) @@ -235,6 +307,14 @@ const SidePanelEditor: FC = ({ message: `Successfully updated ${table.name}!`, }) } + if (projectRef) { + updateSavedStateAfterTableUpdate( + projectRef, + selectedTableToEdit.name, + selectedTableToEdit.schema, + columns + ) + } } } catch (error: any) { saveTableError = true From c2d0825d42b2867a7dea8754bff9040ad1a02f70 Mon Sep 17 00:00:00 2001 From: gitstart Date: Mon, 26 Dec 2022 17:24:31 +0000 Subject: [PATCH 003/157] chore: remove unavailable columns from sorts and filters --- studio/components/grid/SupabaseGrid.tsx | 30 +++++++ studio/components/grid/utils/row.ts | 15 +++- .../SidePanelEditor/SidePanelEditor.tsx | 82 +------------------ 3 files changed, 43 insertions(+), 84 deletions(-) diff --git a/studio/components/grid/SupabaseGrid.tsx b/studio/components/grid/SupabaseGrid.tsx index 75b3abf65b9..c454b675216 100644 --- a/studio/components/grid/SupabaseGrid.tsx +++ b/studio/components/grid/SupabaseGrid.tsx @@ -95,6 +95,36 @@ const SupabaseGridLayout = forwardRef((props }, []) useEffect(() => { + if (state.refreshPageFlag == 0) { + return + } + + const columnNames = state.table?.columns.map((column) => column.name) + + if ( + columnNames && + (sorts.some((sortInstance) => !columnNames.includes(sortInstance.column)) || + filters.some((filterInstance) => !columnNames.includes(filterInstance.column))) + ) { + // Remove unavailable columns from "sort" and "filter" query string parameters + setParams((prevParams) => { + return { + ...prevParams, + filter: filters + .filter((filterInstance) => columnNames.includes(filterInstance.column)) + .map( + (filterInstance) => + `${filterInstance.column}:${filterInstance.operator}:${filterInstance.value}` + ), + sort: sorts + .filter((sortInstance) => columnNames.includes(sortInstance.column)) + .map( + (sortInstance) => `${sortInstance.column}:${sortInstance.ascending ? 'asc' : 'desc'}` + ), + } + }) + } + if (state.refreshPageFlag === REFRESH_PAGE_IMMEDIATELY) { fetchPage(state, dispatch, sorts, filters) } else if (state.refreshPageFlag !== 0) { diff --git a/studio/components/grid/utils/row.ts b/studio/components/grid/utils/row.ts index aabf2c85a78..59f43166ee4 100644 --- a/studio/components/grid/utils/row.ts +++ b/studio/components/grid/utils/row.ts @@ -9,7 +9,11 @@ export async function fetchCount( ) { if (!state.rowService) return - const { data, error } = await state.rowService.count(filters) + // Remove unavailable columns from filters + const columnNames = state.table?.columns.map((column) => column.name) ?? [] + const cleanedFilters = filters.filter((filter) => columnNames.includes(filter.column)) + + const { data, error } = await state.rowService.count(cleanedFilters) if (error) { // TODO: handle fetch rows count error } else { @@ -35,11 +39,16 @@ export async function fetchPage( dispatch({ type: 'SET_IS_LOADING', payload: { isLoading: true } }) + // Remove unavailable columns from sorts & filter + const columnNames = state.table?.columns.map((column) => column.name) ?? [] + const cleanedFilters = filters.filter((filter) => columnNames.includes(filter.column)) + const cleanedSorts = sorts.filter((sort) => columnNames.includes(sort.column)) + const { data, error } = await state.rowService.fetchPage( state.page, state.rowsPerPage, - filters, - sorts + cleanedFilters, + cleanedSorts ) if (error) { // TODO: handle fetch rows data error diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx index b0b8efcac56..7e410f7d2ab 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx @@ -4,13 +4,11 @@ import { Query, Dictionary } from 'components/grid' import { Modal } from 'ui' import { PostgresRelationship, PostgresTable, PostgresColumn } from '@supabase/postgres-meta' -import { useStore, useUrlState } from 'hooks' +import { useStore } from 'hooks' import { RowEditor, ColumnEditor, TableEditor } from '.' import { ImportContent } from './TableEditor/TableEditor.types' import { ColumnField, CreateColumnPayload, UpdateColumnPayload } from './SidePanelEditor.types' import ConfirmationModal from 'components/ui/ConfirmationModal' -import { onLoadStorage, saveStorageDebounced } from 'components/grid/SupabaseGrid.utils' -import { InitialStateType } from 'components/grid/store/reducers' interface Props { selectedSchema: string @@ -45,11 +43,9 @@ const SidePanelEditor: FC = ({ onColumnSaved = () => {}, }) => { const { meta, ui } = useStore() - const projectRef = ui.selectedProject?.ref const [isEdited, setIsEdited] = useState(false) const [isClosingPanel, setIsClosingPanel] = useState(false) - const [_, setParams] = useUrlState({ arrayKeys: ['filter', 'sort'] }) const tables = meta.tables.list() @@ -117,65 +113,6 @@ const SidePanelEditor: FC = ({ } } - /** - * Renames a column's filter and/or sort rules if the related column has been renamed. - */ - const updateSortsAndFiltersAfterColumnRename = (oldColumnName: string, newColumnName: string) => { - setParams((prevParams) => { - const existingFilters = (prevParams?.filter ?? []) as string[] - const existingSorts = (prevParams?.sort ?? []) as string[] - - return { - ...prevParams, - filter: existingFilters.map((filter: string) => { - const [column] = filter.split(':') - return column === oldColumnName ? filter.replace(column, newColumnName) : filter - }), - sort: existingSorts.map((sort: string) => { - const [column] = sort.split(':') - return column === oldColumnName ? sort.replace(column, newColumnName) : sort - }), - } - }) - } - - /** - * Removes tables's column filter and/or sort rules if the related column unavailable - * (resulting from name change or deleted column) - */ - const updateSavedStateAfterTableUpdate = ( - storageRef: string, - tableName: string, - schema: string | null, - tableColumns: ColumnField[] - ) => { - const savedState = onLoadStorage(storageRef, tableName, schema) - const columnNames = tableColumns.map((columnInstance) => columnInstance.name) - const { filters, sorts }: { filters?: string[]; sorts?: string[] } = savedState - const state = { - table: { name: tableName, schema }, - gridColumns: savedState?.gridColumns, - } as InitialStateType - - const newSorts = sorts?.filter((filterQuery) => { - const [column] = filterQuery.split(':') - - return columnNames.includes(column) - }) - const newFilters = filters?.filter((sortrQuery) => { - const [column] = sortrQuery.split(':') - - return columnNames.includes(column) - }) - - saveStorageDebounced( - state, - storageRef, - newSorts?.length ? newSorts : undefined, - newFilters?.length ? newFilters : undefined - ) - } - const saveColumn = async ( payload: CreateColumnPayload | UpdateColumnPayload, foreignKey: Partial | undefined, @@ -201,15 +138,6 @@ const SidePanelEditor: FC = ({ if (response?.error) { ui.setNotification({ category: 'error', message: response.error.message }) } else { - if ( - !isNewRecord && - payload.name && - selectedColumnToEdit && - selectedColumnToEdit.name !== payload.name - ) { - updateSortsAndFiltersAfterColumnRename(selectedColumnToEdit.name, payload.name) - } - await meta.tables.loadById(selectedTable!.id) onColumnSaved(configuration.isEncrypted) setIsEdited(false) @@ -305,14 +233,6 @@ const SidePanelEditor: FC = ({ message: `Successfully updated ${table.name}!`, }) } - if (projectRef) { - updateSavedStateAfterTableUpdate( - projectRef, - selectedTableToEdit.name, - selectedTableToEdit.schema, - columns - ) - } } } catch (error: any) { saveTableError = true From de0082e8006ad03b20737dc5bc531fb257be3cc2 Mon Sep 17 00:00:00 2001 From: Akos <65110142+projectaki@users.noreply.github.com> Date: Mon, 2 Jan 2023 18:48:05 +0900 Subject: [PATCH 004/157] Update with-expo.mdx Instead of AsyncStorage use expos SecureStorage API. This is the recommended place to store sensitive information such as public client auth details. The storage key in the auth config of the supabase client expects: setItem, getItem and removeItem so a simple adapter can be written for SecureStore. --- .../getting-started/tutorials/with-expo.mdx | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/apps/docs/pages/guides/getting-started/tutorials/with-expo.mdx b/apps/docs/pages/guides/getting-started/tutorials/with-expo.mdx index b60db313a51..e5e150532f8 100644 --- a/apps/docs/pages/guides/getting-started/tutorials/with-expo.mdx +++ b/apps/docs/pages/guides/getting-started/tutorials/with-expo.mdx @@ -38,6 +38,7 @@ Then let's install the additional dependencies: [supabase-js](https://github.com ```bash npm install @supabase/supabase-js npm install react-native-elements @react-native-async-storage/async-storage react-native-url-polyfill +npx expo install expo-secure-store ``` Now let's create a helper file to initialize the Supabase client. @@ -46,15 +47,27 @@ These variables will be exposed on the browser, and that's completely fine since [Row Level Security](/docs/guides/auth#row-level-security) enabled on our Database. ```ts title=lib/supabase.ts -import AsyncStorage from '@react-native-async-storage/async-storage' +import * as SecureStore from "expo-secure-store"; import { createClient } from '@supabase/supabase-js' +const ExpoSecureStoreAdapter = { + getItem: async (key: string) => { + return await SecureStore.getItemAsync(key); + }, + setItem: async (key: string, value: string) => { + SecureStore.setItemAsync(key, JSON.stringify(value)); + }, + removeItem: async (key: string) => { + await SecureStore.deleteItemAsync(key); + }, +}; + const supabaseUrl = YOUR_REACT_NATIVE_SUPABASE_URL const supabaseAnonKey = YOUR_REACT_NATIVE_SUPABASE_ANON_KEY export const supabase = createClient(supabaseUrl, supabaseAnonKey, { auth: { - storage: AsyncStorage as any, + storage: ExpoSecureStoreAdapter as any, autoRefreshToken: true, persistSession: true, detectSessionInUrl: false, From a335a47136fa17e34611ad137523400e3b0b8f83 Mon Sep 17 00:00:00 2001 From: Akos Date: Mon, 2 Jan 2023 22:44:45 +0900 Subject: [PATCH 005/157] fix: parse the return value into an object Go true client expects an object as the returned value, and the session will not be restored otherwise --- .../guides/getting-started/tutorials/with-expo.mdx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/docs/pages/guides/getting-started/tutorials/with-expo.mdx b/apps/docs/pages/guides/getting-started/tutorials/with-expo.mdx index e5e150532f8..bcbe40c50cd 100644 --- a/apps/docs/pages/guides/getting-started/tutorials/with-expo.mdx +++ b/apps/docs/pages/guides/getting-started/tutorials/with-expo.mdx @@ -50,15 +50,17 @@ These variables will be exposed on the browser, and that's completely fine since import * as SecureStore from "expo-secure-store"; import { createClient } from '@supabase/supabase-js' -const ExpoSecureStoreAdapter = { +const ExpoSecureStorageAdapter = { getItem: async (key: string) => { - return await SecureStore.getItemAsync(key); + const value = (await SecureStore.getItemAsync(key)) as string; + + return JSON.parse(value); }, - setItem: async (key: string, value: string) => { + setItem: (key: string, value: string) => { SecureStore.setItemAsync(key, JSON.stringify(value)); }, - removeItem: async (key: string) => { - await SecureStore.deleteItemAsync(key); + removeItem: (key: string) => { + SecureStore.deleteItemAsync(key); }, }; From 7c1054c0817322660a940ba0e63d8570db7fafe8 Mon Sep 17 00:00:00 2001 From: Akos Date: Mon, 2 Jan 2023 23:08:20 +0900 Subject: [PATCH 006/157] fix: remove manual JSON parse/stringify, handled by GoTrue --- .../pages/guides/getting-started/tutorials/with-expo.mdx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/apps/docs/pages/guides/getting-started/tutorials/with-expo.mdx b/apps/docs/pages/guides/getting-started/tutorials/with-expo.mdx index bcbe40c50cd..6541bf460dc 100644 --- a/apps/docs/pages/guides/getting-started/tutorials/with-expo.mdx +++ b/apps/docs/pages/guides/getting-started/tutorials/with-expo.mdx @@ -51,13 +51,11 @@ import * as SecureStore from "expo-secure-store"; import { createClient } from '@supabase/supabase-js' const ExpoSecureStorageAdapter = { - getItem: async (key: string) => { - const value = (await SecureStore.getItemAsync(key)) as string; - - return JSON.parse(value); + getItem: (key: string) => { + return SecureStore.getItemAsync(key); }, setItem: (key: string, value: string) => { - SecureStore.setItemAsync(key, JSON.stringify(value)); + SecureStore.setItemAsync(key, value); }, removeItem: (key: string) => { SecureStore.deleteItemAsync(key); From c480be7e791f3ddeeda94133386365557db146cb Mon Sep 17 00:00:00 2001 From: gitstart Date: Fri, 13 Jan 2023 05:18:47 +0000 Subject: [PATCH 007/157] chore: hide subscription in views --- .../interfaces/Docs/ResourceContent.tsx | 89 ++++++++++--------- studio/pages/project/[ref]/api/index.tsx | 13 +++ 2 files changed, 59 insertions(+), 43 deletions(-) diff --git a/studio/components/interfaces/Docs/ResourceContent.tsx b/studio/components/interfaces/Docs/ResourceContent.tsx index a37ed884081..6fc63ae6b95 100644 --- a/studio/components/interfaces/Docs/ResourceContent.tsx +++ b/studio/components/interfaces/Docs/ResourceContent.tsx @@ -12,6 +12,7 @@ const ResourceContent = ({ selectedLang, showApiKey, refreshDocs, + isView, }: any) => { if (!paths || !definitions) return null @@ -221,49 +222,51 @@ const ResourceContent = ({ )} - <> -

Subscribe to changes

-
-
-

- Supabase provides realtime functionality and broadcasts database changes to authorized - users depending on Row Level Security (RLS) policies. -

-

- - Learn more. - -

-
-
- - - - - -
-
- + {!isView && ( + <> +

Subscribe to changes

+
+
+

+ Supabase provides realtime functionality and broadcasts database changes to + authorized users depending on Row Level Security (RLS) policies. +

+

+ + Learn more. + +

+
+
+ + + + + +
+
+ + )} <>

Much more

diff --git a/studio/pages/project/[ref]/api/index.tsx b/studio/pages/project/[ref]/api/index.tsx index 482494908ff..a4ac6b6cc21 100644 --- a/studio/pages/project/[ref]/api/index.tsx +++ b/studio/pages/project/[ref]/api/index.tsx @@ -75,6 +75,7 @@ const DocView: FC = observer(({}) => { const API_KEY = data?.autoApiService?.defaultApiKey const swaggerUrl = data?.autoApiService?.restUrl const headers: any = { apikey: API_KEY } + const selectedSchema = data?.autoApiService?.app_config?.db_schema const canReadServiceKey = checkPermissions( PermissionAction.READ, @@ -88,10 +89,19 @@ const DocView: FC = observer(({}) => { (url: string) => get(url, { headers, credentials: 'omit' }).then((res) => res) ) + const { meta } = useStore() + useEffect(() => { PageState.setJsonSchema(jsonSchema) }, [jsonSchema]) + useEffect(() => { + const fetchViews = async (selectedSchema: string) => { + await meta.schemas.loadViews(selectedSchema) + } + if (selectedSchema) fetchViews(selectedSchema) + }, [selectedSchema]) + const refreshDocs = async () => { // A bit hacky calling coding this up twice - at some point we should move this function // and the SWR into the store. @@ -128,6 +138,8 @@ const DocView: FC = observer(({}) => { const PAGE_KEY: any = resource || rpc || page || 'index' + const resourceIsView = meta.schemas.views?.some((view) => view.name === resource) + return (
@@ -216,6 +228,7 @@ const DocView: FC = observer(({}) => { paths={paths} showApiKey={showApiKey.key} refreshDocs={refreshDocs} + isView={resourceIsView} /> ) : rpc ? ( Date: Fri, 27 Jan 2023 21:22:42 +0900 Subject: [PATCH 008/157] fix: typo --- apps/docs/pages/guides/getting-started/tutorials/with-expo.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/pages/guides/getting-started/tutorials/with-expo.mdx b/apps/docs/pages/guides/getting-started/tutorials/with-expo.mdx index 6541bf460dc..d141a8b1321 100644 --- a/apps/docs/pages/guides/getting-started/tutorials/with-expo.mdx +++ b/apps/docs/pages/guides/getting-started/tutorials/with-expo.mdx @@ -50,7 +50,7 @@ These variables will be exposed on the browser, and that's completely fine since import * as SecureStore from "expo-secure-store"; import { createClient } from '@supabase/supabase-js' -const ExpoSecureStorageAdapter = { +const ExpoSecureStoreAdapter = { getItem: (key: string) => { return SecureStore.getItemAsync(key); }, From 72b716eb22dbf704d1d412e7f12ec705616dc4aa Mon Sep 17 00:00:00 2001 From: gitstart Date: Thu, 2 Feb 2023 09:46:37 +0000 Subject: [PATCH 009/157] chore: support column name update with active filter --- studio/components/grid/store/reducers/base.ts | 8 ++--- .../SidePanelEditor/SidePanelEditor.tsx | 34 ++++++++++++++++++- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/studio/components/grid/store/reducers/base.ts b/studio/components/grid/store/reducers/base.ts index 1307eb23007..9795680c2df 100644 --- a/studio/components/grid/store/reducers/base.ts +++ b/studio/components/grid/store/reducers/base.ts @@ -83,15 +83,15 @@ const BaseReducer = (state: BaseInitialState, action: BASE_ACTIONTYPE) => { case 'UPDATE_FILTERS': { const newState: any = { ...state } newState.page = 1 - // newState.refreshPageFlag = Date.now() - newState.refreshPageFlag = REFRESH_PAGE_IMMEDIATELY + newState.refreshPageFlag = Date.now() + // newState.refreshPageFlag = REFRESH_PAGE_IMMEDIATELY newState.totalRows = TOTAL_ROWS_RESET return newState } case 'UPDATE_SORTS': { const newState: any = { ...state } - // newState.refreshPageFlag = Date.now() - newState.refreshPageFlag = REFRESH_PAGE_IMMEDIATELY + newState.refreshPageFlag = Date.now() + // newState.refreshPageFlag = REFRESH_PAGE_IMMEDIATELY return newState } default: diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx index 7d8c40bd8d0..23dc0ebb37c 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/SidePanelEditor.tsx @@ -4,7 +4,7 @@ import { Query, Dictionary } from 'components/grid' import { Modal } from 'ui' import type { PostgresRelationship, PostgresTable, PostgresColumn } from '@supabase/postgres-meta' -import { useStore } from 'hooks' +import { useStore, useUrlState } from 'hooks' import { RowEditor, ColumnEditor, TableEditor } from '.' import { ImportContent } from './TableEditor/TableEditor.types' import { ColumnField, CreateColumnPayload, UpdateColumnPayload } from './SidePanelEditor.types' @@ -42,6 +42,7 @@ const SidePanelEditor: FC = ({ onTableCreated = () => {}, onColumnSaved = () => {}, }) => { + const [_, setParams] = useUrlState({ arrayKeys: ['filter', 'sort'] }) const { meta, ui } = useStore() const [isEdited, setIsEdited] = useState(false) @@ -139,6 +140,15 @@ const SidePanelEditor: FC = ({ ui.setNotification({ category: 'error', message: response.error.message }) } else { await meta.tables.loadById(selectedTable!.id) + if ( + !isNewRecord && + payload.name && + selectedColumnToEdit && + selectedColumnToEdit.name !== payload.name + ) { + reAddRenamedColumnSortAndFilter(selectedColumnToEdit.name, payload.name) + } + onColumnSaved(configuration.isEncrypted) setIsEdited(false) closePanel() @@ -151,6 +161,28 @@ const SidePanelEditor: FC = ({ resolve() } + /** + * Adds the renamed column's filter and/or sort rules. + */ + const reAddRenamedColumnSortAndFilter = (oldColumnName: string, newColumnName: string) => { + setParams((prevParams) => { + const existingFilters = (prevParams?.filter ?? []) as string[] + const existingSorts = (prevParams?.sort ?? []) as string[] + + return { + ...prevParams, + filter: existingFilters.map((filter: string) => { + const [column] = filter.split(':') + return column === oldColumnName ? filter.replace(column, newColumnName) : filter + }), + sort: existingSorts.map((sort: string) => { + const [column] = sort.split(':') + return column === oldColumnName ? sort.replace(column, newColumnName) : sort + }), + } + }) + } + const saveTable = async ( payload: any, columns: ColumnField[], From 4e36eb80f84020048848f3adb3cb82af750ce6b2 Mon Sep 17 00:00:00 2001 From: Liau Jian Jie Date: Sat, 18 Feb 2023 21:27:20 +0800 Subject: [PATCH 010/157] Docs: remove unused `session` variable Removes the destructured `session` variable from `useSupabase()`. This variable is never referenced and is not returned as a property from `useSupabase()`. --- .../guides/auth/auth-helpers/nextjs-server-components.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/docs/pages/guides/auth/auth-helpers/nextjs-server-components.mdx b/apps/docs/pages/guides/auth/auth-helpers/nextjs-server-components.mdx index a31afc3d055..a2bae56dc30 100644 --- a/apps/docs/pages/guides/auth/auth-helpers/nextjs-server-components.mdx +++ b/apps/docs/pages/guides/auth/auth-helpers/nextjs-server-components.mdx @@ -506,7 +506,7 @@ import { useSupabase } from './supabase-provider' // Supabase auth needs to be triggered client-side export default function Login() { - const { supabase, session } = useSupabase() + const { supabase } = useSupabase() const handleEmailLogin = async () => { await supabase.auth.signInWithPassword({ @@ -546,7 +546,7 @@ import { useSupabase } from './supabase-provider' // Supabase auth needs to be triggered client-side export default function Login() { - const { supabase, session } = useSupabase() + const { supabase } = useSupabase() const handleEmailLogin = async () => { await supabase.auth.signInWithPassword({ From 0c0a3d8152d6fc71508217da42d4e15839c4bf2f Mon Sep 17 00:00:00 2001 From: Jonathan Eldridge Date: Sat, 25 Feb 2023 11:12:28 -0800 Subject: [PATCH 011/157] Clarify need for database type generation When you start building the Account page, `Account.tsx` includes `import { Database } from '../utils/database.types'`, but that is not created in any of the previous steps and there's nothing telling you how to generate it. --- .../docs/pages/guides/getting-started/tutorials/with-nextjs.mdx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/docs/pages/guides/getting-started/tutorials/with-nextjs.mdx b/apps/docs/pages/guides/getting-started/tutorials/with-nextjs.mdx index bbee6933442..adf2c12b5bf 100644 --- a/apps/docs/pages/guides/getting-started/tutorials/with-nextjs.mdx +++ b/apps/docs/pages/guides/getting-started/tutorials/with-nextjs.mdx @@ -317,6 +317,8 @@ export default function Account({ session }) { Let's create a new component for that called `Account.tsx` within a `components` folder. +First, [generate type definitions from your database using the Supabase CLI](https://supabase.com/docs/guides/database/api/generating-types) to `../utils/database.types`. + ```tsx title=components/Account.tsx import { useState, useEffect } from 'react' import { useUser, useSupabaseClient, Session } from '@supabase/auth-helpers-react' From d1634e7e6675fe6c4e2b341c4c4dc89ee47b976c Mon Sep 17 00:00:00 2001 From: Copple <10214025+kiwicopple@users.noreply.github.com> Date: Wed, 1 Mar 2023 15:31:54 +0100 Subject: [PATCH 012/157] Adds a react quickstart --- .../NavigationMenu.constants.ts | 4 + .../pages/guides/auth/quickstarts/react.mdx | 133 ++++++++++++++++++ package-lock.json | 2 +- 3 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 apps/docs/pages/guides/auth/quickstarts/react.mdx diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index 11043ca46c4..0d088c00d05 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -292,6 +292,10 @@ export const auth = { name: 'Overview', url: '/guides/auth/overview', }, + { + name: 'Quickstarts', + items: [{ name: 'Auth with React', url: '/guides/auth/quickstarts/react', items: [] }], + }, { name: 'Authentication', url: undefined, diff --git a/apps/docs/pages/guides/auth/quickstarts/react.mdx b/apps/docs/pages/guides/auth/quickstarts/react.mdx new file mode 100644 index 00000000000..f47e374a48c --- /dev/null +++ b/apps/docs/pages/guides/auth/quickstarts/react.mdx @@ -0,0 +1,133 @@ +import Layout from '~/layouts/DefaultGuideLayout' +import StepHikeCompact from '~/components/StepHikeCompact' + +export const meta = { + title: 'Use Supabase Auth with React', + subtitle: 'Learn how to Supabase Auth with React.js.', + breadcrumb: 'Auth Quickstarts', +} + + + + + + + [Launch a new project](https://app.supabase.com) in the Supabase Dashboard. + + Your new database has a table for storing your users. You can see that this table is currently empty by running some SQL in the [SQL Editor](https://app.supabase.com/project/_/sql). + + + + + + ```sql SQL_EDITOR + select * from auth.users; + ```` + + + + + + + + + + Create a React app using the `create-react-app` command. + + + + + + ```bash Terminal + npx create-react-app my-app + ``` + + + + + + + + + The fastest way to get started is to use Supabase's `auth-ui-react` library which provides a convenient interface for working with Supabase Auth from a React app. + + Navigate to the React app and install the Supabase libraries. + + + + + + ```bash Terminal + cd my-app && npm install @supabase/supabase-js @supabase/auth-ui-react + ``` + + + + + + + + + In `index.js`, create a Supabase client using your [Project URL and public API (anon) key](https://app.supabase.com/project/_/settings/api). + + You can configure the Auth component to display whenever there is no session inside `supabase.auth.getSession()` + + + + + + ```js src/index.js + import './index.css' + import { useState, useEffect } from 'react' + import { createClient } from '@supabase/supabase-js' + import { Auth, ThemeSupa } from '@supabase/auth-ui-react' + + const supabase = createClient('https://.supabase.co', '') + + export default function App() { + const [session, setSession] = useState(null) + + useEffect(() => { + supabase.auth.getSession().then(({ data: { session } }) => { + setSession(session) + }) + + supabase.auth.onAuthStateChange((_event, session) => { + setSession(session) + }) + }, []) + + if (!session) { + return () + } + else { + return (
Logged in!
) + } + } + ``` + +
+ +
+ + + + + Start the app, go to http://localhost:3000 in a browser, and open the browser console and you should be able to log in. + + + + + + ```bash Terminal + npm start + ``` + + + + +
+ +export const Page = ({ children }) => + +export default Page diff --git a/package-lock.json b/package-lock.json index dbf8afff773..fa926877ce0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46189,7 +46189,7 @@ "@sinclair/typebox": "^0.25.1", "pg": "^8.7.1", "pg-format": "^1.0.4", - "pgsql-parser": "13.4.0", + "pgsql-parser": "^13.3.0", "postgres-array": "^3.0.1", "prettier": "^2.6.0", "prettier-plugin-sql": "^0.12.1" From 13cf232ceda02cc874660f7b07481f0fbdd0e613 Mon Sep 17 00:00:00 2001 From: Copple <10214025+kiwicopple@users.noreply.github.com> Date: Wed, 1 Mar 2023 15:39:51 +0100 Subject: [PATCH 013/157] Adds sidebar (again?) --- apps/docs/components/Navigation/Navigation.constants.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/docs/components/Navigation/Navigation.constants.ts b/apps/docs/components/Navigation/Navigation.constants.ts index 94f40fce1ee..ade5cc58244 100644 --- a/apps/docs/components/Navigation/Navigation.constants.ts +++ b/apps/docs/components/Navigation/Navigation.constants.ts @@ -80,6 +80,7 @@ export const menuItems: NavMenu = { label: 'Auth', items: [ { name: 'Overview', url: '/guides/auth', items: [] }, + { name: 'Quickstarts', url: '/guides/auth/quickstarts/react', items: [] }, { name: 'Authentication', url: undefined, From 856688bf81a1fef7baf06f85a6011543b18812fd Mon Sep 17 00:00:00 2001 From: Isaiah Hamilton Date: Tue, 7 Mar 2023 18:07:09 -0500 Subject: [PATCH 014/157] update og images readme --- supabase/functions/og-images/README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/supabase/functions/og-images/README.md b/supabase/functions/og-images/README.md index 886b9f69b9e..c2a093815df 100644 --- a/supabase/functions/og-images/README.md +++ b/supabase/functions/og-images/README.md @@ -38,3 +38,11 @@ supabase functions serve og-images ``` Now we can visit [localhost:54321/functions/v1/og-images/?site=docs&title=Title&description=Description&type=Auth](http://localhost:54321/functions/v1/og-images/?site=docs&title=Title&description=Description&type=Auth) to see your changes localy. + +## Deploy + +To deploy this function, you currently need to depoy it locally. To do this follow the steps below. + +```bash +supabase functions deploy og-images --no-verify-jwt +``` From b8714805dec7aef5222cf2b52dea05c950aac347 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Thu, 9 Mar 2023 22:46:41 +0700 Subject: [PATCH 015/157] Update server-side-rendering.mdx Fix "AuthApiError: invalid claim: missing sub claim" from the server side. --- apps/docs/pages/guides/auth/server-side-rendering.mdx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/docs/pages/guides/auth/server-side-rendering.mdx b/apps/docs/pages/guides/auth/server-side-rendering.mdx index 346984a63fb..c5abc72a4e3 100644 --- a/apps/docs/pages/guides/auth/server-side-rendering.mdx +++ b/apps/docs/pages/guides/auth/server-side-rendering.mdx @@ -130,6 +130,11 @@ if (refreshToken && accessToken) { await supabase.auth.setSession({ refresh_token: refreshToken, access_token: accessToken, + { + auth: { + persistSession: false, + }, + } }) } else { // make sure you handle this case! From 627587e6b23c10b61a965f5bdd6b69cd849b24b3 Mon Sep 17 00:00:00 2001 From: Isaiah Hamilton Date: Mon, 13 Mar 2023 17:55:29 -0400 Subject: [PATCH 016/157] change button to link tag --- .../Navigation/NavigationMenu/TopNavBarRef.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/apps/docs/components/Navigation/NavigationMenu/TopNavBarRef.tsx b/apps/docs/components/Navigation/NavigationMenu/TopNavBarRef.tsx index baa7eaf6bc1..5a5f4952158 100644 --- a/apps/docs/components/Navigation/NavigationMenu/TopNavBarRef.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/TopNavBarRef.tsx @@ -136,16 +136,15 @@ const TopNavBarRef: FC = () => { Dashboard