mirror of
https://fast.feibisi.com/https://github.com/parcelvoy/platform.git
synced 2025-09-01 12:26:08 +08:00
update sidebar to use select field, update select field api (#60)
This commit is contained in:
parent
5b0b0adb5a
commit
b091431574
11 changed files with 297 additions and 236 deletions
|
@ -28,7 +28,7 @@ export const createJourney = async (projectId: number, params: JourneyParams): P
|
|||
}, trx)
|
||||
|
||||
// auto-create entrance step
|
||||
await JourneyEntrance.create(journey.id)
|
||||
await JourneyEntrance.create(journey.id, undefined, trx)
|
||||
|
||||
return journey
|
||||
})
|
||||
|
|
|
@ -10,6 +10,7 @@ import { random, snakeCase, uuid } from '../utilities'
|
|||
import App from '../app'
|
||||
import JourneyProcessJob from './JourneyProcessJob'
|
||||
import jsonpath from 'jsonpath'
|
||||
import { Database } from '../config/database'
|
||||
|
||||
export class JourneyUserStep extends Model {
|
||||
user_id!: number
|
||||
|
@ -98,7 +99,7 @@ export class JourneyEntrance extends JourneyStep {
|
|||
this.list_id = json?.data.list_id
|
||||
}
|
||||
|
||||
static async create(journeyId: number, listId?: number): Promise<JourneyEntrance> {
|
||||
static async create(journeyId: number, listId?: number, db?: Database): Promise<JourneyEntrance> {
|
||||
return await JourneyEntrance.insertAndFetch({
|
||||
type: this.type,
|
||||
external_id: uuid(),
|
||||
|
@ -108,7 +109,7 @@ export class JourneyEntrance extends JourneyStep {
|
|||
},
|
||||
x: 0,
|
||||
y: 0,
|
||||
})
|
||||
}, db)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { ComponentType, Dispatch, ReactNode, SetStateAction } from 'react'
|
||||
import { ComponentType, Dispatch, Key, ReactNode, SetStateAction } from 'react'
|
||||
import { FieldPath, FieldValues, UseFormReturn } from 'react-hook-form'
|
||||
|
||||
export type Class<T> = new () => T
|
||||
|
||||
|
@ -12,10 +13,26 @@ export interface CommonInputProps {
|
|||
disabled?: boolean
|
||||
label?: ReactNode
|
||||
subtitle?: ReactNode
|
||||
hideLabel?: boolean
|
||||
error?: ReactNode
|
||||
}
|
||||
|
||||
export type ControlledInputProps<T> = ControlledProps<T> & CommonInputProps
|
||||
|
||||
export interface FieldProps<X extends FieldValues, P extends FieldPath<X>> extends CommonInputProps {
|
||||
form: UseFormReturn<X>
|
||||
name: P
|
||||
}
|
||||
|
||||
export type FieldBindingsProps<I extends ControlledInputProps<T>, T, X extends FieldValues, P extends FieldPath<X>> = Omit<I, keyof ControlledProps<T>> & FieldProps<X, P>
|
||||
|
||||
export interface OptionsProps<O, V = O> {
|
||||
options: O[]
|
||||
toValue?: (option: O) => V
|
||||
getValueKey?: (option: V) => Key
|
||||
getOptionDisplay?: (option: O) => ReactNode
|
||||
}
|
||||
|
||||
export type UseStateContext<T> = [T, Dispatch<SetStateAction<T>>]
|
||||
|
||||
export interface OAuthResponse {
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
border-right: 1px solid var(--color-grey);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.sidebar + * {
|
||||
|
@ -28,95 +29,29 @@
|
|||
}
|
||||
|
||||
.sidebar-header .logo svg {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
flex-shrink: 0;
|
||||
fill: var(--color-primary);
|
||||
}
|
||||
|
||||
.sidebar-header .project-switcher {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
.sidebar .project-switcher.select-button {
|
||||
padding: 15px 20px;
|
||||
border-radius: 0;
|
||||
border-width: 0 0 1px;
|
||||
border-color: var(--color-grey);
|
||||
}
|
||||
|
||||
.project-switcher .switcher-button {
|
||||
border: 0px;
|
||||
background: var(--color-background);
|
||||
display: flex;
|
||||
text-align: left;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.project-switcher .switcher-button .switcher-text {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.project-switcher .switcher-button span {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.project-switcher .switcher-label {
|
||||
.project-switcher-label {
|
||||
color: var(--color-primary-soft);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.project-switcher .switcher-value {
|
||||
.project-switcher-value {
|
||||
color: var(--color-primary);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.project-switcher .project-switcher-icon {
|
||||
color: var(--color-primary);
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.project-switcher .switcher-options {
|
||||
position: absolute;
|
||||
background: var(--color-background);
|
||||
border-radius: 0 0 var(--border-radius) var(--border-radius);
|
||||
box-shadow: 0px 10px 20px rgba(0,0,0,0.1);
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
left: 0px;
|
||||
margin: 0;
|
||||
top: 100%;
|
||||
outline: none;
|
||||
border: 1px solid var(--color-grey);
|
||||
}
|
||||
|
||||
.project-switcher .switcher-options .switcher-option {
|
||||
display: flex;
|
||||
border-bottom: 1px solid var(--color-grey);
|
||||
padding: 15px 20px;
|
||||
justify-content: space-between;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.project-switcher .switcher-options .switcher-option:hover:not(.disabled) {
|
||||
background: var(--color-background-soft);
|
||||
}
|
||||
|
||||
.project-switcher .switcher-options .switcher-option:last-child {
|
||||
border-bottom: 0px;
|
||||
}
|
||||
|
||||
.project-switcher .switcher-options .switcher-option svg {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
flex-shrink: 0;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.project-switcher .switcher-options .switcher-option.selected svg {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
nav {
|
||||
padding: 20px;
|
||||
flex-grow: 1;
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import './Sidebar.css'
|
||||
import NavLink from './NavLink'
|
||||
import { ReactComponent as Logo } from '../assets/logo-icon.svg'
|
||||
import { ReactComponent as Logo } from '../assets/logo.svg'
|
||||
import { Link, NavLinkProps, useNavigate } from 'react-router-dom'
|
||||
import { PropsWithChildren, useCallback, useContext } from 'react'
|
||||
import { PropsWithChildren, useContext } from 'react'
|
||||
import { AdminContext, ProjectContext } from '../contexts'
|
||||
import api from '../api'
|
||||
import { PreferencesContext } from './PreferencesContext'
|
||||
import { Listbox } from '@headlessui/react'
|
||||
import { CheckIcon, ChevronUpDownIcon } from './icons'
|
||||
import useResolver from '../hooks/useResolver'
|
||||
import { LinkButton } from './Button'
|
||||
import { SelectField } from './form/SelectField'
|
||||
import Button, { LinkButton } from './Button'
|
||||
import ButtonGroup from './ButtonGroup'
|
||||
|
||||
interface SidebarProps {
|
||||
links?: Array<NavLinkProps & { key: string, icon: string }>
|
||||
|
@ -20,7 +20,7 @@ export default function Sidebar({ children, links }: PropsWithChildren<SidebarPr
|
|||
const profile = useContext(AdminContext)
|
||||
const [project] = useContext(ProjectContext)
|
||||
const [preferences, setPreferences] = useContext(PreferencesContext)
|
||||
const [projects] = useResolver(useCallback(async () => await api.projects.all(), []))
|
||||
const [projects] = useResolver(api.projects.all)
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -29,41 +29,34 @@ export default function Sidebar({ children, links }: PropsWithChildren<SidebarPr
|
|||
<Link className="logo" to='/'>
|
||||
<Logo />
|
||||
</Link>
|
||||
<div className="project-switcher">
|
||||
<Listbox
|
||||
value={project}
|
||||
onChange={(project) => navigate(`/projects/${project.id}`)}>
|
||||
<Listbox.Button className="switcher-button">
|
||||
<div className="switcher-text">
|
||||
<span className="switcher-label">Project</span>
|
||||
<span className="switcher-value">{project.name}</span>
|
||||
</div>
|
||||
<span className="project-switcher-icon">
|
||||
<ChevronUpDownIcon aria-hidden="true" />
|
||||
</span>
|
||||
</Listbox.Button>
|
||||
|
||||
<Listbox.Options className="switcher-options">
|
||||
{projects?.map((project) => (
|
||||
<Listbox.Option
|
||||
key={project.id}
|
||||
value={project}
|
||||
className={
|
||||
({ active, selected }) => `switcher-option ${active ? 'active' : ''} ${selected ? 'selected' : ''}` }
|
||||
>
|
||||
<span>{project.name}</span>
|
||||
<span className="option-icon">
|
||||
<CheckIcon aria-hidden="true" />
|
||||
</span>
|
||||
</Listbox.Option>
|
||||
))}
|
||||
<div className="switcher-option disabled">
|
||||
<LinkButton size="small" icon="plus-lg" to="/projects/new">Create New Project</LinkButton>
|
||||
</div>
|
||||
</Listbox.Options>
|
||||
</Listbox>
|
||||
</div>
|
||||
</div>
|
||||
<SelectField
|
||||
value={project}
|
||||
onChange={project => navigate(`/projects/${project.id}`)}
|
||||
options={projects ?? [project]}
|
||||
getSelectedOptionDisplay={p => (
|
||||
<>
|
||||
<div className='project-switcher-label'>Project</div>
|
||||
<div className='project-switcher-value'>{p.name}</div>
|
||||
</>
|
||||
)}
|
||||
hideLabel
|
||||
buttonClassName='project-switcher'
|
||||
variant='minimal'
|
||||
optionsFooter={
|
||||
<div
|
||||
style={{
|
||||
borderTop: '1px solid var(--color-grey)',
|
||||
paddingTop: '10px',
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
<LinkButton size='small' variant='primary' to='/projects/new' icon='plus'>
|
||||
{'Create Project'}
|
||||
</LinkButton>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<nav>
|
||||
{
|
||||
links?.map(({ key, ...props }) => (
|
||||
|
@ -76,13 +69,23 @@ export default function Sidebar({ children, links }: PropsWithChildren<SidebarPr
|
|||
<div className="sidebar-profile">
|
||||
<div className="profile-image"></div>
|
||||
<span className="profile-name">{`${profile.first_name} ${profile.last_name}`}</span>
|
||||
<span className="profile-role">
|
||||
<button onClick={async () => await api.logout()}>
|
||||
Sign Out
|
||||
</button>
|
||||
<button onClick={() => {
|
||||
setPreferences({ ...preferences, mode: preferences.mode === 'dark' ? 'light' : 'dark' })
|
||||
}}>Toggle Theme</button>
|
||||
<span className='profile-role'>
|
||||
<ButtonGroup>
|
||||
<Button
|
||||
variant='plain'
|
||||
size='small'
|
||||
icon={preferences.mode === 'dark' ? 'moon' : 'sun'}
|
||||
onClick={() => setPreferences({ ...preferences, mode: preferences.mode === 'dark' ? 'light' : 'dark' })}
|
||||
/>
|
||||
<Button
|
||||
variant='plain'
|
||||
size='small'
|
||||
icon=''
|
||||
onClick={async () => await api.logout()}
|
||||
>
|
||||
{'Sign Out'}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -10,10 +10,8 @@
|
|||
.ui-select .select-button {
|
||||
background: var(--color-background);
|
||||
appearance: none;
|
||||
border: 1px solid var(--color-grey);
|
||||
box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05);
|
||||
border: 1px solid transparent;
|
||||
padding: 12px 15px;
|
||||
border-radius: var(--border-radius);
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
color: var(--color-primary);
|
||||
|
@ -29,11 +27,21 @@
|
|||
gap: 5px;
|
||||
}
|
||||
|
||||
.ui-select .select-button:hover {
|
||||
border: 1px solid var(--color-grey-hard);
|
||||
.ui-select.plain .select-button {
|
||||
border-color: var(--color-grey);
|
||||
box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.ui-select.plain .select-button:hover {
|
||||
border-color: var(--color-grey-hard);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.ui-select.minimal .select-button:hover {
|
||||
background-color: var(--color-background-soft);
|
||||
}
|
||||
|
||||
.ui-select .select-button.small {
|
||||
padding: 5px 7px;
|
||||
border-radius: var(--border-radius-inner);
|
||||
|
@ -82,7 +90,6 @@
|
|||
list-style: none;
|
||||
padding: 5px;
|
||||
z-index: 999;
|
||||
overflow: hidden;
|
||||
max-height: 275px;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
|
|
@ -1,27 +1,43 @@
|
|||
import { Listbox, Transition } from '@headlessui/react'
|
||||
import { Fragment, Key, useEffect, useId, useState } from 'react'
|
||||
import { Fragment, ReactNode } from 'react'
|
||||
import { CheckIcon, ChevronUpDownIcon } from '../icons'
|
||||
import { FieldPath, FieldValues, useController } from 'react-hook-form'
|
||||
import { FieldOption, FieldProps } from './Field'
|
||||
import './SelectField.css'
|
||||
import { usePopperSelectDropdown } from '../utils'
|
||||
import { defaultGetOptionDisplay, defaultGetValueKey, usePopperSelectDropdown } from '../utils'
|
||||
import { ControlledInputProps, FieldBindingsProps, OptionsProps } from '../../types'
|
||||
import clsx from 'clsx'
|
||||
|
||||
interface OptionFieldProps<X extends FieldValues, P extends FieldPath<X>> extends FieldProps<X, P> {
|
||||
options: FieldOption[]
|
||||
value?: Key
|
||||
onChange?: (value: Key) => void
|
||||
export interface SelectFieldProps<T, O = T> extends ControlledInputProps<T>, OptionsProps<O, T> {
|
||||
className?: string
|
||||
buttonClassName?: string
|
||||
getSelectedOptionDisplay?: (option: O) => ReactNode
|
||||
optionsFooter?: ReactNode
|
||||
size?: 'small' | 'regular'
|
||||
variant?: 'plain' | 'minimal'
|
||||
}
|
||||
|
||||
export default function SelectField<X extends FieldValues, P extends FieldPath<X>>(props: OptionFieldProps<X, P>) {
|
||||
const id = useId()
|
||||
const { form, label, name, options, size = 'regular' } = props
|
||||
let { value, onChange } = props
|
||||
if (form) {
|
||||
const { field } = useController({ name, control: form?.control })
|
||||
value = field.value
|
||||
onChange = field.onChange
|
||||
}
|
||||
const defaultToValue = (o: any) => o
|
||||
|
||||
export function SelectField<T, U = T>({
|
||||
buttonClassName,
|
||||
className,
|
||||
disabled,
|
||||
error,
|
||||
getOptionDisplay = defaultGetOptionDisplay,
|
||||
getSelectedOptionDisplay = getOptionDisplay,
|
||||
hideLabel,
|
||||
label,
|
||||
options,
|
||||
optionsFooter,
|
||||
onChange,
|
||||
required,
|
||||
size,
|
||||
subtitle,
|
||||
toValue = defaultToValue,
|
||||
getValueKey = defaultGetValueKey,
|
||||
value,
|
||||
variant,
|
||||
}: SelectFieldProps<T, U>) {
|
||||
|
||||
const {
|
||||
setReferenceElement,
|
||||
|
@ -30,64 +46,113 @@ export default function SelectField<X extends FieldValues, P extends FieldPath<X
|
|||
styles,
|
||||
} = usePopperSelectDropdown()
|
||||
|
||||
// Get an internal default value based on options list
|
||||
const [defaultValue, setDefaultValue] = useState<FieldOption>({ key: id, label: 'Loading...' })
|
||||
useEffect(() => {
|
||||
const option = options.find(item => value && item.key === value) ?? options[0]
|
||||
setDefaultValue(option)
|
||||
if (!value && options.length > 0) {
|
||||
onChange?.(option.key)
|
||||
}
|
||||
}, [value, options])
|
||||
const selectedOption = options.find(o => Object.is(getValueKey(toValue(o)), getValueKey(value)))
|
||||
|
||||
return (
|
||||
<div className="ui-select">
|
||||
<Listbox
|
||||
value={defaultValue}
|
||||
onChange={(value) => onChange?.(value.key) }
|
||||
name={name}>
|
||||
{label && <Listbox.Label>
|
||||
<span>
|
||||
{label}
|
||||
{props.required && <span style={{ color: 'red' }}> *</span>}
|
||||
<Listbox
|
||||
as='div'
|
||||
className={clsx('ui-select', className, variant ?? 'plain')}
|
||||
by={(left: T, right: T) => Object.is(getValueKey(left), getValueKey(right))}
|
||||
disabled={disabled}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
>
|
||||
<Listbox.Label style={hideLabel ? { display: 'none' } : undefined}>
|
||||
{label}
|
||||
{
|
||||
required && (
|
||||
<span style={{ color: 'red' }}> *</span>
|
||||
)
|
||||
}
|
||||
</Listbox.Label>
|
||||
{
|
||||
subtitle && (
|
||||
<span className='label-subtitle'>
|
||||
{subtitle}
|
||||
</span>
|
||||
</Listbox.Label>}
|
||||
<Listbox.Button className={`select-button ${size}`} ref={setReferenceElement}>
|
||||
<span className="select-button-label">{defaultValue?.label}</span>
|
||||
<span className="select-button-icon">
|
||||
<ChevronUpDownIcon aria-hidden="true" />
|
||||
)
|
||||
}
|
||||
<Listbox.Button className={clsx('select-button', size, buttonClassName)} ref={setReferenceElement}>
|
||||
<span className="select-button-label">
|
||||
{
|
||||
selectedOption === undefined
|
||||
? ''
|
||||
: getSelectedOptionDisplay(selectedOption)
|
||||
}
|
||||
</span>
|
||||
<span className="select-button-icon">
|
||||
<ChevronUpDownIcon aria-hidden="true" />
|
||||
</span>
|
||||
</Listbox.Button>
|
||||
{
|
||||
(error && !hideLabel) && (
|
||||
<span className='field-error'>
|
||||
{error}
|
||||
</span>
|
||||
</Listbox.Button>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
leave="transition-leave"
|
||||
leaveFrom="transition-leave-from"
|
||||
leaveTo="transition-leave-to"
|
||||
enter="transition-enter"
|
||||
enterFrom="transition-enter-from"
|
||||
enterTo="transition-enter-to"
|
||||
)
|
||||
}
|
||||
<Transition
|
||||
as={Fragment}
|
||||
leave="transition-leave"
|
||||
leaveFrom="transition-leave-from"
|
||||
leaveTo="transition-leave-to"
|
||||
enter="transition-enter"
|
||||
enterFrom="transition-enter-from"
|
||||
enterTo="transition-enter-to"
|
||||
>
|
||||
<Listbox.Options
|
||||
className="select-options"
|
||||
ref={setPopperElement}
|
||||
style={styles.popper}
|
||||
{...attributes.popper}
|
||||
>
|
||||
<Listbox.Options
|
||||
className="select-options"
|
||||
ref={setPopperElement}
|
||||
style={styles.popper}
|
||||
{...attributes.popper}>
|
||||
{options.map((option) => (
|
||||
{options.map((option) => {
|
||||
const value = toValue(option)
|
||||
return (
|
||||
<Listbox.Option
|
||||
key={option.key}
|
||||
value={option}
|
||||
className={
|
||||
({ active, selected }) => `select-option ${active ? 'active' : ''} ${selected ? 'selected' : ''}` }
|
||||
key={getValueKey(value)}
|
||||
value={value}
|
||||
className={({ active, selected }) => clsx(
|
||||
'select-option',
|
||||
active && 'active',
|
||||
selected && 'selected',
|
||||
)}
|
||||
>
|
||||
<span>{option.label}</span>
|
||||
<span>{getOptionDisplay(option)}</span>
|
||||
<span className="option-icon">
|
||||
<CheckIcon aria-hidden="true" />
|
||||
</span>
|
||||
</Listbox.Option>
|
||||
))}
|
||||
</Listbox.Options>
|
||||
</Transition>
|
||||
</Listbox>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
{optionsFooter}
|
||||
</Listbox.Options>
|
||||
</Transition>
|
||||
</Listbox>
|
||||
)
|
||||
}
|
||||
|
||||
SelectField.Field = function SelectFieldField<T, X extends FieldValues, P extends FieldPath<X>>({
|
||||
form,
|
||||
name,
|
||||
required,
|
||||
...rest
|
||||
}: FieldBindingsProps<SelectFieldProps<T>, T, X, P>) {
|
||||
|
||||
const { field, fieldState } = useController({
|
||||
control: form.control,
|
||||
name,
|
||||
rules: {
|
||||
required,
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<SelectField
|
||||
{...rest}
|
||||
{...field}
|
||||
required={required}
|
||||
error={fieldState.error?.message}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useState } from 'react'
|
||||
import { Key, useState } from 'react'
|
||||
import { Modifier, usePopper } from 'react-popper'
|
||||
|
||||
const modifiers: Array<Partial<Modifier<any, any>>> = [
|
||||
|
@ -12,7 +12,7 @@ const modifiers: Array<Partial<Modifier<any, any>>> = [
|
|||
{
|
||||
name: 'offset',
|
||||
options: {
|
||||
offset: [0, -12],
|
||||
offset: [0, 4],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -47,3 +47,7 @@ export function usePopperSelectDropdown() {
|
|||
attributes,
|
||||
}
|
||||
}
|
||||
|
||||
export const defaultGetValueKey = (option: any) => (typeof option === 'object' ? option.id : option) as Key
|
||||
|
||||
export const defaultGetOptionDisplay = (option: any) => (typeof option === 'object' ? option.label ?? option.name : option) as string
|
||||
|
|
|
@ -8,7 +8,7 @@ import FormWrapper from '../../ui/form/FormWrapper'
|
|||
import Heading from '../../ui/Heading'
|
||||
import Modal from '../../ui/Modal'
|
||||
import ListTable from '../users/ListTable'
|
||||
import SelectField from '../../ui/form/SelectField'
|
||||
import { SelectField } from '../../ui/form/SelectField'
|
||||
import { snakeToTitle } from '../../utils'
|
||||
import OptionField from '../../ui/form/OptionField'
|
||||
import { SelectionProps } from '../../ui/form/Field'
|
||||
|
@ -59,12 +59,15 @@ const SubscriptionSelection = ({ subscriptions, form }: { subscriptions: Subscri
|
|||
label: item.name,
|
||||
})), watchChannel)
|
||||
|
||||
return <SelectField
|
||||
form={form}
|
||||
name="subscription_id"
|
||||
label="Subscription Group"
|
||||
options={options}
|
||||
required />
|
||||
return (
|
||||
<SelectField.Field
|
||||
form={form}
|
||||
name="subscription_id"
|
||||
label="Subscription Group"
|
||||
options={options}
|
||||
required
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const ProviderSelection = ({ providers, form }: { providers: Provider[], form: UseFormReturn<CampaignCreateParams> }) => {
|
||||
|
@ -75,12 +78,15 @@ const ProviderSelection = ({ providers, form }: { providers: Provider[], form: U
|
|||
label: item.name,
|
||||
})), watchChannel)
|
||||
|
||||
return <SelectField
|
||||
form={form}
|
||||
name="provider_id"
|
||||
label="Provider"
|
||||
options={options}
|
||||
required />
|
||||
return (
|
||||
<SelectField.Field
|
||||
form={form}
|
||||
name="provider_id"
|
||||
label="Provider"
|
||||
options={options}
|
||||
required
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default function CampaignEditModal({ campaign, open, onClose, onSave }: CampaignEditParams) {
|
||||
|
|
|
@ -4,7 +4,7 @@ import { LocaleContext } from '../../contexts'
|
|||
import { Campaign, UseStateContext } from '../../types'
|
||||
import Button from '../../ui/Button'
|
||||
import ButtonGroup from '../../ui/ButtonGroup'
|
||||
import SelectField from '../../ui/form/SelectField'
|
||||
import { SelectField } from '../../ui/form/SelectField'
|
||||
import CreateLocaleModal from './CreateLocaleModal'
|
||||
|
||||
interface LocaleSelectorParams {
|
||||
|
@ -28,21 +28,33 @@ export default function LocaleSelector({ campaignState, openState }: LocaleSelec
|
|||
|
||||
return <>
|
||||
<ButtonGroup>
|
||||
{ currentLocale && <SelectField
|
||||
options={allLocales}
|
||||
name="locale"
|
||||
size="small"
|
||||
value={currentLocale}
|
||||
onChange={(currentLocale) => setLocale({ currentLocale, allLocales })} />}
|
||||
{ campaign.state !== 'finished' && <Button
|
||||
size="small"
|
||||
variant="secondary"
|
||||
onClick={() => setOpen(true)}>Add Locale</Button>}
|
||||
{
|
||||
currentLocale && (
|
||||
<SelectField
|
||||
options={allLocales}
|
||||
size="small"
|
||||
value={currentLocale}
|
||||
onChange={(currentLocale) => setLocale({ currentLocale, allLocales })}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
campaign.state !== 'finished' && (
|
||||
<Button
|
||||
size="small"
|
||||
variant="secondary"
|
||||
onClick={() => setOpen(true)}
|
||||
>
|
||||
{'Add Locale'}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
</ButtonGroup>
|
||||
<CreateLocaleModal
|
||||
open={open}
|
||||
setIsOpen={setOpen}
|
||||
campaign={campaign}
|
||||
setCampaign={handleCampaignCreate} />
|
||||
setCampaign={handleCampaignCreate}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Operator, Rule, RuleType, WrapperRule } from '../../types'
|
||||
import Button from '../../ui/Button'
|
||||
import ButtonGroup from '../../ui/ButtonGroup'
|
||||
import SelectField from '../../ui/form/SelectField'
|
||||
import { SelectField } from '../../ui/form/SelectField'
|
||||
import TextField from '../../ui/form/TextField'
|
||||
import './RuleBuilder.css'
|
||||
|
||||
|
@ -269,12 +269,16 @@ const OperatorSelector = ({ type, value, onChange }: OperatorParams) => {
|
|||
|
||||
const operators = types[type]
|
||||
|
||||
return <SelectField
|
||||
options={operators}
|
||||
name="operator"
|
||||
size="small"
|
||||
value={value}
|
||||
onChange={(key) => onChange(key as Operator)} />
|
||||
return (
|
||||
<SelectField
|
||||
options={operators}
|
||||
size="small"
|
||||
toValue={o => o.key}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
hideLabel
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
interface TypeParams {
|
||||
|
@ -283,7 +287,10 @@ interface TypeParams {
|
|||
}
|
||||
|
||||
const TypeOperator = ({ value, onChange }: TypeParams) => {
|
||||
const types = [
|
||||
const types: Array<{
|
||||
key: RuleType
|
||||
label: string
|
||||
}> = [
|
||||
{ key: 'string', label: 'String' },
|
||||
{ key: 'number', label: 'Number' },
|
||||
{ key: 'boolean', label: 'Boolean' },
|
||||
|
@ -291,12 +298,16 @@ const TypeOperator = ({ value, onChange }: TypeParams) => {
|
|||
{ key: 'array', label: 'Array' },
|
||||
]
|
||||
|
||||
return <SelectField
|
||||
options={types}
|
||||
name="type"
|
||||
size="small"
|
||||
value={value}
|
||||
onChange={(key) => onChange(key as RuleType)} />
|
||||
return (
|
||||
<SelectField
|
||||
options={types}
|
||||
size="small"
|
||||
toValue={o => o.key}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
hideLabel
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
interface RuleBuilderParams {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue