mirror of
https://fast.feibisi.com/https://github.com/parcelvoy/platform.git
synced 2025-09-01 12:26:08 +08:00
Splits text field into a field and plain input
This commit is contained in:
parent
c93ae4996a
commit
8dc440742d
29 changed files with 231 additions and 326 deletions
|
@ -32,7 +32,6 @@ export default class BasicAuthProvider extends AuthProvider {
|
|||
|
||||
async validate(ctx: Context) {
|
||||
|
||||
console.log('basic auth!', ctx.request.body)
|
||||
const { email, password } = ctx.request.body
|
||||
if (!email || !password) throw new RequestError(AuthError.InvalidCredentials)
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import { useDebounceControl, useResolver } from '../hooks'
|
|||
import { SearchParams, SearchResult } from '../types'
|
||||
import { TagPicker } from '../views/settings/TagPicker'
|
||||
import { DataTable, DataTableProps } from './DataTable'
|
||||
import TextField from './form/TextField'
|
||||
import TextInput from './form/TextInput'
|
||||
import Heading from './Heading'
|
||||
import { SearchIcon } from './icons'
|
||||
import Pagination from './Pagination'
|
||||
|
@ -128,12 +128,13 @@ export function SearchTable<T extends Record<string, any>>({
|
|||
|
||||
if (enableSearch) {
|
||||
filters.push(
|
||||
<TextField
|
||||
<TextInput
|
||||
key="search"
|
||||
name="search"
|
||||
value={search}
|
||||
placeholder={searchPlaceholder}
|
||||
onChange={setSearch}
|
||||
hideLabel={true}
|
||||
icon={<SearchIcon />}
|
||||
/>,
|
||||
)
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
.ui-stack {
|
||||
--spacing: 20px;
|
||||
--spacing: 10px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin: calc(var(--spacing) / -2);
|
||||
}
|
||||
|
||||
.ui-stack > * {
|
||||
padding: calc(var(--spacing) / 2);
|
||||
gap: var(--spacing);
|
||||
padding-bottom: var(--spacing);
|
||||
}
|
||||
|
||||
.ui-stack.ui-stack-vertical {
|
||||
|
|
|
@ -1,75 +0,0 @@
|
|||
.ui-date-picker {
|
||||
background: var(--color-background);
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: 0px 10px 20px rgba(16, 24, 40, 0.1);
|
||||
z-index: 10;
|
||||
max-width: 320px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.ui-date-picker .calendar-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7,1fr);
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.ui-date-picker .calendar-pagination {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.ui-date-picker .calendar-pagination p {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.ui-date-picker .calendar-pagination button {
|
||||
background: var(--color-background);
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
color: var(--color-primary);
|
||||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.ui-date-picker .calendar-header {
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.ui-date-picker .calendar-header li {
|
||||
font-weight: 500;
|
||||
color: var(--color-primary-soft);
|
||||
padding: 2px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ui-date-picker .calendar li {
|
||||
position: relative;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.ui-date-picker .calendar button {
|
||||
background: var(--color-background);
|
||||
border: 0px;
|
||||
text-align: center;
|
||||
font-size: 15px;
|
||||
border-radius: 50%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
aspect-ratio: 1 / 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ui-date-picker .calendar button:hover {
|
||||
background: var(--color-background-soft);
|
||||
}
|
||||
|
||||
.ui-date-picker .calendar button.today {
|
||||
background: var(--color-grey);
|
||||
}
|
||||
|
||||
.ui-date-picker .calendar button.selected {
|
||||
background: var(--color-primary);
|
||||
color: var(--color-on-primary);
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
import React, { useState } from 'react'
|
||||
import { useDatePicker } from '@rehookify/datepicker'
|
||||
import { FieldPath, FieldValues } from 'react-hook-form'
|
||||
import './DateField.css'
|
||||
import clsx from 'clsx'
|
||||
import TextField, { TextFieldProps } from './TextField'
|
||||
import { usePopper } from 'react-popper'
|
||||
import { formatISO } from 'date-fns'
|
||||
|
||||
interface DateFieldProps<X extends FieldValues, P extends FieldPath<X>> extends TextFieldProps<X, P> { }
|
||||
|
||||
export default function DateField<X extends FieldValues, P extends FieldPath<X>>({ form, ...params }: DateFieldProps<X, P>) {
|
||||
|
||||
const [showCalendar, setShowCalendar] = useState(false)
|
||||
const [referenceElement, setReferenceElement] = useState<Element | null | undefined>()
|
||||
const [popperElement, setPopperElement] = useState<HTMLElement | null | undefined>()
|
||||
const { styles, attributes } = usePopper(referenceElement, popperElement, {
|
||||
placement: 'bottom-start',
|
||||
})
|
||||
const [selectedDates, onDatesChange] = useState<Date[]>([])
|
||||
|
||||
const {
|
||||
data: { weekDays, calendars },
|
||||
propGetters: {
|
||||
dayButton,
|
||||
previousMonthButton,
|
||||
nextMonthButton,
|
||||
},
|
||||
} = useDatePicker({
|
||||
selectedDates,
|
||||
onDatesChange: (dates: Date[]) => {
|
||||
form?.setValue(params.name, formatISO(dates[0]) as any)
|
||||
onDatesChange(dates)
|
||||
},
|
||||
})
|
||||
|
||||
const { year, month, days } = calendars[0]
|
||||
|
||||
return (
|
||||
<div className="ui-date-field">
|
||||
<TextField
|
||||
form={form} {...params}
|
||||
inputRef={setReferenceElement}
|
||||
onFocus={() => setShowCalendar(true)} />
|
||||
{showCalendar && <section
|
||||
className="ui-date-picker"
|
||||
ref={setPopperElement}
|
||||
style={styles.popper}
|
||||
{...attributes.popper}>
|
||||
<header>
|
||||
<div className="calendar-pagination">
|
||||
<button {...previousMonthButton()}><</button>
|
||||
<p>{month} {year}</p>
|
||||
<button {...nextMonthButton()}>></button>
|
||||
</div>
|
||||
<ul className="calendar-header calendar-grid">
|
||||
{weekDays.map((day) => (
|
||||
<li key={`${month}-${day}`}>{day}</li>
|
||||
))}
|
||||
</ul>
|
||||
</header>
|
||||
<ul className="calendar calendar-grid">
|
||||
{days.map((dpDay) => (
|
||||
<li key={dpDay.date}>
|
||||
<button {...dayButton(dpDay)} className={clsx({ today: dpDay.isToday, selected: dpDay.selected })}>{dpDay.day}</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>}
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -26,6 +26,7 @@ export default function FormWrapper<T extends FieldValues>({
|
|||
})
|
||||
|
||||
const handleSubmit = form.handleSubmit(async data => {
|
||||
console.log('submitting', data)
|
||||
setIsLoading(true)
|
||||
onSubmit(data, navigate).finally(() => {
|
||||
setIsLoading(false)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { snakeToTitle } from '../../utils'
|
||||
import TextField from './TextField'
|
||||
import TextInput from './TextInput'
|
||||
import './SchemaFields.css'
|
||||
import SwitchField from './SwitchField'
|
||||
|
||||
|
@ -30,7 +30,7 @@ export default function SchemaFields({ title, parent, form, schema }: SchemaProp
|
|||
const item = props[key]
|
||||
const required = schema.required?.includes(key)
|
||||
if (item.type === 'string' || item.type === 'number') {
|
||||
return <TextField
|
||||
return <TextInput.Field
|
||||
key={key}
|
||||
form={form}
|
||||
name={`${parent}.${key}`}
|
||||
|
|
|
@ -1,104 +0,0 @@
|
|||
import clsx from 'clsx'
|
||||
import { useId, Ref, ReactNode } from 'react'
|
||||
import { FieldPath, FieldValues } from 'react-hook-form'
|
||||
import { snakeToTitle } from '../../utils'
|
||||
import { FieldProps } from './Field'
|
||||
import './TextField.css'
|
||||
|
||||
export interface TextFieldProps<X extends FieldValues, P extends FieldPath<X>> extends FieldProps<X, P> {
|
||||
type?: 'text' | 'time' | 'date' | 'datetime-local' | 'number' | 'password'
|
||||
textarea?: boolean
|
||||
size?: 'small' | 'regular'
|
||||
value?: string | number | readonly string[] | undefined
|
||||
placeholder?: string
|
||||
onChange?: (value: string) => void
|
||||
onBlur?: React.FocusEventHandler<HTMLTextAreaElement | HTMLInputElement>
|
||||
onFocus?: React.FocusEventHandler<HTMLTextAreaElement | HTMLInputElement>
|
||||
inputRef?: Ref<HTMLLabelElement>
|
||||
min?: number
|
||||
max?: number
|
||||
icon?: ReactNode
|
||||
}
|
||||
|
||||
export default function TextField<X extends FieldValues, P extends FieldPath<X>>({
|
||||
type = 'text',
|
||||
disabled,
|
||||
form,
|
||||
label,
|
||||
subtitle,
|
||||
min,
|
||||
max,
|
||||
name,
|
||||
required,
|
||||
textarea,
|
||||
size = 'regular',
|
||||
value,
|
||||
onChange,
|
||||
onBlur: onInputBlur,
|
||||
onFocus,
|
||||
placeholder,
|
||||
inputRef,
|
||||
icon,
|
||||
}: TextFieldProps<X, P>) {
|
||||
const id = useId()
|
||||
const formParams = form?.register(name, { disabled, required })
|
||||
const onBlur = formParams?.onBlur
|
||||
|
||||
return (
|
||||
<label ref={inputRef} className={clsx('ui-text-field', { 'hide-label': !form && !label })}>
|
||||
{
|
||||
(!!form || !!label) && (
|
||||
<span>
|
||||
{label ?? snakeToTitle(name)}
|
||||
{required && <span style={{ color: 'red' }}> *</span>}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
{subtitle && <span className="label-subtitle">{subtitle}</span>}
|
||||
<div className={clsx(icon && 'ui-text-field-icon-wrapper')}>
|
||||
{
|
||||
textarea
|
||||
? (
|
||||
<textarea
|
||||
value={value}
|
||||
{...formParams}
|
||||
onChange={(event) => onChange?.(event?.target.value)}
|
||||
onBlur={async (event) => {
|
||||
await onBlur?.(event)
|
||||
onInputBlur?.(event)
|
||||
}}
|
||||
onFocus={onFocus}
|
||||
id={id}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<input
|
||||
type={type}
|
||||
value={value}
|
||||
className={size}
|
||||
placeholder={placeholder}
|
||||
{...formParams}
|
||||
onChange={(event) => onChange?.(event?.target.value)}
|
||||
onBlur={async (event) => {
|
||||
await onBlur?.(event)
|
||||
onInputBlur?.(event)
|
||||
}}
|
||||
onFocus={onFocus}
|
||||
id={id}
|
||||
min={min}
|
||||
max={max}
|
||||
disabled={disabled}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
icon && (
|
||||
<span className="ui-text-field-icon">
|
||||
{icon}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</label>
|
||||
)
|
||||
}
|
|
@ -1,13 +1,13 @@
|
|||
.ui-text-field-icon-wrapper {
|
||||
.ui-text-input-icon-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ui-text-field-icon-wrapper input,
|
||||
.ui-text-field-icon-wrapper textarea {
|
||||
.ui-text-input-icon-wrapper input,
|
||||
.ui-text-input-icon-wrapper textarea {
|
||||
padding-left: 36px;
|
||||
}
|
||||
|
||||
.ui-text-field-icon {
|
||||
.ui-text-input-icon {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 12px;
|
153
apps/ui/src/ui/form/TextInput.tsx
Normal file
153
apps/ui/src/ui/form/TextInput.tsx
Normal file
|
@ -0,0 +1,153 @@
|
|||
import clsx from 'clsx'
|
||||
import { Ref, ReactNode } from 'react'
|
||||
import { FieldPath, FieldValues, useController } from 'react-hook-form'
|
||||
import { ControlledInputProps, FieldProps } from '../../types'
|
||||
import { snakeToTitle } from '../../utils'
|
||||
import './TextInput.css'
|
||||
|
||||
type TextInputValue = string | number | readonly string[] | undefined
|
||||
export interface BaseTextInputProps<T extends TextInputValue> extends Partial<ControlledInputProps<T>> {
|
||||
type?: 'text' | 'time' | 'date' | 'datetime-local' | 'number' | 'password'
|
||||
textarea?: boolean
|
||||
size?: 'small' | 'regular'
|
||||
value?: T
|
||||
name: string
|
||||
placeholder?: string
|
||||
onChange?: (value: T) => void
|
||||
onBlur?: React.FocusEventHandler<HTMLTextAreaElement | HTMLInputElement>
|
||||
onFocus?: React.FocusEventHandler<HTMLTextAreaElement | HTMLInputElement>
|
||||
labelRef?: Ref<HTMLLabelElement>
|
||||
inputRef?: Ref<HTMLInputElement | HTMLTextAreaElement>
|
||||
hideLabel?: boolean
|
||||
min?: number
|
||||
max?: number
|
||||
icon?: ReactNode
|
||||
}
|
||||
|
||||
export type TextInputProps<T extends TextInputValue> = BaseTextInputProps<T> & (
|
||||
| {
|
||||
textarea: true
|
||||
inputRef?: Ref<HTMLTextAreaElement>
|
||||
} | {
|
||||
textarea: false
|
||||
inputRef?: Ref<HTMLInputElement>
|
||||
} | {
|
||||
textarea?: undefined
|
||||
inputRef?: Ref<HTMLInputElement>
|
||||
}
|
||||
)
|
||||
|
||||
export default function TextInput<X extends TextInputValue>({
|
||||
type = 'text',
|
||||
disabled,
|
||||
label,
|
||||
subtitle,
|
||||
min,
|
||||
max,
|
||||
name,
|
||||
required,
|
||||
textarea,
|
||||
size = 'regular',
|
||||
value,
|
||||
onChange,
|
||||
onBlur,
|
||||
onFocus,
|
||||
placeholder,
|
||||
labelRef,
|
||||
inputRef,
|
||||
hideLabel = false,
|
||||
icon,
|
||||
}: TextInputProps<X>) {
|
||||
return (
|
||||
<label ref={labelRef} className={clsx('ui-text-input', { 'hide-label': hideLabel })}>
|
||||
{
|
||||
!hideLabel && (
|
||||
<span>
|
||||
{label ?? snakeToTitle(name)}
|
||||
{required && <span style={{ color: 'red' }}> *</span>}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
{subtitle && <span className="label-subtitle">{subtitle}</span>}
|
||||
<div className={clsx(icon && 'ui-text-input-icon-wrapper')}>
|
||||
{
|
||||
textarea
|
||||
? (
|
||||
<textarea
|
||||
value={value}
|
||||
onChange={(event) => onChange?.(event?.target.value as X)}
|
||||
onBlur={onBlur}
|
||||
onFocus={onFocus}
|
||||
ref={inputRef}
|
||||
disabled={disabled}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<input
|
||||
type={type}
|
||||
value={value}
|
||||
className={size}
|
||||
placeholder={placeholder}
|
||||
onChange={(event) => {
|
||||
const inputValue = typeof value === 'number'
|
||||
? event?.target.valueAsNumber
|
||||
: event?.target.value
|
||||
onChange?.(inputValue as X)
|
||||
}}
|
||||
onBlur={onBlur}
|
||||
onFocus={onFocus}
|
||||
ref={inputRef}
|
||||
min={min}
|
||||
max={max}
|
||||
disabled={disabled}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
icon && (
|
||||
<span className="ui-text-input-icon">
|
||||
{icon}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</label>
|
||||
)
|
||||
}
|
||||
|
||||
TextInput.Field = function TextInputField<X extends FieldValues, P extends FieldPath<X>>({
|
||||
form,
|
||||
name,
|
||||
required,
|
||||
value,
|
||||
onChange,
|
||||
onBlur,
|
||||
...rest
|
||||
}: TextInputProps<P> & FieldProps<X, P>) {
|
||||
|
||||
const { field: { ref, ...field }, fieldState } = useController({
|
||||
control: form.control,
|
||||
name,
|
||||
rules: {
|
||||
required,
|
||||
},
|
||||
})
|
||||
|
||||
return (
|
||||
<TextInput
|
||||
{...rest}
|
||||
{...field}
|
||||
inputRef={ref}
|
||||
onBlur={async (event) => {
|
||||
await field.onBlur?.()
|
||||
onBlur?.(event)
|
||||
}}
|
||||
onChange={async (event) => {
|
||||
await field.onChange?.(event)
|
||||
onChange?.(event)
|
||||
}}
|
||||
required={required}
|
||||
error={fieldState.error?.message}
|
||||
/>
|
||||
)
|
||||
}
|
|
@ -4,7 +4,7 @@ import api from '../../api'
|
|||
import { ReactComponent as Logo } from '../../assets/logo.svg'
|
||||
import Alert from '../../ui/Alert'
|
||||
import FormWrapper from '../../ui/form/FormWrapper'
|
||||
import TextField from '../../ui/form/TextField'
|
||||
import TextInput from '../../ui/form/TextInput'
|
||||
import './Auth.css'
|
||||
|
||||
interface LoginBasicParams {
|
||||
|
@ -39,8 +39,8 @@ export default function Login() {
|
|||
<FormWrapper<LoginBasicParams>
|
||||
onSubmit={handleLogin}>
|
||||
{form => <>
|
||||
<TextField form={form} name="email" />
|
||||
<TextField form={form} name="password" type="password" />
|
||||
<TextInput.Field form={form} name="email" />
|
||||
<TextInput.Field form={form} name="password" type="password" />
|
||||
</>}
|
||||
</FormWrapper>
|
||||
</div>
|
||||
|
|
|
@ -3,7 +3,7 @@ import api from '../../api'
|
|||
import { ProjectContext } from '../../contexts'
|
||||
import { Campaign, CampaignCreateParams, List, Project, Provider, SearchParams, Subscription } from '../../types'
|
||||
import { useController, UseFormReturn, useWatch } from 'react-hook-form'
|
||||
import TextField from '../../ui/form/TextField'
|
||||
import TextInput from '../../ui/form/TextInput'
|
||||
import FormWrapper from '../../ui/form/FormWrapper'
|
||||
import Heading from '../../ui/Heading'
|
||||
import ListTable from '../users/ListTable'
|
||||
|
@ -222,7 +222,7 @@ export function CampaignForm({ campaign, disableListSelection, onSave }: Campaig
|
|||
>
|
||||
{form => (
|
||||
<>
|
||||
<TextField form={form}
|
||||
<TextInput.Field form={form}
|
||||
name="name"
|
||||
label="Campaign Name"
|
||||
required
|
||||
|
|
|
@ -11,7 +11,7 @@ import LocaleSelector from './LocaleSelector'
|
|||
import Alert from '../../ui/Alert'
|
||||
import Button from '../../ui/Button'
|
||||
import { Column, Columns } from '../../ui/Columns'
|
||||
import TextField from '../../ui/form/TextField'
|
||||
import TextInput from '../../ui/form/TextInput'
|
||||
import ButtonGroup from '../../ui/ButtonGroup'
|
||||
import Modal, { ModalProps } from '../../ui/Modal'
|
||||
import { SearchTable, useSearchTableState } from '../../ui/SearchTable'
|
||||
|
@ -25,7 +25,7 @@ interface UserLookupProps extends Omit<ModalProps, 'title'> {
|
|||
const UserLookup = ({ open, onClose, onSelected }: UserLookupProps) => {
|
||||
const [project] = useContext(ProjectContext)
|
||||
const state = useSearchTableState(useCallback(async params => await api.users.search(project.id, params), [project]))
|
||||
const [value, setValue] = useState('')
|
||||
const [value, setValue] = useState<string>('')
|
||||
|
||||
return <Modal
|
||||
title="User Lookup"
|
||||
|
@ -34,7 +34,7 @@ const UserLookup = ({ open, onClose, onSelected }: UserLookupProps) => {
|
|||
size="regular">
|
||||
<div className="user-lookup">
|
||||
<ButtonGroup>
|
||||
<TextField name="search" placeholder="Enter email..." onChange={setValue} />
|
||||
<TextInput<string> name="search" placeholder="Enter email..." onChange={setValue} />
|
||||
<Button onClick={() => state.setParams({
|
||||
...state.params,
|
||||
q: value,
|
||||
|
@ -70,7 +70,7 @@ const SendProof = ({ open, onClose, onSubmit, type }: SendProofProps) => {
|
|||
<FormWrapper<TemplateProofParams>
|
||||
onSubmit={async ({ recipient }) => await onSubmit(recipient)}>
|
||||
{form => (
|
||||
<TextField form={form} name="recipient" required />
|
||||
<TextInput.Field form={form} name="recipient" required />
|
||||
)}
|
||||
</FormWrapper>
|
||||
</Modal>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Campaign } from '../../types'
|
||||
import TextField from '../../ui/form/TextField'
|
||||
import TextInput from '../../ui/form/TextInput'
|
||||
import FormWrapper from '../../ui/form/FormWrapper'
|
||||
import Modal from '../../ui/Modal'
|
||||
import { languageName } from '../../utils'
|
||||
|
@ -26,7 +26,7 @@ const LocaleTextField = ({ form }: { form: UseFormReturn<{ locale: string }> })
|
|||
}
|
||||
|
||||
return <>
|
||||
<TextField form={form}
|
||||
<TextInput.Field form={form}
|
||||
name="locale"
|
||||
label="Locale"
|
||||
onChange={handlePreviewLanguage}
|
||||
|
|
|
@ -5,7 +5,7 @@ import { CampaignContext, ProjectContext } from '../../contexts'
|
|||
import { CampaignLaunchParams } from '../../types'
|
||||
import OptionField from '../../ui/form/OptionField'
|
||||
import SwitchField from '../../ui/form/SwitchField'
|
||||
import TextField from '../../ui/form/TextField'
|
||||
import TextInput from '../../ui/form/TextInput'
|
||||
import FormWrapper from '../../ui/form/FormWrapper'
|
||||
import Modal from '../../ui/Modal'
|
||||
import Alert from '../../ui/Alert'
|
||||
|
@ -52,7 +52,7 @@ export default function LaunchCampaign({ open, onClose }: LaunchCampaignParams)
|
|||
value={launchType}
|
||||
onChange={setLaunchType} />
|
||||
{launchType === 'later' && <>
|
||||
<TextField
|
||||
<TextInput.Field
|
||||
type="datetime-local"
|
||||
form={form}
|
||||
name="send_at"
|
||||
|
|
|
@ -9,7 +9,7 @@ import { InfoTable } from '../../ui/InfoTable'
|
|||
import Modal from '../../ui/Modal'
|
||||
import FormWrapper from '../../ui/form/FormWrapper'
|
||||
import { EmailTemplateData, PushTemplateData, Template, TemplateUpdateParams, TextTemplateData } from '../../types'
|
||||
import TextField from '../../ui/form/TextField'
|
||||
import TextInput from '../../ui/form/TextInput'
|
||||
import api from '../../api'
|
||||
|
||||
const EmailTable = ({ data }: { data: EmailTemplateData }) => <InfoTable rows={{
|
||||
|
@ -22,21 +22,21 @@ const EmailTable = ({ data }: { data: EmailTemplateData }) => <InfoTable rows={{
|
|||
}} />
|
||||
|
||||
const EmailForm = ({ form }: { form: UseFormReturn<TemplateUpdateParams, any> }) => <>
|
||||
<TextField
|
||||
<TextInput.Field
|
||||
form={form}
|
||||
name="data.subject"
|
||||
label="Subject"
|
||||
textarea
|
||||
required />
|
||||
<TextField
|
||||
<TextInput.Field
|
||||
form={form}
|
||||
name="data.preheader"
|
||||
label="Preheader"
|
||||
textarea />
|
||||
<TextField form={form} name="data.from" label="From Email" required />
|
||||
<TextField form={form} name="data.reply_to" label="Reply To" />
|
||||
<TextField form={form} name="data.cc" label="CC" />
|
||||
<TextField form={form} name="data.bcc" label="BCC" />
|
||||
<TextInput.Field form={form} name="data.from" label="From Email" required />
|
||||
<TextInput.Field form={form} name="data.reply_to" label="Reply To" />
|
||||
<TextInput.Field form={form} name="data.cc" label="CC" />
|
||||
<TextInput.Field form={form} name="data.bcc" label="BCC" />
|
||||
</>
|
||||
|
||||
const TextTable = ({ data: { text } }: { data: TextTemplateData }) => {
|
||||
|
@ -51,7 +51,7 @@ const TextTable = ({ data: { text } }: { data: TextTemplateData }) => {
|
|||
}
|
||||
|
||||
const TextForm = ({ form }: { form: UseFormReturn<TemplateUpdateParams, any> }) => <>
|
||||
<TextField
|
||||
<TextInput.Field
|
||||
form={form}
|
||||
name="data.text"
|
||||
label="Message"
|
||||
|
@ -66,18 +66,18 @@ const PushTable = ({ data }: { data: PushTemplateData }) => <InfoTable rows={{
|
|||
}} />
|
||||
|
||||
const PushForm = ({ form }: { form: UseFormReturn<TemplateUpdateParams, any> }) => <>
|
||||
<TextField
|
||||
<TextInput.Field
|
||||
form={form}
|
||||
name="data.title"
|
||||
label="Title"
|
||||
required />
|
||||
<TextField
|
||||
<TextInput.Field
|
||||
form={form}
|
||||
name="data.body"
|
||||
label="Body"
|
||||
textarea
|
||||
required />
|
||||
<TextField
|
||||
<TextInput.Field
|
||||
form={form}
|
||||
name="data.topic"
|
||||
label="Topic"
|
||||
|
|
|
@ -4,7 +4,7 @@ import api from '../../api'
|
|||
import { ProjectContext } from '../../contexts'
|
||||
import { Journey } from '../../types'
|
||||
import FormWrapper from '../../ui/form/FormWrapper'
|
||||
import TextField from '../../ui/form/TextField'
|
||||
import TextInput from '../../ui/form/TextInput'
|
||||
import { TagPicker } from '../settings/TagPicker'
|
||||
|
||||
interface JourneyFormProps {
|
||||
|
@ -28,12 +28,12 @@ export function JourneyForm({ journey, onSaved }: JourneyFormProps) {
|
|||
{
|
||||
form => (
|
||||
<>
|
||||
<TextField
|
||||
<TextInput.Field
|
||||
form={form}
|
||||
name="name"
|
||||
required
|
||||
/>
|
||||
<TextField
|
||||
<TextInput.Field
|
||||
form={form}
|
||||
name="description"
|
||||
textarea
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { JourneyStepType } from '../../../types'
|
||||
import TextField from '../../../ui/form/TextField'
|
||||
import TextInput from '../../../ui/form/TextInput'
|
||||
import { DelayStepIcon } from '../../../ui/icons'
|
||||
import { snakeToTitle } from '../../../utils'
|
||||
|
||||
|
@ -27,7 +27,7 @@ export const delayStep: JourneyStepType<DelayStepConfig> = {
|
|||
<>
|
||||
{
|
||||
['days', 'hours', 'minutes'].map(name => (
|
||||
<TextField
|
||||
<TextInput
|
||||
key={name}
|
||||
name={name}
|
||||
label={snakeToTitle(name)}
|
||||
|
@ -35,7 +35,7 @@ export const delayStep: JourneyStepType<DelayStepConfig> = {
|
|||
size="small"
|
||||
min={0}
|
||||
value={value[name as keyof DelayStepConfig] ?? 0}
|
||||
onChange={n => onChange({ ...value, [name]: parseInt(n) })}
|
||||
onChange={n => onChange({ ...value, [name]: n })}
|
||||
/>
|
||||
))
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { JourneyStepType } from '../../../types'
|
||||
import TextField from '../../../ui/form/TextField'
|
||||
import TextInput from '../../../ui/form/TextInput'
|
||||
import { ExperimentStepIcon } from '../../../ui/icons'
|
||||
import { round } from '../../../utils'
|
||||
|
||||
|
@ -29,14 +29,14 @@ export const experimentStep: JourneyStepType<{}, ExperimentStepChildConfig> = {
|
|||
const totalRatio = siblingData.reduce((a, c) => a + c.ratio ?? 0, ratio)
|
||||
const percentage = totalRatio > 0 ? round(ratio / totalRatio * 100, 2) : 0
|
||||
return (
|
||||
<TextField
|
||||
<TextInput
|
||||
name="ratio"
|
||||
label="Ratio"
|
||||
subtitle={`${percentage}% of users will follow this path.`}
|
||||
type="number"
|
||||
size="small"
|
||||
value={value.ratio ?? 0}
|
||||
onChange={str => onChange({ ...value, ratio: parseFloat(str) })}
|
||||
onChange={ratio => onChange({ ...value, ratio })}
|
||||
/>
|
||||
)
|
||||
},
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import api from '../../api'
|
||||
import TextField from '../../ui/form/TextField'
|
||||
import TextInput from '../../ui/form/TextInput'
|
||||
import { Project } from '../../types'
|
||||
import FormWrapper from '../../ui/form/FormWrapper'
|
||||
import { SingleSelect } from '../../ui/form/SingleSelect'
|
||||
|
@ -28,9 +28,9 @@ export default function ProjectForm({ onSave }: ProjectFormProps) {
|
|||
{
|
||||
form => (
|
||||
<>
|
||||
<TextField form={form} name="name" required />
|
||||
<TextField form={form} name="description" textarea />
|
||||
<TextField form={form}
|
||||
<TextInput.Field form={form} name="name" required />
|
||||
<TextInput.Field form={form} name="description" textarea />
|
||||
<TextInput.Field form={form}
|
||||
name="locale"
|
||||
label="Default Locale"
|
||||
subtitle="This locale will be used as the default when creating campaigns and when a users locale does not match any available ones."
|
||||
|
|
|
@ -4,7 +4,7 @@ import { ProjectContext } from '../../contexts'
|
|||
import { ProjectApiKey, projectRoles } from '../../types'
|
||||
import Button from '../../ui/Button'
|
||||
import OptionField from '../../ui/form/OptionField'
|
||||
import TextField from '../../ui/form/TextField'
|
||||
import TextInput from '../../ui/form/TextInput'
|
||||
import FormWrapper from '../../ui/form/FormWrapper'
|
||||
import Modal from '../../ui/Modal'
|
||||
import { SearchTable, useSearchTableState } from '../../ui/SearchTable'
|
||||
|
@ -109,13 +109,13 @@ export default function ProjectApiKeys() {
|
|||
const scope = form.watch('scope')
|
||||
return (
|
||||
<>
|
||||
<TextField
|
||||
<TextInput.Field
|
||||
form={form}
|
||||
name="name"
|
||||
label="Name"
|
||||
required
|
||||
/>
|
||||
<TextField
|
||||
<TextInput.Field
|
||||
form={form}
|
||||
name="description"
|
||||
label="Description"
|
||||
|
|
|
@ -6,7 +6,7 @@ import { Provider, ProviderCreateParams, ProviderMeta, ProviderUpdateParams } fr
|
|||
import Alert from '../../ui/Alert'
|
||||
import Button from '../../ui/Button'
|
||||
import SchemaFields from '../../ui/form/SchemaFields'
|
||||
import TextField from '../../ui/form/TextField'
|
||||
import TextInput from '../../ui/form/TextInput'
|
||||
import FormWrapper from '../../ui/form/FormWrapper'
|
||||
import Modal, { ModalProps } from '../../ui/Modal'
|
||||
import Tile, { TileGrid } from '../../ui/Tile'
|
||||
|
@ -79,18 +79,22 @@ export default function IntegrationModal({ onChange, provider, ...props }: Integ
|
|||
{provider?.id
|
||||
? <>
|
||||
<h4>Details</h4>
|
||||
<TextField name="id" label="ID" value={provider.id} disabled />
|
||||
<TextInput
|
||||
name="id"
|
||||
label="ID"
|
||||
value={provider.id}
|
||||
disabled />
|
||||
{meta.paths && Object.keys(meta.paths).map(key => {
|
||||
const value = meta.paths?.[key]
|
||||
const url = `${window.location.origin}/providers/${provider?.id}${value}`
|
||||
return <TextField name="unsubscribe" key={key} label={key} value={url} disabled />
|
||||
return <TextInput name="unsubscribe" key={key} label={key} value={url} disabled />
|
||||
})}
|
||||
</>
|
||||
: <Alert title={meta.name} variant="plain">Fill out the fields below to setup this integration. For more information on this integration please see the documentation on our website</Alert>
|
||||
}
|
||||
|
||||
<h4>Config</h4>
|
||||
<TextField form={form} name="name" required />
|
||||
<TextInput.Field form={form} name="name" required />
|
||||
<SchemaFields parent="data" schema={meta.schema.properties.data} form={form} />
|
||||
</>
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import { useContext } from 'react'
|
|||
import api from '../../api'
|
||||
import { ProjectContext } from '../../contexts'
|
||||
import { Project } from '../../types'
|
||||
import TextField from '../../ui/form/TextField'
|
||||
import TextInput from '../../ui/form/TextInput'
|
||||
import FormWrapper from '../../ui/form/FormWrapper'
|
||||
import Heading from '../../ui/Heading'
|
||||
import { toast } from 'react-hot-toast'
|
||||
|
@ -25,15 +25,15 @@ export default function ProjectSettings() {
|
|||
{
|
||||
form => (
|
||||
<>
|
||||
<TextField form={form} name="name" required />
|
||||
<TextField form={form} name="description" textarea />
|
||||
<TextField form={form}
|
||||
<TextInput.Field form={form} name="name" required />
|
||||
<TextInput.Field form={form} name="description" textarea />
|
||||
<TextInput.Field form={form}
|
||||
name="locale"
|
||||
label="Default Locale"
|
||||
subtitle="This locale will be used as the default when creating campaigns and when a users locale does not match any available ones."
|
||||
required
|
||||
/>
|
||||
<TextField form={form}
|
||||
<TextInput.Field form={form}
|
||||
name="timezone"
|
||||
label="Timezone"
|
||||
required
|
||||
|
|
|
@ -6,7 +6,7 @@ import FormWrapper from '../../ui/form/FormWrapper'
|
|||
import Modal from '../../ui/Modal'
|
||||
import { SearchTable, useSearchTableState } from '../../ui/SearchTable'
|
||||
import { Subscription } from '../../types'
|
||||
import TextField from '../../ui/form/TextField'
|
||||
import TextInput from '../../ui/form/TextInput'
|
||||
import { SingleSelect } from '../../ui/form/SingleSelect'
|
||||
import Button from '../../ui/Button'
|
||||
import { PlusIcon } from '../../ui/icons'
|
||||
|
@ -59,7 +59,7 @@ export default function Subscriptions() {
|
|||
{
|
||||
form => (
|
||||
<>
|
||||
<TextField
|
||||
<TextInput.Field
|
||||
form={form}
|
||||
name="name"
|
||||
required
|
||||
|
|
|
@ -4,7 +4,7 @@ import { ProjectContext } from '../../contexts'
|
|||
import { Tag } from '../../types'
|
||||
import Button from '../../ui/Button'
|
||||
import FormWrapper from '../../ui/form/FormWrapper'
|
||||
import TextField from '../../ui/form/TextField'
|
||||
import TextInput from '../../ui/form/TextInput'
|
||||
import { PlusIcon } from '../../ui/icons'
|
||||
import Modal from '../../ui/Modal'
|
||||
import { SearchTable, useSearchTableState } from '../../ui/SearchTable'
|
||||
|
@ -67,7 +67,7 @@ export default function Tags() {
|
|||
{
|
||||
form => (
|
||||
<>
|
||||
<TextField form={form} name="name" required />
|
||||
<TextInput.Field form={form} name="name" required />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import { ProjectContext } from '../../contexts'
|
|||
import { List, ListCreateParams } from '../../types'
|
||||
import FormWrapper from '../../ui/form/FormWrapper'
|
||||
import OptionField from '../../ui/form/OptionField'
|
||||
import TextField from '../../ui/form/TextField'
|
||||
import TextInput from '../../ui/form/TextInput'
|
||||
import { TagPicker } from '../settings/TagPicker'
|
||||
import { createWrapperRule } from './RuleBuilder'
|
||||
|
||||
|
@ -31,7 +31,7 @@ export function ListCreateForm({ onCreated }: ListCreateFormProps) {
|
|||
>
|
||||
{form => (
|
||||
<>
|
||||
<TextField
|
||||
<TextInput.Field
|
||||
form={form}
|
||||
name="name"
|
||||
label="List Name"
|
||||
|
|
|
@ -9,7 +9,7 @@ import PageContent from '../../ui/PageContent'
|
|||
import RuleBuilder from './RuleBuilder'
|
||||
import Modal from '../../ui/Modal'
|
||||
import FormWrapper from '../../ui/form/FormWrapper'
|
||||
import TextField from '../../ui/form/TextField'
|
||||
import TextInput from '../../ui/form/TextInput'
|
||||
import { ListTag } from './ListTable'
|
||||
import { InfoTable } from '../../ui/InfoTable'
|
||||
import { snakeToTitle } from '../../utils'
|
||||
|
@ -103,7 +103,7 @@ export default function ListDetail() {
|
|||
>
|
||||
{form => (
|
||||
<>
|
||||
<TextField
|
||||
<TextInput.Field
|
||||
form={form}
|
||||
name="name"
|
||||
label="List Name"
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Operator, Rule, RuleType, WrapperRule } from '../../types'
|
|||
import Button from '../../ui/Button'
|
||||
import ButtonGroup from '../../ui/ButtonGroup'
|
||||
import { SingleSelect } from '../../ui/form/SingleSelect'
|
||||
import TextField from '../../ui/form/TextField'
|
||||
import TextInput from '../../ui/form/TextInput'
|
||||
import { PlusIcon, TrashIcon } from '../../ui/icons'
|
||||
import './RuleBuilder.css'
|
||||
|
||||
|
@ -188,7 +188,7 @@ const RuleView = ({ rule, onChange, onDelete }: RuleParams) => {
|
|||
value={rule.type}
|
||||
onChange={type => handleUpdate({ type })}
|
||||
/>
|
||||
<TextField
|
||||
<TextInput
|
||||
size="small"
|
||||
type="text"
|
||||
name="path"
|
||||
|
@ -200,7 +200,7 @@ const RuleView = ({ rule, onChange, onDelete }: RuleParams) => {
|
|||
type={rule.type}
|
||||
value={rule.operator}
|
||||
onChange={operator => handleUpdate({ operator })} />
|
||||
<TextField
|
||||
<TextInput
|
||||
size="small"
|
||||
type="text"
|
||||
name="value"
|
||||
|
|
|
@ -36,6 +36,7 @@ export default function UserDetailEvents() {
|
|||
}}
|
||||
/>
|
||||
<Modal title={event?.name}
|
||||
size="regular"
|
||||
open={event != null}
|
||||
onClose={() => setEvent(undefined)}
|
||||
>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue