react-icons/md#MdAdd TypeScript Examples
The following examples show how to use
react-icons/md#MdAdd.
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: Navigation.tsx From website-docs with MIT License | 6 votes |
function TocMenu({ data, level, active }: ItemProps) {
const [head, ...rest] = active
const [open, setOpen] = useState(data === head)
const content = <TocContent content={data.content} />
const children = (
<ul className={open ? listOpen : undefined}>
{data.children!.map((child, index) => (
<TocItem data={child} level={level + 1} active={rest} key={index} />
))}
</ul>
)
if (level === 0) {
const icon = open ? <MdSort /> : <MdMenu />
return (
<li className={navItem}>
<div className={menu} onClick={() => setOpen(open => !open)}>
{content}
{icon}
</div>
{children}
</li>
)
}
const icon = open ? <MdRemove /> : <MdAdd />
return (
<li className={navItem}>
<div className={menu} onClick={() => setOpen(open => !open)}>
{icon}
{content}
</div>
{children}
</li>
)
}
Example #2
Source File: RoomsList.tsx From convoychat with GNU General Public License v3.0 | 5 votes |
RoomsList: React.FC = () => {
const history = useHistory();
const { dispatch } = useModalContext();
const [modalRoomId, setModalRoomId] = useState<string>("");
const { data, loading, error } = useListCurrentUserRoomsQuery({
onCompleted(data) {
// redirect to first room on the list on initial load
if (
history.location.pathname === "/" &&
data.currentUserRooms.length > 1
) {
history.push(`/room/${data.currentUserRooms[0].id}`);
}
},
});
return (
<section>
<InviteMembers roomId={modalRoomId} />
<Flex align="center" justify="space-between">
<h3>Your Rooms</h3>
<Tooltip placement="top" message={<span>Create new room</span>}>
<IconButton
onClick={() => dispatch({ type: "OPEN", modal: "CreateRoom" })}
icon={<MdAdd />}
/>
</Tooltip>
</Flex>
<Spacer gap="large" />
{loading && <Loader />}
{error && <span>Error Loading Rooms</span>}
{data?.currentUserRooms?.map(room => {
return (
<RoomLink
id={room.id}
key={room.id}
name={room.name}
isSelected={!!history.location.pathname.match(room.id)}
onInviteMemberClick={roomId => setModalRoomId(roomId)}
/>
);
})}
</section>
);
}
Example #3
Source File: Footer.tsx From website-docs with MIT License | 5 votes |
export function Footer() {
const { language } = useI18next()
const [spread, setSpread] = useState<number | undefined>(undefined)
const { FooterLogoSVG } = useStaticQuery(graphql`
query {
FooterLogoSVG: file(relativePath: { eq: "pingcap-logo.svg" }) {
publicURL
}
}
`)
const footerColumns = language === 'zh' ? zh : en
const handleSpreadItems = (index: number) => () => {
const screenWidth = window.screen.width
if (screenWidth > 768) {
return
}
setSpread(spread === index ? undefined : index)
}
return (
<BulmaFooter className={footer}>
<Container>
<Columns>
{footerColumns.map((column, index) => (
<Column key={column.name}>
<Title
className={title}
size={6}
onClick={handleSpreadItems(index)}>
{column.name}
<span
className={clsx(spreadStyle, index === spread && clicked)}>
<MdAdd />
</span>
</Title>
<ul className={clsx(items, index === spread && displayed)}>
{column.items.map(item => (
<li key={item.name}>
{item.url.startsWith('/') ? (
<Link to={item.url}>{item.name}</Link>
) : (
<a href={item.url} target="_blank" rel="noreferrer">
{item.name}
</a>
)}
</li>
))}
</ul>
</Column>
))}
<Column>
<Columns className={socials} multiline>
<Socials
className={clsx('column is-4', column)}
locale={language as Locale}
/>
</Columns>
</Column>
</Columns>
<div className={annotations}>
<div className={copyright}>
©{new Date().getFullYear()} PingCAP. All Rights Reserved.
</div>
<a href="https://pingcap.com" target="_blank" rel="noreferrer">
<img className={logo} src={FooterLogoSVG.publicURL} alt="PingCAP" />
</a>
</div>
</Container>
</BulmaFooter>
)
}
Example #4
Source File: index.tsx From hub with Apache License 2.0 | 4 votes |
MembersSection = (props: Props) => {
const history = useHistory();
const { ctx } = useContext(AppCtx);
const [isGettingMembers, setIsGettingMembers] = useState(false);
const [members, setMembers] = useState<Member[] | undefined>(undefined);
const [modalMemberOpen, setModalMemberOpen] = useState(false);
const [confirmedMembersNumber, setConfirmedMembersNumber] = useState<number>(0);
const [activeOrg, setActiveOrg] = useState<undefined | string>(ctx.prefs.controlPanel.selectedOrg);
const [apiError, setApiError] = useState<null | string>(null);
const [activePage, setActivePage] = useState<number>(props.activePage ? parseInt(props.activePage) : 1);
const calculateOffset = (pageNumber?: number): number => {
return DEFAULT_LIMIT * ((pageNumber || activePage) - 1);
};
const [offset, setOffset] = useState<number>(calculateOffset());
const [total, setTotal] = useState<number | undefined>(undefined);
const onPageNumberChange = (pageNumber: number): void => {
setOffset(calculateOffset(pageNumber));
setActivePage(pageNumber);
};
const updatePageNumber = () => {
history.replace({
search: `?page=${activePage}`,
});
};
const getConfirmedMembersNumber = (members: Member[]): number => {
const confirmedMembers = members.filter((member: Member) => member.confirmed);
return confirmedMembers.length;
};
async function fetchMembers() {
try {
setIsGettingMembers(true);
const data = await API.getOrganizationMembers(
{
limit: DEFAULT_LIMIT,
offset: offset,
},
activeOrg!
);
const total = parseInt(data.paginationTotalCount);
if (total > 0 && data.items.length === 0) {
onPageNumberChange(1);
} else {
setMembers(data.items);
setTotal(total);
setConfirmedMembersNumber(getConfirmedMembersNumber(data.items));
}
updatePageNumber();
setApiError(null);
setIsGettingMembers(false);
} catch (err: any) {
setIsGettingMembers(false);
if (err.kind !== ErrorKind.Unauthorized) {
setMembers([]);
setApiError('An error occurred getting the organization members, please try again later.');
} else {
props.onAuthError();
}
}
}
useEffect(() => {
if (props.activePage && activePage !== parseInt(props.activePage)) {
fetchMembers();
}
}, [activePage]); /* eslint-disable-line react-hooks/exhaustive-deps */
useEffect(() => {
if (!isUndefined(members)) {
if (activePage === 1) {
// fetchMembers is forced when context changes
fetchMembers();
} else {
// when current page is different to 1, to update page number fetchMembers is called
onPageNumberChange(1);
}
}
}, [activeOrg]); /* eslint-disable-line react-hooks/exhaustive-deps */
useEffect(() => {
if (activeOrg !== ctx.prefs.controlPanel.selectedOrg) {
setActiveOrg(ctx.prefs.controlPanel.selectedOrg);
}
}, [ctx.prefs.controlPanel.selectedOrg]); /* eslint-disable-line react-hooks/exhaustive-deps */
useEffect(() => {
fetchMembers();
}, []); /* eslint-disable-line react-hooks/exhaustive-deps */
return (
<main
role="main"
className="px-xs-0 px-sm-3 px-lg-0 d-flex flex-column flex-md-row justify-content-between my-md-4"
>
<div className="flex-grow-1 w-100 mb-4">
<div>
<div className="d-flex flex-row align-items-center justify-content-between pb-2 border-bottom">
<div className={`h3 pb-0 ${styles.title}`}>Members</div>
<div>
<ActionBtn
className={`btn btn-outline-secondary btn-sm text-uppercase ${styles.btnAction}`}
contentClassName="justify-content-center"
onClick={(e: ReactMouseEvent<HTMLButtonElement>) => {
e.preventDefault();
setModalMemberOpen(true);
}}
action={AuthorizerAction.AddOrganizationMember}
label="Open invite member modal"
>
<div className="d-flex flex-row align-items-center">
<MdAdd className="d-inline d-md-none" />
<MdAddCircle className="d-none d-md-inline me-2" />
<span className="d-none d-md-inline">Invite</span>
</div>
</ActionBtn>
</div>
</div>
</div>
{(isGettingMembers || isUndefined(members)) && <Loading />}
<div className="mt-5">
{!isUndefined(members) && (
<>
{members.length === 0 ? (
<NoData issuesLinkVisible={!isNull(apiError)}>
{isNull(apiError) ? (
<>
<p className="h6 my-4 lh-base">Do you want to add a member?</p>
<button
type="button"
className="btn btn-sm btn-outline-secondary"
onClick={() => setModalMemberOpen(true)}
aria-label="Open modal"
>
<div className="d-flex flex-row align-items-center text-uppercase">
<MdAddCircle className="me-2" />
<span>Add member</span>
</div>
</button>
</>
) : (
<>{apiError}</>
)}
</NoData>
) : (
<>
<div className="row mt-4 mt-md-5 gx-0 gx-xxl-4">
{members.map((member: Member) => (
<MemberCard
key={`member_${member.alias}`}
member={member}
onAuthError={props.onAuthError}
onSuccess={fetchMembers}
membersNumber={confirmedMembersNumber}
/>
))}
</div>
{!isUndefined(total) && (
<div className="mx-auto">
<Pagination
limit={DEFAULT_LIMIT}
offset={offset}
total={total}
active={activePage}
className="my-5"
onChange={onPageNumberChange}
/>
</div>
)}
</>
)}
</>
)}
</div>
</div>
{modalMemberOpen && (
<MemberModal
open={modalMemberOpen}
membersList={members}
onSuccess={fetchMembers}
onAuthError={props.onAuthError}
onClose={() => setModalMemberOpen(false)}
/>
)}
</main>
);
}
Example #5
Source File: index.tsx From hub with Apache License 2.0 | 4 votes |
OrganizationsSection = (props: Props) => {
const history = useHistory();
const [isLoading, setIsLoading] = useState(false);
const [modalStatus, setModalStatus] = useState<ModalStatus>({
open: false,
});
const [organizations, setOrganizations] = useState<Organization[] | undefined>(undefined);
const [apiError, setApiError] = useState<null | string>(null);
const [activePage, setActivePage] = useState<number>(props.activePage ? parseInt(props.activePage) : 1);
const calculateOffset = (pageNumber?: number): number => {
return DEFAULT_LIMIT * ((pageNumber || activePage) - 1);
};
const [offset, setOffset] = useState<number>(calculateOffset());
const [total, setTotal] = useState<number | undefined>(undefined);
const onPageNumberChange = (pageNumber: number): void => {
setOffset(calculateOffset(pageNumber));
setActivePage(pageNumber);
};
const updatePageNumber = () => {
history.replace({
search: `?page=${activePage}`,
});
};
async function fetchOrganizations() {
try {
setIsLoading(true);
const data = await API.getUserOrganizations({
limit: DEFAULT_LIMIT,
offset: offset,
});
const total = parseInt(data.paginationTotalCount);
if (total > 0 && data.items.length === 0) {
onPageNumberChange(1);
} else {
setOrganizations(data.items);
setTotal(total);
}
updatePageNumber();
setApiError(null);
setIsLoading(false);
} catch (err: any) {
setIsLoading(false);
if (err.kind !== ErrorKind.Unauthorized) {
setOrganizations([]);
setApiError('An error occurred getting your organizations, please try again later.');
} else {
props.onAuthError();
}
}
}
useEffect(() => {
fetchOrganizations();
}, []); /* eslint-disable-line react-hooks/exhaustive-deps */
useEffect(() => {
if (props.activePage && activePage !== parseInt(props.activePage)) {
fetchOrganizations();
}
}, [activePage]); /* eslint-disable-line react-hooks/exhaustive-deps */
return (
<main
role="main"
className="px-xs-0 px-sm-3 px-lg-0 d-flex flex-column flex-md-row justify-content-between my-md-4"
>
<div className="flex-grow-1 w-100">
<div>
<div className="d-flex flex-row align-items-center justify-content-between pb-2 border-bottom">
<div className={`h3 pb-0 ${styles.title}`}>Organizations</div>
<div>
<button
className={`btn btn-outline-secondary btn-sm text-uppercase ${styles.btnAction}`}
onClick={() => setModalStatus({ open: true })}
aria-label="Open modal"
>
<div className="d-flex flex-row align-items-center justify-content-center">
<MdAdd className="d-inline d-md-none" />
<MdAddCircle className="d-none d-md-inline me-2" />
<span className="d-none d-md-inline">Add</span>
</div>
</button>
</div>
</div>
{(isLoading || isUndefined(organizations)) && <Loading />}
{!isUndefined(organizations) && (
<>
{organizations.length === 0 ? (
<NoData issuesLinkVisible={!isNull(apiError)}>
{isNull(apiError) ? (
<>
<p className="h6 my-4 lh-base">Do you need to create a organization?</p>
<button
type="button"
className="btn btn-sm btn-outline-secondary"
onClick={() => setModalStatus({ open: true })}
aria-label="Open modal for adding first organization"
>
<div className="d-flex flex-row align-items-center text-uppercase">
<MdAddCircle className="me-2" />
<span>Add new organization</span>
</div>
</button>
</>
) : (
<>{apiError}</>
)}
</NoData>
) : (
<>
<div className="row mt-4 mt-md-5 gx-0 gx-xxl-4">
{organizations.map((org: Organization, index: number) => (
<OrganizationCard
key={`org_${org.name}_${index}`}
organization={org}
onAuthError={props.onAuthError}
onSuccess={fetchOrganizations}
/>
))}
</div>
{!isUndefined(total) && (
<div className="mx-auto">
<Pagination
limit={DEFAULT_LIMIT}
offset={offset}
total={total}
active={activePage}
className="my-5"
onChange={onPageNumberChange}
/>
</div>
)}
</>
)}
</>
)}
</div>
</div>
<OrganizationModal
{...modalStatus}
onSuccess={fetchOrganizations}
onAuthError={props.onAuthError}
onClose={() => setModalStatus({ open: false })}
/>
</main>
);
}
Example #6
Source File: index.tsx From hub with Apache License 2.0 | 4 votes |
RepositoriesSection = (props: Props) => {
const history = useHistory();
const { ctx, dispatch } = useContext(AppCtx);
const [isLoading, setIsLoading] = useState(false);
const [modalStatus, setModalStatus] = useState<ModalStatus>({
open: false,
});
const [openClaimRepo, setOpenClaimRepo] = useState<boolean>(false);
const [repositories, setRepositories] = useState<Repo[] | undefined>(undefined);
const [activeOrg, setActiveOrg] = useState<undefined | string>(ctx.prefs.controlPanel.selectedOrg);
const [apiError, setApiError] = useState<null | string>(null);
const [activePage, setActivePage] = useState<number>(props.activePage ? parseInt(props.activePage) : 1);
const calculateOffset = (pageNumber?: number): number => {
return DEFAULT_LIMIT * ((pageNumber || activePage) - 1);
};
const [offset, setOffset] = useState<number>(calculateOffset());
const [total, setTotal] = useState<number | undefined>(undefined);
const onPageNumberChange = (pageNumber: number): void => {
setOffset(calculateOffset(pageNumber));
setActivePage(pageNumber);
};
const updatePageNumber = () => {
history.replace({
search: `?page=${activePage}${props.repoName ? `&repo-name=${props.repoName}` : ''}${
props.visibleModal ? `&modal=${props.visibleModal}` : ''
}`,
});
};
async function fetchRepositories() {
try {
setIsLoading(true);
let filters: { [key: string]: string[] } = {};
if (activeOrg) {
filters.org = [activeOrg];
} else {
filters.user = [ctx.user!.alias];
}
let query: SearchQuery = {
offset: offset,
limit: DEFAULT_LIMIT,
filters: filters,
};
const data = await API.searchRepositories(query);
const total = parseInt(data.paginationTotalCount);
if (total > 0 && data.items.length === 0) {
onPageNumberChange(1);
} else {
const repos = data.items;
setRepositories(repos);
setTotal(total);
// Check if active repo logs modal is in the available repos
if (!isUndefined(props.repoName)) {
const activeRepo = repos.find((repo: Repo) => repo.name === props.repoName);
// Clean query string if repo is not available
if (isUndefined(activeRepo)) {
dispatch(unselectOrg());
history.replace({
search: `?page=${activePage}`,
});
}
} else {
updatePageNumber();
}
}
setApiError(null);
setIsLoading(false);
} catch (err: any) {
setIsLoading(false);
if (err.kind !== ErrorKind.Unauthorized) {
setApiError('An error occurred getting the repositories, please try again later.');
setRepositories([]);
} else {
props.onAuthError();
}
}
}
useEffect(() => {
if (isUndefined(props.activePage)) {
updatePageNumber();
}
}, []); /* eslint-disable-line react-hooks/exhaustive-deps */
useEffect(() => {
if (props.activePage && activePage !== parseInt(props.activePage)) {
fetchRepositories();
}
}, [activePage]); /* eslint-disable-line react-hooks/exhaustive-deps */
useEffect(() => {
if (!isUndefined(repositories)) {
if (activePage === 1) {
// fetchRepositories is forced when context changes
fetchRepositories();
} else {
// when current page is different to 1, to update page number fetchRepositories is called
onPageNumberChange(1);
}
}
}, [activeOrg]); /* eslint-disable-line react-hooks/exhaustive-deps */
useEffect(() => {
if (activeOrg !== ctx.prefs.controlPanel.selectedOrg) {
setActiveOrg(ctx.prefs.controlPanel.selectedOrg);
}
}, [ctx.prefs.controlPanel.selectedOrg]); /* eslint-disable-line react-hooks/exhaustive-deps */
useEffect(() => {
fetchRepositories();
}, []); /* eslint-disable-line react-hooks/exhaustive-deps */
return (
<main
role="main"
className="pe-xs-0 pe-sm-3 pe-md-0 d-flex flex-column flex-md-row justify-content-between my-md-4"
>
<div className="flex-grow-1 w-100">
<div>
<div className="d-flex flex-row align-items-center justify-content-between pb-2 border-bottom">
<div className={`h3 pb-0 ${styles.title}`}>Repositories</div>
<div>
<button
className={`btn btn-outline-secondary btn-sm text-uppercase me-0 me-md-2 ${styles.btnAction}`}
onClick={fetchRepositories}
aria-label="Refresh repositories list"
>
<div className="d-flex flex-row align-items-center justify-content-center">
<IoMdRefresh className="d-inline d-md-none" />
<IoMdRefreshCircle className="d-none d-md-inline me-2" />
<span className="d-none d-md-inline">Refresh</span>
</div>
</button>
<button
className={`btn btn-outline-secondary btn-sm text-uppercase me-0 me-md-2 ${styles.btnAction}`}
onClick={() => setOpenClaimRepo(true)}
aria-label="Open claim repository modal"
>
<div className="d-flex flex-row align-items-center justify-content-center">
<RiArrowLeftRightLine className="me-md-2" />
<span className="d-none d-md-inline">Claim ownership</span>
</div>
</button>
<ActionBtn
className={`btn btn-outline-secondary btn-sm text-uppercase ${styles.btnAction}`}
contentClassName="justify-content-center"
onClick={(e: ReactMouseEvent<HTMLButtonElement>) => {
e.preventDefault();
setModalStatus({ open: true });
}}
action={AuthorizerAction.AddOrganizationRepository}
label="Open add repository modal"
>
<>
<MdAdd className="d-inline d-md-none" />
<MdAddCircle className="d-none d-md-inline me-2" />
<span className="d-none d-md-inline">Add</span>
</>
</ActionBtn>
</div>
</div>
</div>
{modalStatus.open && (
<RepositoryModal
open={modalStatus.open}
repository={modalStatus.repository}
onSuccess={fetchRepositories}
onAuthError={props.onAuthError}
onClose={() => setModalStatus({ open: false })}
/>
)}
{openClaimRepo && (
<ClaimOwnershipRepositoryModal
open={openClaimRepo}
onAuthError={props.onAuthError}
onClose={() => setOpenClaimRepo(false)}
onSuccess={fetchRepositories}
/>
)}
{(isLoading || isUndefined(repositories)) && <Loading />}
<p className="mt-5">
If you want your repositories to be labeled as <span className="fw-bold">Verified Publisher</span>, you can
add a{' '}
<ExternalLink
href="https://github.com/artifacthub/hub/blob/master/docs/metadata/artifacthub-repo.yml"
className="text-reset"
label="Open documentation"
>
<u>metadata file</u>
</ExternalLink>{' '}
to each of them including the repository ID provided below. This label will let users know that you own or
have control over the repository. The repository metadata file must be located at the path used in the
repository URL.
</p>
{!isUndefined(repositories) && (
<>
{repositories.length === 0 ? (
<NoData issuesLinkVisible={!isNull(apiError)}>
{isNull(apiError) ? (
<>
<p className="h6 my-4">Add your first repository!</p>
<ActionBtn
className="btn btn-sm btn-outline-secondary"
onClick={(e: ReactMouseEvent<HTMLButtonElement>) => {
e.preventDefault();
setModalStatus({ open: true });
}}
action={AuthorizerAction.AddOrganizationRepository}
label="Open add first repository modal"
>
<div className="d-flex flex-row align-items-center text-uppercase">
<MdAddCircle className="me-2" />
<span>Add repository</span>
</div>
</ActionBtn>
</>
) : (
<>{apiError}</>
)}
</NoData>
) : (
<>
<div className="row mt-3 mt-md-4 gx-0 gx-xxl-4" data-testid="repoList">
{repositories.map((repo: Repo) => (
<RepositoryCard
key={`repo_${repo.name}`}
repository={repo}
visibleModal={
// Legacy - old tracking errors email were not passing modal param
!isUndefined(props.repoName) && repo.name === props.repoName
? props.visibleModal || 'tracking'
: undefined
}
setModalStatus={setModalStatus}
onSuccess={fetchRepositories}
onAuthError={props.onAuthError}
/>
))}
</div>
{!isUndefined(total) && (
<Pagination
limit={DEFAULT_LIMIT}
offset={offset}
total={total}
active={activePage}
className="my-5"
onChange={onPageNumberChange}
/>
)}
</>
)}
</>
)}
</div>
</main>
);
}
Example #7
Source File: index.tsx From hub with Apache License 2.0 | 4 votes |
APIKeysSection = (props: Props) => {
const history = useHistory();
const [isLoading, setIsLoading] = useState(false);
const [apiKeysList, setApiKeysList] = useState<APIKey[] | undefined>(undefined);
const [apiError, setApiError] = useState<string | JSX.Element | null>(null);
const [modalStatus, setModalStatus] = useState<ModalStatus>({
open: false,
});
const [activePage, setActivePage] = useState<number>(props.activePage ? parseInt(props.activePage) : 1);
const calculateOffset = (pageNumber?: number): number => {
return DEFAULT_LIMIT * ((pageNumber || activePage) - 1);
};
const [offset, setOffset] = useState<number>(calculateOffset());
const [total, setTotal] = useState<number | undefined>(undefined);
const onPageNumberChange = (pageNumber: number): void => {
setOffset(calculateOffset(pageNumber));
setActivePage(pageNumber);
};
const updatePageNumber = () => {
history.replace({
search: `?page=${activePage}`,
});
};
async function getAPIKeys() {
try {
setIsLoading(true);
const data = await API.getAPIKeys({
limit: DEFAULT_LIMIT,
offset: offset,
});
const total = parseInt(data.paginationTotalCount);
if (total > 0 && data.items.length === 0) {
onPageNumberChange(1);
} else {
setApiKeysList(data.items);
setTotal(total);
}
updatePageNumber();
setApiError(null);
setIsLoading(false);
} catch (err: any) {
setIsLoading(false);
if (err.kind !== ErrorKind.Unauthorized) {
setApiError('An error occurred getting your API keys, please try again later.');
setApiKeysList([]);
} else {
props.onAuthError();
}
}
}
useEffect(() => {
getAPIKeys();
}, []); /* eslint-disable-line react-hooks/exhaustive-deps */
useEffect(() => {
if (props.activePage && activePage !== parseInt(props.activePage)) {
getAPIKeys();
}
}, [activePage]); /* eslint-disable-line react-hooks/exhaustive-deps */
return (
<div className="d-flex flex-column flex-grow-1">
{(isUndefined(apiKeysList) || isLoading) && <Loading />}
<main role="main" className="p-0">
<div className="flex-grow-1">
<div className="d-flex flex-row align-items-center justify-content-between pb-2 border-bottom">
<div className={`h3 pb-0 ${styles.title}`}>API keys</div>
<div>
<button
className={`btn btn-outline-secondary btn-sm text-uppercase ${styles.btnAction}`}
onClick={() => setModalStatus({ open: true })}
aria-label="Open modal to add API key"
>
<div className="d-flex flex-row align-items-center justify-content-center">
<MdAdd className="d-inline d-md-none" />
<MdAddCircle className="d-none d-md-inline me-2" />
<span className="d-none d-md-inline">Add</span>
</div>
</button>
</div>
</div>
<div className="mt-4">
{!isUndefined(apiKeysList) && (
<div className="mt-4 mt-md-5">
{apiKeysList.length === 0 ? (
<NoData issuesLinkVisible={!isNull(apiError)}>
{isNull(apiError) ? (
<>
<p className="h6 my-4">Add your first API key!</p>
<button
type="button"
className="btn btn-sm btn-outline-secondary"
onClick={() => setModalStatus({ open: true })}
aria-label="Open API key modal to add the first one"
>
<div className="d-flex flex-row align-items-center text-uppercase">
<MdAddCircle className="me-2" />
<span>Add API key</span>
</div>
</button>
</>
) : (
<>{apiError}</>
)}
</NoData>
) : (
<>
<div className="row mt-4 mt-md-5 gx-0 gx-xxl-4" data-testid="apiKeysList">
{apiKeysList.map((apiKey: APIKey) => (
<APIKeyCard
key={apiKey.apiKeyId!}
apiKey={apiKey}
setModalStatus={setModalStatus}
onSuccess={getAPIKeys}
onAuthError={props.onAuthError}
/>
))}
</div>
{!isUndefined(total) && (
<div className="mx-auto">
<Pagination
limit={DEFAULT_LIMIT}
offset={offset}
total={total}
active={activePage}
className="my-5"
onChange={onPageNumberChange}
/>
</div>
)}
</>
)}
</div>
)}
</div>
<APIKeyModal
{...modalStatus}
onSuccess={getAPIKeys}
onClose={() => setModalStatus({ open: false })}
onAuthError={props.onAuthError}
/>
</div>
</main>
</div>
);
}
Example #8
Source File: index.tsx From hub with Apache License 2.0 | 4 votes |
PackagesSection = (props: Props) => {
const title = useRef<HTMLDivElement>(null);
const [isLoading, setIsLoading] = useState(false);
const [packages, setPackages] = useState<Package[] | undefined>(undefined);
const [modalStatus, setModalStatus] = useState<boolean>(false);
const [activePage, setActivePage] = useState<number>(1);
const calculateOffset = (pageNumber?: number): number => {
return DEFAULT_LIMIT * ((pageNumber || activePage) - 1);
};
const [offset, setOffset] = useState<number>(calculateOffset());
const [total, setTotal] = useState<number | undefined>(undefined);
const onPageNumberChange = (pageNumber: number): void => {
setOffset(calculateOffset(pageNumber));
setActivePage(pageNumber);
if (title && title.current) {
title.current.scrollIntoView({ block: 'start', inline: 'nearest', behavior: 'smooth' });
}
};
const getNotificationTitle = (kind: EventKind): string => {
let title = '';
const notif = PACKAGE_SUBSCRIPTIONS_LIST.find((subs: SubscriptionItem) => subs.kind === kind);
if (notif) {
title = notif.title.toLowerCase();
}
return title;
};
const updateSubscriptionsPackagesOptimistically = (kind: EventKind, isActive: boolean, packageId: string) => {
const packageToUpdate = packages ? packages.find((item: Package) => item.packageId === packageId) : undefined;
if (packageToUpdate && packageToUpdate.eventKinds) {
const newPackages = packages!.filter((item: Package) => item.packageId !== packageId);
if (isActive) {
packageToUpdate.eventKinds = packageToUpdate.eventKinds.filter((notifKind: number) => notifKind !== kind);
} else {
packageToUpdate.eventKinds.push(kind);
}
if (packageToUpdate.eventKinds.length > 0) {
newPackages.push(packageToUpdate);
}
setPackages(newPackages);
}
};
async function getSubscriptions() {
try {
setIsLoading(true);
const data = await API.getUserSubscriptions({
limit: DEFAULT_LIMIT,
offset: offset,
});
const total = parseInt(data.paginationTotalCount);
if (total > 0 && data.items.length === 0) {
onPageNumberChange(1);
} else {
setPackages(data.items);
setTotal(total);
}
setIsLoading(false);
} catch (err: any) {
setIsLoading(false);
if (err.kind !== ErrorKind.Unauthorized) {
alertDispatcher.postAlert({
type: 'danger',
message: 'An error occurred getting your subscriptions, please try again later.',
});
setPackages([]);
} else {
props.onAuthError();
}
}
}
async function changeSubscription(packageId: string, kind: EventKind, isActive: boolean, packageName: string) {
updateSubscriptionsPackagesOptimistically(kind, isActive, packageId);
try {
if (isActive) {
await API.deleteSubscription(packageId, kind);
} else {
await API.addSubscription(packageId, kind);
}
getSubscriptions();
} catch (err: any) {
if (err.kind !== ErrorKind.Unauthorized) {
alertDispatcher.postAlert({
type: 'danger',
message: `An error occurred ${isActive ? 'unsubscribing from' : 'subscribing to'} ${getNotificationTitle(
kind
)} notification for ${packageName} package, please try again later.`,
});
getSubscriptions(); // Get subscriptions if changeSubscription fails
} else {
props.onAuthError();
}
}
}
useEffect(() => {
getSubscriptions();
}, [activePage]); /* eslint-disable-line react-hooks/exhaustive-deps */
return (
<>
{(isUndefined(packages) || isLoading) && <Loading />}
<div className="d-flex flex-row align-items-start justify-content-between pb-2 mt-5">
<div ref={title} className={`h4 mb-0 ${styles.title}`}>
Packages
</div>
<div>
<button
className={`btn btn-outline-secondary btn-sm text-uppercase ${styles.btnAction}`}
onClick={() => setModalStatus(true)}
aria-label="Open subscription modal"
>
<div className="d-flex flex-row align-items-center justify-content-center">
<MdAdd className="d-inline d-md-none" />
<MdAddCircle className="d-none d-md-inline me-2" />
<span className="d-none d-md-inline">Add</span>
</div>
</button>
</div>
</div>
<div className="mx-auto mt-3 mt-md-3">
<p className="m-0">
You will receive an email notification when an event that matches any of the subscriptions in the list is
fired.
</p>
<div className="mt-4 mt-md-5">
{!isUndefined(packages) && packages.length > 0 && (
<>
<div className="d-none d-sm-inline" data-testid="packagesList">
<div className="row">
<div className="col-12 col-xxxl-10">
<table className={`table table-bordered table-hover ${styles.table}`}>
<thead>
<tr className={styles.tableTitle}>
<th
scope="col"
className={`align-middle text-center d-none d-sm-table-cell ${styles.fitCell}`}
>
Kind
</th>
<th scope="col" className="align-middle w-50">
Package
</th>
<th scope="col" className="align-middle w-50">
Publisher
</th>
{PACKAGE_SUBSCRIPTIONS_LIST.map((subs: SubscriptionItem) => (
<th
scope="col"
className={`align-middle text-nowrap ${styles.fitCell}`}
key={`title_${subs.kind}`}
>
<div className="d-flex flex-row align-items-center justify-content-center">
{subs.icon}
<span className="d-none d-lg-inline ms-2">{subs.title}</span>
</div>
</th>
))}
</tr>
</thead>
<tbody className={styles.body}>
{packages.map((item: Package) => (
<tr key={`subs_${item.packageId}`} data-testid="subsTableCell">
<td className="align-middle text-center d-none d-sm-table-cell">
<RepositoryIcon kind={item.repository.kind} className={`h-auto ${styles.icon}`} />
</td>
<td className="align-middle">
<div className="d-flex flex-row align-items-center">
<div
className={`d-flex align-items-center justify-content-center overflow-hidden p-1 rounded-circle border bg-white ${styles.imageWrapper} imageWrapper`}
>
<Image
alt={item.displayName || item.name}
imageId={item.logoImageId}
className={`fs-4 ${styles.image}`}
kind={item.repository.kind}
/>
</div>
<Link
data-testid="packageLink"
className="ms-2 text-dark"
to={{
pathname: buildPackageURL(item.normalizedName, item.repository, item.version!),
}}
aria-label={`Open ${item.displayName || item.name} package`}
>
{item.displayName || item.name}
</Link>
</div>
</td>
<td className="align-middle position-relative">
{item.repository.userAlias ? (
<Link
data-testid="userLink"
className="text-dark"
to={{
pathname: '/packages/search',
search: prepareQueryString({
pageNumber: 1,
filters: {
user: [item.repository.userAlias!],
},
}),
}}
aria-label={`Filter by ${item.repository.userAlias} user`}
>
{item.repository.userAlias}
</Link>
) : (
<Link
data-testid="orgLink"
className="text-dark"
to={{
pathname: '/packages/search',
search: prepareQueryString({
pageNumber: 1,
filters: {
org: [item.repository.organizationName!],
},
}),
}}
aria-label={`Filter by ${
item.repository.organizationDisplayName || item.repository.organizationName
} organization`}
>
{item.repository.organizationDisplayName || item.repository.organizationName}
</Link>
)}
<small className="ms-2">
(<span className={`text-uppercase text-muted ${styles.legend}`}>Repo: </span>
<Link
data-testid="repoLink"
className="text-dark"
to={{
pathname: '/packages/search',
search: prepareQueryString({
pageNumber: 1,
filters: {
repo: [item.repository.name],
},
}),
}}
aria-label={`Filter by ${
item.repository.displayName || item.repository.name
} repository`}
>
{item.repository.displayName || item.repository.name}
</Link>
)
</small>
</td>
{PACKAGE_SUBSCRIPTIONS_LIST.map((subs: SubscriptionItem) => {
const isActive = !isUndefined(item.eventKinds) && item.eventKinds.includes(subs.kind);
const id = `subs_${item.packageId}_${subs.kind}`;
return (
<td className="align-middle text-center" key={`td_${item.normalizedName}_${subs.kind}`}>
<div className="text-center">
<div className="form-switch">
<input
data-testid={`${item.name}_${subs.name}_input`}
id={id}
type="checkbox"
role="switch"
className={`form-check-input ${styles.checkbox}`}
disabled={!subs.enabled}
onChange={() =>
changeSubscription(
item.packageId,
subs.kind,
isActive,
item.displayName || item.name
)
}
checked={isActive}
/>
</div>
</div>
</td>
);
})}
</tr>
))}
{!isUndefined(total) && total > DEFAULT_LIMIT && (
<tr className={styles.paginationCell}>
<td className="align-middle text-center" colSpan={5}>
<Pagination
limit={DEFAULT_LIMIT}
offset={offset}
total={total}
active={activePage}
className="my-3"
onChange={onPageNumberChange}
/>
</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
</div>
<div className="d-inline d-sm-none">
{packages.map((item: Package) => (
<PackageCard key={item.packageId} package={item} changeSubscription={changeSubscription} />
))}
{!isUndefined(total) && (
<div className="mx-auto">
<Pagination
limit={DEFAULT_LIMIT}
offset={offset}
total={total}
active={activePage}
className="my-5"
onChange={onPageNumberChange}
/>
</div>
)}
</div>
</>
)}
</div>
</div>
<SubscriptionModal
open={modalStatus}
subscriptions={packages}
onSuccess={getSubscriptions}
onClose={() => setModalStatus(false)}
getNotificationTitle={getNotificationTitle}
/>
</>
);
}
Example #9
Source File: index.tsx From hub with Apache License 2.0 | 4 votes |
WebhooksSection = (props: Props) => {
const history = useHistory();
const { ctx } = useContext(AppCtx);
const [isGettingWebhooks, setIsGettingWebhooks] = useState(false);
const [webhooks, setWebhooks] = useState<Webhook[] | undefined>(undefined);
const [apiError, setApiError] = useState<null | string>(null);
const [visibleForm, setVisibleForm] = useState<VisibleForm | null>(null);
const [activePage, setActivePage] = useState<number>(props.activePage ? parseInt(props.activePage) : 1);
const calculateOffset = (pageNumber?: number): number => {
return DEFAULT_LIMIT * ((pageNumber || activePage) - 1);
};
const [offset, setOffset] = useState<number>(calculateOffset());
const [total, setTotal] = useState<number | undefined>(undefined);
const onPageNumberChange = (pageNumber: number): void => {
setOffset(calculateOffset(pageNumber));
setActivePage(pageNumber);
};
const updatePageNumber = () => {
history.replace({
search: `?page=${activePage}`,
});
};
async function fetchWebhooks() {
try {
setIsGettingWebhooks(true);
const data = await API.getWebhooks(
{
limit: DEFAULT_LIMIT,
offset: offset,
},
ctx.prefs.controlPanel.selectedOrg
);
const total = parseInt(data.paginationTotalCount);
if (total > 0 && data.items.length === 0) {
onPageNumberChange(1);
} else {
setWebhooks(data.items);
setTotal(total);
}
updatePageNumber();
setApiError(null);
setIsGettingWebhooks(false);
} catch (err: any) {
setIsGettingWebhooks(false);
if (err.kind !== ErrorKind.Unauthorized) {
setWebhooks([]);
setApiError('An error occurred getting webhooks, please try again later.');
} else {
props.onAuthError();
}
}
}
useEffect(() => {
fetchWebhooks();
}, []); /* eslint-disable-line react-hooks/exhaustive-deps */
useEffect(() => {
if (props.activePage && activePage !== parseInt(props.activePage)) {
fetchWebhooks();
}
}, [activePage]); /* eslint-disable-line react-hooks/exhaustive-deps */
return (
<div className="d-flex flex-column flex-grow-1">
<main role="main" className="p-0">
<div className="flex-grow-1">
{!isNull(visibleForm) ? (
<WebhookForm
onClose={() => setVisibleForm(null)}
onSuccess={fetchWebhooks}
webhook={visibleForm.webhook}
{...props}
/>
) : (
<>
<div>
<div className="d-flex flex-row align-items-center justify-content-between pb-2 border-bottom">
<div className={`h3 pb-0 ${styles.title}`}>Webhooks</div>
<div>
<button
className={`btn btn-outline-secondary btn-sm text-uppercase ${styles.btnAction}`}
onClick={() => setVisibleForm({ visible: true })}
aria-label="Open webhook form"
>
<div className="d-flex flex-row align-items-center justify-content-center">
<MdAdd className="d-inline d-md-none" />
<MdAddCircle className="d-none d-md-inline me-2" />
<span className="d-none d-md-inline">Add</span>
</div>
</button>
</div>
</div>
</div>
{(isGettingWebhooks || isUndefined(webhooks)) && <Loading />}
<div className="mt-4 mt-md-5">
<p className="m-0">Webhooks notify external services when certain events happen.</p>
{!isUndefined(webhooks) && (
<>
{webhooks.length === 0 ? (
<NoData issuesLinkVisible={!isNull(apiError)}>
{isNull(apiError) ? (
<>
<p className="h6 my-4 lh-base">
You have not created any webhook yet. You can create your first one by clicking on the
button below.
</p>
<button
type="button"
className="btn btn-sm btn-outline-secondary"
onClick={() => setVisibleForm({ visible: true })}
aria-label="Open form for creating your first webhook"
>
<div className="d-flex flex-row align-items-center text-uppercase">
<MdAddCircle className="me-2" />
<span>Add webhook</span>
</div>
</button>
</>
) : (
<>{apiError}</>
)}
</NoData>
) : (
<>
<div className="row mt-3 mt-md-4 gx-0 gx-xxl-4">
{webhooks.map((webhook: Webhook) => (
<WebhookCard
key={`webhook_${webhook.webhookId}`}
webhook={webhook}
onEdition={() => setVisibleForm({ visible: true, webhook: webhook })}
onAuthError={props.onAuthError}
onDeletion={fetchWebhooks}
/>
))}
</div>
{!isUndefined(total) && (
<div className="mx-auto">
<Pagination
limit={DEFAULT_LIMIT}
offset={offset}
total={total}
active={activePage}
className="my-5"
onChange={onPageNumberChange}
/>
</div>
)}
</>
)}
</>
)}
</div>
</>
)}
</div>
</main>
</div>
);
}