Client: fix editing client form

This commit is contained in:
Timofey Boyko 2023-11-02 17:43:32 +03:00
parent fe29d4f897
commit 147779cb13
11 changed files with 152 additions and 91 deletions

View File

@ -15,6 +15,8 @@ interface BasicBlockProps {
descriptionValue: string;
changeValue: (name: string, value: string) => void;
isEdit: boolean;
}
const BasicBlock = ({
@ -24,6 +26,8 @@ const BasicBlock = ({
logoValue,
descriptionValue,
changeValue,
isEdit,
}: BasicBlockProps) => {
const [error, setError] = React.useState({
name: "",
@ -73,6 +77,7 @@ const BasicBlock = ({
value={websiteUrlValue}
error={error.websiteUrl}
onChange={onChange}
disabled={isEdit}
/>
<SelectGroup
label={"App icon"}

View File

@ -9,9 +9,16 @@ interface ClientBlockProps {
idValue: string;
secretValue: string;
onResetClick: () => void;
}
const ClientBlock = ({ t, idValue, secretValue }: ClientBlockProps) => {
const ClientBlock = ({
t,
idValue,
secretValue,
onResetClick,
}: ClientBlockProps) => {
const [value, setValue] = React.useState<{ [key: string]: string }>({
id: idValue,
secret: secretValue,
@ -46,6 +53,7 @@ Note: Any enterprise admin who knows the app's client ID will be able to retriev
withCopy
isPassword
buttonLabel={"Reset"}
onButtonClick={onResetClick}
/>
</StyledInputBlock>
</StyledBlock>

View File

@ -33,6 +33,8 @@ interface InputGroupProps {
withCopy?: boolean;
onCopyClick?: (name: string) => void;
isPassword?: boolean;
disabled?: boolean;
}
const InputGroup = ({
@ -54,6 +56,7 @@ const InputGroup = ({
withCopy,
onCopyClick,
isPassword,
disabled,
}: InputGroupProps) => {
return (
<StyledInputGroup>
@ -82,7 +85,7 @@ const InputGroup = ({
tabIndex={0}
maxLength={255}
isReadOnly={withCopy}
isDisabled={withCopy}
isDisabled={withCopy || disabled}
iconName={withCopy ? CopyReactSvgUrl : null}
onIconClick={withCopy && onCopyClick}
type={isPassword ? "password" : "text"}

View File

@ -26,6 +26,8 @@ interface MultiInputGroupProps {
onAdd: (name: string, value: string, remove?: boolean) => void;
helpButtonText?: string;
isDisabled?: boolean;
}
const MultiInputGroup = ({
@ -36,6 +38,7 @@ const MultiInputGroup = ({
onAdd,
helpButtonText,
isDisabled,
}: MultiInputGroupProps) => {
const [value, setValue] = React.useState("");
@ -71,12 +74,15 @@ const MultiInputGroup = ({
scale
tabIndex={0}
maxLength={255}
isDisabled={isDisabled}
/>
<SelectorAddButton
onClick={() => {
if (isDisabled) return;
onAdd(name, value);
setValue("");
}}
isDisabled={isDisabled}
/>
</StyledInputRow>
<StyledChipsContainer>
@ -85,7 +91,9 @@ const MultiInputGroup = ({
key={`${v}-${index}`}
isInline
label={v}
onClose={() => onAdd(name, v)}
onClose={() => {
!isDisabled && onAdd(name, v);
}}
/>
))}
</StyledChipsContainer>

View File

@ -11,6 +11,8 @@ interface OAuthBlockProps {
allowedOriginsValue: string[];
changeValue: (name: string, value: string) => void;
isEdit: boolean;
}
const OAuthBlock = ({
@ -19,6 +21,8 @@ const OAuthBlock = ({
allowedOriginsValue,
changeValue,
isEdit,
}: OAuthBlockProps) => {
return (
<StyledBlock>
@ -31,6 +35,7 @@ const OAuthBlock = ({
onAdd={changeValue}
currentValue={redirectUrisValue}
helpButtonText={"Redirect uris"}
isDisabled={isEdit}
/>
<MultiInputGroup
label={"Allowed origins"}
@ -39,6 +44,7 @@ const OAuthBlock = ({
onAdd={changeValue}
currentValue={allowedOriginsValue}
helpButtonText={"Allowed origins"}
isDisabled={isEdit}
/>
</StyledInputBlock>
</StyledBlock>

View File

@ -26,6 +26,7 @@ interface IScopesBlockProps {
selectedScopes: string[];
onAddScope: (name: string, scope: string) => void;
t: any;
isEdit: boolean;
}
const ScopesBlock = ({
@ -33,6 +34,7 @@ const ScopesBlock = ({
selectedScopes,
onAddScope,
t,
isEdit,
}: IScopesBlockProps) => {
const [checkedScopes, setCheckedScopes] = React.useState<string[]>([]);
const [filteredScopes, setFilteredScopes] = React.useState<IFilteredScopes>(
@ -152,7 +154,7 @@ const ScopesBlock = ({
<Checkbox
className="checkbox-read"
isChecked={isReadChecked}
isDisabled={isReadDisabled}
isDisabled={isReadDisabled || isEdit}
onChange={() =>
onAddCheckedScope(
key as ScopeGroup,
@ -165,6 +167,7 @@ const ScopesBlock = ({
<StyledScopesCheckbox>
<Checkbox
isChecked={isReadDisabled}
isDisabled={isEdit}
onChange={() =>
onAddCheckedScope(
key as ScopeGroup,

View File

@ -11,6 +11,8 @@ interface SupportBlockProps {
termsUrlValue: string;
changeValue: (name: string, value: string) => void;
isEdit: boolean;
}
const SupportBlock = ({
@ -19,6 +21,8 @@ const SupportBlock = ({
termsUrlValue,
changeValue,
isEdit,
}: SupportBlockProps) => {
const [error, setError] = React.useState({
policyUrl: "",
@ -45,6 +49,7 @@ const SupportBlock = ({
helpButtonText={
"Provide a URL link to your Privacy Policy that must comply with applicable laws and regulations and that make clear how you collect, use, share, retain and otherwise process personal information."
}
disabled={isEdit}
/>
<InputGroup
label={"Terms of Service URL"}
@ -54,6 +59,7 @@ const SupportBlock = ({
error={error.termsUrl}
onChange={onChange}
helpButtonText={"Terms of service help"}
disabled={isEdit}
/>
</StyledInputBlock>
</StyledBlock>

View File

@ -44,8 +44,9 @@ const ClientForm = ({
const [isRequestRunning, setIsRequestRunning] =
React.useState<boolean>(false);
const [initClient, setInitClient] = React.useState<IClientProps | null>(null);
const [initialClient, setInitialClient] = React.useState<IClientProps>(
{} as IClientProps
);
const [form, setForm] = React.useState<IClientReqDTO>({
name: "",
logo: "",
@ -67,7 +68,7 @@ const ClientForm = ({
const [clientId, setClientId] = React.useState<string>("");
const [clientSecret, setClientSecret] = React.useState<string>("");
const isEdit = !!id || !!client;
const isEdit = !!id;
// const onInputChange = React.useCallback(
// (e: React.ChangeEvent<HTMLInputElement>) => {
@ -98,6 +99,7 @@ const ClientForm = ({
const onSaveClick = async () => {
if (!id) {
if (!saveClient) return;
setIsRequestRunning(true);
await saveClient(form);
@ -115,47 +117,12 @@ const ClientForm = ({
navigate("/portal-settings/developer-tools/oauth");
};
// const onResetClick = React.useCallback(async () => {
// if (!regenerateSecret) return;
// const newSecret = await regenerateSecret(clientId);
const onResetClick = React.useCallback(async () => {
if (!regenerateSecret) return;
const newSecret = await regenerateSecret(clientId);
// setSecret(newSecret);
// }, [clientId, regenerateSecret]);
// const getClient = React.useCallback(async () => {
// if (!fetchClient || !id) return;
// const client = await fetchClient(id);
// setClient(client);
// }, [id, fetchClient]);
// const setClient = React.useCallback(async (client: ClientProps) => {
// setForm({
// appName: client.name,
// appIcon: client.logoUrl || "",
// description: client.description,
// redirectUrl: client.redirectUri,
// privacyURL: client.policyUrl,
// termsUrl: client.termsUrl,
// logoutRedirectUrl: client.logoutRedirectUri,
// authenticationMethod: client.authenticationMethod,
// });
// setSecret(client.secret);
// setCheckedScopes([...client.scopes]);
// setInitClient({ ...client, scopes: [...client.scopes] });
// setIsLoading(false);
// }, []);
// React.useEffect(() => {
// setIsLoading(true);
// }, []);
setClientSecret(newSecret);
}, [clientId, regenerateSecret]);
const onChangeForm = (name: string, value: string) => {
setForm((val) => {
@ -180,35 +147,64 @@ const ClientForm = ({
});
};
const getScopeList = React.useCallback(async () => {
if (!fetchScopes) return;
const getClientData = React.useCallback(async () => {
if (!fetchScopes || !fetchClient) return;
const actions = [];
if (id && !client) {
actions.push(fetchClient(id));
}
actions.push(fetchScopes());
const [fetchedClient, ...rest] = await Promise.all(actions);
if (id && fetchedClient) {
setForm({
name: fetchedClient.name,
logo: fetchedClient.logo,
website_url: fetchedClient.websiteUrl,
description: fetchedClient.description,
redirect_uris: fetchedClient.redirectUris,
allowed_origins: fetchedClient.allowedOrigins,
logout_redirect_uri: fetchedClient.logoutRedirectUri,
terms_url: fetchedClient.termsUrl,
policy_url: fetchedClient.policyUrl,
authentication_method: fetchedClient.authenticationMethod,
scopes: fetchedClient.scopes,
});
setClientId(fetchedClient.clientId);
setClientSecret(fetchedClient.clientSecret);
setInitialClient(fetchedClient);
}
await fetchScopes();
setIsLoading(false);
}, [fetchScopes]);
}, [id, client, fetchScopes]);
React.useEffect(() => {
if (scopeList && scopeList?.length !== 0) return;
setIsLoading(true);
getScopeList();
}, [id, scopeList, getScopeList, fetchScopes]);
// React.useEffect(() => {
// if (id) {
// setClientId(id);
// if (!client) {
// getClient();
// } else {
// setClient(client);
// }
// }
// }, [id, client, fetchClient, getClient, setClient]);
getClientData();
}, [id, scopeList, client, getClientData, fetchScopes]);
const compareAndValidate = () => {
let isValid = true;
if (isEdit) {
return (
form.name !== initialClient.name ||
form.logo !== initialClient.logo ||
form.description !== initialClient.description
);
}
for (let key in form) {
switch (key) {
case "name":
@ -272,27 +268,36 @@ const ClientForm = ({
descriptionValue={form.description}
logoValue={form.logo}
changeValue={onChangeForm}
isEdit={isEdit}
/>
{isEdit && (
<ClientBlock t={t} idValue={clientId} secretValue={clientSecret} />
<ClientBlock
t={t}
idValue={clientId}
secretValue={clientSecret}
onResetClick={onResetClick}
/>
)}
<OAuthBlock
t={t}
redirectUrisValue={form.redirect_uris}
allowedOriginsValue={form.allowed_origins}
changeValue={onChangeForm}
isEdit={isEdit}
/>
<ScopesBlock
t={t}
scopes={scopeList || []}
selectedScopes={[]}
selectedScopes={form.scopes}
onAddScope={onChangeForm}
isEdit={isEdit}
/>
<SupportBlock
t={t}
policyUrlValue={form.policy_url}
termsUrlValue={form.terms_url}
changeValue={onChangeForm}
isEdit={isEdit}
/>
<ButtonsBlock
saveLabel={"Save"}

View File

@ -24,6 +24,8 @@ import SettingsIcon from "PUBLIC_DIR/images/catalog.settings.react.svg?url";
import DeleteIcon from "PUBLIC_DIR/images/delete.react.svg?url";
import EnableReactSvgUrl from "PUBLIC_DIR/images/enable.react.svg?url";
import RemoveReactSvgUrl from "PUBLIC_DIR/images/remove.react.svg?url";
import PencilReactSvgUrl from "PUBLIC_DIR/images/pencil.react.svg?url";
import CodeReactSvgUrl from "PUBLIC_DIR/images/code.react.svg?url";
const PAGE_LIMIT = 100;
@ -235,7 +237,7 @@ class OAuthStore implements OAuthStoreProps {
}
};
updateClient = async (clientId: string, client: ClientProps) => {
updateClient = async (clientId: string, client: IClientProps) => {
try {
const newClient = await updateClient(clientId, client);
@ -269,9 +271,9 @@ class OAuthStore implements OAuthStoreProps {
regenerateSecret = async (clientId: string) => {
try {
const secret = await regenerateSecret(clientId);
const { client_secret } = await regenerateSecret(clientId);
return secret;
return client_secret;
} catch (e) {
console.log(e);
}
@ -336,8 +338,6 @@ class OAuthStore implements OAuthStoreProps {
this.activeClients = [];
this.selection = [];
});
//TODO OAuth, show toast
} catch (e) {}
} else {
this.setActiveClient(clientId);
@ -348,13 +348,27 @@ class OAuthStore implements OAuthStoreProps {
}
};
const settingsOption = {
key: "settings",
icon: SettingsIcon,
label: t("Settings"),
const editOption = {
key: "edit",
icon: PencilReactSvgUrl,
label: t("Edit"),
onClick: () => this.editClient(clientId),
};
const authButtonOption = {
key: "auth-button",
icon: CodeReactSvgUrl,
label: "Auth button",
onClick: () => console.log(clientId),
};
const infoOption = {
key: "info",
icon: SettingsIcon,
label: "Info",
onClick: () => console.log(clientId),
};
const enableOption = {
key: "enable",
icon: EnableReactSvgUrl,
@ -404,7 +418,9 @@ class OAuthStore implements OAuthStoreProps {
contextOptions.unshift(enableOption);
}
contextOptions.unshift(settingsOption);
contextOptions.unshift(infoOption);
contextOptions.unshift(authButtonOption);
contextOptions.unshift(editOption);
}
return contextOptions;

View File

@ -82,28 +82,27 @@ export const updateClient = async (
data: transformToClientReqDTO(data),
});
// TODO: OAuth, get it from request
client.enabled = true;
return transformToClientProps(client);
};
export const changeClientStatus = async (
clientId: string,
status: boolean
): Promise<boolean> => {
console.log(`Change client:${clientId} status to ${status}`);
return !status;
};
export const regenerateSecret = async (clientId: string): Promise<string> => {
const clientSecret: string = (
): Promise<void> => {
await request({
method: "patch",
url: `/clients/${clientId}`,
})
).client_secret;
url: `/clients/${clientId}/activation`,
data: { body: status },
});
};
export const regenerateSecret = async (
clientId: string
): Promise<{ client_secret: string }> => {
const clientSecret: { client_secret: string } = await request({
method: "patch",
url: `/clients/${clientId}/regenerate`,
});
return clientSecret;
};

View File

@ -63,8 +63,10 @@ const ScopeList = ({ selectedScopes, scopes, t }: IScopeListProps) => {
for (let key in filteredScopes) {
if (filteredScopes[key].isChecked) {
if (filteredScopes[key].checkedType === ScopeType.read) {
//@ts-ignore
result.push(filteredScopes[key].read.tKey || "");
} else {
//@ts-ignore
result.push(filteredScopes[key].write.tKey || "");
}
}