@chakra-ui/react#ButtonGroup JavaScript Examples
The following examples show how to use
@chakra-ui/react#ButtonGroup.
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: BuildCard.jsx From scaffold-directory with MIT License | 6 votes |
BuildCard = ({ build }) => {
const { borderColor, secondaryFontColor } = useCustomColorModes();
return (
<Box borderWidth="1px" borderRadius="lg" borderColor={borderColor} overflow="hidden">
<Box bgColor={borderColor} borderBottom="1px" borderColor={borderColor}>
<Image src={`${ASSETS_BASE_URL}/${build.image}`} h="200px" mx="auto" />
</Box>
<Flex pt={9} pb={4} px={4} direction="column" minH="240px">
<Text fontWeight="bold">{build.name}</Text>
<Text color={secondaryFontColor}>{build.desc}</Text>
<Spacer />
<ButtonGroup variant="outline" size="sm" spacing="2">
<Button disabled variant="outline" isFullWidth>
Fund
</Button>
<Button isFullWidth as="a" href={build.branch} target="_blank" rel="noopener noreferrer">
Fork
</Button>
</ButtonGroup>
</Flex>
</Box>
);
}
Example #2
Source File: Details.js From web-client with Apache License 2.0 | 5 votes |
VulnerabilityTemplateDetails = () => {
const navigate = useNavigate();
const { templateId } = useParams();
const [vulnerability] = useFetch(`/vulnerabilities/${templateId}`)
const cloneProject = async (templateId) => {
secureApiFetch(`/vulnerabilities/${templateId}/clone`, { method: 'POST' })
.then(resp => resp.json())
.then(data => {
navigate(`/vulnerabilities/${data.vulnerabilityId}/edit`);
});
}
const destroy = useDelete('/vulnerabilities/', () => {
navigate('/vulnerabilities/templates');
});
if (!vulnerability) return <Loading />
if (vulnerability && !vulnerability.is_template) {
return <Navigate to={`/vulnerabilities/${vulnerability.id}`} />
}
return (
<>
<div>
<div className='heading'>
<Breadcrumb>
<Link to="/vulnerabilities">Vulnerabilities</Link>
<Link to="/vulnerabilities/templates">Templates</Link>
</Breadcrumb>
<ButtonGroup>
<PrimaryButton onClick={() => cloneProject(vulnerability.id)} leftIcon={<IconPlusCircle />}>Clone and edit</PrimaryButton>
<RestrictedComponent roles={['administrator', 'superuser', 'user']}>
<LinkButton href={`/vulnerabilities/${vulnerability.id}/edit`}>Edit</LinkButton>
<DeleteButton onClick={() => destroy(vulnerability.id)} />
</RestrictedComponent>
</ButtonGroup>
</div>
<article>
<PageTitle value={`${vulnerability.summary} vulnerability template`} />
<Title type='Vulnerability template' title={vulnerability.summary} icon={<IconFlag />} />
<Tabs>
<TabList>
<Tab>Description</Tab>
<Tab>Remediation</Tab>
</TabList>
<TabPanels>
<TabPanel>
<VulnerabilityDescriptionPanel vulnerability={vulnerability} />
</TabPanel>
<TabPanel>
<VulnerabilityRemediationPanel vulnerability={vulnerability} />
</TabPanel>
</TabPanels>
</Tabs>
</article>
</div>
</>
)
}
Example #3
Source File: LikeCounter.js From benjamincarlson.io with MIT License | 5 votes |
LikeCounter = ({ id }) => {
const [likes, setLikes] = useState('')
const [loading, setLoading] = useState(false)
const [liked, setLiked] = useState(false)
const [color, setColor] = useState('gray')
const toast = useToast()
useEffect(() => {
const onLikes = (newLikes) => setLikes(newLikes.val())
let db
const fetchData = async () => {
db = await loadDb()
db.ref('likes').child(id).on('value', onLikes)
}
fetchData()
return () => {
if (db) {
db.ref('likes').child(id).off('value', onLikes)
}
}
}, [id])
const like = async (e) => {
if (!liked) {
e.preventDefault()
setLoading(true)
const registerLike = () =>
fetch(`/api/increment-likes?id=${encodeURIComponent(id)}`)
registerLike()
setLoading(false)
setLiked(true)
setColor('yellow.500')
toast({
title: "Thanks for liking!",
status: "success",
duration: 3000,
isClosable: true,
})
} else {
toast({
title: "Already Liked!",
status: "error",
duration: 3000,
isClosable: true,
})
}
}
return (
<>
<ButtonGroup>
<Button
leftIcon={<BiLike />}
colorScheme="gray"
variant="outline"
onClick={like}
isLoading={loading}
color={color}
fontSize="sm"
px={2}
>
{likes ? format(likes) : '–––'}
</Button>
</ButtonGroup>
</>
)
}
Example #4
Source File: Profile.js From web-client with Apache License 2.0 | 5 votes |
UserProfile = () => {
const navigate = useNavigate();
const { userId } = useParams();
const [user] = useFetch(`/users/${userId}`)
const [auditLog] = useFetch(`/users/${userId}/activity`)
const deleteUser = useDelete('/users/');
const onDeleteButtonClick = ev => {
ev.preventDefault();
deleteUser(userId).then(() => {
navigate('/users');
})
}
if (!user) return <Loading />
return <>
<div className='heading'>
<Breadcrumb>
<Link to="/users">Users</Link>
</Breadcrumb>
<ButtonGroup>
<RestrictedComponent roles={['administrator', 'superuser', 'user']}>
<LinkButton href={`/users/${user.id}/edit`}>Edit</LinkButton>
<DeleteButton onClick={onDeleteButtonClick} />
</RestrictedComponent>
</ButtonGroup>
</div>
<div>
<PageTitle value={`${user.full_name} user`} />
<Title type='User profile' title={user.full_name}
icon={<UserAvatar email={user.email} />} />
<Tabs isLazy>
<TabList>
<Tab>Details</Tab>
<Tab>Activity</Tab>
</TabList>
<TabPanels>
<TabPanel>
<div className="grid grid-two">
<div>
<h4>Properties</h4>
<dl>
<dt>Short bio</dt>
<dd>{user.short_bio ? user.short_bio : <EmptyField />}</dd>
<dt>Role</dt>
<dd><UserRoleBadge role={user.role} /><br /></dd>
<dt>Timezone</dt>
<dd>{user.timezone}</dd>
<dt>Active?</dt>
<dd><BooleanText value={user.active} /></dd>
<dt>2FA enabled?</dt>
<dd><BooleanText value={user.mfa_enabled} /></dd>
</dl>
</div>
<div>
<TimestampsSection entity={user} />
</div>
</div>
</TabPanel>
<TabPanel>
<h4>Activity (<Link to="/auditlog">view full audit log</Link>)</h4>
{auditLog ? <AuditLogsTable auditLog={auditLog} hideUserColumns="true" /> : <Loading />}
</TabPanel>
</TabPanels>
</Tabs>
</div>
</>
}
Example #5
Source File: Details.js From web-client with Apache License 2.0 | 5 votes |
ReportTemplateDetails = () => {
const navigate = useNavigate();
const { templateId } = useParams();
const [vulnerability] = useFetch(`/vulnerabilities/${templateId}`)
const cloneProject = async (templateId) => {
secureApiFetch(`/vulnerabilities/${templateId}/clone`, { method: 'POST' })
.then(resp => resp.json())
.then(data => {
navigate(`/vulnerabilities/${data.vulnerabilityId}/edit`);
});
}
const destroy = useDelete('/vulnerabilities/', () => {
navigate('/vulnerabilities/templates');
});
if (!vulnerability) return <Loading />
if (vulnerability && !vulnerability.is_template) {
return <Navigate to={`/vulnerabilities/${vulnerability.id}`} />
}
return (
<>
<div>
<div className='heading'>
<Breadcrumb>
<Link to="/vulnerabilities">Vulnerabilities</Link>
<Link to="/vulnerabilities/templates">Templates</Link>
</Breadcrumb>
<ButtonGroup>
<PrimaryButton onClick={() => cloneProject(vulnerability.id)} leftIcon={<IconPlusCircle />}>Clone and edit</PrimaryButton>
<RestrictedComponent roles={['administrator', 'superuser', 'user']}>
<LinkButton href={`/vulnerabilities/${vulnerability.id}/edit`}>Edit</LinkButton>
<DeleteButton onClick={() => destroy(vulnerability.id)} />
</RestrictedComponent>
</ButtonGroup>
</div>
<article>
<PageTitle value={`${vulnerability.summary} vulnerability template`} />
<Title type='Vulnerability template' title={vulnerability.summary} icon={<IconFlag />} />
<Tabs>
<TabList>
<Tab>Description</Tab>
<Tab>Remediation</Tab>
</TabList>
<TabPanels>
<TabPanel>
</TabPanel>
<TabPanel>
</TabPanel>
</TabPanels>
</Tabs>
</article>
</div>
</>
)
}
Example #6
Source File: Details.js From web-client with Apache License 2.0 | 5 votes |
TemplateDetails = () => {
const navigate = useNavigate();
const { templateId } = useParams();
const [template] = useFetch(`/projects/${templateId}`)
const cloneProject = async (templateId) => {
secureApiFetch(`/projects/${templateId}/clone`, { method: 'POST' })
.then(resp => resp.json())
.then(data => {
navigate(`/projects/${data.projectId}/edit`);
});
}
const destroy = useDelete('/projects/', () => {
navigate('/projects/templates');
});
if (template && !template.is_template) {
return <Navigate to={`/projects/${template.id}`} />
}
return <>
<div className='heading'>
<Breadcrumb>
<Link to="/projects">Projects</Link>
<Link to="/projects/templates">Templates</Link>
</Breadcrumb>
{template &&
<ButtonGroup>
<PrimaryButton onClick={() => cloneProject(template.id)} leftIcon={<IconPlusCircle />}>Create project from template</PrimaryButton>
<LinkButton href={`/projects/${template.id}/edit`}>Edit</LinkButton>
<DeleteButton onClick={() => destroy(template.id)} />
</ButtonGroup>
}
</div>
{!template ?
<Loading /> :
<article>
<PageTitle value={`${template.name} project template`} />
<Title title={template.name} type='Project template' icon={<IconFolder />} />
<Tabs>
<TabList>
<Tab>Details</Tab>
<Tab>Tasks</Tab>
</TabList>
<TabPanels>
<TabPanel><ProjectDetailsTab project={template} /></TabPanel>
<TabPanel><ProjectTasks project={template} /></TabPanel>
</TabPanels>
</Tabs>
</article>}
</>
}
Example #7
Source File: List.js From web-client with Apache License 2.0 | 5 votes |
NotificationsList = () => {
const [notifications, fetchNotifications] = useFetch('/notifications')
const markNotificationAsRead = notification => {
secureApiFetch(`/notifications/${notification.id}`, {
method: 'PUT',
body: JSON.stringify({ status: 'read' })
}).then(() => {
fetchNotifications();
})
}
const deleteNotification = useDelete('/notifications/', fetchNotifications);
return <>
<PageTitle value="Notifications" />
<div className='heading'>
<Breadcrumb />
</div>
<Title title='Notifications' icon={<BellIcon />} />
<Table>
<Thead>
<Tr>
<Th w={50}> </Th>
<Th w={200}>Date/time</Th>
<Th>Content</Th>
<Th> </Th>
</Tr>
</Thead>
<Tbody>
{null === notifications && <LoadingTableRow numColumns={3} />}
{null !== notifications && notifications.length === 0 && <NoResultsTableRow numColumns={3} />}
{null !== notifications && notifications.length > 0 &&
notifications.map(notification =>
<Tr key={notification.id}>
<Th>{notification.status === 'read' ? <FontAwesomeIcon icon={faCheck} /> : <> </>}</Th>
<Td><RelativeDateFormatter date={notification.insert_ts} /></Td>
<Td>
<strong>{notification.title}</strong>
<div>{notification.content}</div>
</Td>
<Td textAlign="right">
<ButtonGroup>
{notification.status === 'unread' && <Button onClick={() => markNotificationAsRead(notification)} leftIcon={<FontAwesomeIcon icon={faCheck} />}>Mark as read</Button>}
<DeleteIconButton onClick={() => deleteNotification(notification.id)} />
</ButtonGroup>
</Td>
</Tr>
)
}
</Tbody>
</Table>
</>
}
Example #8
Source File: Details.js From web-client with Apache License 2.0 | 5 votes |
DocumentDetailsPage = () => {
const { documentId } = useParams();
const navigate = useNavigate();
const [serverDoc] = useFetch(`/documents/${documentId}`)
const deleteDocument = useDelete(`/documents/`)
const handleDelete = async () => {
const confirmed = await deleteDocument(documentId);
if (confirmed)
navigate('/documents');
}
if (!serverDoc) {
return <Loading />
}
return <div>
<div className='heading'>
<Breadcrumb>
<Link to="/documents">Documents</Link>
</Breadcrumb>
<ButtonGroup>
<EditButton onClick={(ev) => {
ev.preventDefault();
navigate(`/documents/${serverDoc.id}/edit`)
}}>Edit</EditButton>
<DeleteButton onClick={handleDelete} />
</ButtonGroup>
</div>
<article>
<div>
<PageTitle value={`${serverDoc.title} document`} />
<Title type="Document" title={serverDoc.title} icon={<IconBriefcase />} />
</div>
<div className="grid grid-two">
<div>
<dl>
<dt>Visibility</dt>
<dd><VisibilityLegend visibility={serverDoc.visibility} /></dd>
<dt>Content</dt>
<dd>
<DocumentPreview content={serverDoc.content} />
</dd>
</dl>
</div>
<div>
<h4>Relations</h4>
<dl>
<dt>Created by</dt>
<dd><UserLink userId={serverDoc.user_id}>{serverDoc.user_name}</UserLink></dd>
</dl>
<TimestampsSection entity={serverDoc} />
</div>
</div>
</article>
</div>
}
Example #9
Source File: List.js From web-client with Apache License 2.0 | 5 votes |
CommandsListPage = () => {
const navigate = useNavigate();
const query = useQuery();
let pageNumber = query.get('page');
pageNumber = pageNumber !== null ? parseInt(pageNumber) : 1;
const apiPageNumber = pageNumber - 1;
const [numberPages, setNumberPages] = useState(1);
const [totalCount, setTotalCount] = useState('?');
const [commands, setCommands] = useState([]);
const onAddCommandClick = ev => {
ev.preventDefault();
navigate('/commands/add');
}
const handlePrev = () => {
const queryParams = new URLSearchParams();
queryParams.set('page', pageNumber - 1);
const url = `/commands?${queryParams.toString()}`;
navigate(url);
}
const handleNext = () => {
const queryParams = new URLSearchParams();
queryParams.set('page', pageNumber + 1);
const url = `/commands?${queryParams.toString()}`;
navigate(url);
}
const reloadCommands = useCallback(() => {
setCommands(null);
const queryParams = new URLSearchParams();
queryParams.set('page', apiPageNumber);
const url = `/commands?${queryParams.toString()}`;
secureApiFetch(url, { method: 'GET' })
.then(resp => {
if (resp.headers.has('X-Page-Count')) {
setNumberPages(resp.headers.get('X-Page-Count'))
}
if (resp.headers.has('X-Total-Count')) {
setTotalCount(resp.headers.get('X-Total-Count'))
}
return resp.json()
})
.then(commands => {
setCommands(commands);
});
}, [setCommands, apiPageNumber]);
const destroy = useDelete('/commands/', reloadCommands);
useEffect(() => {
reloadCommands()
}, [reloadCommands])
return <div>
<PageTitle value="Commands" />
<div className='heading'>
<Breadcrumb />
<Pagination page={apiPageNumber} total={numberPages} handlePrev={handlePrev} handleNext={handleNext} />
<ButtonGroup isAttached>
<CreateButton onClick={onAddCommandClick}>Add command</CreateButton>
<Menu>
<MenuButton as={IconButton} aria-label='Options' icon={<FontAwesomeIcon icon={faEllipsis} />} variant='outline' />
<MenuList>
<ExportMenuItem entity="commands" />
</MenuList>
</Menu>
</ButtonGroup>
</div>
<Title title={`Commands (${totalCount})`} icon={<IconFolder />} />
<CommandsTable commands={commands} onDeleteCallback={destroy} />
</div>
}
Example #10
Source File: List.js From web-client with Apache License 2.0 | 5 votes |
ClientsList = () => {
const navigate = useNavigate();
const [clients, updateTasks] = useFetch('/clients')
const destroy = useDelete('/clients/', updateTasks);
const handleCreateClient = () => {
navigate(`/clients/create`)
}
return <>
<PageTitle value="Clients" />
<div className='heading'>
<Breadcrumb />
<ButtonGroup isAttached>
<CreateButton onClick={handleCreateClient}>Add client</CreateButton>
<Menu>
<MenuButton as={IconButton} aria-label='Options' icon={<FontAwesomeIcon icon={faEllipsis} />} variant='outline' />
<MenuList>
<ExportMenuItem entity="clients" />
</MenuList>
</Menu>
</ButtonGroup>
</div>
<Title title='Clients' icon={<IconBriefcase />} />
<Table>
<Thead>
<Tr>
<Th>Name</Th>
<Th>Address</Th>
<Th>URL</Th>
<Th>Number of contacts</Th>
<Th> </Th>
</Tr>
</Thead>
<Tbody>
{null === clients && <LoadingTableRow numColumns={5} />}
{null !== clients && 0 === clients.length && <NoResultsTableRow numColumns={5} />}
{null !== clients && 0 < clients.length && clients.map(client =>
<Tr key={client.id}>
<Td><ClientLink clientId={client.id}>{client.name}</ClientLink></Td>
<Td>{client.address || '-'}</Td>
<Td>{client.url ? <ExternalLink href={client.url}>{client.url}</ExternalLink> : '-'}</Td>
<Td>{client.num_contacts}</Td>
<Td textAlign="right">
<LinkButton href={`/clients/${client.id}/edit`}>Edit</LinkButton>
<DeleteIconButton onClick={() => destroy(client.id)} />
</Td>
</Tr>
)
}
</Tbody>
</Table>
</>
}
Example #11
Source File: Outputs.js From web-client with Apache License 2.0 | 4 votes |
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> </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>
</>
}
Example #12
Source File: Details.js From web-client with Apache License 2.0 | 4 votes |
CommandDetails = () => {
const { commandId } = useParams();
const navigate = useNavigate();
const [command] = useFetch(`/commands/${commandId}`)
const deleteClient = useDelete(`/commands/`)
const handleDelete = async () => {
const confirmed = await deleteClient(commandId);
if (confirmed)
navigate('/commands');
}
if (!command) {
return <Loading />
}
return <div>
<div className='heading'>
<Breadcrumb>
<Link to="/commands">Commands</Link>
</Breadcrumb>
<ButtonGroup>
<RestrictedComponent roles={['administrator', 'superuser', 'user']}>
<EditButton onClick={(ev) => {
ev.preventDefault();
navigate(`/commands/${command.id}/edit`)
}}>Edit</EditButton>
<DeleteButton onClick={handleDelete} />
</RestrictedComponent>
</ButtonGroup>
</div>
<article>
<div>
<PageTitle value={`${command.name} command`} />
<Title type='Command' title={command.name} icon={<IconBriefcase />} />
<Tags values={command.tags} />
</div>
<Tabs>
<TabList>
<Tab>Details</Tab>
<Tab>Run instructions</Tab>
<Tab>Command outputs</Tab>
<Tab>Terminal</Tab>
</TabList>
<TabPanels>
<TabPanel>
<div className="grid grid-two">
<div>
<dl>
<dt>Description</dt>
<dd>{command.description ? <ReactMarkdown>{command.description}</ReactMarkdown> : <EmptyField />}</dd>
{command.output_parser && <>
<dt>Output parser support</dt>
<dl>Yes ({command.output_parser})</dl>
</>}
{command.more_info_url && <>
<dt>More information URL</dt>
<dl>{command.more_info_url ? <ExternalLink href={command.more_info_url}>{command.more_info_url}</ExternalLink> : <EmptyField />}</dl>
</>}
{CommandService.hasCommand(command) && <>
{CommandService.isHost(command) && <>
<dt>Command line example</dt>
<dd>
<ShellCommand>{HostCommandLineGenerator.generateEntryPoint(command)} {HostCommandLineGenerator.renderArguments(command)}</ShellCommand>
</dd>
</>}
<dt>Command line example using rmap CLI</dt>
<dd>
<ShellCommand>{CommandService.generateEntryPoint(command)} {CommandService.renderArguments(command)}</ShellCommand>
</dd>
</>}
</dl>
</div>
<div>
<h4>Relations</h4>
<dl>
<dt>Created by</dt>
<dd><UserLink userId={command.creator_uid}>{command.creator_full_name}</UserLink></dd>
</dl>
<TimestampsSection entity={command} />
</div>
</div>
</TabPanel>
<TabPanel>
<CommandInstructions command={command} />
</TabPanel>
<TabPanel>
<CommandOutputs command={command} />
</TabPanel>
<TabPanel>
<CommandTerminal commands={[]} />
</TabPanel>
</TabPanels>
</Tabs>
</article>
</div >
}
Example #13
Source File: Details.js From web-client with Apache License 2.0 | 4 votes |
ProjectDetails = () => {
const navigate = useNavigate();
const { projectId } = useParams();
const { colorMode } = useColorMode()
const [project, updateProject] = useFetch(`/projects/${projectId}`)
const [users] = useFetch(`/projects/${projectId}/users`)
const destroy = useDelete(`/projects/`, updateProject);
const handleGenerateReport = () => {
navigate(`/projects/${project.id}/report`)
}
const handleManageTeam = () => {
navigate(`/projects/${project.id}/membership`)
}
const onArchiveButtonClick = (project) => {
secureApiFetch(`/projects/${project.id}`, {
method: 'PATCH',
body: JSON.stringify({ archived: !project.archived })
})
.then(() => {
updateProject();
actionCompletedToast('The project has been updated.');
})
.catch(err => console.error(err))
}
if (project && project.is_template) {
return <Navigate to={`/projects/templates/${project.id}`} />
}
return <>
<div className='heading'>
<Breadcrumb>
<Link to="/projects">Projects</Link>
</Breadcrumb>
{project && <>
<PageTitle value={`${project.name} project`} />
<ProjectTeam project={project} users={users} />
<ButtonGroup isAttached>
<RestrictedComponent roles={['administrator', 'superuser', 'user']}>
{!project.archived && <>
<LinkButton href={`/projects/${project.id}/edit`}>Edit</LinkButton>
<SecondaryButton onClick={handleGenerateReport} leftIcon={<IconClipboardCheck />}>Report</SecondaryButton>
<SecondaryButton onClick={handleManageTeam} leftIcon={<IconUserGroup />}>Membership</SecondaryButton>
</>}
<Menu>
<MenuButton as={IconButton} aria-label='Options' icon={<FontAwesomeIcon icon={faEllipsis} />} variant='outline' />
<MenuList>
<MenuItem onClick={() => onArchiveButtonClick(project)}>{project.archived ? 'Unarchive' : 'Archive'}</MenuItem>
<MenuItem icon={<FontAwesomeIcon icon={faTrash} />} color={colorMode === "light" ? "red.500" : "red.400"} onClick={() => destroy(project.id)}>Delete</MenuItem>
</MenuList>
</Menu>
</RestrictedComponent>
</ButtonGroup>
</>}
</div>
{!project ? <Loading /> :
<>
<Title title={project.name} type="Project" icon={<IconFolder />} />
<Tabs>
<TabList>
<Tab>Details</Tab>
<Tab>Targets</Tab>
<Tab>Tasks</Tab>
<Tab>Vulnerabilities</Tab>
<Tab>Notes</Tab>
<Tab>Attachments</Tab>
<Tab>Vault</Tab>
</TabList>
<TabPanels>
<TabPanel><ProjectDetailsTab project={project} /></TabPanel>
<TabPanel><ProjectTargets project={project} /></TabPanel>
<TabPanel><ProjectTasks project={project} /></TabPanel>
<TabPanel><ProjectVulnerabilities project={project} /></TabPanel>
<TabPanel><ProjectNotesTab project={project} /></TabPanel>
<TabPanel><ProjectAttachmentsTab project={project} /></TabPanel>
<TabPanel><ProjectVaultTab project={project} /></TabPanel>
</TabPanels>
</Tabs>
</>
}
</>
}
Example #14
Source File: Targets.js From web-client with Apache License 2.0 | 4 votes |
ProjectTargets = ({ project }) => {
const query = useQuery();
const urlPageNumber = query.get('page') !== null ? parseInt(query.get('page')) : 1;
const [pageNumber, setPageNumber] = useState(urlPageNumber);
const [numberPages, setNumberPages] = useState(1);
const [targets, setTargets] = useState([]);
const { isOpen: isAddTargetDialogOpen, onOpen: openAddTargetDialog, onClose: closeAddTargetDialog } = useDisclosure();
const onDeleteButtonClick = (ev, targetId) => {
ev.preventDefault();
secureApiFetch(`/targets/${targetId}`, { method: 'DELETE' })
.then(() => {
reloadTargets();
})
}
const onTargetFormSaved = () => {
reloadTargets();
closeAddTargetDialog();
}
const reloadTargets = useCallback(() => {
setTargets([]);
secureApiFetch(`/targets?projectId=${project.id}&page=${pageNumber - 1}`, { method: 'GET' })
.then(resp => {
if (resp.headers.has('X-Page-Count')) {
setNumberPages(resp.headers.get('X-Page-Count'))
}
return resp.json()
})
.then(data => {
setTargets(data);
});
}, [pageNumber, project]);
const onPrevPageClick = () => {
setPageNumber(pageNumber - 1);
}
const onNextPageClick = () => {
setPageNumber(pageNumber + 1);
}
useEffect(() => {
reloadTargets()
}, [reloadTargets])
return <section>
<h4>
<IconServer />Targets
<RestrictedComponent roles={['administrator', 'superuser', 'user']}>
<ButtonGroup>
<TargetModalDialog project={project} isOpen={isAddTargetDialogOpen} onSubmit={onTargetFormSaved} onCancel={closeAddTargetDialog} />
<CreateButton onClick={openAddTargetDialog}>Add target...</CreateButton>
</ButtonGroup>
</RestrictedComponent>
</h4>
{!targets ? <Loading /> :
<>
{numberPages > 1 && <Center>
<Pagination page={pageNumber - 1} total={numberPages} handlePrev={onPrevPageClick} handleNext={onNextPageClick} />
</Center>}
<Table>
<Thead>
<Tr>
<Th>Name</Th>
<Th>Sub-target</Th>
<Th>Kind</Th>
<Th>Vulnerable?</Th>
<Th> </Th>
</Tr>
</Thead>
<Tbody>
{targets.length === 0 && <NoResultsTableRow numColumns={4} />}
{targets.map((target, index) =>
<Tr key={index}>
<Td>
{target.parent_id === null &&
<HStack>
<Link to={`/targets/${target.id}`}><TargetBadge name={target.name} /></Link>
</HStack>
}
{target.parent_id !== null &&
<>{target.parent_name ?? '-'}</>
}
</Td>
<Td>{target.parent_id !== null ?
<>
<Link to={`/targets/${target.id}`}><TargetBadge name={target.name} /></Link>
</> : '-'}</Td>
<Td>{target.kind} <Tags values={target.tags} /></Td>
<Td>{target.num_vulnerabilities > 0 ? `Yes (${target.num_vulnerabilities} vulnerabilities found)` : "No"}</Td>
<Td>
<RestrictedComponent roles={['administrator', 'superuser', 'user']}>
<DeleteIconButton onClick={ev => onDeleteButtonClick(ev, target.id)} />
</RestrictedComponent>
</Td>
</Tr>
)}
</Tbody>
</Table>
</>
}
</section>
}
Example #15
Source File: Details.js From web-client with Apache License 2.0 | 4 votes |
ClientDetails = () => {
const { clientId } = useParams();
const navigate = useNavigate();
const [client] = useFetch(`/clients/${clientId}`);
const [contacts, fetchContacts] = useFetch(`/clients/${clientId}/contacts`);
const [contact, setContact] = useState(new Contact());
const onContactFormChange = ev => {
setContact({ ...contact, [ev.target.name]: ev.target.value });
}
const deleteClient = useDelete(`/clients/`)
const [logo, setLogo] = useState(null);
const [smallLogo, setSmallLogo] = useState(null);
const handleDelete = async () => {
const confirmed = await deleteClient(clientId);
if (confirmed)
navigate('/clients');
}
const onFormSubmit = ev => {
ev.preventDefault();
secureApiFetch(`/clients/${clientId}/contacts`, { method: 'POST', body: JSON.stringify(contact) })
.then(resp => {
if (resp.status === 201) {
setContact(new Contact());
fetchContacts();
actionCompletedToast(`The contact has been added.`);
} else {
errorToast("The contact could not be saved. Review the form data or check the application logs.")
}
})
}
const onContactDelete = contactId => {
secureApiFetch(`/contacts/${contactId}`, { method: 'DELETE' })
.then(() => {
fetchContacts();
actionCompletedToast("The contact has been deleted.");
})
.catch(err => console.error(err))
}
useEffect(() => {
if (client) {
if (client.small_logo_attachment_id) {
downloadAndDisplayLogo(client.small_logo_attachment_id, 'small_logo');
}
if (client.logo_attachment_id) {
downloadAndDisplayLogo(client.logo_attachment_id, 'logo');
}
}
}, [client]);
const downloadAndDisplayLogo = (logoId, type) => {
secureApiFetch(`/attachments/${logoId}`, { method: 'GET' })
.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 url = URL.createObjectURL(blob);
if (type === 'small_logo') {
setSmallLogo(url);
} else {
setLogo(url);
}
})
}
if (!client) {
return <Loading />
}
return <div>
<div className='heading'>
<Breadcrumb>
<Link to="/clients">Clients</Link>
</Breadcrumb>
<ButtonGroup>
<RestrictedComponent roles={['administrator', 'superuser', 'user']}>
<EditButton onClick={(ev) => {
ev.preventDefault();
navigate(`/clients/${client.id}/edit`)
}}>Edit</EditButton>
<DeleteButton onClick={handleDelete} />
</RestrictedComponent>
</ButtonGroup>
</div>
<article>
<div>
<PageTitle value={`${client.name} client`} />
<Title type='Client' title={client.name} icon={<IconBriefcase />} />
</div>
<Tabs isLazy>
<TabList>
<Tab>Details</Tab>
<Tab>Contacts</Tab>
<Tab>Projects</Tab>
</TabList>
<TabPanels>
<TabPanel>
<div className="grid grid-two">
<div>
<h4>Properties</h4>
<dl>
<dt>Address</dt>
<dd>{client.address ?? '-'}</dd>
<dt>URL</dt>
<dd><ExternalLink href={client.url}>{client.url}</ExternalLink></dd>
</dl>
<h4>Branding</h4>
<dl>
<dt>Main logo</dt>
<dd>{logo ? <img src={logo} alt="The main logo" /> : <EmptyField />}</dd>
<dt>Small logo</dt>
<dd>{smallLogo ? <img src={smallLogo} alt="The smaller version of the logo" /> : <EmptyField />}</dd>
</dl>
</div>
<div>
<h4>Relations</h4>
<dl>
<dt>Created by</dt>
<dd><UserLink userId={client.creator_uid}>{client.creator_full_name}</UserLink></dd>
</dl>
<TimestampsSection entity={client} />
</div>
</div>
</TabPanel>
<TabPanel>
<h4>Contacts</h4>
{contacts && <>
<form onSubmit={onFormSubmit}>
<Table>
<Thead>
<Tr>
<Th>Kind</Th>
<Th>Name</Th>
<Th>Role</Th>
<Th>Email</Th>
<Th>Phone</Th>
<Th> </Th>
</Tr>
</Thead>
<Tbody>
<Tr>
<Td>
<Select name="kind" onChange={onContactFormChange} value={contact.kind || ""} isRequired>
<option value="general">General</option>
<option value="technical">Technical</option>
<option value="billing">Billing</option>
</Select>
</Td>
<Td>
<Input type="text" name="name" onChange={onContactFormChange} value={contact.name || ""} isRequired />
</Td>
<Td>
<Input type="text" name="role" onChange={onContactFormChange} value={contact.role || ""} />
</Td>
<Td>
<Input type="email" name="email" onChange={onContactFormChange} value={contact.email || ""} isRequired />
</Td>
<Td>
<Input type="tel" name="phone" onChange={onContactFormChange} value={contact.phone || ""} />
</Td>
<Td>
<Button type="submit">Add</Button>
</Td>
</Tr>
{0 === contacts.length && <NoResultsTableRow numColumns={6} />}
{contacts.map(contact => <>
<Tr key={contact.id}>
<Td>{contact.kind}</Td>
<Td>{contact.name}</Td>
<Td>{contact.role}</Td>
<Td><MailLink email={contact.email} /></Td>
<Td><TelephoneLink number={contact.phone} /></Td>
<Td><DeleteIconButton onClick={onContactDelete.bind(this, contact.id)} /></Td>
</Tr>
</>)}
</Tbody>
</Table>
</form>
</>}
</TabPanel>
<TabPanel>
<ClientProjectsTab clientId={clientId} />
</TabPanel>
</TabPanels>
</Tabs>
</article>
</div>
}
Example #16
Source File: List.js From web-client with Apache License 2.0 | 4 votes |
UsersList = () => {
const navigate = useNavigate();
const loggedInUser = Auth.getLoggedInUser();
const [users, updateUsers] = useFetch("/users");
const deleteUser = useDelete("/users/", updateUsers);
const handleCreate = () => {
navigate("/users/create");
};
const [selectedUsers, setSelectedUsers] = useState([]);
const onTaskCheckboxChange = (ev) => {
const target = ev.target;
const targetUserId = parseInt(target.value);
if (target.checked) {
setSelectedUsers([...selectedUsers, targetUserId]);
} else {
setSelectedUsers(
selectedUsers.filter(value => value !== targetUserId)
);
}
};
const handleBulkDelete = () => {
secureApiFetch(`/users`, {
method: "PATCH",
headers: {
"Bulk-Operation": "DELETE",
},
body: JSON.stringify(selectedUsers),
})
.then(updateUsers)
.then(() => {
setSelectedUsers([]);
actionCompletedToast("All selected users were deleted.");
})
.catch(err => console.error(err));
};
const handleDelete = (id) => {
deleteUser(id);
updateUsers();
};
return <>
<PageTitle value="Users" />
<div className="heading">
<Breadcrumb />
<ButtonGroup isAttached>
<CreateButton onClick={handleCreate}>
Create user
</CreateButton>
<RestrictedComponent roles={['administrator']}>
<DeleteButton onClick={handleBulkDelete} disabled={selectedUsers.length === 0}>
Delete selected
</DeleteButton>
</RestrictedComponent>
<Menu>
<EllipsisMenuButton />
<MenuList>
<ExportMenuItem entity="users" />
</MenuList>
</Menu>
</ButtonGroup>
</div>
<Title title="Users and roles" icon={<IconUserGroup />} />
<Table>
<Thead>
<Tr>
<Th style={{ width: "32px" }}> </Th>
<Th style={{ width: "64px" }}> </Th>
<Th>Full name</Th>
<Th>Username</Th>
<Th>Role</Th>
<Th>Active?</Th>
<Th>2FA enabled?</Th>
<Th> </Th>
</Tr>
</Thead>
<Tbody>
{null === users && <LoadingTableRow numColumns={8} />}
{null !== users && 0 === users.length && <NoResultsTableRow numColumns={8} />}
{null !== users && 0 !== users.length && users.map((user, index) => (
<Tr key={index}>
<Td>
<input
type="checkbox"
value={user.id}
onChange={onTaskCheckboxChange}
checked={selectedUsers.includes(user.id)}
/>
</Td>
<Td>
<UserAvatar email={user.email} />
</Td>
<Td>
<Link to={`/users/${user.id}`}>
{user.full_name}
</Link>
</Td>
<Td>
<UserLink userId={user.id}>
{user.username}
</UserLink>
</Td>
<Td>
<UserRoleBadge role={user.role} />
</Td>
<Td><BooleanText value={user.active} /></Td>
<Td><BooleanText value={user.mfa_enabled} /></Td>
<Td textAlign="right">
<LinkButton href={`/users/${user.id}/edit`}>
Edit
</LinkButton>
<DeleteIconButton
onClick={() => handleDelete(user.id)}
disabled={
parseInt(user.id) ===
loggedInUser.id
? "disabled"
: ""
}
/>
</Td>
</Tr>
))}
</Tbody>
</Table>
</>
}
Example #17
Source File: BuilderListView.jsx From scaffold-directory with MIT License | 4 votes |
export default function BuilderListView({ serverUrl, mainnetProvider, userRole }) {
const [builders, setBuilders] = useState([]);
const [isLoadingBuilders, setIsLoadingBuilders] = useState(false);
const { secondaryFontColor } = useCustomColorModes();
const isAdmin = userRole === USER_ROLES.admin;
const columns = useMemo(
() => [
{
Header: "Builder",
accessor: "builder",
disableSortBy: true,
Cell: ({ value }) => <BuilderAddressCell builderId={value} mainnetProvider={mainnetProvider} />,
},
{
Header: "Challenges",
accessor: "challenges",
sortDescFirst: true,
},
{
Header: "Socials",
accessor: "socials",
disableSortBy: true,
Cell: ({ value }) => <BuilderSocialLinksCell builder={value} isAdmin={isAdmin} />,
},
{
Header: "Last Activity",
accessor: "lastActivity",
sortDescFirst: true,
Cell: ({ value }) => <DateWithTooltip timestamp={value} />,
},
],
// eslint-disable-next-line
[userRole],
);
useEffect(() => {
async function fetchBuilders() {
setIsLoadingBuilders(true);
const fetchedBuilders = await axios.get(serverUrl + serverPath);
const processedBuilders = fetchedBuilders.data.map(builder => ({
builder: builder.id,
challenges: getAcceptedChallenges(builder?.challenges)?.length ?? 0,
socials: builder,
lastActivity: builderLastActivity(builder),
}));
setBuilders(processedBuilders);
setIsLoadingBuilders(false);
}
fetchBuilders();
}, [serverUrl]);
const {
getTableProps,
getTableBodyProps,
headerGroups,
prepareRow,
page,
canPreviousPage,
canNextPage,
pageOptions,
pageCount,
gotoPage,
nextPage,
previousPage,
setPageSize,
state: { pageIndex, pageSize },
} = useTable(
{
columns,
data: builders,
initialState: { pageIndex: 0, pageSize: 25, sortBy: useMemo(() => [{ id: "lastActivity", desc: true }], []) },
},
useSortBy,
usePagination,
);
return (
<Container maxW="container.lg">
<Container maxW="container.md" centerContent>
<Heading as="h1" mb="4">
All Builders
</Heading>
<Text color={secondaryFontColor} textAlign="center" mb="10">
List of Ethereum builders creating products, prototypes, and tutorials with{" "}
<Link href="https://github.com/scaffold-eth/scaffold-eth" color="teal.500" isExternal>
scaffold-eth
</Link>
.
</Text>
</Container>
{isLoadingBuilders ? (
<BuilderListSkeleton />
) : (
<Box overflowX="auto" mb={8}>
<Center mb={5}>
<chakra.strong mr={2}>Total builders:</chakra.strong> {builders.length}
</Center>
<Table {...getTableProps()}>
<Thead>
{headerGroups.map(headerGroup => (
<Tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(column => (
<Th {...column.getHeaderProps(column.getSortByToggleProps())}>
{column.render("Header")}
<chakra.span pl="4">
{column.isSorted ? (
column.isSortedDesc ? (
<TriangleDownIcon aria-label="sorted descending" />
) : (
<TriangleUpIcon aria-label="sorted ascending" />
)
) : null}
</chakra.span>
</Th>
))}
</Tr>
))}
</Thead>
<Tbody {...getTableBodyProps()}>
{page.map(row => {
prepareRow(row);
return (
<Tr {...row.getRowProps()}>
{row.cells.map(cell => (
<Td {...cell.getCellProps()}>{cell.render("Cell")}</Td>
))}
</Tr>
);
})}
</Tbody>
</Table>
<Center mt={4}>
<ButtonGroup>
<Button onClick={() => gotoPage(0)} disabled={!canPreviousPage}>
{"<<"}
</Button>
<Button onClick={() => previousPage()} disabled={!canPreviousPage}>
{"<"}
</Button>
<Button onClick={() => nextPage()} disabled={!canNextPage}>
{">"}
</Button>
<Button onClick={() => gotoPage(pageCount - 1)} disabled={!canNextPage}>
{">>"}
</Button>
</ButtonGroup>
</Center>
<Center mt={4}>
<Text mr={4}>
Page{" "}
<strong>
{pageIndex + 1} of {pageOptions.length}
</strong>{" "}
</Text>
<Box>
<Select
isFullWidth={false}
value={pageSize}
onChange={e => {
setPageSize(Number(e.target.value));
}}
>
{[25, 50, 100].map(pageSizeOption => (
<option key={pageSizeOption} value={pageSizeOption}>
Show {pageSizeOption}
</option>
))}
</Select>
</Box>
</Center>
</Box>
)}
</Container>
);
}
Example #18
Source File: Details.js From web-client with Apache License 2.0 | 4 votes |
TemplateDetails = () => {
const navigate = useNavigate();
const { templateId } = useParams();
const [vulnerability] = useFetch(`/vulnerabilities/${templateId}`)
const cloneProject = async (templateId) => {
secureApiFetch(`/vulnerabilities/${templateId}/clone`, { method: 'POST' })
.then(resp => resp.json())
.then(data => {
navigate(`/vulnerabilities/${data.vulnerabilityId}/edit`);
});
}
const destroy = useDelete('/vulnerabilities/', () => {
navigate('/vulnerabilities/templates');
});
if (!vulnerability) return <Loading />
if (vulnerability && !vulnerability.is_template) {
return <Navigate to={`/vulnerabilities/${vulnerability.id}`} />
}
return (
<>
<div>
<div className='heading'>
<Breadcrumb>
<Link to="/vulnerabilities">Vulnerabilities</Link>
<Link to="/vulnerabilities/templates">Templates</Link>
</Breadcrumb>
<ButtonGroup>
<PrimaryButton onClick={() => cloneProject(vulnerability.id)} leftIcon={<IconPlusCircle />}> Clone and edit</PrimaryButton>
<RestrictedComponent roles={['administrator', 'superuser', 'user']}>
<LinkButton href={`/vulnerabilities/${vulnerability.id}/edit`}>Edit</LinkButton>
<DeleteButton onClick={() => destroy(vulnerability.id)} />
</RestrictedComponent>
</ButtonGroup>
</div>
<article>
<PageTitle value={`${vulnerability.summary} vulnerability template`} />
<Title type='Vulnerability template' title={vulnerability.summary} icon={<IconFlag />} />
<div className="grid grid-two">
<div>
<h4>Description</h4>
{vulnerability.description ? <ReactMarkdown>{vulnerability.description}</ReactMarkdown> : <EmptyField />}
{vulnerability.solution &&
<>
<h4>Solution</h4>
<ReactMarkdown>{vulnerability.solution}</ReactMarkdown>
</>
}
<h4>Proof of concept</h4>
{vulnerability.proof_of_concept ? <ReactMarkdown>{vulnerability.proof_of_concept}</ReactMarkdown> : <EmptyField />}
<h4>Impact</h4>
{vulnerability.impact ? <ReactMarkdown>{vulnerability.impact}</ReactMarkdown> : <EmptyField />}
<h4>Properties</h4>
<dl>
<dt>Status</dt>
<dd><VulnerabilityStatusBadge vulnerability={vulnerability} /></dd>
<dt>Risk</dt>
<dd><RiskBadge risk={vulnerability.risk} /></dd>
<dt>Category</dt>
<dd>{vulnerability.category_name || '-'}</dd>
<dt><CvssAbbr /> score</dt>
<dd><CvssScore score={vulnerability.cvss_score} /></dd>
<dt>CVSS vector</dt>
<dd><ExternalLink
href={`https://www.first.org/cvss/calculator/3.0#${vulnerability.cvss_vector}`}>{vulnerability.cvss_vector}</ExternalLink>
</dd>
</dl>
</div>
<div>
<h4>Relations</h4>
<dl>
<dt>Created by</dt>
<dd><UserLink userId={vulnerability.creator_uid}>{vulnerability.creator_full_name}</UserLink></dd>
</dl>
<TimestampsSection entity={vulnerability} />
</div>
</div>
</article>
</div>
</>
)
}
Example #19
Source File: ChallengeExpandedCard.jsx From scaffold-directory with MIT License | 4 votes |
ChallengeExpandedCard = ({ challengeId, challenge, builderAttemptedChallenges }) => {
const { borderColor, secondaryFontColor } = useCustomColorModes();
const builderHasCompletedDependenciesChallenges = challenge.dependencies?.every(id => {
if (!builderAttemptedChallenges[id]) {
return false;
}
if (!(builderAttemptedChallenges[id].status === CHALLENGE_SUBMISSION_STATUS.ACCEPTED)) {
return false;
}
if (challenge.lockedTimestamp) {
return (
new Date().getTime() - builderAttemptedChallenges[id].submittedTimestamp > challenge.lockedTimestamp * 60 * 1000
);
}
return true;
});
const pendingDependenciesChallenges = challenge.dependencies?.filter(dependency => {
return (
!builderAttemptedChallenges[dependency] ||
builderAttemptedChallenges[dependency].status !== CHALLENGE_SUBMISSION_STATUS.ACCEPTED
);
});
const lockReasonToolTip = "The following challenges are not completed: " + pendingDependenciesChallenges.join(", ");
const isRampUpChallenge = challenge.dependencies?.length === 0;
const challengeStatus = builderAttemptedChallenges[challengeId]?.status;
let colorScheme;
let label;
switch (challengeStatus) {
case CHALLENGE_SUBMISSION_STATUS.ACCEPTED: {
colorScheme = "green";
label = "Accepted";
break;
}
case CHALLENGE_SUBMISSION_STATUS.REJECTED: {
colorScheme = "red";
label = "Rejected";
break;
}
case CHALLENGE_SUBMISSION_STATUS.SUBMITTED: {
label = "Submitted";
break;
}
default:
// do nothing
}
const isChallengeLocked = challenge.disabled || !builderHasCompletedDependenciesChallenges;
if (challenge.checkpoint) {
return (
<Box bg="#f9f9f9">
<Flex maxW={500} overflow="hidden" m="0 auto 24px" opacity={isChallengeLocked ? "0.5" : "1"}>
<Flex pt={6} pb={4} px={4} direction="column" grow={1}>
<Flex alignItems="center" pb={4} direction="column">
<Text fontWeight="bold" fontSize="lg" mb={2}>
{challenge.label}
</Text>
<Center borderBottom="1px" borderColor={borderColor} w="200px" flexShrink={0} p={1}>
<Image src={challenge.previewImage} objectFit="cover" />
</Center>
</Flex>
<Text color={secondaryFontColor} mb={4} textAlign="center">
{challenge.description}
</Text>
<Spacer />
<ButtonGroup>
<Button
as={isChallengeLocked ? Button : Link}
href={isChallengeLocked ? null : challenge.externalLink?.link}
isDisabled={isChallengeLocked}
variant={isChallengeLocked ? "outline" : "solid"}
isFullWidth
isExternal
>
{builderHasCompletedDependenciesChallenges ? (
<chakra.span>{challenge.externalLink.claim}</chakra.span>
) : (
<>
<span role="img" aria-label="lock icon">
?
</span>
<chakra.span ml={1}>Locked</chakra.span>
</>
)}
</Button>
{!builderHasCompletedDependenciesChallenges && (
<Tooltip label={lockReasonToolTip}>
<IconButton icon={<QuestionOutlineIcon />} />
</Tooltip>
)}
</ButtonGroup>
</Flex>
</Flex>
</Box>
);
}
return (
<Flex maxW={880} borderWidth="1px" borderRadius="lg" borderColor={borderColor} overflow="hidden" mb={6}>
<Center borderBottom="1px" borderColor={borderColor} w="200px" flexShrink={0} p={1}>
{challenge.previewImage ? (
<Image src={challenge.previewImage} objectFit="cover" />
) : (
<Text p={3} textAlign="center">
{challengeId} image
</Text>
)}
</Center>
<Flex pt={6} pb={4} px={4} direction="column" grow={1}>
<Flex justify="space-between" pb={4}>
<Text fontWeight="bold">{challenge.label}</Text>
{isRampUpChallenge && challengeStatus && (
<Badge borderRadius="xl" colorScheme={colorScheme} textTransform="none" py={0.5} px={2.5}>
{label}
</Badge>
)}
</Flex>
<Text color={secondaryFontColor} mb={4}>
{challenge.description}
</Text>
<Spacer />
{challenge.externalLink?.link ? (
// Redirect to externalLink if set (instead of challenge detail view)
<ButtonGroup>
<Button
as={isChallengeLocked ? Button : Link}
href={isChallengeLocked ? null : challenge.externalLink?.link}
isDisabled={isChallengeLocked}
variant={isChallengeLocked ? "outline" : "solid"}
isFullWidth
isExternal
>
{builderHasCompletedDependenciesChallenges ? (
<chakra.span>{challenge.externalLink.claim}</chakra.span>
) : (
<>
<span role="img" aria-label="lock icon">
?
</span>
<chakra.span ml={1}>Locked</chakra.span>
</>
)}
</Button>
{!builderHasCompletedDependenciesChallenges && (
<Tooltip label={lockReasonToolTip}>
<IconButton icon={<QuestionOutlineIcon />} />
</Tooltip>
)}
</ButtonGroup>
) : (
<ButtonGroup>
<Button
as={RouteLink}
to={!isChallengeLocked && `/challenge/${challengeId}`}
isDisabled={isChallengeLocked}
variant={isChallengeLocked ? "outline" : "solid"}
isFullWidth
>
{!isChallengeLocked ? (
<>
{" "}
<span role="img" aria-label="castle icon">
⚔️
</span>
<chakra.span ml={1}>Quest</chakra.span>
</>
) : (
<>
<span role="img" aria-label="lock icon">
?
</span>
<chakra.span ml={1}>Locked</chakra.span>
</>
)}
</Button>
{!builderHasCompletedDependenciesChallenges && (
<Tooltip label={lockReasonToolTip}>
<IconButton icon={<QuestionOutlineIcon />} />
</Tooltip>
)}
</ButtonGroup>
)}
</Flex>
</Flex>
);
}