Fixed the types of Andreas original (#33019)
* Fixed the types of Andreas original in favour of: #32913 fixes: #21261 Co-authored-by: Andreas Kozadinos <koza-sparrow@hotmail.com> Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * better types Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> --------- Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> Co-authored-by: Andreas Kozadinos <koza-sparrow@hotmail.com>
This commit is contained in:
parent
d01f531b82
commit
75c548f862
3 changed files with 211 additions and 32 deletions
|
@ -1,34 +1,49 @@
|
|||
import { useEnvironment } from "@keycloak/keycloak-ui-shared";
|
||||
import { DataList, Stack, StackItem, Title } from "@patternfly/react-core";
|
||||
import { useMemo, useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getLinkedAccounts } from "../api/methods";
|
||||
import { getLinkedAccounts, LinkedAccountQueryParams } from "../api/methods";
|
||||
import { LinkedAccountRepresentation } from "../api/representations";
|
||||
import { EmptyRow } from "../components/datalist/EmptyRow";
|
||||
import { Page } from "../components/page/Page";
|
||||
import { usePromise } from "../utils/usePromise";
|
||||
import { AccountRow } from "./AccountRow";
|
||||
import { useEnvironment } from "@keycloak/keycloak-ui-shared";
|
||||
import { LinkedAccountsToolbar } from "./LinkedAccountsToolbar";
|
||||
|
||||
export const LinkedAccounts = () => {
|
||||
const { t } = useTranslation();
|
||||
const context = useEnvironment();
|
||||
const [accounts, setAccounts] = useState<LinkedAccountRepresentation[]>([]);
|
||||
const [linkedAccounts, setLinkedAccounts] = useState<
|
||||
LinkedAccountRepresentation[]
|
||||
>([]);
|
||||
const [unlinkedAccounts, setUninkedAccounts] = useState<
|
||||
LinkedAccountRepresentation[]
|
||||
>([]);
|
||||
|
||||
const [paramsUnlinked, setParamsUnlinked] =
|
||||
useState<LinkedAccountQueryParams>({
|
||||
first: 0,
|
||||
max: 6,
|
||||
linked: false,
|
||||
});
|
||||
const [paramsLinked, setParamsLinked] = useState<LinkedAccountQueryParams>({
|
||||
first: 0,
|
||||
max: 6,
|
||||
linked: true,
|
||||
});
|
||||
const [key, setKey] = useState(1);
|
||||
const refresh = () => setKey(key + 1);
|
||||
|
||||
usePromise((signal) => getLinkedAccounts({ signal, context }), setAccounts, [
|
||||
key,
|
||||
]);
|
||||
|
||||
const linkedAccounts = useMemo(
|
||||
() => accounts.filter((account) => account.connected),
|
||||
[accounts],
|
||||
usePromise(
|
||||
(signal) => getLinkedAccounts({ signal, context }, paramsUnlinked),
|
||||
setUninkedAccounts,
|
||||
[paramsUnlinked, key],
|
||||
);
|
||||
|
||||
const unLinkedAccounts = useMemo(
|
||||
() => accounts.filter((account) => !account.connected),
|
||||
[accounts],
|
||||
usePromise(
|
||||
(signal) => getLinkedAccounts({ signal, context }, paramsLinked),
|
||||
setLinkedAccounts,
|
||||
[paramsLinked, key],
|
||||
);
|
||||
|
||||
return (
|
||||
|
@ -41,16 +56,47 @@ export const LinkedAccounts = () => {
|
|||
<Title headingLevel="h2" className="pf-v5-u-mb-lg" size="xl">
|
||||
{t("linkedLoginProviders")}
|
||||
</Title>
|
||||
<LinkedAccountsToolbar
|
||||
onFilter={(search) =>
|
||||
setParamsLinked({ ...paramsLinked, first: 0, search })
|
||||
}
|
||||
count={linkedAccounts.length}
|
||||
first={paramsLinked["first"]}
|
||||
max={paramsLinked["max"]}
|
||||
onNextClick={() => {
|
||||
setParamsLinked({
|
||||
...paramsLinked,
|
||||
first: paramsLinked.first + paramsLinked.max - 1,
|
||||
});
|
||||
}}
|
||||
onPreviousClick={() =>
|
||||
setParamsLinked({
|
||||
...paramsLinked,
|
||||
first: paramsLinked.first - paramsLinked.max + 1,
|
||||
})
|
||||
}
|
||||
onPerPageSelect={(first, max) =>
|
||||
setParamsLinked({
|
||||
...paramsLinked,
|
||||
first,
|
||||
max,
|
||||
})
|
||||
}
|
||||
hasNext={linkedAccounts.length > paramsLinked.max - 1}
|
||||
/>
|
||||
<DataList id="linked-idps" aria-label={t("linkedLoginProviders")}>
|
||||
{linkedAccounts.length > 0 ? (
|
||||
linkedAccounts.map((account) => (
|
||||
<AccountRow
|
||||
key={account.providerName}
|
||||
account={account}
|
||||
isLinked
|
||||
refresh={refresh}
|
||||
/>
|
||||
))
|
||||
linkedAccounts.map(
|
||||
(account, index) =>
|
||||
index !== paramsLinked.max - 1 && (
|
||||
<AccountRow
|
||||
key={account.providerName}
|
||||
account={account}
|
||||
isLinked
|
||||
refresh={refresh}
|
||||
/>
|
||||
),
|
||||
)
|
||||
) : (
|
||||
<EmptyRow message={t("linkedEmpty")} />
|
||||
)}
|
||||
|
@ -64,15 +110,46 @@ export const LinkedAccounts = () => {
|
|||
>
|
||||
{t("unlinkedLoginProviders")}
|
||||
</Title>
|
||||
<LinkedAccountsToolbar
|
||||
onFilter={(search) =>
|
||||
setParamsUnlinked({ ...paramsUnlinked, first: 0, search })
|
||||
}
|
||||
count={unlinkedAccounts.length}
|
||||
first={paramsUnlinked["first"]}
|
||||
max={paramsUnlinked["max"]}
|
||||
onNextClick={() => {
|
||||
setParamsUnlinked({
|
||||
...paramsUnlinked,
|
||||
first: paramsUnlinked.first + paramsUnlinked.max - 1,
|
||||
});
|
||||
}}
|
||||
onPreviousClick={() =>
|
||||
setParamsUnlinked({
|
||||
...paramsUnlinked,
|
||||
first: paramsUnlinked.first - paramsUnlinked.max + 1,
|
||||
})
|
||||
}
|
||||
onPerPageSelect={(first, max) =>
|
||||
setParamsUnlinked({
|
||||
...paramsUnlinked,
|
||||
first,
|
||||
max,
|
||||
})
|
||||
}
|
||||
hasNext={unlinkedAccounts.length > paramsUnlinked.max - 1}
|
||||
/>
|
||||
<DataList id="unlinked-idps" aria-label={t("unlinkedLoginProviders")}>
|
||||
{unLinkedAccounts.length > 0 ? (
|
||||
unLinkedAccounts.map((account) => (
|
||||
<AccountRow
|
||||
key={account.providerName}
|
||||
account={account}
|
||||
refresh={refresh}
|
||||
/>
|
||||
))
|
||||
{unlinkedAccounts.length > 0 ? (
|
||||
unlinkedAccounts.map(
|
||||
(account, index) =>
|
||||
index !== paramsUnlinked.max - 1 && (
|
||||
<AccountRow
|
||||
key={account.providerName}
|
||||
account={account}
|
||||
refresh={refresh}
|
||||
/>
|
||||
),
|
||||
)
|
||||
) : (
|
||||
<EmptyRow message={t("unlinkedEmpty")} />
|
||||
)}
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
Pagination,
|
||||
SearchInput,
|
||||
PaginationToggleTemplateProps,
|
||||
Toolbar,
|
||||
ToolbarContent,
|
||||
ToolbarItem,
|
||||
} from "@patternfly/react-core";
|
||||
|
||||
type LinkedAccountsToolbarProps = {
|
||||
onFilter: (nameFilter: string) => void;
|
||||
count: number;
|
||||
first: number;
|
||||
max: number;
|
||||
onNextClick: (page: number) => void;
|
||||
onPreviousClick: (page: number) => void;
|
||||
onPerPageSelect: (max: number, first: number) => void;
|
||||
hasNext: boolean;
|
||||
};
|
||||
|
||||
export const LinkedAccountsToolbar = ({
|
||||
count,
|
||||
first,
|
||||
max,
|
||||
onNextClick,
|
||||
onPreviousClick,
|
||||
onPerPageSelect,
|
||||
onFilter,
|
||||
hasNext,
|
||||
}: LinkedAccountsToolbarProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [nameFilter, setNameFilter] = useState("");
|
||||
|
||||
const page = Math.round(first / max) + 1;
|
||||
return (
|
||||
<Toolbar>
|
||||
<ToolbarContent>
|
||||
<ToolbarItem>
|
||||
<SearchInput
|
||||
placeholder={t("filterByName")}
|
||||
aria-label={t("filterByName")}
|
||||
value={nameFilter}
|
||||
onChange={(_, value) => {
|
||||
setNameFilter(value);
|
||||
}}
|
||||
onSearch={() => onFilter(nameFilter)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
onFilter(nameFilter);
|
||||
}
|
||||
}}
|
||||
onClear={() => {
|
||||
setNameFilter("");
|
||||
onFilter("");
|
||||
}}
|
||||
/>
|
||||
</ToolbarItem>
|
||||
<ToolbarItem variant="pagination">
|
||||
<Pagination
|
||||
isCompact
|
||||
perPageOptions={[
|
||||
{ title: "5", value: 6 },
|
||||
{ title: "10", value: 11 },
|
||||
{ title: "20", value: 21 },
|
||||
]}
|
||||
toggleTemplate={({
|
||||
firstIndex,
|
||||
lastIndex,
|
||||
}: PaginationToggleTemplateProps) => (
|
||||
<b>
|
||||
{firstIndex && firstIndex > 1 ? firstIndex - 1 : firstIndex} -{" "}
|
||||
{lastIndex && lastIndex > 1 ? lastIndex - 1 : lastIndex}
|
||||
</b>
|
||||
)}
|
||||
itemCount={count + (page - 1) * max + (hasNext ? 1 : 0)}
|
||||
page={page}
|
||||
perPage={max}
|
||||
onNextClick={(_, p) => onNextClick((p - 1) * max)}
|
||||
onPreviousClick={(_, p) => onPreviousClick((p - 1) * max)}
|
||||
onPerPageSelect={(_, m, f) => onPerPageSelect(f - 1, m)}
|
||||
/>
|
||||
</ToolbarItem>
|
||||
</ToolbarContent>
|
||||
</Toolbar>
|
||||
);
|
||||
};
|
|
@ -109,8 +109,22 @@ export async function getCredentials({ signal, context }: CallOptions) {
|
|||
return parseResponse<CredentialContainer[]>(response);
|
||||
}
|
||||
|
||||
export async function getLinkedAccounts({ signal, context }: CallOptions) {
|
||||
const response = await request("/linked-accounts", context, { signal });
|
||||
export type LinkedAccountQueryParams = PaginationParams & {
|
||||
search?: string;
|
||||
linked?: boolean;
|
||||
};
|
||||
|
||||
export async function getLinkedAccounts(
|
||||
{ signal, context }: CallOptions,
|
||||
query: LinkedAccountQueryParams,
|
||||
) {
|
||||
const response = await request("/linked-accounts", context, {
|
||||
searchParams: Object.entries(query).reduce(
|
||||
(acc, [key, value]) => ({ ...acc, [key]: value.toString() }),
|
||||
{},
|
||||
),
|
||||
signal,
|
||||
});
|
||||
return parseResponse<LinkedAccountRepresentation[]>(response);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue