diff --git a/apps/studio/components/interfaces/Auth/AuthProvidersForm/AuthProvidersForm.tsx b/apps/studio/components/interfaces/Auth/AuthProvidersForm/AuthProvidersForm.tsx index 9d2c89bdea9..b4f42017d00 100644 --- a/apps/studio/components/interfaces/Auth/AuthProvidersForm/AuthProvidersForm.tsx +++ b/apps/studio/components/interfaces/Auth/AuthProvidersForm/AuthProvidersForm.tsx @@ -1,73 +1,24 @@ -import { PermissionAction } from '@supabase/shared-types/out/constants' import { ExternalLink } from 'lucide-react' import Link from 'next/link' -import { useState, useEffect } from 'react' -import { useForm } from 'react-hook-form' -import { yupResolver } from '@hookform/resolvers/yup' -import { toast } from 'sonner' import { useParams } from 'common' import { FormHeader } from 'components/ui/Forms/FormHeader' import { HorizontalShimmerWithIcon } from 'components/ui/Shimmers/Shimmers' import { useAuthConfigQuery } from 'data/auth/auth-config-query' -import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation' -import { BASE_PATH } from 'lib/constants' import { AlertDescription_Shadcn_, AlertTitle_Shadcn_, Alert_Shadcn_, Button, WarningIcon, - Sheet, - SheetContent, - Badge, - SheetHeader, - SheetTitle, - SheetDescription, - SheetFooter, - Form_Shadcn_, } from 'ui' -import { ResourceList } from 'components/ui/Resource/ResourceList' -import { ResourceItem } from 'components/ui/Resource/ResourceItem' import { getPhoneProviderValidationSchema, PROVIDERS_SCHEMAS } from '../AuthProvidersFormValidation' import { ProviderCollapsibleClasses } from './AuthProvidersForm.constants' import ProviderForm from './ProviderForm' import { SectionHeader } from 'components/layouts/PageLayout' -import { DocsButton } from 'components/ui/DocsButton' -import type { Provider } from './AuthProvidersForm.types' - -const generateInitialValues = (provider: Provider, config: any) => { - const doubleNegativeKeys = ['MAILER_AUTOCONFIRM', 'SMS_AUTOCONFIRM'] - const initialValues: { [x: string]: string | boolean } = {} - - Object.keys(provider.properties).forEach((key) => { - const isDoubleNegative = doubleNegativeKeys.includes(key) - - if (provider.title === 'SAML 2.0') { - const configValue = (config as any)[key] - initialValues[key] = configValue || (provider.properties[key].type === 'boolean' ? false : '') - } else { - if (isDoubleNegative) { - initialValues[key] = !(config as any)[key] - } else { - const configValue = (config as any)[key] - initialValues[key] = configValue - ? configValue - : provider.properties[key].type === 'boolean' - ? false - : '' - } - } - }) - - return initialValues -} const AuthProvidersForm = () => { const { ref: projectRef } = useParams() - const [selectedProvider, setSelectedProvider] = useState(null) - const { mutate: updateAuthConfig, isLoading: isUpdatingConfig } = useAuthConfigUpdateMutation() - const { isLoading, error: authConfigError, @@ -76,49 +27,6 @@ const AuthProvidersForm = () => { isSuccess, } = useAuthConfigQuery({ projectRef }) - const activeProvider = selectedProvider - ? PROVIDERS_SCHEMAS.find((provider) => provider.title === selectedProvider) - : null - - const activeProviderSchema = - activeProvider?.title === 'Phone' && authConfig - ? { ...activeProvider, validationSchema: getPhoneProviderValidationSchema(authConfig) } - : activeProvider - - const form = useForm({ - defaultValues: activeProviderSchema - ? generateInitialValues(activeProviderSchema, authConfig) - : {}, - }) - - useEffect(() => { - if (activeProviderSchema) { - form.reset(generateInitialValues(activeProviderSchema, authConfig)) - } - }, [activeProviderSchema]) - - const onSubmit = (values: any) => { - const doubleNegativeKeys = ['MAILER_AUTOCONFIRM', 'SMS_AUTOCONFIRM'] - const payload = { ...values } - - Object.keys(values).map((x: string) => { - if (doubleNegativeKeys.includes(x)) payload[x] = !values[x] - if (payload[x] === '') payload[x] = null - }) - - updateAuthConfig( - { projectRef: projectRef!, config: payload }, - { - onSuccess: () => { - setSelectedProvider(null) - toast.success('Successfully updated settings') - }, - } - ) - } - - if (!authConfig) return null - return (
{ subtitle="Authenticate your users through a suite of providers and login methods" /> -
+
{authConfig?.EXTERNAL_EMAIL_ENABLED && authConfig?.MAILER_OTP_EXP > 3600 && ( {
)} - {isLoading && ( -
- {PROVIDERS_SCHEMAS.map((provider) => ( + {isLoading && + PROVIDERS_SCHEMAS.map((provider) => { + return (
- ))} -
- )} + ) + })} {isError && ( @@ -168,110 +75,21 @@ const AuthProvidersForm = () => { {authConfigError.message} )} - {isSuccess && ( - - {PROVIDERS_SCHEMAS.map((provider) => { - const isSAMLEnabled = provider.title === 'SAML 2.0' && authConfig?.SAML_ENABLED - const isLinkedInOIDCEnabled = - provider.title === 'LinkedIn (OIDC)' && authConfig?.EXTERNAL_LINKEDIN_OIDC_ENABLED - const isSlackOIDCEnabled = - provider.title === 'Slack (OIDC)' && authConfig?.EXTERNAL_SLACK_OIDC_ENABLED - const isExternalProviderAndEnabled = - authConfig && - (authConfig as any)[`EXTERNAL_${provider?.title?.toUpperCase()}_ENABLED`] - - const isActive = - isSAMLEnabled || - isExternalProviderAndEnabled || - isLinkedInOIDCEnabled || - isSlackOIDCEnabled - - return ( - - } - meta={ - - {isActive ? 'Enabled' : 'Disabled'} - - } - onClick={() => setSelectedProvider(provider.title)} - > -
-
{provider.title}
- {'helper' in provider.misc && ( -

{provider.misc.helper}

- )} -
-
- ) - })} -
- )} + {isSuccess && + PROVIDERS_SCHEMAS.map((provider) => { + const providerSchema = + provider.title === 'Phone' + ? { ...provider, validationSchema: getPhoneProviderValidationSchema(authConfig) } + : provider + return ( + + ) + })}
- - !open && setSelectedProvider(null)} - > - - {activeProviderSchema && ( - -
- - - {`${activeProviderSchema.title} - {activeProviderSchema.title} Configuration - - -
- setSelectedProvider(null)} - form={form} - /> -
- -
- -
- - -
-
-
-
-
- )} -
-
) } diff --git a/apps/studio/components/interfaces/Auth/AuthProvidersForm/FormField.tsx b/apps/studio/components/interfaces/Auth/AuthProvidersForm/FormField.tsx index 54a2b611384..11fdbf2264f 100644 --- a/apps/studio/components/interfaces/Auth/AuthProvidersForm/FormField.tsx +++ b/apps/studio/components/interfaces/Auth/AuthProvidersForm/FormField.tsx @@ -1,32 +1,19 @@ import dayjs from 'dayjs' -import { Eye, EyeOff, CalendarIcon } from 'lucide-react' -import { useState } from 'react' -import { BASE_PATH } from 'lib/constants' -import { format } from 'date-fns' -import { cn } from 'ui/src/lib/utils/cn' +import { Eye, EyeOff } from 'lucide-react' +import { useEffect, useState } from 'react' import ReactMarkdown from 'react-markdown' -import { - Button, - Input_Shadcn_, - Switch, - Select_Shadcn_, - SelectContent_Shadcn_, - SelectItem_Shadcn_, - SelectTrigger_Shadcn_, - SelectValue_Shadcn_, - Popover_Shadcn_, - PopoverTrigger_Shadcn_, - PopoverContent_Shadcn_, - Calendar, - FormControl_Shadcn_, - TextArea_Shadcn_, -} from 'ui' + +import { Markdown } from 'components/interfaces/Markdown' +import { DatePicker } from 'components/ui/DatePicker' +import { BASE_PATH } from 'lib/constants' +import { Button, Input, InputNumber, Listbox, Toggle } from 'ui' import type { Enum } from './AuthProvidersForm.types' interface FormFieldProps { - field: any name: string properties: any + formValues: any + setFieldValue: (field: string, v: any) => any disabled?: boolean } @@ -34,151 +21,268 @@ function formatDate(date: Date): string { return dayjs(date).format('dddd, MMMM D, YYYY HH:mm:ss Z') } -const FormField = ({ field, name, properties, disabled = false }: FormFieldProps) => { +const FormField = ({ + name, + properties, + formValues, + disabled = false, + setFieldValue, +}: FormFieldProps) => { const [hidden, setHidden] = useState(!!properties.isSecret) + const [dateAsText, setDateAsText] = useState( + formValues[name] ? formatDate(new Date(formValues[name])) : '' + ) + + useEffect(() => { + if (properties.show && properties.show.key && !formValues[properties.show.key]) { + setFieldValue(name, '') + setDateAsText('') + } + }, [properties.show && properties.show.key && !formValues[properties.show.key]]) if (properties.show) { if (properties.show.matches) { - if (!properties.show.matches.includes(properties.show.key)) { + if (!properties.show.matches.includes(formValues[properties.show.key])) { return null } - } else if (!properties.show.key) { + } else if (!formValues[properties.show.key]) { return null } } - const renderMarkdown = (content?: string) => { - if (!content) return null - return ( - - {content} - - ) - } - switch (properties.type) { case 'datetime': return ( - - - - - - - - { - if (date) { - // Set time to 24 hours from now - date.setHours(date.getHours() + 24) - field.onChange(date.toISOString()) + + {properties.descriptionOptional} + + ) : null + } + descriptionText={ + properties.description ? ( + + {properties.description} + + ) : null + } + actions={ + { + if (date && date.to) { + setFieldValue(name, date.to) + setDateAsText(formatDate(new Date(date.to))) + } else { + setDateAsText('') + setFieldValue(name, '') } }} - disabled={(date: Date) => date < new Date()} - initialFocus - /> - - + > + Pick + + } + /> ) case 'string': return ( -
- - {properties.isSecret && ( -
+ + ) : null + } + descriptionText={ + properties.description ? ( + + ) : null + } + actions={ + !!properties.isSecret ? ( + + +
+ {({ handleReset, initialValues, values, setFieldValue }: any) => { + const noChanges = JSON.stringify(initialValues) === JSON.stringify(values) + return ( + +
+
+ {showAlert(provider.title)} + {Object.keys(provider.properties).map((x: string) => ( + + ))} - {provider.misc.requiresRedirect && ( - - - } - orientation="horizontal" - > - {() => ( -
- - + {provider?.misc?.alert && ( + + {provider.misc.alert.description} + + } + /> + )} + + {provider.misc.requiresRedirect && ( + <> + + } + /> + + )} +
+ +
+ + + Save + +
+
- )} -
-
- )} - -
+
+
+ ) + }} +
+ ) } diff --git a/apps/studio/components/interfaces/Auth/BasicAuthSettingsForm/BasicAuthSettingsForm.tsx b/apps/studio/components/interfaces/Auth/BasicAuthSettingsForm/BasicAuthSettingsForm.tsx index 81a7a2c810d..88698ee2389 100644 --- a/apps/studio/components/interfaces/Auth/BasicAuthSettingsForm/BasicAuthSettingsForm.tsx +++ b/apps/studio/components/interfaces/Auth/BasicAuthSettingsForm/BasicAuthSettingsForm.tsx @@ -239,11 +239,16 @@ const BasicAuthSettingsForm = () => { )} - + + {form.formState.isDirty && ( + + )}