@chakra-ui/react#FormLabel TypeScript Examples
The following examples show how to use
@chakra-ui/react#FormLabel.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: FormControl.tsx From openchakra with MIT License | 6 votes |
FormControl: React.FC<FormControlPropType> = ({ label, htmlFor, children, hasColumn, }) => ( <ChakraFormControl mb={3} as={Grid} display="flex" alignItems="center" justifyItems="center" > <FormLabel p={0} mr={2} color="gray.500" lineHeight="1rem" width={hasColumn ? '2.5rem' : '90px'} fontSize="xs" htmlFor={htmlFor} > {label} </FormLabel> <Box display="flex" alignItems="center" justifyItems="center" width={hasColumn ? '30px' : '130px'} > {children} </Box> </ChakraFormControl> )
Example #2
Source File: NgrokRegionField.tsx From bluebubbles-server with Apache License 2.0 | 6 votes |
NgrokRegionField = ({ helpText }: NgrokRegionFieldProps): JSX.Element => {
const ngrokRegion: string = (useAppSelector(state => state.config.ngrok_region) ?? '');
return (
<FormControl>
<FormLabel htmlFor='ngrok_region'>Ngrok Region</FormLabel>
<Flex flexDirection='row' justifyContent='flex-start' alignItems='center'>
<Select
id='ngrok_region'
placeholder='Select your Ngrok Region'
maxWidth="15em"
mr={3}
value={ngrokRegion}
onChange={(e) => {
if (!e.target.value || e.target.value.length === 0) return;
onSelectChange(e);
}}
>
<option value='us'>North America</option>
<option value='eu'>Europe</option>
<option value='ap'>Asia/Pacific</option>
<option value='au'>Australia</option>
<option value='sa'>South America</option>
<option value='jp'>Japan</option>
<option value='in'>India</option>
</Select>
</Flex>
<FormHelperText>
{helpText ?? 'Select the region closest to you. This will ensure latency is at its lowest when connecting to the server.'}
</FormHelperText>
</FormControl>
);
}
Example #3
Source File: UserForm.tsx From next-crud with MIT License | 6 votes |
UserForm = ({ initialValues, onSubmit }: IProps) => {
const { register, formState, handleSubmit } = useForm<IFormValues>({
defaultValues: initialValues,
resolver: yupResolver(schema),
mode: 'onChange',
})
return (
<VStack
as="form"
onSubmit={handleSubmit(onSubmit)}
spacing={4}
width="100%"
>
<FormControl
id="username"
isInvalid={!!formState.errors.username?.message}
>
<FormLabel>Username</FormLabel>
<Input name="username" ref={register} />
<FormErrorMessage>
{formState.errors.username?.message}
</FormErrorMessage>
</FormControl>
<Button
type="submit"
colorScheme="blue"
isLoading={formState.isSubmitting}
disabled={!formState.isValid}
>
Submit
</Button>
</VStack>
)
}
Example #4
Source File: UrlField.tsx From calories-in with MIT License | 6 votes |
function UrlField({ canEdit, food }: Props) {
const { register } = useFormContext<FoodForm>()
return (
<Flex minHeight={canEdit ? '200px' : undefined} flexDirection="column">
{canEdit && (
<Alert status="info" mb={3}>
<AlertIcon color="teal.400" />
Add a link will open a web page when the food is clicked. This is
useful if you want to show a specific product.
</Alert>
)}
<FormControl id="email">
<Flex alignItems="center">
<FormLabel mb={0} flexShrink={0}>
Link:
</FormLabel>
{canEdit ? (
<Input
{...register('url')}
placeholder="http://example.com"
type="email"
/>
) : (
<Link
href={food?.url}
target="_blank"
noOfLines={1}
color="teal.500"
>
{food?.url}
</Link>
)}
</Flex>
</FormControl>
</Flex>
)
}
Example #5
Source File: index.tsx From formik-chakra-ui with MIT License | 6 votes |
FormControl: FC<BaseProps> = (props: BaseProps) => {
const {
children,
name,
label,
labelProps,
helperText,
helperTextProps,
errorMessageProps,
...rest
} = props;
const [, { error, touched }] = useField(name);
return (
<ChakraFormControl isInvalid={!!error && touched} {...rest}>
{label && (
<FormLabel htmlFor={name} {...labelProps}>
{label}
</FormLabel>
)}
{children}
{error && (
<FormErrorMessage {...errorMessageProps}>{error}</FormErrorMessage>
)}
{helperText && (
<FormHelperText {...helperTextProps}>{helperText}</FormHelperText>
)}
</ChakraFormControl>
);
}
Example #6
Source File: AddNewFeedForm.tsx From nextjs-hasura-boilerplate with MIT License | 5 votes |
AddNewFeedForm = () => {
const [body, setBody] = useState("");
const [session] = useSession();
const [
insertFeed,
{ loading: insertFeedFetching, error: insertFeedError },
] = useInsertFeedMutation();
if (!session) {
return (
<AccessDeniedIndicator message="You need to be signed in to add a new feed!" />
);
}
const handleSubmit = async () => {
await insertFeed({
variables: {
author_id: session.id,
body,
},
});
setBody("");
};
const errorNode = () => {
if (!insertFeedError) {
return false;
}
return (
<Alert status="error">
<AlertIcon />
<AlertTitle>{insertFeedError}</AlertTitle>
<CloseButton position="absolute" right="8px" top="8px" />
</Alert>
);
};
return (
<Stack spacing={4}>
{errorNode()}
<Box p={4} shadow="lg" rounded="lg">
<Stack spacing={4}>
<FormControl isRequired>
<FormLabel htmlFor="body">What's on your mind?</FormLabel>
<Textarea
id="body"
value={body}
onChange={(e: ChangeEvent<HTMLTextAreaElement>) =>
setBody(e.currentTarget.value)
}
isDisabled={insertFeedFetching}
/>
</FormControl>
<FormControl>
<Button
loadingText="Posting..."
onClick={handleSubmit}
isLoading={insertFeedFetching}
isDisabled={!body.trim()}
>
Post
</Button>
</FormControl>
</Stack>
</Box>
</Stack>
);
}
Example #7
Source File: LocalPortField.tsx From bluebubbles-server with Apache License 2.0 | 5 votes |
LocalPortField = ({ helpText }: LocalPortFieldProps): JSX.Element => {
const dispatch = useAppDispatch();
const port: number = useAppSelector(state => state.config.socket_port) ?? 1234;
const [newPort, setNewPort] = useState(port);
const [portError, setPortError] = useState('');
const hasPortError: boolean = (portError?? '').length > 0;
useEffect(() => { setNewPort(port); }, [port]);
/**
* A handler & validator for saving a new port.
*
* @param theNewPort - The new port to save
*/
const savePort = (theNewPort: number): void => {
// Validate the port
if (theNewPort < 1024 || theNewPort > 65635) {
setPortError('Port must be between 1,024 and 65,635');
return;
} else if (theNewPort === port) {
setPortError('You have not changed the port since your last save!');
return;
}
dispatch(setConfig({ name: 'socket_port', value: theNewPort }));
if (hasPortError) setPortError('');
showSuccessToast({
id: 'settings',
duration: 4000,
description: 'Successfully saved new port! Restarting Proxy & HTTP services...'
});
};
return (
<FormControl isInvalid={hasPortError}>
<FormLabel htmlFor='socket_port'>Local Port</FormLabel>
<Flex flexDirection='row' justifyContent='flex-start' alignItems='center'>
<Input
id='socket_port'
type='number'
maxWidth="5em"
value={newPort}
onChange={(e) => {
if (hasPortError) setPortError('');
setNewPort(Number.parseInt(e.target.value));
}}
/>
<IconButton
ml={3}
verticalAlign='top'
aria-label='Save port'
icon={<AiOutlineSave />}
onClick={() => savePort(newPort)}
/>
</Flex>
{!hasPortError ? (
<FormHelperText>
{helpText ?? 'Enter the local port for the socket server to run on'}
</FormHelperText>
) : (
<FormErrorMessage>{portError}</FormErrorMessage>
)}
</FormControl>
);
}
Example #8
Source File: IconCustomizerDrawer.tsx From lucide with ISC License | 5 votes |
export function IconCustomizerDrawer() {
const [showCustomize, setShowCustomize] = useState(false);
const { color, setColor, size, setSize, strokeWidth, setStroke, resetStyle } = useContext(IconStyleContext);
return (
<>
<Button as="a" leftIcon={<Edit />} size="lg" onClick={() => setShowCustomize(true)}>
Customize
</Button>
<Drawer isOpen={showCustomize} placement="right" onClose={() => setShowCustomize(false)}>
<DrawerOverlay />
<DrawerContent>
<DrawerCloseButton />
<DrawerHeader>Customize Icons</DrawerHeader>
<DrawerBody>
<Grid gridGap={'1em'}>
<FormControl>
<ColorPicker
color={color}
value={color}
onChangeComplete={(col) => setColor(col.hex)}
/>
</FormControl>
<FormControl>
<FormLabel htmlFor="stroke">
<Flex>
<Text flexGrow={1} fontWeight={'bold'}>
Stroke
</Text>
<Text>{strokeWidth}px</Text>
</Flex>
</FormLabel>
<Slider
value={strokeWidth}
onChange={setStroke}
min={0.5}
max={3}
step={0.5}
name={'stroke'}
>
<SliderTrack>
<SliderFilledTrack bg={color} />
</SliderTrack>
<SliderThumb />
</Slider>
</FormControl>
<FormControl>
<FormLabel htmlFor="size">
<Flex>
<Text flexGrow={1} fontWeight={'bold'}>
Size
</Text>
<Text>{size}px</Text>
</Flex>
</FormLabel>
<Slider value={size} onChange={setSize} min={12} max={64} step={1} name={'size'}>
<SliderTrack>
<SliderFilledTrack bg={color} />
</SliderTrack>
<SliderThumb />
</Slider>
</FormControl>
<FormControl>
<Button onClick={resetStyle}>Reset</Button>
</FormControl>
</Grid>
</DrawerBody>
</DrawerContent>
</Drawer>
</>
);
}
Example #9
Source File: ColorPicker.tsx From lucide with ISC License | 5 votes |
function ColorPicker({ hsv, hsl, onChange, value: color }: ColorPickerProps) {
const [value, setValue] = useState(color);
const input = useRef<HTMLInputElement>(null);
useEffect(() => {
if (color !== value && input.current !== document.activeElement) {
setValue(color === 'currentColor' ? color : String(color).toUpperCase());
}
}, [color]);
const handleChange = (e) => {
let value = e.target.value;
setValue(value);
onChange(value, e);
};
return (
<div>
<FormLabel htmlFor="color" fontWeight={'bold'}>
Color
</FormLabel>
<InputGroup>
<InputLeftElement
children={
<Icon>
<rect x={0} width={24} y={0} height={24} fill={value} rx={2} />
</Icon>
}
/>
<Input value={value} name="color" onChange={handleChange} ref={input} />
</InputGroup>
<div
style={{
width: '100%',
paddingBottom: '75%',
position: 'relative',
overflow: 'hidden',
marginTop: '0.5rem',
borderRadius: '0.375rem',
border: '1px solid',
borderColor: 'inherit',
}}
>
<Saturation hsl={hsl} hsv={hsv} onChange={onChange} />
</div>
<div
style={{
minHeight: '2em',
position: 'relative',
margin: '0.5rem 0 0 0',
borderRadius: '0.375rem',
border: '1px solid',
borderColor: 'inherit',
overflow: 'hidden',
}}
>
<Hue hsl={hsl} onChange={onChange} direction={'horizontal'} />
</div>
</div>
);
}
Example #10
Source File: index.tsx From coindrop with GNU General Public License v3.0 | 5 votes |
UserSettingsPage: FC = () => {
const { user } = useUser();
const userId = user?.id;
const fetcher = () => getUserData(userId);
const { data: userData, error: fetchError, mutate } = useSWR(
userId ? 'user-data' : null,
fetcher,
);
const email = user?.email;
const Settings = () => {
if (fetchError) {
return (
<Text>
⚠️ Error fetching user data. Please refresh the page or contact support.
</Text>
);
}
if (userData) {
return (
<UserDataForm
userData={userData}
mutate={mutate}
userId={userId}
/>
);
}
return (
<Center>
<Spinner data-testid="no-user-data-spinner" />
</Center>
);
};
if (!user) {
return <Spinner data-testid="no-user-spinner" />;
}
return (
<Box>
<Heading as="h1" textAlign="center" my={4}>
My Account
</Heading>
<SectionHeading size="lg">
Profile
</SectionHeading>
<SectionContainer>
<FormLabel>Email address</FormLabel>
<Text ml={2}>{email}</Text>
</SectionContainer>
<SectionHeading size="lg">
Settings
</SectionHeading>
<Settings />
<SectionHeading size="lg">
Danger Zone
</SectionHeading>
<DeleteAccount />
</Box>
);
}
Example #11
Source File: index.tsx From nextjs-hasura-boilerplate with MIT License | 5 votes |
MyAccountPageComponent = ({ user }) => {
const [name, setName] = useState(user.name);
const [session] = useSession();
const [updateUser, { loading: updateUserFetching, error: updateUserError }] =
useUpdateUserMutation();
const handleSubmit = () => {
updateUser({
variables: {
userId: session.id,
name,
},
});
};
const errorNode = () => {
if (!updateUserError) {
return false;
}
return (
<Alert status="error">
<AlertIcon />
<AlertTitle>{updateUserError}</AlertTitle>
<CloseButton position="absolute" right="8px" top="8px" />
</Alert>
);
};
return (
<Stack spacing={8}>
<Heading>My Account</Heading>
{errorNode()}
<Box shadow="lg" rounded="lg" p={4}>
<Stack spacing={4}>
<FormControl isRequired>
<FormLabel htmlFor="name">Name</FormLabel>
<Input
type="text"
id="name"
value={name}
onChange={(e: FormEvent<HTMLInputElement>) =>
setName(e.currentTarget.value)
}
isDisabled={updateUserFetching}
/>
</FormControl>
<FormControl>
<Button
loadingText="Saving..."
onClick={handleSubmit}
isLoading={updateUserFetching}
isDisabled={!name.trim()}
>
Save
</Button>
</FormControl>
</Stack>
</Box>
</Stack>
);
}
Example #12
Source File: ControlAttendeeEdit.tsx From takeout-app with MIT License | 5 votes |
ControlAttendeeEdit: React.FC<Props> = () => {
const match = useRouteMatch<{ id: string }>();
const id = parseInt(match.params.id, 10);
const { data } = ControlApi.useAttendee(id);
const [errorAlert, setErrorAlert] = React.useState<JSX.Element | null>(null);
const [isRequesting, setIsRequesting] = React.useState<boolean>(false);
const { register, handleSubmit, reset } = useForm<ControlUpdateAttendeeRequestAttendee>({
defaultValues: {
name: React.useMemo(() => data?.attendee?.name, [data]),
is_staff: React.useMemo(() => data?.attendee?.is_staff, [data]),
is_speaker: React.useMemo(() => data?.attendee?.is_speaker, [data]),
is_committer: React.useMemo(() => data?.attendee?.is_committer, [data]),
presentation_slugs: React.useMemo(() => data?.attendee?.presentation_slugs || [], [data]),
},
});
const onSubmit = handleSubmit(async (data) => {
if (isRequesting) return;
setIsRequesting(true);
try {
await ControlApi.updateAttendee(id, data);
setErrorAlert(null);
} catch (e) {
setErrorAlert(
<Box my={2}>
<ErrorAlert error={e} />
</Box>,
);
}
setIsRequesting(false);
});
React.useEffect(() => {
if (data) reset(data.attendee);
}, [data]);
if (!data) return <p>Loading</p>;
// TODO: link to registration page and support email
return (
<>
{errorAlert}
<Container mt="20px">
<form onSubmit={onSubmit}>
<Text>
<Link href={data.ticket.admin_url} isExternal textDecoration="underline">
{data.ticket.reference}
</Link>
</Text>
<FormControl mt={4} id="attendee__name" isRequired>
<FormLabel>Name</FormLabel>
<Input {...register("name")} />
</FormControl>
<FormControl mt={4} id="attendee__staff" isRequired>
<FormLabel>Staff</FormLabel>
<Checkbox {...register("is_staff")} />
</FormControl>
<FormControl mt={4} id="attendee__speaker" isRequired>
<FormLabel>Speaker</FormLabel>
<Checkbox {...register("is_speaker")} />
</FormControl>
<FormControl mt={4} id="attendee__committer" isRequired>
<FormLabel>Committer</FormLabel>
<Checkbox {...register("is_committer")} />
</FormControl>
<FormControl mt={4} id="attendee__presentation_slugs">
<FormLabel>Presentation Slugs</FormLabel>
<Input {...register("presentation_slugs.0")} />
</FormControl>
<Button mt={4} size="lg" type="submit" isLoading={isRequesting}>
Save
</Button>
</form>
</Container>
</>
);
}
Example #13
Source File: ControlTrackCardForm.tsx From takeout-app with MIT License | 5 votes |
ControlTrackCardForm: React.FC<Props> = ({ track }) => {
const { data: controlConferenceData } = ControlApi.useConference();
// const history = useHistory();
const [errorAlert, setErrorAlert] = React.useState<JSX.Element | null>(null);
const [isRequesting, setIsRequesting] = React.useState<boolean>(false);
const { register, handleSubmit, reset } = useForm<{
slug: ConferencePresentationSlug;
inSeconds: number;
}>({
defaultValues: {
slug: "",
inSeconds: 0,
},
});
const onSubmit = handleSubmit(async (data) => {
if (!controlConferenceData) return;
if (isRequesting) return;
setIsRequesting(true);
try {
const card: TrackCard = {
track: track.slug,
at: dayjs().add(data.inSeconds, "seconds").unix(),
ut: 0,
...generateTrackCardFromPresentation(controlConferenceData, data.slug),
};
console.log(card);
await ControlApi.createTrackCard(card);
setErrorAlert(null);
} catch (e) {
setErrorAlert(
<Box my={2}>
<ErrorAlert error={e} />
</Box>,
);
}
setIsRequesting(false);
reset();
});
if (!controlConferenceData) {
return <p>Loading</p>;
}
//<Input {...register("at", { valueAsDate: true })} type="datetime-local" />
return (
<Box>
{errorAlert}
<form onSubmit={onSubmit}>
<FormControl mt={4} id="cardform_slug" isRequired>
<FormLabel>Name</FormLabel>
<Input {...register("slug")} />
</FormControl>
<FormControl mt={4} id="cardform_at">
<FormLabel>Activation In</FormLabel>
<Input {...register("inSeconds", { valueAsNumber: true })} type="number" />
</FormControl>
<Button mt={4} size="lg" type="submit" isLoading={isRequesting}>
Save
</Button>
</form>
</Box>
);
}
Example #14
Source File: Content.tsx From calories-in with MIT License | 5 votes |
function Content({ title, onClose, initialRef, variantFormIndex }: Props) {
const { register } = useFormContext()
const nameRegister = register('name')
const nameInputRef = useMergeRefs(nameRegister.ref, initialRef)
const onSubmit = useSubmitVariantNameForm({
variantFormIndex,
onComplete: onClose,
})
const { errorMessage, isInvalid } = useFormError('name')
useSelectInputText(initialRef)
return (
<form onSubmit={onSubmit}>
<ModalContent>
<ModalHeader>{title}</ModalHeader>
<ModalCloseButton />
<ModalBody>
<FormControl isInvalid={isInvalid}>
<FormLabel>Name</FormLabel>
<Input
autoComplete="off"
{...nameRegister}
ref={nameInputRef}
focusBorderColor={isInvalid ? 'red.500' : undefined}
placeholder="Enter name"
/>
<Collapse animateOpacity={true} in={Boolean(errorMessage)}>
<Box minHeight="21px">
<FormErrorMessage>{errorMessage}</FormErrorMessage>
</Box>
</Collapse>
</FormControl>
</ModalBody>
<ModalFooter>
<Button mr={3} onClick={onClose}>
Close
</Button>
<Button
type="submit"
colorScheme="teal"
variant="solid"
onClick={onSubmit}
>
Rename
</Button>
</ModalFooter>
</ModalContent>
</form>
)
}
Example #15
Source File: PollIntervalField.tsx From bluebubbles-server with Apache License 2.0 | 5 votes |
PollIntervalField = ({ helpText }: PollIntervalFieldProps): JSX.Element => {
const dispatch = useAppDispatch();
const pollInterval: number = useAppSelector(state => state.config.db_poll_interval) ?? 1000;
const [newInterval, setNewInterval] = useState(pollInterval);
const [intervalError, setIntervalError] = useState('');
const hasIntervalError: boolean = (intervalError?? '').length > 0;
useEffect(() => { setNewInterval(pollInterval); }, [pollInterval]);
/**
* A handler & validator for saving a new poll interval
*
* @param theNewInterval - The new interval to save
*/
const saveInterval = (theNewInterval: number): void => {
// Validate the interval
if (theNewInterval < 500) {
setIntervalError('The interval must be at least 500ms or else database locks can occur');
return;
}
dispatch(setConfig({ name: 'db_poll_interval', value: theNewInterval }));
if (hasIntervalError) setIntervalError('');
showSuccessToast({
id: 'settings',
duration: 4000,
description: 'Successfully saved new poll interval! Restarting DB listeners...'
});
};
return (
<FormControl isInvalid={hasIntervalError}>
<FormLabel htmlFor='db_poll_interval'>Database Poll Interval (ms)</FormLabel>
<Flex flexDirection='row' justifyContent='flex-start' alignItems='center'>
<Input
id='db_poll_interval'
type='number'
maxWidth="5em"
value={newInterval}
onChange={(e) => {
if (hasIntervalError) setIntervalError('');
setNewInterval(Number.parseInt(e.target.value));
}}
/>
<IconButton
ml={3}
verticalAlign='top'
aria-label='Save poll interval'
icon={<AiOutlineSave />}
onClick={() => saveInterval(newInterval)}
/>
</Flex>
{!hasIntervalError ? (
<FormHelperText>
{helpText ?? 'Enter how often (in milliseconds) you want the server to check for new messages in the database'}
</FormHelperText>
) : (
<FormErrorMessage>{intervalError}</FormErrorMessage>
)}
</FormControl>
);
}
Example #16
Source File: ServerPasswordField.tsx From bluebubbles-server with Apache License 2.0 | 5 votes |
ServerPasswordField = ({ helpText }: ServerPasswordFieldProps): JSX.Element => {
const dispatch = useAppDispatch();
const password: string = (useAppSelector(state => state.config.password) ?? '');
const [showPassword, setShowPassword] = useBoolean();
const [newPassword, setNewPassword] = useState(password);
const [passwordError, setPasswordError] = useState('');
const hasPasswordError: boolean = (passwordError?? '').length > 0;
useEffect(() => { setNewPassword(password); }, [password]);
/**
* A handler & validator for saving a new password.
*
* @param theNewPassword - The new password to save
*/
const savePassword = (theNewPassword: string): void => {
// Validate the port
if (theNewPassword.length < 8) {
setPasswordError('Your password must be at least 8 characters!');
return;
} else if (theNewPassword === password) {
setPasswordError('You have not changed the password since your last save!');
return;
}
dispatch(setConfig({ name: 'password', value: theNewPassword }));
if (hasPasswordError) setPasswordError('');
showSuccessToast({
id: 'settings',
description: 'Successfully saved new password!'
});
};
return (
<FormControl isInvalid={hasPasswordError}>
<FormLabel htmlFor='password'>Server Password</FormLabel>
<Input
id='password'
type={showPassword ? 'text' : 'password'}
maxWidth="20em"
value={newPassword}
onChange={(e) => {
if (hasPasswordError) setPasswordError('');
setNewPassword(e.target.value);
}}
/>
<IconButton
ml={3}
verticalAlign='top'
aria-label='View password'
icon={showPassword ? <AiFillEye /> : <AiFillEyeInvisible />}
onClick={() => setShowPassword.toggle()}
/>
<IconButton
ml={3}
verticalAlign='top'
aria-label='Save password'
icon={<AiOutlineSave />}
onClick={() => savePassword(newPassword)}
/>
{!hasPasswordError ? (
<FormHelperText>
{helpText ?? 'Enter a password to use for clients to authenticate with the server'}
</FormHelperText>
) : (
<FormErrorMessage>{passwordError}</FormErrorMessage>
)}
</FormControl>
);
}
Example #17
Source File: index.tsx From calories-in with MIT License | 5 votes |
function Form({
ownerName,
notes,
onClose,
initialRef,
onEditNotes,
fieldId,
textAreaHeight,
}: Props) {
const { register, handleSubmit } = useFormContext()
const notesRegister = register('notes')
const notesInputRef = useMergeRefs(notesRegister.ref, initialRef)
const oneTimeCheckActions = useOneTimeCheckActions()
const onSubmit = handleSubmit((form: NotesForm) => {
oneTimeCheckActions.set(`notes-${fieldId}`)
onEditNotes(form.notes || undefined)
onClose()
})
const { errorMessage, isInvalid } = useFormError('name')
return (
<form onSubmit={onSubmit}>
<ModalContent>
<Header ownerName={ownerName} notes={notes} />
<ModalCloseButton />
<ModalBody>
<FormControl isInvalid={isInvalid}>
<FormLabel>Notes</FormLabel>
<Textarea
autoComplete="off"
{...notesRegister}
ref={notesInputRef}
focusBorderColor={isInvalid ? 'red.500' : undefined}
placeholder="Enter notes"
height={textAreaHeight}
/>
<Collapse animateOpacity={true} in={Boolean(errorMessage)}>
<Box minHeight="21px">
<FormErrorMessage>{errorMessage}</FormErrorMessage>
</Box>
</Collapse>
</FormControl>
</ModalBody>
<ModalFooter>
<Button mr={3} onClick={onClose}>
Close
</Button>
<Button
type="submit"
colorScheme="teal"
variant="solid"
onClick={onSubmit}
>
Save
</Button>
</ModalFooter>
</ModalContent>
</form>
)
}
Example #18
Source File: TrackStreamOptionsSelector.tsx From takeout-app with MIT License | 5 votes |
TrackStreamOptionsSelector: React.FC<Props> = ({
track,
streamOptionsState: [options, setOptions],
instance,
}) => {
const handleOnChange = (key: "caption" | "interpretation") => {
return (e: React.ChangeEvent<HTMLInputElement>) => {
const newOptions = { ...options, [`${key}`]: e.target.checked };
setOptions(newOptions);
};
};
const enAndJa = (track.card?.topic?.labels?.indexOf("EN & JA") ?? -1) !== -1;
const interpretationLabel = enAndJa ? "English track" : "Japanese to English interpretation";
return (
<Box>
<Stack direction={["row", "row", "row", "column"]} spacing={0}>
<Tooltip label="Caption (English only)" aria-label="">
<FormControl display="flex" alignItems="center" h="30px">
<FormLabel htmlFor={`TrackStreamOptions_${instance}__CC`} aria-hidden="true" m={0} mr={1}>
<ClosedCaptionIcon w="24px" h="24px" />
</FormLabel>
<Switch
aria-label="Closed Caption"
id={`TrackStreamOptions_${instance}__CC`}
isChecked={options.caption}
onChange={handleOnChange("caption")}
/>
</FormControl>
</Tooltip>
{track.interpretation && track.card?.interpretation ? (
<Tooltip label={interpretationLabel} aria-label="">
<FormControl display="flex" alignItems="center" h="30px">
<FormLabel htmlFor={`TrackStreamOptions_${instance}__interpret`} aria-hidden="true" m={0} mr={1}>
<TranslateIcon w="24px" h="24px" />
</FormLabel>
<Switch
aria-label={interpretationLabel}
id={`TrackStreamOptions_${instance}__interpret`}
isChecked={options.interpretation}
onChange={handleOnChange("interpretation")}
/>
</FormControl>
</Tooltip>
) : null}
</Stack>
</Box>
);
}
Example #19
Source File: Login.tsx From takeout-app with MIT License | 5 votes |
LoginForm: React.FC = () => {
const history = useHistory();
const { data: conferenceData } = Api.useConference();
const [errorAlert, setErrorAlert] = React.useState<JSX.Element | null>(null);
const [isRequesting, setIsRequesting] = React.useState<boolean>(false);
const { register, handleSubmit } = useForm<{
email: string;
reference: string;
}>({ defaultValues: { email: "", reference: "" } });
const onSubmit = handleSubmit(async (data) => {
if (isRequesting) return;
setIsRequesting(true);
try {
const resp = await Api.createSession(data.email, data.reference);
setErrorAlert(null);
if (resp.attendee.is_ready) {
if (conferenceData) {
history.push(`/tracks/${encodeURIComponent(conferenceData.conference.default_track)}`);
} else {
location.href = "/";
}
} else {
history.push("/attendee");
}
} catch (e) {
setErrorAlert(
<Box my={2}>
<ErrorAlert error={e} />
</Box>,
);
}
setIsRequesting(false);
});
// TODO: link to registration page and support email
return (
<Box maxW="360px" w="100%">
<form onSubmit={onSubmit}>
<FormControl mt={4} id="login_email" isRequired>
<FormLabel>Email Address</FormLabel>
<FormHelperText color={Colors.textMuted} my={1}>
Must be identital to the one registered to your ticket
</FormHelperText>
<Input {...register("email")} type="email" autoFocus backgroundColor="white" />
</FormControl>
<FormControl mt={4} id="login_reference" isRequired>
<FormLabel>Ticket ID (Reference Code)</FormLabel>
<FormHelperText color={Colors.textMuted} my={1}>
Code should be shown at{" "}
<Link href="/assets/ticket-email.png" isExternal target="_blank" textDecoration="underline">
the upper right of the confirmation email
</Link>{" "}
you received.
</FormHelperText>
<Input
{...register("reference")}
type="password"
placeholder="e.g. ABCD-1, XY1N-10, ..."
backgroundColor="white"
/>
</FormControl>
<Flex direction="row" justifyContent="space-around" w="100%" mt="30px">
<Button type="submit" w="160px" h="46px" colorScheme="rk" isLoading={isRequesting}>
Log in
</Button>
</Flex>
</form>
{errorAlert}
</Box>
);
}
Example #20
Source File: index.tsx From jsonschema-editor-react with Apache License 2.0 | 5 votes |
AdvancedBoolean: React.FunctionComponent<AdvancedItemStateProps> = (
props: React.PropsWithChildren<AdvancedItemStateProps>
) => {
const { itemStateProp } = props;
const item = useState(itemStateProp);
return (
<Flex direction="column" wrap="nowrap">
<Stack
isInline
alignItems="center"
justifyContent="center"
alignContent="center"
m={1}
>
<FormLabel mr={2} htmlFor="default">
Default:{" "}
</FormLabel>
<Select
variant="outline"
value={(item.default.value as string) ?? ""}
size="sm"
margin={2}
placeholder="Choose data type"
onChange={(evt: React.ChangeEvent<HTMLSelectElement>) => {
item.default.set(evt.target.value);
}}
>
<option key="true" value="true">
true
</option>
<option key="false" value="false">
false
</option>
</Select>
</Stack>
</Flex>
);
}
Example #21
Source File: ControlLogin.tsx From takeout-app with MIT License | 5 votes |
ControlLogin: React.FC<Props> = () => {
const history = useHistory();
const [errorAlert, setErrorAlert] = React.useState<JSX.Element | null>(null);
const [isRequesting, setIsRequesting] = React.useState<boolean>(false);
const { register, handleSubmit } = useForm<{
password: string;
}>({ defaultValues: { password: "" } });
const onSubmit = handleSubmit(async (data) => {
if (isRequesting) return;
setIsRequesting(true);
try {
await ControlApi.createControlSession(data.password);
setErrorAlert(null);
history.push("/control");
} catch (e) {
setErrorAlert(
<Box my={2}>
<ErrorAlert error={e} />
</Box>,
);
}
setIsRequesting(false);
});
// TODO: link to registration page and support email
return (
<>
{errorAlert}
<Container mt="20px">
<p>Beep beep, beep boop?</p>
<form onSubmit={onSubmit}>
<FormControl mt={4} id="login_password" isRequired>
<FormLabel>Control Password</FormLabel>
<Input {...register("password")} type="password" />
</FormControl>
<Button mt={4} size="lg" type="submit" isLoading={isRequesting}>
Take control
</Button>
</form>
</Container>
</>
);
}
Example #22
Source File: AttendeeEdit.tsx From takeout-app with MIT License | 4 votes |
AttendeeEdit: React.FC = () => {
const { data: conferenceData } = Api.useConference();
const { data: session, error: sessionError } = Api.useSession();
const history = useHistory();
const [errorAlert, setErrorAlert] = React.useState<JSX.Element | null>(null);
const [isRequesting, setIsRequesting] = React.useState<boolean>(false);
const { register, handleSubmit, reset } = useForm<{
name: string;
gravatar_email: string;
}>({
defaultValues: {
name: React.useMemo(() => session?.attendee?.name, [session]),
gravatar_email: "",
},
});
React.useEffect(() => {
if (session?.attendee) reset({ name: session.attendee.name, gravatar_email: "" });
}, [session?.attendee]);
const onSubmit = handleSubmit(async (data) => {
//const wasReady = session!.attendee?.is_ready;
if (isRequesting) return;
setIsRequesting(true);
try {
await Api.updateAttendee(data.name, data.gravatar_email);
setErrorAlert(null);
if (conferenceData) {
history.push(`/tracks/${encodeURIComponent(conferenceData.conference.default_track)}`);
} else {
location.href = "/";
}
} catch (e) {
setErrorAlert(
<Box my={2}>
<ErrorAlert error={e} />
</Box>,
);
}
setIsRequesting(false);
});
if (!session?.attendee) {
return (
<Container maxW={["auto", "auto", "auto", "1000px"]} px="15px" py="22px">
<VStack>
{sessionError ? (
<Box my={2}>
<ErrorAlert error={sessionError} />
</Box>
) : null}
<Spinner size="xl" />
</VStack>
</Container>
);
}
return (
<Container maxW={["auto", "auto", "auto", "1000px"]} px="15px" py="22px">
<VStack justify="start" alignItems="start" spacing="30px">
<Heading as="h2" color={Colors.main}>
Settings
</Heading>
<HStack spacing="30px">
<Avatar size="xl" bg={Colors.defaultAvatarBg} src={session.attendee.avatar_url} loading="lazy" />
<Box maxW="750px">
<Text mb={2}>
Confirm your name and avatar used at live chat. These informations may be shared with other attendees once
submitted.
</Text>
<Text>
Be remember to abide by{" "}
<Link href="https://rubykaigi.org/2021-takeout/policies" isExternal textDecoration="underline">
our policies
</Link>
.
</Text>
</Box>
</HStack>
<form onSubmit={onSubmit}>
<VStack justify="start" alignItems="start" spacing="30px">
<FormControl id="login_reference" isRequired>
<FormLabel>Name</FormLabel>
<FormHelperText my={1}>Feel free to use nicknames, usernames, or handles :)</FormHelperText>
<Input {...register("name")} maxW="460px" autoFocus />
</FormControl>
<FormControl id="login_email">
<FormLabel>Gravatar Email Address</FormLabel>
<FormHelperText my={1}>
We use avatar images registered on{" "}
<Link href="https://www.gravatar.com" isExternal textDecoration="underline">
Gravatar
</Link>
. Fill the following field if you desire to choose different email address for your Gravatar image.
</FormHelperText>
<Input
{...register("gravatar_email")}
type="email"
maxW="460px"
placeholder="(leave empty to remain unchanged)"
/>
</FormControl>
<Button type="submit" w="160px" h="46px" colorScheme="rk" isLoading={isRequesting}>
{session.attendee.is_ready ? "Save" : "Continue"}
</Button>
</VStack>
</form>
{errorAlert}
</VStack>
</Container>
);
}
Example #23
Source File: index.tsx From coindrop with GNU General Public License v3.0 | 4 votes |
UserDataForm: FC<UserDataFormProps> = ({ userData, mutate, userId }) => {
const [isSubmitting, setIsSubmitting] = useState(false);
const toast = useToast();
const { register, handleSubmit, formState: { isDirty }, reset } = useForm();
const email_lists = userData?.email_lists;
const onSubmit = async (rawFormData) => {
setIsSubmitting(true);
const userDataForDb = {
email_lists: [],
};
Object.keys(optionalEmailLists).forEach(emailListId => {
if (rawFormData.email_lists[emailListId]) {
userDataForDb.email_lists.push(emailListId);
}
});
try {
await updateUserData({ data: userDataForDb, userId });
mutate(userDataForDb);
reset();
toast({
title: "Account updated",
status: "success",
duration: 6000,
isClosable: true,
});
} catch (err) {
toast({
title: "Error updating account",
description: "Please try again or contact support",
status: "error",
duration: 9000,
isClosable: true,
});
} finally {
setIsSubmitting(false);
}
};
return (
<SectionContainer>
<form onSubmit={handleSubmit(onSubmit)} data-testid="settings-form">
<SectionHeading size="md">
E-mail
</SectionHeading>
<Box
id="email-preferences-content"
m={4}
>
<FormLabel>Newsletters</FormLabel>
<Flex wrap="wrap">
{Object.entries(optionalEmailLists).map(([emailListId, emailListDisplayName]: [EmailListIds, string]) => {
return (
<Checkbox
key={emailListId}
mr={6}
name={`email_lists.${emailListId}`}
colorScheme="orange"
defaultChecked={email_lists?.includes(emailListId)}
ref={register()}
>
{emailListDisplayName}
</Checkbox>
);
})}
{alwaysEnabledEmailLists.map(listName => (
<Checkbox
key={listName}
mr={6}
colorScheme="orange"
defaultChecked
isDisabled
>
{listName}
</Checkbox>
))}
</Flex>
</Box>
<Box>
<Button
colorScheme="green"
type="submit"
isDisabled={!isDirty || isSubmitting}
leftIcon={isSubmitting ? <Spinner size="sm" /> : undefined}
>
{isSubmitting ? 'Saving' : 'Save'}
</Button>
</Box>
</form>
</SectionContainer>
);
}
Example #24
Source File: PaymentMethodsInput.tsx From coindrop with GNU General Public License v3.0 | 4 votes |
PaymentMethodsInput: FC<Props> = ({ fieldArrayName, fields, control, register, remove, append }) => {
const { colors } = useTheme();
const paymentMethodsDataWatch: PaymentMethod[] = useWatch({
control,
name: fieldArrayName,
});
const [openAccordionItemIndex, setOpenAccordionItemIndex] = useState(-1);
useEffect(() => {
if (
paymentMethodsDataWatch[paymentMethodsDataWatch.length - 1]?.paymentMethodId === "default-blank"
|| paymentMethodsDataWatch[paymentMethodsDataWatch.length - 1]?.address === ""
) {
setOpenAccordionItemIndex(paymentMethodsDataWatch.length - 1);
}
}, [paymentMethodsDataWatch]);
const containsInvalidAddress = paymentMethodsDataWatch.some(paymentMethod => !paymentMethod.address);
const { isAddressTouched, setIsAddressTouched } = useContext(AdditionalValidation);
// optgroup not compatible with Chakra dark mode: https://github.com/chakra-ui/chakra-ui/issues/2853
// const optionsGroup = (category: Category) => {
// const optgroupLabels: Record<Category, string> = {
// "digital-wallet": 'Digital Wallets',
// "digital-asset": "Digital Assets",
// "subscription-platform": "Subscription Platforms",
// };
// return (
// <optgroup label={optgroupLabels[category]}>
// {paymentMethods
// .filter(paymentMethod => paymentMethod.category === category)
// .sort((a, b) => (a.id < b.id ? -1 : 1))
// .map(({ id, displayName }) => (
// <option
// key={id}
// value={id}
// style={{display: paymentMethodsDataWatch.map(paymentMethodDataWatch => paymentMethodDataWatch.paymentMethodId).includes(id) ? "none" : undefined }}
// >
// {displayName}
// </option>
// ))}
// </optgroup>
// );
// };
return (
<>
{fields.length < 1
? 'No payment methods defined yet.'
: (
<Accordion
allowToggle
defaultIndex={-1}
index={openAccordionItemIndex}
>
{
fields
.map((item, index) => {
const watchedData = paymentMethodsDataWatch.find(watchedPaymentMethod => watchedPaymentMethod.id === item.id);
const PaymentMethodIcon = paymentMethodIcons[watchedData?.paymentMethodId];
return (
<AccordionItem
key={item.id}
>
<AccordionButton
onClick={() => {
setIsAddressTouched(true);
if (openAccordionItemIndex !== index && !paymentMethodsDataWatch.find(paymentMethod => paymentMethod.address === "")) {
setOpenAccordionItemIndex(index);
} else {
setOpenAccordionItemIndex(undefined);
}
}}
>
<Flex flex="1" textAlign="left" align="center">
<Flex mr={1} align="center">
{PaymentMethodIcon ? <PaymentMethodIcon mr={2} /> : <QuestionOutlineIcon mr={2} />}
{paymentMethodNames[watchedData?.paymentMethodId] ?? 'Add payment method'}
</Flex>
{watchedData?.isPreferred && (
<Flex>
<StarIcon
ml={2}
size="16px"
color={colors.yellow['400']}
/>
<Text
as="span"
fontSize="xs"
ml={1}
>
<i>Preferred</i>
</Text>
</Flex>
)}
</Flex>
<AccordionIcon />
</AccordionButton>
<AccordionPanel pb={4} id={`accordion-panel-${watchedData.paymentMethodId}`}>
<input
ref={register()}
name={`${fieldArrayName}[${index}].id`}
defaultValue={item.id}
style={{display: 'none'}}
/>
<Box
display={paymentMethodNames[watchedData?.paymentMethodId] ? "none" : "block"}
data-cy={`select-payment-method-container-${watchedData.paymentMethodId}`}
>
<Select
name={`${fieldArrayName}[${index}].paymentMethodId`}
ref={register()}
defaultValue={paymentMethodNames[item.paymentMethodId] ? item.paymentMethodId : 'default-blank'}
isInvalid={containsInvalidAddress && isAddressTouched}
onChange={() => setIsAddressTouched(false)}
>
<option hidden disabled value="default-blank">Select...</option>
{/* optgroup not compatible with Chakra dark mode: https://github.com/chakra-ui/chakra-ui/issues/2853 */}
{Object.entries(paymentMethodNames)
.sort((a, b) => {
const [aId] = a;
const [bId] = b;
return aId < bId ? -1 : 1;
})
.map(([paymentMethodId, paymentMethodDisplayName]) => (
<option
key={paymentMethodId}
value={paymentMethodId}
style={{display: paymentMethodsDataWatch.map(paymentMethod => paymentMethod.paymentMethodId).includes(paymentMethodId) ? "none" : undefined }}
>
{paymentMethodDisplayName}
</option>
))}
</Select>
</Box>
<Box
mx={3}
display={paymentMethodNames[watchedData?.paymentMethodId] ? "block" : "none"}
>
<FormControl isRequired>
<FormLabel htmlFor={`${fieldArrayName}[${index}].address`}>Address</FormLabel>
<Input
id={`address-input-${watchedData.paymentMethodId}`}
name={`${fieldArrayName}[${index}].address`}
ref={register()}
defaultValue={item.address}
isInvalid={containsInvalidAddress && isAddressTouched}
/>
<Box>
<Checkbox
name={`${fieldArrayName}[${index}].isPreferred`}
ref={register()}
defaultValue={item?.isPreferred ? 1 : 0}
defaultIsChecked={item?.isPreferred}
mt={1}
colorScheme="yellow"
>
Preferred
</Checkbox>
</Box>
</FormControl>
</Box>
<Flex
justify={watchedData?.paymentMethodId === 'default-blank' ? 'space-between' : 'flex-end'}
mt={3}
wrap="wrap"
align="center"
>
{watchedData?.paymentMethodId === 'default-blank' && (
<Text fontSize="xs" ml={1}>
<Link href="/blog/payment-method-request" isExternal>
Payment method not listed?
</Link>
</Text>
)}
<Button
onClick={() => {
remove(index);
setIsAddressTouched(false);
}}
leftIcon={<MinusIcon />}
size="sm"
>
{'Remove '}
{/* {paymentMethodNames[watchedData?.paymentMethodId]} */}
</Button>
</Flex>
</AccordionPanel>
</AccordionItem>
);
})
}
</Accordion>
)}
<Flex
justify="center"
mt={2}
>
<Button
id="add-payment-method-button"
onClick={() => {
append({});
setIsAddressTouched(false);
}}
leftIcon={<AddIcon />}
variant="ghost"
size="sm"
isDisabled={
(
fields.length > 0
&& !paymentMethodNames[paymentMethodsDataWatch[paymentMethodsDataWatch.length - 1]?.paymentMethodId]
)
|| containsInvalidAddress
}
>
Add payment method
</Button>
</Flex>
</>
);
}
Example #25
Source File: Form.tsx From ksana.in with Apache License 2.0 | 4 votes |
export function Form() {
const router = useRouter()
const { showAlert, hideAlert } = useAlertContext()
const bgBox = useColorModeValue('white', 'gray.700')
const [loading, setLoading] = useState<boolean>(false)
const [errorForm, setErrorForm] = useState<string>('')
const [email, setEmail] = useState<string>('')
const handleChangeEmail = (e: ChangeEvent<HTMLInputElement>) => {
const value = e.target.value
setEmail(value)
}
const checkIsEmpty = () => {
if (email === '') {
setErrorForm('Email tidak boleh dikosongkan.')
return true
}
setErrorForm('')
return false
}
const handleResetPassword = async () => {
const { error } = await forgetPassword({ email })
if (!error) {
showAlert({
title: 'Lupa password',
message: 'Tautan untuk melakukan setel ulang kata sandi telah dikirim ke email kamu.',
onClose: () => {
hideAlert()
router.push('/')
}
})
}
}
const handleSubmit = async () => {
setLoading(true)
const isEmpty = checkIsEmpty()
if (!isEmpty) {
await handleResetPassword()
}
setLoading(false)
}
return (
<Stack spacing={8} mx={'auto'} mt="20" maxW={'lg'} py={12} px={6}>
<Stack align={'center'}>
<Heading fontSize={'4xl'} color="orange.400">
Lupa password
</Heading>
</Stack>
<Box rounded={'lg'} bg={bgBox} boxShadow={'lg'} p={8}>
<Stack spacing={4}>
<FormControl id="email" isRequired>
<FormLabel>Email</FormLabel>
<Input
isInvalid={Boolean(errorForm)}
type="email"
name="email"
value={email}
onChange={handleChangeEmail}
autoComplete="username"
/>
</FormControl>
<Button
isLoading={loading}
loadingText="Memproses"
w="full"
bg="orange.400"
_hover={{
bg: 'orange.500'
}}
onClick={handleSubmit}
>
Minta reset password
</Button>
</Stack>
</Box>
</Stack>
)
}
Example #26
Source File: index.tsx From calories-in with MIT License | 4 votes |
function StatFormField(props: Props) {
const {
name,
label,
inputType,
isIdented = false,
nutritionValueUnit = 'g',
textInputRef,
isReadOnly = false,
isEmphasized = false,
isValueBold = false,
isCaption = false,
isRequired,
children,
labelDetail,
dividerProps = {},
hasDivider = true,
dailyValuePercent,
labelElement,
formLabelProps,
...rest
} = props
const { errorMessage, isInvalid } = useFormError(name)
const inputElement = useGetInputElement({
isInvalid,
name,
inputType,
textInputRef,
isReadOnly,
nutritionValueUnit,
isBold: isValueBold,
})
const labelDetailElement = labelDetail ? (
<Text
as={isReadOnly ? 'span' : undefined}
fontSize="sm"
fontWeight="thin"
ml={1}
>
{labelDetail}
</Text>
) : null
const isValueNextToLabel = isReadOnly && !(isCaption || isEmphasized)
return (
<FormControl
isInvalid={isInvalid}
id={name}
pl={isIdented ? 10 : 0}
isRequired={!isReadOnly && isRequired}
{...rest}
>
<VStack spacing={2} alignItems="stretch">
{hasDivider && <Divider {...dividerProps} />}
<Flex justifyContent={'space-between'} alignItems="center">
<Flex>
<FormLabel
fontWeight={
isIdented ? 'normal' : isEmphasized ? 'semibold' : 'medium'
}
flexShrink={0}
fontSize={isCaption ? 'lg' : 'md'}
m={0}
{...formLabelProps}
>
{label || labelElement}
{isReadOnly && labelDetailElement}
</FormLabel>
{!isReadOnly && labelDetailElement}
{isValueNextToLabel && <Box ml={2}>{inputElement}</Box>}
</Flex>
<Flex ml={2} justifyContent="flex-end">
{!isValueNextToLabel && inputElement}
{dailyValuePercent !== undefined && isValueNextToLabel && (
<Text fontWeight="medium">{`${dailyValuePercent}%`}</Text>
)}
{!isReadOnly && inputType === 'nutritionValue' && (
<Flex
width={9}
flexShrink={0}
justifyContent="flex-start"
alignItems="center"
ml={1}
>
<Text textColor="gray.500">{nutritionValueUnit}</Text>
</Flex>
)}
</Flex>
</Flex>
</VStack>
<Collapse animateOpacity={true} in={Boolean(errorMessage)}>
<Box minHeight="21px">
<FormErrorMessage>{errorMessage}</FormErrorMessage>
</Box>
</Collapse>
{children}
</FormControl>
)
}
Example #27
Source File: EditPiggybankModal.tsx From coindrop with GNU General Public License v3.0 | 4 votes |
EditPiggybankModal: FunctionComponent<Props> = ({ isOpen, onClose }) => {
const [isSubmitting, setIsSubmitting] = useState(false);
const { colors } = useTheme();
const { user } = useUser();
const { colorMode } = useColorMode();
const accentColorLevelInitial = getAccentColorLevelInitial(colorMode);
const accentColorLevelHover = getAccentColorLevelHover(colorMode);
const { push: routerPush, query: { piggybankName } } = useRouter();
const initialPiggybankId = Array.isArray(piggybankName) ? piggybankName[0] : piggybankName;
const { piggybankDbData } = useContext(PublicPiggybankDataContext);
const { avatar_storage_id: currentAvatarStorageId } = piggybankDbData;
const initialPaymentMethodsDataFieldArray = convertPaymentMethodsDataToFieldArray(piggybankDbData.paymentMethods);
const initialAccentColor = piggybankDbData.accentColor ?? 'orange';
const {
register,
handleSubmit,
setValue,
watch,
control,
formState: { isDirty },
} = useForm({
defaultValues: {
piggybankId: initialPiggybankId,
accentColor: initialAccentColor,
website: piggybankDbData.website ?? '',
name: piggybankDbData.name ?? '',
verb: piggybankDbData.verb ?? 'pay',
paymentMethods: sortByIsPreferredThenAlphabetical(initialPaymentMethodsDataFieldArray),
},
});
const paymentMethodsFieldArrayName = "paymentMethods";
const { fields, append, remove } = useFieldArray({
control,
name: paymentMethodsFieldArrayName,
});
const {
accentColor: watchedAccentColor,
piggybankId: watchedPiggybankId,
} = watch(["accentColor", "piggybankId"]);
const isAccentColorDirty = initialAccentColor !== watchedAccentColor;
const isUrlUnchanged = initialPiggybankId === watchedPiggybankId;
const { isPiggybankIdAvailable, setIsAddressTouched } = useContext(AdditionalValidation);
const onSubmit = async (formData) => {
try {
setIsSubmitting(true);
const dataToSubmit = {
...formData,
paymentMethods: convertPaymentMethodsFieldArrayToDbMap(formData.paymentMethods ?? []),
owner_uid: user.id,
avatar_storage_id: currentAvatarStorageId ?? null,
};
if (isUrlUnchanged) {
await db.collection('piggybanks').doc(initialPiggybankId).set(dataToSubmit);
mutate(['publicPiggybankData', initialPiggybankId], dataToSubmit);
} else {
await axios.post(
'/api/createPiggybank',
{
oldPiggybankName: initialPiggybankId,
newPiggybankName: formData.piggybankId,
piggybankData: dataToSubmit,
},
{
headers: {
token: user.token,
},
},
);
try {
await db.collection('piggybanks').doc(initialPiggybankId).delete();
} catch (err) {
console.log('error deleting old Coindrop page');
}
routerPush(`/${formData.piggybankId}`);
}
fetch(`/${initialPiggybankId}`, { headers: { isToForceStaticRegeneration: "true" }});
onClose();
} catch (error) {
setIsSubmitting(false);
// TODO: handle errors
throw error;
}
};
const handleAccentColorChange = (e) => {
e.preventDefault();
setValue("accentColor", e.target.dataset.colorname);
};
useEffect(() => {
register("accentColor");
}, [register]);
const formControlTopMargin = 2;
return (
<Modal
isOpen={isOpen}
onClose={onClose}
size="xl"
closeOnOverlayClick={false}
>
<ModalOverlay />
<ModalContent>
<ModalHeader>Configure</ModalHeader>
<ModalCloseButton />
<form id="configure-coindrop-form" onSubmit={handleSubmit(onSubmit)}>
<ModalBody>
<AvatarInput />
<FormControl
isRequired
mt={formControlTopMargin}
>
<FormLabel htmlFor="input-piggybankId">URL</FormLabel>
<EditUrlInput
register={register}
value={watchedPiggybankId}
/>
</FormControl>
<FormControl
mt={formControlTopMargin}
>
<FormLabel
htmlFor="input-accentColor"
>
Theme
</FormLabel>
<Flex wrap="wrap" justify="center">
{themeColorOptions.map(colorName => {
const isColorSelected = watchedAccentColor === colorName;
const accentColorInitial = colors[colorName][accentColorLevelInitial];
const accentColorHover = colors[colorName][accentColorLevelHover];
return (
<Box
key={colorName}
as="button"
bg={isColorSelected ? accentColorHover : accentColorInitial}
_hover={{
bg: accentColorHover,
}}
w="36px"
h="36px"
borderRadius="50%"
mx={1}
my={1}
onClick={handleAccentColorChange}
data-colorname={colorName}
>
{isColorSelected && (
<CheckIcon color="#FFF" />
)}
</Box>
);
})}
</Flex>
</FormControl>
<FormControl
isRequired
mt={formControlTopMargin}
>
<FormLabel
htmlFor="input-name"
>
Name
</FormLabel>
<Input
id="input-name"
name="name"
ref={register}
/>
</FormControl>
<FormControl
isRequired
mt={formControlTopMargin}
>
<FormLabel
htmlFor="input-verb"
>
Payment action name
</FormLabel>
<Select
id="input-verb"
name="verb"
ref={register}
>
<option value="pay">Pay</option>
<option value="donate to">Donate to</option>
<option value="support">Support</option>
<option value="tip">Tip</option>
</Select>
</FormControl>
<FormControl
mt={formControlTopMargin}
>
<FormLabel
htmlFor="input-website"
>
Website
</FormLabel>
<Input
id="input-website"
name="website"
ref={register}
placeholder="http://"
type="url"
/>
</FormControl>
<FormControl
mt={formControlTopMargin}
>
<FormLabel
htmlFor="input-paymentmethods"
>
Payment Methods
</FormLabel>
<PaymentMethodsInput
fields={fields}
control={control}
register={register}
remove={remove}
append={append}
fieldArrayName={paymentMethodsFieldArrayName}
/>
</FormControl>
</ModalBody>
<Flex
id="modal-footer"
justify="space-between"
m={6}
>
<DeleteButton
piggybankName={initialPiggybankId}
/>
<Flex>
<Button
variant="ghost"
onClick={onClose}
>
Cancel
</Button>
<Button
id="save-configuration-btn"
colorScheme="green"
mx={1}
type="submit"
isLoading={isSubmitting}
loadingText="Saving"
isDisabled={
(
!isDirty
&& !isAccentColorDirty // controlled accentColor field is not showing up in formState.dirtyFields
)
|| !isPiggybankIdAvailable
|| !initialPiggybankId
}
onClick={() => setIsAddressTouched(true)}
>
Save
</Button>
</Flex>
</Flex>
</form>
</ModalContent>
</Modal>
);
}
Example #28
Source File: AvatarInput.tsx From coindrop with GNU General Public License v3.0 | 4 votes |
AvatarInput: FunctionComponent = () => {
const inputRef = useRef<FileInputRef>(null);
const { piggybankDbData } = useContext(PublicPiggybankDataContext);
const currentAvatarStorageId = piggybankDbData.avatar_storage_id;
const { query: { piggybankName: piggybankNameQuery } } = useRouter();
const piggybankName = typeof piggybankNameQuery === 'string' ? piggybankNameQuery : piggybankNameQuery[0];
const { user } = useUser();
const uid = user?.id;
const piggybankRef = db.collection('piggybanks').doc(piggybankName);
const fileSizeError = "Image too large";
const contentTypeError = "Only images are accepted";
const imageDimensionsError = "Image height and width must be >= 250px";
const [fileSelectErrorMessage, setFileSelectErrorMessage] = useState("");
function clearInput() { inputRef.current.value = null; }
const setAvatar = async (newAvatarStorageId) => {
Promise.all([
piggybankRef.set({ avatar_storage_id: newAvatarStorageId }, { merge: true }),
deleteImage({
storageId: currentAvatarStorageId,
ownerUid: uid,
piggybankName,
}),
]);
mutate(['publicPiggybankData', piggybankName], { ...piggybankDbData, avatar_storage_id: newAvatarStorageId });
};
const onInputChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
setFileSelectErrorMessage(null);
const file = event?.target?.files?.[0];
const storageRef = storage.ref();
const newAvatarStorageId = uuidV4();
const newAvatarPath = piggybankImageStoragePath({ ownerUid: uid, piggybankName, imageAs: "avatar", imageStorageId: newAvatarStorageId });
const newAvatarRef = storageRef.child(newAvatarPath);
if (file) {
try {
const contentType = file.type;
if (!contentType.startsWith("image/")) {
throw new Error(contentTypeError);
}
if (file.size > 1000000) {
throw new Error(fileSizeError);
}
const { width, height } = await getImageDimensions(file);
if (width < 250 || height < 250) {
throw new Error(imageDimensionsError);
}
await newAvatarRef.put(file);
clearInput();
setAvatar(newAvatarStorageId);
} catch (err) {
const { message } = err;
if (message === fileSizeError) {
setFileSelectErrorMessage("Image too large. Please resize image to < 1MB.");
} else if (message === contentTypeError) {
setFileSelectErrorMessage("Only .jpg, .png, and .webp images are accepted.");
} else if (message === imageDimensionsError) {
setFileSelectErrorMessage("Width and height must be at least 250px");
} else {
setFileSelectErrorMessage("Error during upload. Please try again.");
}
clearInput();
}
}
};
return (
<>
<FormLabel htmlFor="avatar-input">Image</FormLabel>
<Stack id="avatar-input-container">
<Box mx="auto">
{
currentAvatarStorageId
? <Avatar />
: (
<NextImage
id="avatar-img"
width={200}
height={200}
src="/avatar-placeholder.png"
alt="avatar placeholder"
data-cy="avatar-placeholder"
/>
)
}
</Box>
<Center>
<Flex wrap="wrap" justify="center">
<Box mt={1}>
<FileInput
text={currentAvatarStorageId ? "Upload new image" : "Upload image"}
id="avatar-input"
ref={inputRef}
accept="image/png, image/jpeg, image/webp"
onChange={onInputChange}
/>
</Box>
{currentAvatarStorageId && (
<Box ml={2} mt={1}>
<Button
onClick={() => {
setAvatar(null);
}}
colorScheme="red"
size="sm"
leftIcon={<DeleteIcon />}
data-cy="remove-image-btn"
>
Remove image
</Button>
</Box>
)}
</Flex>
</Center>
{fileSelectErrorMessage && (
<Text textAlign="center" color="red.500">
<WarningIcon mr={2} />
{fileSelectErrorMessage}
</Text>
)}
</Stack>
</>
);
}
Example #29
Source File: PaddingPanel.tsx From openchakra with MIT License | 4 votes |
PaddingPanel = ({ type }: PaddingPanelPropsType) => {
const { setValueFromEvent } = useForm()
const all = usePropsSelector(ATTRIBUTES[type].all)
const left = usePropsSelector(ATTRIBUTES[type].left)
const right = usePropsSelector(ATTRIBUTES[type].right)
const bottom = usePropsSelector(ATTRIBUTES[type].bottom)
const top = usePropsSelector(ATTRIBUTES[type].top)
return (
<Box mb={4}>
<FormControl>
<FormLabel fontSize="xs" htmlFor="width" textTransform="capitalize">
{type}
</FormLabel>
<InputGroup size="sm">
<Input
mb={1}
placeholder="All"
size="sm"
type="text"
name={ATTRIBUTES[type].all}
value={all || ''}
onChange={setValueFromEvent}
/>
</InputGroup>
<SimpleGrid columns={2} spacing={1}>
<InputGroup size="sm">
<InputLeftElement
children={
<ArrowBackIcon path="" fontSize="md" color="gray.300" />
}
/>
<Input
placeholder="left"
size="sm"
type="text"
name={ATTRIBUTES[type].left}
value={left || ''}
onChange={setValueFromEvent}
autoComplete="off"
/>
</InputGroup>
<InputGroup size="sm">
<InputLeftElement
children={
<ArrowForwardIcon path="" fontSize="md" color="gray.300" />
}
/>
<Input
placeholder="right"
size="sm"
type="text"
value={right || ''}
name={ATTRIBUTES[type].right}
onChange={setValueFromEvent}
autoComplete="off"
/>
</InputGroup>
<InputGroup size="sm">
<InputLeftElement
children={<ArrowUpIcon path="" fontSize="md" color="gray.300" />}
/>
<Input
placeholder="top"
size="sm"
type="text"
value={top || ''}
name={ATTRIBUTES[type].top}
onChange={setValueFromEvent}
autoComplete="off"
/>
</InputGroup>
<InputGroup size="sm">
<InputLeftElement
children={
<ChevronDownIcon path="" fontSize="md" color="gray.300" />
}
/>
<Input
placeholder="bottom"
size="sm"
type="text"
value={bottom || ''}
name={ATTRIBUTES[type].bottom}
onChange={setValueFromEvent}
autoComplete="off"
/>
</InputGroup>
</SimpleGrid>
</FormControl>
</Box>
)
}