@chakra-ui/react#Textarea JavaScript Examples

The following examples show how to use @chakra-ui/react#Textarea. 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: Form.js    From web-client with Apache License 2.0 6 votes vote down vote up
NotesForm = ({ note, onFormSubmit, noteSetter: setNote }) => {
    const onFormInputChange = ev => {
        const target = ev.target;
        const name = target.name;
        const value = target.value;

        setNote({ ...note, [name]: value });
    };

    return <form onSubmit={onFormSubmit}>
        <FormControl id="content" isRequired>
            <FormLabel>Content</FormLabel>
            <Textarea name="content" style={{ width: '100%' }} value={note.content}
                onChange={onFormInputChange} autoFocus /><br />
        </FormControl>
        <FormControl id="visibility" isRequired>
            <FormLabel>Visibility</FormLabel>
            <Select name="visibility" value={note.visibility} onChange={onFormInputChange}>
                <option value="private">Private</option>
                <option value="public">Public</option>
            </Select>
        </FormControl>
    </form>
}
Example #2
Source File: LogsPage.js    From web-client with Apache License 2.0 6 votes vote down vote up
SystemLogsPage = () => {
    const [logs, fetchLogs] = useFetch('/system/logs', true);

    const isLoading = null === logs;

    return <div style={{ height: '100%' }}>
        <div className='heading'>
            <Breadcrumb />
        </div>
        <Title type="System" title="Logs" icon={<IconDownloadDocument />} />

        <Button leftIcon={<FontAwesomeIcon icon={faRefresh} />} onClick={fetchLogs} disabled={isLoading}>Refresh</Button>
        <Textarea variant="filled" size="sm" style={{ height: '100%' }} value={isLoading ? "Loading..." : logs} isReadOnly />
    </div>
}
Example #3
Source File: Send.js    From web-client with Apache License 2.0 5 votes vote down vote up
SendReport = () => {
    const navigate = useNavigate();
    const { projectId } = useParams();
    const [project] = useFetch(`/projects/${projectId}`)
    const [revisions] = useFetch(`/reports?projectId=${projectId}`)

    const [deliverySettings, setDeliverySettings] = useState({
        report_id: null,
        recipients: null,
        subject: "[CONFIDENTIAL] Security report attached",
        body: "Please review attachment containing a security report."
    })

    const handleSend = async (ev) => {
        ev.preventDefault();

        secureApiFetch(`/reports/${deliverySettings.report_id}/send`, { method: 'POST', body: JSON.stringify(deliverySettings) })
            .then(() => {
                navigate(`/projects/${project.id}/report`);
            })
            .catch(err => {
                console.error(err);
            })
    }

    const handleFormChange = ev => {
        const target = ev.target;
        const name = target.name;
        const value = target.value;
        setDeliverySettings({ ...deliverySettings, [name]: value });
    };

    useEffect(() => {
        if (revisions && deliverySettings.report_id === null)
            setDeliverySettings({ ...deliverySettings, report_id: revisions[0].id })
    }, [revisions, deliverySettings]);

    if (!project) return <Loading />

    return <div>
        <PageTitle value="Send report" />
        <div className='heading'>
            <Breadcrumb>
                <Link to="/projects">Projects</Link>
                {project && <Link to={`/projects/${project.id}`}>{project.name}</Link>}
                {project && <Link to={`/projects/${project.id}/report`}>Report</Link>}
            </Breadcrumb>
        </div>
        <form onSubmit={handleSend}>
            <Title title='Send report' />
            <FormControl isRequired>
                <FormLabel for="reportId">Revision</FormLabel>
                <Select id="reportId" name="report_id" onChange={handleFormChange}>
                    {revisions && revisions.map(revision => <option value={revision.id}>{revision.version_name}</option>)}
                </Select>
            </FormControl>
            <FormControl isRequired>
                <FormLabel>Recipients</FormLabel>
                <Input type="text" name="recipients" onChange={handleFormChange} autoFocus
                    placeholder="[email protected]" />
                <FormHelperText>Comma separated list of email addresses.</FormHelperText>
            </FormControl>
            <FormControl isRequired>
                <FormLabel>Subject</FormLabel>
                <Input type="text" name="subject" onChange={handleFormChange}
                    value={deliverySettings.subject} />
            </FormControl>
            <FormControl isRequired>
                <FormLabel>Body</FormLabel>
                <Textarea name="body" onChange={handleFormChange} value={deliverySettings.body} />
            </FormControl>

            <PrimaryButton type="submit">Send</PrimaryButton>
        </form>
    </div>
}
Example #4
Source File: Form.js    From web-client with Apache License 2.0 5 votes vote down vote up
SupportForm = () => {

    const { user } = useContext(AuthContext);

    const systemInfo = `User
----
ID: ${user.id}
Name: ${user.full_name}
Role: ${user.role}

Client
------
URL: ${document.location.protocol + "//" + document.location.host}
User agent: ${navigator.userAgent}
Version: ${process.env.REACT_APP_VERSION}
Build: ${process.env.REACT_APP_GIT_COMMIT_HASH}

Server
------
API URL: ${Configuration.getDefaultApiUrl()}
Notifications API (host:port): ${Configuration.getNotificationsServiceHostPort()}
Agent API (host:port)): ${Configuration.getAgentServiceHostPort()}

`;

    const onCopyToClipboardClick = ev => {
        ev.preventDefault();

        navigator.clipboard.writeText(systemInfo).then(() => {
            ev.target.innerText = 'Copied!';
        }, () => {
            ev.target.innerText = 'Unable to copy.'
        });

        const target = ev.target;

        setInterval(() => {
            target.innerText = 'Copy to clipboard';
        }, 2000);
    }

    return <div className="SupportForm">
        <h2>Support</h2>

        <p>If there is something wrong with the app you can report it <ExternalLink href={ServerIssuesUrl}>here</ExternalLink>. Include the information below in the ticket
            if possible as this could accelerate its resolution.</p>

        <Textarea id="systemInfoControl" value={systemInfo} readOnly />
        <PrimaryButton onClick={onCopyToClipboardClick}>Copy to clipboard</PrimaryButton>
    </div >
}
Example #5
Source File: MakeClaim.js    From DAOInsure with MIT License 4 votes vote down vote up
function MakeClaim() {
	const [currentImage, setCurrentImage] = useState(undefined);
	const [images, setImages] = useState([]);
	const [isPageLoading, setIsPageLoading] = useState(false);
	const { textileClient } = useContext(AppContext);
	const [claimTitle, setClaimTitle] = useState();
	const [claimSummary, setClaimSummary] = useState();
	const [dateOfIncident, setDateOfIncident] = useState();
	const [startTime, setStartTime] = useState();

	const web3Context = useContext(Web3Context);
	const {
		createProposal,
		signerAddress,
		claimableAmount,
		getClaimableAmount,
	} = web3Context;

	useEffect(() => {
		getClaimableAmount();
	}, []);

	const handleImage = async (e) => {
		console.log("uploading");
		let file = e.target.files[0];
		try {
			let result = await uploadToSlate(file);
			setImages([
				...images,
				{
					isUploading: false,
					url: `https://slate.textile.io/ipfs/${result.data.cid}`,
				},
			]);
			setCurrentImage(`https://slate.textile.io/ipfs/${result.data.cid}`);
			console.log("uploaded");
		} catch (e) {
			console.log(e);
		}
	};

	const handleCurrentImage = (e) => {
		e.preventDefault();
		setCurrentImage(e.target.src);
	};

	const handleInputChange = (e, setter) => {
		e.preventDefault();
		setter(e.target.value);
	};

	const handleClaimSubmit = async (e) => {
		e.preventDefault();
		let imageUrls = images.map((image) => {
			return image.url;
		});
		let claimObj = {
			images: imageUrls,
			claimTitle,
			claimSummary,
			dateOfIncident,
			claimAmount: claimableAmount,
			author: signerAddress,
		};
		console.log(claimObj);

		// tried using fleek instead of threadDB.
		// let response = await fleekStorage.upload({
		// 	apiKey: "3aFyv9UlnpyVvuhdoy+WMA==",
		// 	apiSecret: "vUREhYRSH5DP8WehKP+N8jTLoOJUBw+RA9TPLUKneK8=",
		// 	key: uuidv4(),
		// 	data: JSON.stringify(claimObj),
		// });

		// adding claim data to threadDB.
		let response = await addToThread(
			textileClient,
			"bafkyspsyykcninhqn4ht6d6jeqmzq4cepy344akmkhjk75dmw36wq4q",
			"claimsData",
			claimObj
		);

		// create proposal on contract basically store the hash.
		createProposal(
			claimTitle,
			(new Date(dateOfIncident).getTime() / 1000).toString(),
			response.hash
		);
	};

	return (
		<Grid
			paddingBottom='20px'
			pt='20px'
			height='100%'
			px='250px'
			width='100%'
			templateColumns='2fr 1fr'
			gridGap={5}
			alignItems='flex-start'>
			<VStack width='100%' alignItems='flex-start'>
				<Skeleton isLoaded={!isPageLoading}>
					<Heading as='h4' fontSize='28px'>
						Make a Claim
					</Heading>
				</Skeleton>
				<form style={{ width: "100%" }}>
					<VStack width='100%' spacing={5} alignItems='flex-start'>
						<input
							multiple
							onChange={(e) => handleImage(e)}
							type='file'
							style={{ display: "none" }}
							id='image-input'
							accept='image/*'
						/>
						{images.length == 0 ? (
							isPageLoading ? (
								<Spinner
									colorScheme='whatsapp'
									color='whatsapp.500'
									alignSelf='center'
								/>
							) : (
								<Box
									cursor='pointer'
									as='label'
									htmlFor='image-input'
									px='35px'
									width='100%'
									borderRadius='10px'
									height='70px'
									borderWidth='1px'
									borderStyle='solid'
									borderColor='whatsapp.500'>
									<VStack
										height='100%'
										width='100%'
										justifyContent='center'>
										<TiPlus style={{ fill: "#22C35E" }} />
										<Text fontWeight='600' color='#22C35E'>
											Image
										</Text>
									</VStack>
								</Box>
							)
						) : (
							<>
								<Box
									mt='10px !important'
									boxShadow='lg'
									borderRadius='10px'>
									<Image
										borderRadius='10px'
										src={currentImage}
									/>
								</Box>
								<HStack width='100%' overflowX='scroll'>
									{images.map((image, index) => {
										return image.isUploading ? (
											<Spinner key={index} />
										) : (
											<Image
												key={image.url}
												onClick={(e) => {
													handleCurrentImage(e);
												}}
												borderRadius='10px'
												height='70px'
												src={image.url}
											/>
										);
									})}
									<Box
										cursor='pointer'
										as='label'
										htmlFor='image-input'
										px='35px'
										borderRadius='10px'
										height='70px'
										borderWidth='1px'
										borderStyle='solid'
										borderColor='whatsapp.500'>
										<VStack
											height='100%'
											width='100%'
											justifyContent='center'>
											<TiPlus
												style={{ fill: "#22C35E" }}
											/>
											<Text
												fontWeight='600'
												color='#22C35E'>
												Image
											</Text>
										</VStack>
									</Box>
								</HStack>
							</>
						)}

						<VStack spacing={5} width='100%'>
							<FormControl isRequired>
								<Skeleton isLoaded={!isPageLoading}>
									<FormLabel>Claim Title</FormLabel>
								</Skeleton>
								<Skeleton isLoaded={!isPageLoading}>
									<Input
										onChange={(e) =>
											handleInputChange(e, setClaimTitle)
										}
									/>
								</Skeleton>
							</FormControl>
							<FormControl isRequired>
								<Skeleton isLoaded={!isPageLoading}>
									<FormLabel>Summary of Incident</FormLabel>
								</Skeleton>
								<Skeleton isLoaded={!isPageLoading}>
									<Textarea
										onChange={(e) =>
											handleInputChange(
												e,
												setClaimSummary
											)
										}
										row='10'
									/>
								</Skeleton>
								<Skeleton isLoaded={!isPageLoading}>
									<FormHelperText>
										Try to precise
									</FormHelperText>
								</Skeleton>
							</FormControl>
							<VStack width='100%'>
								<HStack width='100%'>
									<FormControl isRequired>
										<Skeleton isLoaded={!isPageLoading}>
											<FormLabel>
												Date of Incident
											</FormLabel>
										</Skeleton>
										<Skeleton isLoaded={!isPageLoading}>
											<Input
												onChange={(e) =>
													handleInputChange(
														e,
														setDateOfIncident
													)
												}
												type='date'
											/>
										</Skeleton>
									</FormControl>
								</HStack>
							</VStack>
							<Button
								onClick={(e) => handleClaimSubmit(e)}
								isLoading={isPageLoading}
								mt='30px !important'
								width='100%'
								textTransform='uppercase'
								type='submit'
								colorScheme='whatsapp'>
								Submit Claim
							</Button>
						</VStack>
					</VStack>
				</form>
			</VStack>
			<VStack width='100%'>
				<Card isLoading={isPageLoading} cardTitle='Claimable Amount'>
					<Skeleton isLoaded={!isPageLoading}>
						<Heading
							textColor='whatsapp.500'
							fontSize='24px'
							as='h3'>
							{parseFloat(claimableAmount).toFixed(6)} DAI
						</Heading>
					</Skeleton>
				</Card>
			</VStack>
		</Grid>
	);
}
Example #6
Source File: ProductAddEdit.js    From react-sample-projects with MIT License 4 votes vote down vote up
ProductAddEdit = () => {
  const categories = useSelector(state => state.product.categories);
  const dispatch = useDispatch();
  const navigate = useNavigate();

  const initialValues = {
    title: '',
    price: '',
    category: '',
    description: '',
    image: '',
  };

  const validationSchema = yup.object({
    title: yup.string().required(),
    price: yup.number().required(),
    category: yup.string().required(),
    description: yup.string().required(),
    image: yup.string().url().required(),
  });

  const onFormSubmit = (values, actions) => {
    actions.setSubmitting(false);
    dispatch(addNewProduct(values));
    navigate('/');
  };

  useEffect(() => {
    dispatch(fetchCategories());
    return () => {};
  }, [dispatch]);

  return (
    <Box boxShadow="base" m={'auto'} width="clamp(300px, 60%, 100%)">
      <Formik
        initialValues={initialValues}
        onSubmit={onFormSubmit}
        validationSchema={validationSchema}
      >
        {props => (
          <Form noValidate>
            <VStack p={3} m="3">
              <Box fontWeight="semibold" mt="1" as="h2" textAlign="left">
                Add Product
              </Box>

              <Field name="title">
                {({ field, form }) => (
                  <FormControl
                    isRequired
                    isInvalid={form.errors.title && form.touched.title}
                  >
                    <FormLabel htmlFor="title">Enter Title</FormLabel>
                    <Input
                      {...field}
                      type="text"
                      id="title"
                      placeholder="Enter Title"
                    />
                    <ErrorMessage
                      name="title"
                      component={FormErrorMessage}
                    ></ErrorMessage>
                  </FormControl>
                )}
              </Field>

              <Field name="price">
                {({ field, form }) => (
                  <FormControl
                    isRequired
                    isInvalid={form.errors.price && form.touched.price}
                  >
                    <FormLabel>Enter price</FormLabel>
                    <Input type="number" placeholder="Enter price" {...field} />
                    <ErrorMessage
                      name="price"
                      component={FormErrorMessage}
                    ></ErrorMessage>
                  </FormControl>
                )}
              </Field>
              <Field name="category">
                {({ field, form }) => (
                  <FormControl
                    name="category"
                    isRequired
                    isInvalid={form.errors.category && form.touched.category}
                  >
                    <FormLabel>Enter category</FormLabel>
                    <Select placeholder="Select category" {...field}>
                      {categories.map((category, index) => (
                        <option key={index}>{category}</option>
                      ))}
                    </Select>
                    <ErrorMessage
                      name="category"
                      component={FormErrorMessage}
                    ></ErrorMessage>
                  </FormControl>
                )}
              </Field>
              <Field name="description">
                {({ field, form }) => (
                  <FormControl
                    name="description"
                    isRequired
                    isInvalid={
                      form.errors.description && form.touched.description
                    }
                  >
                    <FormLabel>Enter description</FormLabel>
                    <Textarea
                      {...field}
                      id="description"
                      placeholder="Enter description"
                    ></Textarea>
                    <ErrorMessage
                      name="description"
                      component={FormErrorMessage}
                    ></ErrorMessage>
                  </FormControl>
                )}
              </Field>
              <Field name="image">
                {({ field, form }) => (
                  <FormControl
                    name="image"
                    isRequired
                    isInvalid={form.errors.image && form.touched.image}
                  >
                    <FormLabel>Enter image</FormLabel>
                    <Input
                      type="url"
                      placeholder="Enter image url"
                      {...field}
                    />

                    <ErrorMessage
                      name="image"
                      component={FormErrorMessage}
                    ></ErrorMessage>
                  </FormControl>
                )}
              </Field>
              <Button
                mt={4}
                colorScheme="teal"
                type="submit"
                isLoading={props.isSubmitting}
              >
                Add Product
              </Button>
            </VStack>
          </Form>
        )}
      </Formik>
    </Box>
  );
}
Example #7
Source File: new.js    From idena-web with MIT License 4 votes vote down vote up
function NewVotingPage() {
  const {t, i18n} = useTranslation()

  const router = useRouter()

  const toast = useToast()

  const {isOpen: isOpenAdvanced, onToggle: onToggleAdvanced} = useDisclosure()

  const epochData = useEpoch()
  const {coinbase, privateKey} = useAuthState()
  const {
    data: {balance},
  } = useBalance()

  const [current, send, service] = useMachine(newVotingMachine, {
    actions: {
      onDone: () => {
        router.push(viewVotingHref(current.context.contractHash))
      },
      onError: (context, {data: {message}}) => {
        toast({
          // eslint-disable-next-line react/display-name
          render: () => (
            <Toast title={humanError(message, context)} status="error" />
          ),
        })
      },
      onInvalidForm: () => {
        toast({
          // eslint-disable-next-line react/display-name
          render: () => (
            <Toast title={t('Please correct form fields')} status="error" />
          ),
        })
      },
    },
  })

  React.useEffect(() => {
    if (epochData && coinbase) send('START', {epoch: epochData.epoch, coinbase})
  }, [coinbase, epochData, privateKey, send])

  const {
    options,
    startDate,
    votingDuration,
    publicVotingDuration,
    shouldStartImmediately,
    isFreeVoting,
    committeeSize,
    quorum = 1,
    winnerThreshold = '66',
    feePerGas,
    oracleReward,
    isWholeNetwork,
    oracleRewardsEstimates,
    ownerFee = 0,
    minOracleReward,
    votingMinPayment,
    dirtyBag,
  } = current.context

  const isInvalid = (field, cond = current.context[field]) =>
    dirtyBag[field] && !cond

  const isInvalidOptions = isInvalid('options', hasValuableOptions(options))
  const hasLinksInOptions = isInvalid('options', hasLinklessOptions(options))

  const handleChange = ({target: {id, value}}) => send('CHANGE', {id, value})
  const dna = toLocaleDna(i18n)

  return (
    <Layout showHamburger={false}>
      <Page px={0} py={0}>
        <Box px={20} py={6} w="full" overflowY="auto">
          <Flex justify="space-between" align="center">
            <PageTitle mb={0}>{t('New voting')}</PageTitle>
            <CloseButton
              ml="auto"
              onClick={() => router.push('/oracles/list')}
            />
          </Flex>
          <SuccessAlert my={8}>
            {t(
              'After publishing or launching, you will not be able to edit the voting parameters.'
            )}
          </SuccessAlert>

          {current.matches('preload.late') && <NewVotingFormSkeleton />}

          {!current.matches('preload') && (
            <Stack spacing={3}>
              <VotingInlineFormControl
                htmlFor="title"
                label={t('Title')}
                isInvalid={isInvalid('title')}
              >
                <Input id="title" onChange={handleChange} />
                {isInvalid('title') && (
                  <FormErrorMessage fontSize="md" mt={1}>
                    {t('You must provide title')}
                  </FormErrorMessage>
                )}
              </VotingInlineFormControl>

              <VotingInlineFormControl
                htmlFor="desc"
                label={t('Description')}
                isInvalid={isInvalid('desc')}
              >
                <Textarea id="desc" w="md" h={32} onChange={handleChange} />
                {isInvalid('desc') && (
                  <FormErrorMessage fontSize="md" mt={1}>
                    {t('You must provide description')}
                  </FormErrorMessage>
                )}
              </VotingInlineFormControl>

              <VotingInlineFormControl
                label={t('Voting options')}
                isInvalid={isInvalidOptions || hasLinksInOptions}
              >
                <Box
                  borderWidth={
                    isInvalidOptions || hasLinksInOptions ? '2px' : 1
                  }
                  borderColor={
                    isInvalidOptions || hasLinksInOptions
                      ? 'red.500'
                      : 'gray.100'
                  }
                  borderRadius="md"
                  p={1}
                  w="md"
                >
                  {options.map(({id, value}, idx) => (
                    <VotingOptionInput
                      key={id}
                      value={value}
                      placeholder={`${t('Option')} ${idx + 1}...`}
                      isLast={idx === options.length - 1}
                      isDisabled={[0, 1].includes(idx)}
                      onChange={({target}) => {
                        send('SET_OPTIONS', {id, value: target.value})
                      }}
                      onAddOption={() => {
                        send('ADD_OPTION')
                      }}
                      onRemoveOption={() => {
                        send('REMOVE_OPTION', {id})
                      }}
                      _invalid={null}
                    />
                  ))}
                </Box>
                {isInvalidOptions && (
                  <FormErrorMessage fontSize="md" mt={1}>
                    {t('You must provide at least 2 options')}
                  </FormErrorMessage>
                )}
                {hasLinksInOptions && (
                  <FormErrorMessage fontSize="md" mt={1}>
                    {t(
                      'Links are not allowed in voting options. Please use Description for links.'
                    )}
                  </FormErrorMessage>
                )}
              </VotingInlineFormControl>

              <VotingInlineFormControl
                htmlFor="startDate"
                label={t('Start date')}
                isDisabled={shouldStartImmediately}
                isInvalid={isInvalid(
                  'startDate',
                  startDate || shouldStartImmediately
                )}
                mt={4}
              >
                <Stack spacing={3} flex={1}>
                  <Input
                    id="startDate"
                    type="datetime-local"
                    onChange={handleChange}
                  />
                  {isInvalid(
                    'startDate',
                    startDate || shouldStartImmediately
                  ) && (
                    <FormErrorMessage fontSize="md" mt={-2}>
                      {t('You must either choose start date or start now')}
                    </FormErrorMessage>
                  )}
                  <Checkbox
                    id="shouldStartImmediately"
                    isChecked={shouldStartImmediately}
                    onChange={({target: {id, checked}}) => {
                      send('CHANGE', {id, value: checked})
                    }}
                  >
                    {t('Start now')}
                  </Checkbox>
                </Stack>
              </VotingInlineFormControl>

              <VotingDurationInput
                id="votingDuration"
                label={t('Voting duration')}
                value={votingDuration}
                tooltip={t('Secret voting period')}
                presets={[
                  durationPreset({hours: 12}),
                  durationPreset({days: 1}),
                  durationPreset({days: 2}),
                  durationPreset({days: 5}),
                  durationPreset({weeks: 1}),
                ]}
                service={service}
                mt={2}
              />

              <NewVotingFormSubtitle>
                {t('Oracles requirements')}
              </NewVotingFormSubtitle>

              <VotingInlineFormControl
                htmlFor="committeeSize"
                label={t('Committee size, oracles')}
                isInvalid={committeeSize < 1}
                tooltip={t(
                  'The number of randomly selected oracles allowed to vote'
                )}
                mt={2}
              >
                <Stack spacing={3} flex={1}>
                  <NumberInput
                    id="committeeSize"
                    value={committeeSize}
                    min={1}
                    step={1}
                    preventInvalidInput
                    isDisabled={isWholeNetwork}
                    onChange={({target: {id, value}}) => {
                      send('CHANGE_COMMITTEE', {id, value})
                    }}
                  />
                  <Checkbox
                    id="isWholeNetwork"
                    onChange={({target: {checked}}) => {
                      send('SET_WHOLE_NETWORK', {checked})
                    }}
                  >
                    {t('Whole network')}
                  </Checkbox>
                </Stack>
              </VotingInlineFormControl>

              <VotingInlineFormControl
                htmlFor="quorum"
                label={t('Quorum')}
                tooltip={t(
                  'The share of Oracle committee sufficient to determine the voting outcome'
                )}
                mt={2}
              >
                <Stack spacing={0} flex={1}>
                  <PercentInput
                    id="quorum"
                    value={quorum}
                    onChange={handleChange}
                  />
                  <NewOracleFormHelperText textAlign="right">
                    {t('{{count}} votes are required', {
                      count: quorumVotesCount({quorum, committeeSize}),
                    })}
                  </NewOracleFormHelperText>
                </Stack>
              </VotingInlineFormControl>

              <VotingInlineFormControl
                htmlFor="votingMinPayment"
                label={t('Voting deposit')}
                tooltip={t(
                  'Refunded when voting in majority and lost when voting in minority'
                )}
                isDisabled={isFreeVoting}
                mt={2}
              >
                <Stack spacing={3} flex={1}>
                  <DnaInput
                    id="votingMinPayment"
                    value={votingMinPayment}
                    isDisabled={isFreeVoting}
                    onChange={handleChange}
                  />
                  <Checkbox
                    id="isFreeVoting"
                    isChecked={isFreeVoting}
                    onChange={({target: {id, checked}}) => {
                      send('CHANGE', {id, value: checked})
                    }}
                  >
                    {t('No voting deposit for oracles')}
                  </Checkbox>
                </Stack>
              </VotingInlineFormControl>

              <NewVotingFormSubtitle>
                {t('Cost of voting')}
              </NewVotingFormSubtitle>

              <PresetFormControl
                label={t('Total funds')}
                tooltip={t(
                  'Total funds locked during the voting and paid to oracles and owner afterwards'
                )}
              >
                <PresetFormControlOptionList
                  value={String(oracleReward)}
                  onChange={value => {
                    send('CHANGE', {
                      id: 'oracleReward',
                      value,
                    })
                  }}
                >
                  {oracleRewardsEstimates.map(({label, value}) => (
                    <PresetFormControlOption key={value} value={String(value)}>
                      {label}
                    </PresetFormControlOption>
                  ))}
                </PresetFormControlOptionList>

                <PresetFormControlInputBox>
                  <DnaInput
                    id="oracleReward"
                    value={oracleReward * committeeSize || 0}
                    min={minOracleReward * committeeSize || 0}
                    onChange={({target: {id, value}}) => {
                      send('CHANGE', {
                        id,
                        value: (value || 0) / Math.max(1, committeeSize),
                      })
                    }}
                  />
                  <NewOracleFormHelperText textAlign="right">
                    {t('Min reward per oracle: {{amount}}', {
                      amount: dna(
                        rewardPerOracle({fundPerOracle: oracleReward, ownerFee})
                      ),
                      nsSeparator: '!',
                    })}
                  </NewOracleFormHelperText>
                </PresetFormControlInputBox>
              </PresetFormControl>

              <VotingInlineFormControl
                htmlFor="ownerFee"
                label={t('Owner fee')}
                tooltip={t('% of the Total funds you receive')}
              >
                <PercentInput
                  id="ownerFee"
                  value={ownerFee}
                  onChange={handleChange}
                />

                <NewOracleFormHelperText textAlign="right">
                  {t('Paid to owner: {{amount}}', {
                    amount: dna(
                      (oracleReward * committeeSize * Math.min(100, ownerFee)) /
                        100 || 0
                    ),
                    nsSeparator: '!',
                  })}
                </NewOracleFormHelperText>
              </VotingInlineFormControl>

              <NewVotingFormSubtitle
                cursor="pointer"
                onClick={onToggleAdvanced}
              >
                {t('Advanced settings')}
                <ChevronDownIcon
                  boxSize={5}
                  color="muted"
                  ml={1}
                  transform={isOpenAdvanced ? 'rotate(180deg)' : ''}
                  transition="all 0.2s ease-in-out"
                />
              </NewVotingFormSubtitle>

              <Collapse in={isOpenAdvanced} mt={2}>
                <Stack spacing={3}>
                  <VotingDurationInput
                    id="publicVotingDuration"
                    value={publicVotingDuration}
                    label={t('Counting duration')}
                    tooltip={t(
                      'Period when secret votes are getting published and results are counted'
                    )}
                    presets={[
                      durationPreset({hours: 12}),
                      durationPreset({days: 1}),
                      durationPreset({days: 2}),
                      durationPreset({days: 5}),
                      durationPreset({weeks: 1}),
                    ]}
                    service={service}
                  />

                  <PresetFormControl
                    label={t('Majority threshold')}
                    tooltip={t(
                      'The minimum share of the votes which an option requires to achieve before it becomes the voting outcome'
                    )}
                  >
                    <PresetFormControlOptionList
                      value={winnerThreshold}
                      onChange={value => {
                        send('CHANGE', {
                          id: 'winnerThreshold',
                          value,
                        })
                      }}
                    >
                      <PresetFormControlOption value="51">
                        {t('Simple majority')}
                      </PresetFormControlOption>
                      <PresetFormControlOption value="66">
                        {t('Super majority')}
                      </PresetFormControlOption>
                      <PresetFormControlOption value="100">
                        {t('N/A (polls)')}
                      </PresetFormControlOption>
                    </PresetFormControlOptionList>

                    <PresetFormControlInputBox>
                      <PercentInput
                        id="winnerThreshold"
                        value={winnerThreshold}
                        onChange={handleChange}
                      />
                    </PresetFormControlInputBox>
                  </PresetFormControl>
                </Stack>
              </Collapse>
            </Stack>
          )}
        </Box>

        <Stack
          isInline
          mt="auto"
          alignSelf="stretch"
          justify="flex-end"
          borderTop="1px"
          borderTopColor="gray.100"
          py={3}
          px={4}
        >
          <PrimaryButton
            isLoading={current.matches('publishing')}
            loadingText={t('Publishing')}
            onClick={() => send('PUBLISH')}
          >
            {t('Publish')}
          </PrimaryButton>
        </Stack>

        <ReviewVotingDrawer
          isOpen={current.matches('publishing')}
          onClose={() => send('CANCEL')}
          from={coinbase}
          available={balance}
          balance={votingMinBalance(oracleReward, committeeSize)}
          minStake={votingMinStake(feePerGas)}
          votingDuration={votingDuration}
          publicVotingDuration={publicVotingDuration}
          ownerFee={ownerFee}
          isLoading={eitherState(
            current,
            'publishing.deploy',
            `publishing.${VotingStatus.Starting}`
          )}
          // eslint-disable-next-line no-shadow
          onConfirm={({balance, stake}) =>
            send('CONFIRM', {privateKey, balance, stake})
          }
        />

        <NewOraclePresetDialog
          isOpen={eitherState(current, 'choosingPreset')}
          onChoosePreset={preset => send('CHOOSE_PRESET', {preset})}
          onCancel={() => send('CANCEL')}
        />
      </Page>
    </Layout>
  )
}
Example #8
Source File: containers.js    From idena-web with MIT License 4 votes vote down vote up
AdForm = React.forwardRef(function AdForm(
  {ad, onSubmit, ...props},
  ref
) {
  const {t} = useTranslation()

  const [thumb, setThumb] = React.useState(ad?.thumb)
  const [media, setMedia] = React.useState(ad?.media)

  const [titleCharacterCount, setTitleCharacterCount] = React.useState(40)
  const [descCharacterCount, setDescCharacterCount] = React.useState(70)

  const [fieldErrors, setFieldErrors] = React.useState({})

  return (
    <form
      ref={ref}
      onChange={e => {
        const {name, value} = e.target

        if (name === 'title') {
          setTitleCharacterCount(40 - value.length)
        }

        if (name === 'desc') {
          setDescCharacterCount(70 - value.length)
        }
      }}
      onSubmit={async e => {
        e.preventDefault()

        const formAd = Object.fromEntries(new FormData(e.target).entries())

        const maybePersistedAd = ad ? await db.table('ads').get(ad.id) : null

        const nextAd = {
          ...formAd,
          thumb: isValidImage(formAd.thumb)
            ? formAd.thumb
            : maybePersistedAd?.thumb,
          media: isValidImage(formAd.media)
            ? formAd.media
            : maybePersistedAd?.media,
        }

        const errors = validateAd(nextAd)

        if (Object.values(errors).some(Boolean)) {
          setFieldErrors(errors)
        } else {
          onSubmit(nextAd)
        }
      }}
      {...props}
    >
      <Stack spacing={6} w="mdx">
        <FormSection>
          <FormSectionTitle>{t('Content')}</FormSectionTitle>
          <Stack spacing={3}>
            <AdFormField label={t('Title')} maybeError={fieldErrors.title}>
              <InputGroup>
                <Input name="title" defaultValue={ad?.title} maxLength={40} />
                <InputCharacterCount>{titleCharacterCount}</InputCharacterCount>
              </InputGroup>
            </AdFormField>
            <AdFormField label={t('Description')} maybeError={fieldErrors.desc}>
              <InputGroup>
                <Textarea
                  name="desc"
                  defaultValue={ad?.desc}
                  maxLength={70}
                  resize="none"
                />
                <InputCharacterCount>{descCharacterCount}</InputCharacterCount>
              </InputGroup>
            </AdFormField>
            <AdFormField label="Link" maybeError={fieldErrors.url}>
              <Input type="url" name="url" defaultValue={ad?.url} />
            </AdFormField>
          </Stack>
        </FormSection>
        <FormSection>
          <FormSectionTitle>{t('Media')}</FormSectionTitle>
          <HStack spacing="10">
            <Box flex={1}>
              <AdMediaInput
                name="media"
                value={media}
                label={t('Upload media')}
                description={t('640x640px, no more than 1 Mb')}
                fallbackSrc="/static/upload-cover-icn.svg"
                maybeError={fieldErrors.media}
                onChange={setMedia}
              />
            </Box>
            <Box flex={1}>
              <AdMediaInput
                name="thumb"
                value={thumb}
                label={t('Upload thumbnail')}
                description={t('80x80px, no more than 1 Mb')}
                fallbackSrc="/static/upload-thumbnail-icn.svg"
                maybeError={fieldErrors.thumb}
                onChange={setThumb}
              />
            </Box>
          </HStack>
        </FormSection>
        <FormSection>
          <FormSectionTitle>{t('Target audience')}</FormSectionTitle>
          <Stack spacing={3} shouldWrapChildren>
            <AdFormField label={t('Language')}>
              <Select
                name="language"
                defaultValue={ad?.language}
                _hover={{
                  borderColor: 'gray.100',
                }}
              >
                <option></option>
                {AVAILABLE_LANGS.map(lang => (
                  <option key={lang}>{lang}</option>
                ))}
              </Select>
            </AdFormField>
            <AdFormField label={t('Min age')}>
              <AdNumberInput
                name="age"
                defaultValue={ad?.age}
                min={0}
                max={Number.MAX_SAFE_INTEGER}
              />
              <FormHelperText color="muted" fontSize="sm" mt="1">
                {t('Min age to see the ad')}
              </FormHelperText>
            </AdFormField>
            <AdFormField label={t('Min stake')}>
              <AdNumberInput
                name="stake"
                defaultValue={ad?.stake}
                min={0}
                max={Number.MAX_SAFE_INTEGER}
                addon={t('iDNA')}
              />
              <FormHelperText color="muted" fontSize="sm" mt="1">
                {t('Min stake amount to see the ad')}
              </FormHelperText>
            </AdFormField>
            <AdFormField label="OS">
              <Select
                name="os"
                defaultValue={ad?.os}
                _hover={{
                  borderColor: 'gray.100',
                }}
              >
                <option></option>
                {Object.entries(OS).map(([k, v]) => (
                  <option key={v} value={v}>
                    {k}
                  </option>
                ))}
              </Select>
            </AdFormField>
          </Stack>
        </FormSection>
      </Stack>
    </form>
  )
})
Example #9
Source File: components.js    From idena-web with MIT License 4 votes vote down vote up
export function CommunityTranslations({
  keywords,
  isOpen,
  isPending,
  onVote,
  onSuggest,
  onToggle,
}) {
  const {t} = useTranslation()

  const {privateKey} = useAuthState()

  const [wordIdx, setWordIdx] = React.useState(0)

  const [
    descriptionCharactersCount,
    setDescriptionCharactersCount,
  ] = React.useState(150)

  const translations = keywords.translations[wordIdx]

  const lastTranslationId =
    translations && translations.length
      ? translations[translations.length - 1].id
      : wordIdx

  return (
    <Stack spacing={isOpen ? 8 : 0}>
      <IconButton
        icon={<CommunityIcon boxSize={5} />}
        color="brandGray.500"
        px={0}
        _hover={{background: 'transparent'}}
        onClick={onToggle}
      >
        {t('Community translation')}
        <ChevronDownIcon boxSize={5} color="muted" ml={2} />
      </IconButton>
      <Collapse isOpen={isOpen}>
        <Stack spacing={8}>
          <RadioGroup isInline value={wordIdx} onChange={setWordIdx}>
            {keywords.words.map(({id, name}, i) => (
              <FlipKeywordRadio key={id} value={i}>
                {name && capitalize(name)}
              </FlipKeywordRadio>
            ))}
          </RadioGroup>
          {translations.map(({id, name, desc, score}) => (
            <Flex key={id} justify="space-between">
              <FlipKeyword>
                <FlipKeywordName>{name}</FlipKeywordName>
                <FlipKeywordDescription>{desc}</FlipKeywordDescription>
              </FlipKeyword>
              <Stack isInline spacing={2} align="center">
                <VoteButton
                  icon={<UpvoteIcon />}
                  onClick={() => onVote({id, up: true, pk: privateKey})}
                />
                <Flex
                  align="center"
                  justify="center"
                  bg={score < 0 ? 'red.010' : 'green.010'}
                  color={score < 0 ? 'red.500' : 'green.500'}
                  fontWeight={500}
                  rounded="md"
                  minW={12}
                  minH={8}
                  style={{fontVariantNumeric: 'tabular-nums'}}
                >
                  {score}
                </Flex>
                <VoteButton
                  icon={<UpvoteIcon />}
                  color="muted"
                  transform="rotate(180deg)"
                  onClick={() => onVote({id, up: false, pk: privateKey})}
                />
              </Stack>
            </Flex>
          ))}

          {translations.length && <Divider borderColor="gray.100" />}

          <Box>
            <Text fontWeight={500} mb={3}>
              {t('Suggest translation')}
            </Text>
            <form
              key={lastTranslationId}
              onSubmit={e => {
                e.preventDefault()
                const {
                  nameInput: {value: name},
                  descInput: {value: desc},
                } = e.target.elements
                onSuggest({wordIdx, name, desc: desc.trim(), pk: privateKey})
              }}
            >
              <FormControl>
                <Input
                  id="nameInput"
                  placeholder={
                    keywords.words[wordIdx].name
                      ? capitalize(keywords.words[wordIdx].name)
                      : 'Name'
                  }
                  px={3}
                  pt={1.5}
                  pb={2}
                  borderColor="gray.100"
                  mb={2}
                  _placeholder={{
                    color: 'muted',
                  }}
                />
              </FormControl>
              <FormControl position="relative">
                <Textarea
                  id="descInput"
                  placeholder={
                    keywords.words[wordIdx].desc
                      ? capitalize(keywords.words[wordIdx].desc)
                      : 'Description'
                  }
                  mb={6}
                  onChange={e =>
                    setDescriptionCharactersCount(150 - e.target.value.length)
                  }
                />
                <Box
                  color={descriptionCharactersCount < 0 ? 'red.500' : 'muted'}
                  fontSize="sm"
                  position="absolute"
                  right={2}
                  bottom={2}
                  zIndex="docked"
                >
                  {descriptionCharactersCount}
                </Box>
              </FormControl>
              <PrimaryButton
                type="submit"
                display="flex"
                ml="auto"
                isLoading={isPending}
              >
                {t('Send')}
              </PrimaryButton>
            </form>
          </Box>
        </Stack>
      </Collapse>
    </Stack>
  )
}
Example #10
Source File: ChallengeReviewRow.jsx    From scaffold-directory with MIT License 4 votes vote down vote up
export default function ChallengeReviewRow({ challenge, isLoading, approveClick, rejectClick, userProvider }) {
  const [comment, setComment] = useState(challenge.reviewComment ?? "");
  const [testPassed, setTestPassed] = useState(null);
  const [isRunningTests, setIsRunningTests] = useState(false);
  const { isOpen, onOpen, onClose } = useDisclosure();
  const address = useUserAddress(userProvider);

  if (!challengeInfo[challenge.id]) {
    return null;
  }

  // We asume that rejected challenges will always have review Comments.
  const isAutograded = challenge.autograding;
  // ToDo. Use the stored events.
  const isResubmitted = !isAutograded && !!challenge.reviewComment;

  const runTests = async () => {
    try {
      console.log("Testing challenge with the auto-grader");

      setIsRunningTests(true);
      setTestPassed(null);

      const result = await runAutograderTest(challenge.id, challenge.contractUrl, address);
      const resultData = result.data;

      console.log("Testing results", resultData);
      setTestPassed(resultData.success);
      setComment(resultData.feedback ?? resultData.error);
    } catch (e) {
      console.log("Error calling the auto-grader", e);
    } finally {
      setIsRunningTests(false);
    }
  };

  const challengeReviewDisplay = (
    <Link as={RouteLink} to={`/challenge/${challenge.id}`}>
      {challengeInfo[challenge.id].label}
      {isResubmitted && (
        <>
          <br />
          <Text fontSize="xs">(Resubmitted)</Text>
        </>
      )}
      {isAutograded && (
        <>
          <br />
          <Text fontSize="xs" color="orange.500">
            (Autograded)
          </Text>
        </>
      )}
    </Link>
  );

  const submittedMoment = moment(challenge.submittedTimestamp);

  const reviewRow = (
    <>
      <Td>
        <Link as={RouteLink} to={`/builders/${challenge.userAddress}`} pos="relative">
          <Address address={challenge.userAddress} w="12.5" fontSize="16" />
        </Link>
      </Td>
      <Td>{challengeReviewDisplay}</Td>
      <Td>
        <DateWithTooltip timestamp={challenge.submittedTimestamp} />
      </Td>
    </>
  );

  return (
    <Tr>
      {reviewRow}
      <Td>
        <Button type="button" colorScheme="blue" disabled={isLoading} className="danger" onClick={onOpen} size="xs">
          Review
        </Button>
      </Td>
      <Modal isOpen={isOpen} onClose={onClose} size="xl">
        <ModalOverlay />
        <ModalContent maxW="56rem">
          <ModalHeader>Review Challenge</ModalHeader>
          <ModalCloseButton />
          <Table mb={4}>
            <Thead>
              <Tr>
                <Th>Builder</Th>
                <Th>Challenge & Links</Th>
              </Tr>
            </Thead>
            <Tbody>
              <Tr>
                <Td>
                  <Link as={RouteLink} to={`/builders/${challenge.userAddress}`} pos="relative">
                    <Address address={challenge.userAddress} w="12.5" fontSize="16" />
                  </Link>
                </Td>
                <Td>
                  {challengeReviewDisplay}
                  <UnorderedList>
                    <ListItem>
                      <Link
                        // Legacy branchUrl
                        href={challenge.contractUrl || challenge.branchUrl}
                        color="teal.500"
                        target="_blank"
                        rel="noopener noreferrer"
                      >
                        Contract
                      </Link>
                    </ListItem>
                    <ListItem>
                      <Link href={challenge.deployedUrl} color="teal.500" target="_blank" rel="noopener noreferrer">
                        Demo
                      </Link>
                    </ListItem>
                    <ListItem>
                      Submitted{" "}
                      <Tooltip label={submittedMoment.format("YYYY-MM-DD, HH:mm")}>
                        <chakra.span cursor="pointer">{submittedMoment.fromNow()}</chakra.span>
                      </Tooltip>
                    </ListItem>
                    <ListItem listStyleType="none" mt={2}>
                      <Flex align="center">
                        <Button onClick={runTests} isLoading={isRunningTests} mr={2}>
                          Run tests
                        </Button>
                        {isBoolean(testPassed) && (
                          <Badge colorScheme={testPassed ? "green" : "red"}>
                            {testPassed ? "Accepted" : "Rejected"}
                          </Badge>
                        )}
                      </Flex>
                    </ListItem>
                  </UnorderedList>
                </Td>
              </Tr>
            </Tbody>
          </Table>
          <ModalBody px={6} pb={0}>
            <Tabs variant="enclosed-colored">
              <TabList>
                <Tab>Write</Tab>
                <Tab>Preview</Tab>
              </TabList>
              <TabPanels align="left">
                <TabPanel p={0}>
                  <Textarea
                    onChange={e => {
                      const value = e.target.value;
                      setComment(value);
                    }}
                    placeholder="Comment"
                    style={{ marginBottom: 10 }}
                    rows={10}
                    value={comment}
                    borderTopRadius={0}
                  />
                </TabPanel>
                <TabPanel>
                  <ReactMarkdown components={ChakraUIRenderer(chakraMarkdownComponents)}>{comment}</ReactMarkdown>
                </TabPanel>
              </TabPanels>
            </Tabs>
          </ModalBody>
          <ModalFooter>
            <Button
              type="button"
              colorScheme="red"
              disabled={isLoading}
              className="danger"
              onClick={() => rejectClick(challenge.userAddress, challenge.id, comment)}
              size="sm"
              isFullWidth
            >
              Reject
            </Button>
            <Button
              type="button"
              colorScheme="green"
              disabled={isLoading}
              ml={3}
              onClick={() => approveClick(challenge.userAddress, challenge.id, comment)}
              size="sm"
              isFullWidth
            >
              Approve
            </Button>
          </ModalFooter>
        </ModalContent>
      </Modal>
    </Tr>
  );
}
Example #11
Source File: Outputs.js    From web-client with Apache License 2.0 4 votes vote down vote up
CommandOutputs = ({ command }) => {
    const [commandOutputs, updateCommandOutputs] = useFetch(`/attachments?parentType=command&parentId=${command.id}`)
    const [modalVisible, setModalVisible] = useState(false);
    const [content, setContent] = useState(null);

    const onDeleteOutputClick = (ev, attachmentId) => {
        ev.preventDefault();

        secureApiFetch(`/attachments/${attachmentId}`, { method: 'DELETE' })
            .then(() => {
                actionCompletedToast("The output has been deleted.");
                updateCommandOutputs();
            })
            .catch(err => console.error(err))
    }

    const onDownloadClick = (ev, attachmentId) => {
        secureApiFetch(`/attachments/${attachmentId}`, { method: 'GET', headers: {} })
            .then(resp => {
                const contentDispositionHeader = resp.headers.get('Content-Disposition');
                const filenameRe = new RegExp(/filename="(.*)";/)
                const filename = filenameRe.exec(contentDispositionHeader)[1]
                return Promise.all([resp.blob(), filename]);
            })
            .then((values) => {
                const blob = values[0];
                const filename = values[1];
                const url = URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url;
                a.download = filename;
                a.click();
            })
    }

    const onViewClick = (ev, attachmentId) => {
        secureApiFetch(`/attachments/${attachmentId}`, { method: 'GET', headers: {} })
            .then(resp => {
                const contentDispositionHeader = resp.headers.get('Content-Disposition');
                const filenameRe = new RegExp(/filename="(.*)";/)
                const filename = filenameRe.exec(contentDispositionHeader)[1]
                return Promise.all([resp.blob(), filename]);
            })
            .then(async (values) => {
                const blob = values[0];
                setContent(await blob.text());
                setModalVisible(true);
            })
    }

    const onModalClose = () => {
        setModalVisible(false);
    }

    return <>
        <ModalDialog visible={modalVisible} title="Preview output" onModalClose={onModalClose} style={{ width: '80%', height: '80%', maxHeight: '80%' }}>
            <Textarea style={{ width: '100%', height: '90%' }} defaultValue={content} readOnly>
            </Textarea>
        </ModalDialog>

        <RestrictedComponent roles={['administrator', 'superuser', 'user']}>
            <AttachmentsDropzone parentType={"command"} parentId={command.id} onUploadFinished={updateCommandOutputs} />
        </RestrictedComponent>

        <h4>
            Command output list
        </h4>

        <Table>
            <Thead>
                <Tr>
                    <Th>Filename</Th>
                    <Th>Mimetype</Th>
                    <Th>File size</Th>
                    <Th>Upload date</Th>
                    <Th>Uploaded by</Th>
                    <Th>&nbsp;</Th>
                </Tr>
            </Thead>
            <Tbody>
                {null === commandOutputs && <LoadingTableRow numColumns={6} />}
                {null !== commandOutputs && commandOutputs.length === 0 && <NoResultsTableRow numColumns={6} />}
                {null !== commandOutputs && commandOutputs.length !== 0 && commandOutputs.map((commandOutput, index) =>
                    <Tr key={index}>
                        <Td>{commandOutput.client_file_name}</Td>
                        <Td>{commandOutput.file_mimetype}</Td>
                        <Td><FileSizeSpan fileSize={commandOutput.file_size} /></Td>
                        <Td><RelativeDateFormatter date={commandOutput.insert_ts} /></Td>
                        <Td><UserLink userId={commandOutput.submitter_uid}>{commandOutput.submitter_name}</UserLink></Td>
                        <Td textAlign="right">
                            <ButtonGroup isAttached>
                                <SecondaryButton onClick={ev => onViewClick(ev, commandOutput.id)}>View</SecondaryButton>
                                <SecondaryButton onClick={ev => onDownloadClick(ev, commandOutput.id)}>Download</SecondaryButton>
                                <DeleteIconButton onClick={ev => onDeleteOutputClick(ev, commandOutput.id)} />
                            </ButtonGroup>
                        </Td>
                    </Tr>
                )}
            </Tbody>
        </Table>
    </>
}