Skip to content

Commit

Permalink
refactor(dynamic fields): to use context for dyncami fields, prevent …
Browse files Browse the repository at this point in the history
…issue with shared state
  • Loading branch information
anteqkois committed Jun 9, 2024
1 parent 6665113 commit db4b795
Show file tree
Hide file tree
Showing 18 changed files with 166 additions and 93 deletions.
19 changes: 13 additions & 6 deletions apps/web/modules/billing/useReachLimitDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,35 @@
'use client'

import { ErrorCodeQuota, PlanConfigurationDetailsValue, PlanProductConfiguration, planConfigurationDetails } from '@linkerry/shared'
import { Dispatch, PropsWithChildren, SetStateAction, createContext, useContext, useMemo, useState } from 'react'
import { Dispatch, PropsWithChildren, SetStateAction, createContext, useContext, useEffect, useMemo, useState } from 'react'

export type CustomConfigurationItemValues = Partial<Record<keyof PlanProductConfiguration, string>>

type ReturnType = {
showDialog: boolean
setShowDialog: Dispatch<SetStateAction<boolean>>
reachedLimitErrorCode: ErrorCodeQuota | undefined
setReachedLimitErrorCode: Dispatch<SetStateAction<ErrorCodeQuota | undefined>>
showDialogBasedOnErrorCode: (error: ErrorCodeQuota) => void
showDialogBasedOnErrorCode: (error: ErrorCodeQuota, _customConfigurationItemValues?: CustomConfigurationItemValues) => void
exceededConfigurationEntry: PlanConfigurationDetailsValue | null
customConfigurationItemValues: Partial<Record<keyof PlanProductConfiguration, string>>
setCustomConfigurationItemValues: Dispatch<SetStateAction<Partial<Record<keyof PlanProductConfiguration, string>>>>
customConfigurationItemValues: CustomConfigurationItemValues
setCustomConfigurationItemValues: Dispatch<SetStateAction<CustomConfigurationItemValues>>
}

const Context = createContext<ReturnType>({} as ReturnType)

export const ReachLimitDialogProvider = ({ children }: PropsWithChildren) => {
const [showDialog, setShowDialog] = useState(false)
const [reachedLimitErrorCode, setReachedLimitErrorCode] = useState<ErrorCodeQuota>()
const [customConfigurationItemValues, setCustomConfigurationItemValues] = useState<Partial<Record<keyof PlanProductConfiguration, string>>>({})
const [customConfigurationItemValues, setCustomConfigurationItemValues] = useState<CustomConfigurationItemValues>({})

useEffect(() => {
if (!showDialog) setCustomConfigurationItemValues({})
}, [showDialog])

const showDialogBasedOnErrorCode = (code: ErrorCodeQuota) => {
const showDialogBasedOnErrorCode = (code: ErrorCodeQuota, _customConfigurationItemValues?: CustomConfigurationItemValues) => {
setReachedLimitErrorCode(code)
if (_customConfigurationItemValues) setCustomConfigurationItemValues(_customConfigurationItemValues)
setShowDialog(true)
}

Expand Down
9 changes: 5 additions & 4 deletions apps/web/modules/editor/action/SelectActionPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ConnectorMetadataSummary } from '@linkerry/connectors-framework'
import { isCustomHttpExceptionAxios, isQuotaErrorCode } from '@linkerry/shared'
import { isCustomHttpExceptionAxios, isQuotaError, isQuotaErrorCode } from '@linkerry/shared'
import { ScrollArea, useToast } from '@linkerry/ui-components/client'
import { useReachLimitDialog } from '../../billing/useReachLimitDialog'
import { CustomConfigurationItemValues, useReachLimitDialog } from '../../billing/useReachLimitDialog'
import { ConnectorsList } from '../../flows/connectors/ConnectorsList'
import { useEditor } from '../useEditor'

Expand All @@ -13,13 +13,14 @@ export const SelectActionPanel = () => {
const handleSelectAction = async (connector: ConnectorMetadataSummary) => {
try {
await handleSelectActionConnector(connector)
} catch (error) {
} catch (error: any) {
console.error(error)
let errorDescription = 'We can not add new step to your flow. Please inform our Team'

if (isCustomHttpExceptionAxios(error)) {
if (isQuotaErrorCode(error.response.data.code)) return showDialogBasedOnErrorCode(error.response.data.code)
else errorDescription = error.response.data.message
}
} else if (isQuotaError(error)) return showDialogBasedOnErrorCode(error.code, error.metadata as CustomConfigurationItemValues)

toast({
title: 'Can not update Flow',
Expand Down
56 changes: 46 additions & 10 deletions apps/web/modules/editor/form/FieldResolver.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { NumberField } from './Inputs/NumberField'
import { SecretTextField } from './Inputs/SecretTextField'
import { ShortTextField } from './Inputs/ShortTextField'
import { VirtualizedCombobox } from './Inputs/VirtualizedCombobox'
import { DynamicFieldProvider } from './useFieldCustomValidation'

interface DynamicFieldProps {
property: ConnectorProperty
Expand All @@ -18,24 +19,59 @@ interface DynamicFieldProps {
export const FieldResolver = ({ name, property, refreshedProperties }: DynamicFieldProps) => {
switch (property.type) {
case PropertyType.SHORT_TEXT:
return <ShortTextField name={name} property={property} refreshedProperties={refreshedProperties} />
return (
<DynamicFieldProvider property={property}>
<ShortTextField name={name} property={property} refreshedProperties={refreshedProperties} />
</DynamicFieldProvider>
)
case PropertyType.LONG_TEXT:
return <LongTextField name={name} property={property} refreshedProperties={refreshedProperties} />
return (
<DynamicFieldProvider property={property}>
<LongTextField name={name} property={property} refreshedProperties={refreshedProperties} />
</DynamicFieldProvider>
)
case PropertyType.SECRET_TEXT:
return <SecretTextField name={name} property={property} refreshedProperties={refreshedProperties} />
return (
<DynamicFieldProvider property={property}>
<SecretTextField name={name} property={property} refreshedProperties={refreshedProperties} />
</DynamicFieldProvider>
)
case PropertyType.NUMBER:
return <NumberField name={name} property={property} refreshedProperties={refreshedProperties} />
return (
<DynamicFieldProvider property={property}>
<NumberField name={name} property={property} refreshedProperties={refreshedProperties} />
</DynamicFieldProvider>
)
case PropertyType.CHECKBOX:
return <CheckboxField name={name} property={property} refreshedProperties={refreshedProperties} />
return (
<DynamicFieldProvider property={property}>
<CheckboxField name={name} property={property} refreshedProperties={refreshedProperties} />
</DynamicFieldProvider>
)
case PropertyType.STATIC_DROPDOWN:
return <VirtualizedCombobox name={name} property={property} refreshedProperties={refreshedProperties} />
return (
<DynamicFieldProvider property={property}>
<VirtualizedCombobox name={name} property={property} refreshedProperties={refreshedProperties} />
</DynamicFieldProvider>
)
case PropertyType.DYNAMIC_DROPDOWN:
return <DynamicVirtualizedSelect name={name} property={property} refreshedProperties={refreshedProperties} />
return (
<DynamicFieldProvider property={property}>
<DynamicVirtualizedSelect name={name} property={property} refreshedProperties={refreshedProperties} />
</DynamicFieldProvider>
)
case PropertyType.MARKDOWN:
return <MarkdownField name={name} property={property} refreshedProperties={refreshedProperties} />
return (
<DynamicFieldProvider property={property}>
<MarkdownField name={name} property={property} refreshedProperties={refreshedProperties} />
</DynamicFieldProvider>
)
case PropertyType.JSON:
return <JsonField name={name} property={property} refreshedProperties={refreshedProperties} />

return (
<DynamicFieldProvider property={property}>
<JsonField name={name} property={property} refreshedProperties={refreshedProperties} />
</DynamicFieldProvider>
)
default:
break
}
Expand Down
8 changes: 3 additions & 5 deletions apps/web/modules/editor/form/Inputs/CheckboxField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ interface CheckboxFieldProps {

export const CheckboxField = ({ property, name, refreshedProperties }: CheckboxFieldProps) => {
const { control, trigger, getValues } = useFormContext()
const { rules, useDynamicValue, setUseDynamicValue } = useDynamicField({
property,
})
const { rules, useDynamicValue, setUseDynamicValue } = useDynamicField()

useEffect(() => {
trigger(name)
Expand All @@ -31,7 +29,7 @@ export const CheckboxField = ({ property, name, refreshedProperties }: CheckboxF
}, [])

return useDynamicValue ? (
<DynamicValueField name={name} property={property} setUseDynamicValue={setUseDynamicValue} showDynamicValueButton={true} />
<DynamicValueField name={name} property={property} showDynamicValueButton={true} />
) : (
<FormField
control={control}
Expand All @@ -43,7 +41,7 @@ export const CheckboxField = ({ property, name, refreshedProperties }: CheckboxF
<Checkbox checked={field.value} onCheckedChange={field.onChange} />
</FormControl>
<div className="space-y-1 leading-none flex-grow">
<PropertyLabel property={property} refreshedProperties={refreshedProperties} setUseDynamicValue={setUseDynamicValue} />
<PropertyLabel property={property} refreshedProperties={refreshedProperties} />
<PropertyDescription>{property.description}</PropertyDescription>
</div>
</FormItem>
Expand Down
7 changes: 6 additions & 1 deletion apps/web/modules/editor/form/Inputs/ConnectionsSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,12 @@ export const ConnectionsSelect = ({ auth, connector }: ConnectionsSelectProps) =
)}
/>
<Dialog open={showDialog} onOpenChange={setShowDialog}>
<DialogContent className="sm:max-w-[425px] max-h-[calc(100vh-10px)] overflow-y-scroll">
<DialogContent
className="sm:max-w-[425px] max-h-[calc(100vh-10px)] overflow-y-scroll"
onInteractOutside={(event) => {
event.preventDefault()
}}
>
{auth.type === PropertyType.CUSTOM_AUTH && (
<CustomAuth onCreateAppConnection={handleAddedConnection} auth={auth} connector={connector} setShowDialog={setShowDialog} />
)}
Expand Down
22 changes: 12 additions & 10 deletions apps/web/modules/editor/form/Inputs/DynamicValueField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
useToast,
} from '@linkerry/ui-components/client'
import { Icons } from '@linkerry/ui-components/server'
import { Dispatch, SetStateAction, useEffect, useRef } from 'react'
import { useEffect, useRef } from 'react'
import { useFormContext } from 'react-hook-form'
import { useEditor } from '../../useEditor'
import { PropertyDescription } from '../PropertyDescription'
Expand All @@ -24,17 +24,15 @@ interface DynamicValueFieldProps {
property: ConnectorProperty
name: string
showDynamicValueButton?: boolean
setUseDynamicValue?: Dispatch<SetStateAction<boolean>>
// setUseDynamicValue?: Dispatch<SetStateAction<boolean>>
}

export const DynamicValueField = ({ property, name, showDynamicValueButton = false, setUseDynamicValue }: DynamicValueFieldProps) => {
export const DynamicValueField = ({ property, name, showDynamicValueButton = false }: DynamicValueFieldProps) => {
const { toast } = useToast()
const inputRef = useRef<HTMLInputElement>(null)
const { setShowDynamicValueModal } = useEditor()
const { control, trigger, setValue, getValues, clearErrors } = useFormContext()
const { rules } = useDynamicField({
property,
})
const { control, trigger, setValue, getValues, clearErrors, unregister } = useFormContext()
const { rules, useDynamicValue, setUseDynamicValue } = useDynamicField()

useEffect(() => {
switch (property.type) {
Expand All @@ -53,10 +51,12 @@ export const DynamicValueField = ({ property, name, showDynamicValueButton = fal
default:
break
}

unregister(name)
trigger(name)

inputRef.current?.focus()
}, [])
}, [rules])

const onSelectData = async (tokenString: string, data: any) => {
// TODO add data validation and better UI
Expand All @@ -80,7 +80,9 @@ export const DynamicValueField = ({ property, name, showDynamicValueButton = fal
control={control}
name={name}
defaultValue={''}
rules={rules}
rules={{
required: rules.required,
}}
render={({ field }) => (
<FormItem>
<FormLabel className="flex justify-between">
Expand All @@ -89,7 +91,7 @@ export const DynamicValueField = ({ property, name, showDynamicValueButton = fal
{showDynamicValueButton ? (
<TooltipProvider delayDuration={100}>
<Tooltip>
<TooltipTrigger onClick={() => setUseDynamicValue?.(false)} className="text-primary opacity-70 hover:opacity-100">
<TooltipTrigger onClick={() => setUseDynamicValue(false)} className="text-primary opacity-70 hover:opacity-100">
<Icons.Power size={'sm'} className="mb-1 mr-2" />
</TooltipTrigger>
<TooltipContent side="bottom" align="start">
Expand Down
8 changes: 3 additions & 5 deletions apps/web/modules/editor/form/Inputs/JsonField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@ interface JsonFieldProps {

export const JsonField = ({ property, name, refreshedProperties }: JsonFieldProps) => {
const { control, trigger, getValues } = useFormContext()
const { rules, useDynamicValue, setUseDynamicValue } = useDynamicField({
property,
})
const { rules, useDynamicValue, setUseDynamicValue } = useDynamicField()

useEffect(() => {
trigger(name)
Expand All @@ -33,7 +31,7 @@ export const JsonField = ({ property, name, refreshedProperties }: JsonFieldProp
}, [])

return useDynamicValue ? (
<DynamicValueField name={name} property={property} setUseDynamicValue={setUseDynamicValue} showDynamicValueButton={true} />
<DynamicValueField name={name} property={property} showDynamicValueButton={true} />
) : (
<FormField
control={control}
Expand All @@ -42,7 +40,7 @@ export const JsonField = ({ property, name, refreshedProperties }: JsonFieldProp
rules={rules}
render={({ field }) => (
<FormItem>
<PropertyLabel property={property} refreshedProperties={refreshedProperties} setUseDynamicValue={setUseDynamicValue} />
<PropertyLabel property={property} refreshedProperties={refreshedProperties} />
<FormControl>
<CodeEditor
value={prepareCodeMirrorValue(field.value)}
Expand Down
8 changes: 3 additions & 5 deletions apps/web/modules/editor/form/Inputs/LongTextField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ interface LongTextFieldProps {

export const LongTextField = ({ property, name, refreshedProperties }: LongTextFieldProps) => {
const { control, trigger, getValues } = useFormContext()
const { rules, useDynamicValue, setUseDynamicValue } = useDynamicField({
property,
})
const { rules, useDynamicValue, setUseDynamicValue } = useDynamicField()

useEffect(() => {
trigger(name)
Expand All @@ -31,7 +29,7 @@ export const LongTextField = ({ property, name, refreshedProperties }: LongTextF
}, [])

return useDynamicValue ? (
<DynamicValueField name={name} property={property} setUseDynamicValue={setUseDynamicValue} showDynamicValueButton={true} />
<DynamicValueField name={name} property={property} showDynamicValueButton={true} />
) : (
<FormField
control={control}
Expand All @@ -40,7 +38,7 @@ export const LongTextField = ({ property, name, refreshedProperties }: LongTextF
rules={rules}
render={({ field }) => (
<FormItem>
<PropertyLabel property={property} refreshedProperties={refreshedProperties} setUseDynamicValue={setUseDynamicValue} />
<PropertyLabel property={property} refreshedProperties={refreshedProperties} />
<FormControl>
<Textarea {...field} />
</FormControl>
Expand Down
17 changes: 8 additions & 9 deletions apps/web/modules/editor/form/Inputs/NumberField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,21 @@ interface NumberFieldProps {

export const NumberField = ({ property, name, refreshedProperties }: NumberFieldProps) => {
const { control, trigger, getValues } = useFormContext()
const { rules, useDynamicValue, setUseDynamicValue } = useDynamicField({
property,
})
const { rules, useDynamicValue, setUseDynamicValue } = useDynamicField()

useEffect(() => {
trigger(name)

const value = getValues(name)
if (typeof value !== 'string') return
else if (hasVariableToken(value)) {

if (typeof value !== 'string') {
trigger(name)
return
} else if (hasVariableToken(value)) {
setUseDynamicValue(true)
}
}, [])

return useDynamicValue ? (
<DynamicValueField name={name} property={property} setUseDynamicValue={setUseDynamicValue} showDynamicValueButton={true} />
<DynamicValueField name={name} property={property} showDynamicValueButton={true} />
) : (
<FormField
control={control}
Expand All @@ -40,7 +39,7 @@ export const NumberField = ({ property, name, refreshedProperties }: NumberField
rules={rules}
render={({ field }) => (
<FormItem>
<PropertyLabel property={property} refreshedProperties={refreshedProperties} setUseDynamicValue={setUseDynamicValue} />
<PropertyLabel property={property} refreshedProperties={refreshedProperties} />
<FormControl>
<Input {...field} type="number" onChange={(event) => field.onChange(+event.target.value)} />
</FormControl>
Expand Down
4 changes: 1 addition & 3 deletions apps/web/modules/editor/form/Inputs/SecretTextField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@ interface SecretTextFieldProps {

export const SecretTextField = ({ property, name, refreshedProperties }: SecretTextFieldProps) => {
const { control, trigger } = useFormContext()
const { rules } = useDynamicField({
property,
})
const { rules } = useDynamicField()

useEffect(() => {
trigger(name)
Expand Down
6 changes: 2 additions & 4 deletions apps/web/modules/editor/form/Inputs/ShortTextField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ interface ShortTextFieldProps {

export const ShortTextField = ({ property, name, refreshedProperties }: ShortTextFieldProps) => {
const { trigger, getValues } = useFormContext()
const { setUseDynamicValue } = useDynamicField({
property,
})
const { setUseDynamicValue } = useDynamicField()

useEffect(() => {
trigger(name)
Expand All @@ -27,5 +25,5 @@ export const ShortTextField = ({ property, name, refreshedProperties }: ShortTex
}
}, [])

return <DynamicValueField name={name} property={property} setUseDynamicValue={setUseDynamicValue} showDynamicValueButton={false} />
return <DynamicValueField name={name} property={property} showDynamicValueButton={false} />
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@ export const TextFieldDEPRECATED = ({ property, name }: TextFieldProps) => {
const { toast } = useToast()
const { setShowDynamicValueModal } = useEditor()
const { control, trigger, setValue, getValues } = useFormContext()
const { rules } = useDynamicField({
property,
})
const { rules } = useDynamicField()

useEffect(() => {
trigger(name)
Expand Down
Loading

0 comments on commit db4b795

Please sign in to comment.