-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add pagination to clients, schemas and identity lists in ui. Ad…
…d identity creation form WD-10253 Signed-off-by: David Edler <[email protected]>
- Loading branch information
Showing
19 changed files
with
414 additions
and
58 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import React, { FC } from "react"; | ||
|
||
const IconLeft: FC = () => { | ||
return <i className="p-icon--chevron-down" style={{ rotate: "90deg" }} />; | ||
}; | ||
|
||
export default IconLeft; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import React, { FC } from "react"; | ||
|
||
const IconRight: FC = () => { | ||
return <i className="p-icon--chevron-down" style={{ rotate: "270deg" }} />; | ||
}; | ||
|
||
export default IconRight; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import React, { FC } from "react"; | ||
import { PaginatedResponse } from "types/api"; | ||
import { Button } from "@canonical/react-components"; | ||
import { usePagination } from "util/usePagination"; | ||
import IconLeft from "components/IconLeft"; | ||
import IconRight from "components/IconRight"; | ||
|
||
interface Props { | ||
response?: PaginatedResponse<unknown[]>; | ||
} | ||
|
||
const Pagination: FC<Props> = ({ response }) => { | ||
const { page, setPage } = usePagination(); | ||
const showFirstLink = page !== ""; | ||
const isMissingNext = !response || !response._meta.next; | ||
const isEmptyPage = response?.data.length === 0; | ||
|
||
if (!showFirstLink && (isMissingNext || isEmptyPage)) { | ||
return null; | ||
} | ||
|
||
const first = response?._meta.first ?? ""; | ||
const last = response?._meta.last ?? ""; | ||
const next = response?._meta.next ?? ""; | ||
const prev = response?._meta.prev ?? ""; | ||
|
||
return ( | ||
<> | ||
{showFirstLink && ( | ||
<Button onClick={() => setPage(first)} title="First page"> | ||
<IconLeft /> | ||
<IconLeft /> | ||
</Button> | ||
)} | ||
{prev && ( | ||
<Button onClick={() => setPage(prev)} title="Previous page"> | ||
<IconLeft /> | ||
</Button> | ||
)} | ||
{next && ( | ||
<Button onClick={() => setPage(next)} title="Next page"> | ||
<IconRight /> | ||
</Button> | ||
)} | ||
{last && ( | ||
<Button onClick={() => setPage(last)} title="Last page"> | ||
<IconRight /> | ||
<IconRight /> | ||
</Button> | ||
)} | ||
</> | ||
); | ||
}; | ||
|
||
export default Pagination; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { FC, useState } from "react"; | ||
import { useNavigate } from "react-router-dom"; | ||
import { queryKeys } from "util/queryKeys"; | ||
import { useQueryClient } from "@tanstack/react-query"; | ||
import { ConfirmationButton, useNotify } from "@canonical/react-components"; | ||
import { Identity } from "types/identity"; | ||
import { deleteIdentity } from "api/identities"; | ||
|
||
interface Props { | ||
identity: Identity; | ||
} | ||
|
||
const DeleteIdentityBtn: FC<Props> = ({ identity }) => { | ||
const notify = useNotify(); | ||
const queryClient = useQueryClient(); | ||
const [isLoading, setLoading] = useState(false); | ||
const navigate = useNavigate(); | ||
|
||
const handleDelete = () => { | ||
setLoading(true); | ||
deleteIdentity(identity.id) | ||
.then(() => { | ||
navigate( | ||
"/identity", | ||
notify.queue( | ||
notify.success(`Identity ${identity.traits?.email} deleted.`), | ||
), | ||
); | ||
}) | ||
.catch((e) => { | ||
notify.failure("Identity deletion failed", e); | ||
}) | ||
.finally(() => { | ||
setLoading(false); | ||
void queryClient.invalidateQueries({ | ||
queryKey: [queryKeys.identities], | ||
}); | ||
}); | ||
}; | ||
|
||
return ( | ||
<ConfirmationButton | ||
className="u-no-margin--bottom" | ||
loading={isLoading} | ||
confirmationModalProps={{ | ||
title: "Confirm delete", | ||
children: ( | ||
<p> | ||
This will permanently delete identity <b>{identity.traits?.email}</b>. | ||
</p> | ||
), | ||
confirmButtonLabel: "Delete identity", | ||
onConfirm: handleDelete, | ||
}} | ||
title="Confirm delete" | ||
> | ||
Delete | ||
</ConfirmationButton> | ||
); | ||
}; | ||
|
||
export default DeleteIdentityBtn; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import React, { FC } from "react"; | ||
import { | ||
ActionButton, | ||
Button, | ||
Col, | ||
Row, | ||
useNotify, | ||
} from "@canonical/react-components"; | ||
import { useFormik } from "formik"; | ||
import * as Yup from "yup"; | ||
import { queryKeys } from "util/queryKeys"; | ||
import { useQueryClient } from "@tanstack/react-query"; | ||
import { useNavigate } from "react-router-dom"; | ||
import IdentityForm, { IdentityFormTypes } from "pages/identities/IdentityForm"; | ||
import { createIdentity } from "api/identities"; | ||
import SidePanel from "components/SidePanel"; | ||
import ScrollableContainer from "components/ScrollableContainer"; | ||
|
||
const IdentityCreate: FC = () => { | ||
const navigate = useNavigate(); | ||
const notify = useNotify(); | ||
const queryClient = useQueryClient(); | ||
|
||
const IdentityCreateSchema = Yup.object().shape({ | ||
email: Yup.string().required("This field is required"), | ||
schemaId: Yup.string().required("This field is required"), | ||
}); | ||
|
||
const formik = useFormik<IdentityFormTypes>({ | ||
initialValues: { | ||
email: "", | ||
schemaId: "", | ||
}, | ||
validationSchema: IdentityCreateSchema, | ||
validateOnMount: true, | ||
onSubmit: (values) => { | ||
const identity = { | ||
schema_id: values.schemaId, | ||
traits: { email: values.email }, | ||
}; | ||
createIdentity(JSON.stringify(identity)) | ||
.then(() => { | ||
void queryClient.invalidateQueries({ | ||
queryKey: [queryKeys.identities], | ||
}); | ||
const msg = `Identity created.`; | ||
navigate("/identity", notify.queue(notify.success(msg))); | ||
}) | ||
.catch((e) => { | ||
formik.setSubmitting(false); | ||
notify.failure("Identity creation failed", e); | ||
}); | ||
}, | ||
}); | ||
|
||
const submitForm = () => { | ||
void formik.submitForm(); | ||
}; | ||
|
||
return ( | ||
<SidePanel hasError={false} loading={false} className="p-panel"> | ||
<ScrollableContainer dependencies={[]} belowId="panel-footer"> | ||
<SidePanel.Header> | ||
<SidePanel.HeaderTitle>Add identity</SidePanel.HeaderTitle> | ||
</SidePanel.Header> | ||
<SidePanel.Content> | ||
<Row> | ||
<IdentityForm formik={formik} /> | ||
</Row> | ||
</SidePanel.Content> | ||
</ScrollableContainer> | ||
<div id="panel-footer"> | ||
<SidePanel.Footer> | ||
<Row className="u-align-text--right"> | ||
<Col size={12}> | ||
<Button appearance="base" onClick={() => navigate("/identity")}> | ||
Cancel | ||
</Button> | ||
<ActionButton | ||
appearance="positive" | ||
loading={formik.isSubmitting} | ||
disabled={!formik.isValid} | ||
onClick={submitForm} | ||
> | ||
Save | ||
</ActionButton> | ||
</Col> | ||
</Row> | ||
</SidePanel.Footer> | ||
</div> | ||
</SidePanel> | ||
); | ||
}; | ||
|
||
export default IdentityCreate; |
Oops, something went wrong.