Skip to content

Commit 5eca538

Browse files
Various fixes for M2M portal UI
ref DEV-2958 ref DEV-2954 ref DEV-2956 ref DEV-2957 ref DEV-2955
2 parents aeaadd7 + 6579c97 commit 5eca538

8 files changed

Lines changed: 106 additions & 94 deletions

File tree

portal/src/components/api-resources/APIResourceScreenLayout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const APIResourceScreenLayout: React.VFC<APIResourceLayoutProps> =
2323
}) {
2424
return (
2525
<ErrorMessageBarContextProvider>
26-
<div className="flex-1 flex flex-col">
26+
<div className="flex-1 flex flex-col overflow-y-auto">
2727
<ErrorMessageBar />
2828
<ScreenContent className="flex-1" layout="list">
2929
<ScreenContentHeader

portal/src/graphql/portal/Authenticated.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,24 @@ export async function startReauthentication<S>(
133133
});
134134
}
135135

136+
export function useStartReauthentication<S>(): {
137+
startReauthentication: typeof startReauthentication<S>;
138+
isRevealing: boolean;
139+
} {
140+
const [isRevealing, setIsRevealing] = useState(false);
141+
const startReauthenticationWithLoading = useCallback(
142+
async (navigate: ReturnType<typeof useNavigate>, state?: S) => {
143+
setIsRevealing(true);
144+
return startReauthentication(navigate, state);
145+
},
146+
[]
147+
);
148+
return {
149+
startReauthentication: startReauthenticationWithLoading,
150+
isRevealing,
151+
};
152+
}
153+
136154
export function useLogout(): () => Promise<void> {
137155
const redirectURI = window.location.origin + "/";
138156
const reset = useReset();

portal/src/graphql/portal/CreateOAuthClientScreen.tsx

Lines changed: 44 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import { useAddResourceToClientIdMutation } from "../adminapi/mutations/addResou
2222

2323
import ScreenContent from "../../ScreenContent";
2424
import ShowError from "../../ShowError";
25-
import ShowLoading from "../../ShowLoading";
2625
import { updateClientConfig } from "./EditOAuthClientForm";
2726
import NavBreadcrumb, { BreadcrumbItem } from "../../NavBreadcrumb";
2827
import {
@@ -47,6 +46,7 @@ import {
4746
} from "../../hook/useAppSecretConfigForm";
4847
import LinkButton from "../../LinkButton";
4948
import { useAppContext } from "../../context/AppContext";
49+
import { useLoadableView } from "../../hook/useLoadableView";
5050

5151
interface FormState {
5252
clients: OAuthClientConfig[];
@@ -167,28 +167,23 @@ function getNextStep(state: FormState): FormStep | null {
167167

168168
interface CreateOAuthClientContentProps {
169169
form: AppSecretConfigFormModel<FormState>;
170+
hasNoAPIResources: boolean;
170171
}
171172

172173
interface StepSelectApplicationTypeProps {
173174
client: OAuthClientConfig;
174175
form: AppSecretConfigFormModel<FormState>;
175176
onClickSave: () => void;
177+
hasNoAPIResources: boolean;
176178
}
177179

178180
const StepSelectApplicationType: React.VFC<StepSelectApplicationTypeProps> =
179181
function StepSelectApplicationType(props) {
180-
const { client, form, onClickSave } = props;
182+
const { client, form, onClickSave, hasNoAPIResources } = props;
181183
const { appNodeID } = useAppContext();
182184
const { state, setState, isDirty, isUpdating } = form;
183185
const { renderToString } = useContext(Context);
184186

185-
const { data } = useResourcesQueryQuery({
186-
variables: {
187-
first: 1,
188-
},
189-
fetchPolicy: "cache-and-network",
190-
});
191-
192187
const onClientConfigChange = useCallback(
193188
(newClient: OAuthClientConfig) => {
194189
setState((s) => ({ ...s, newClient }));
@@ -217,8 +212,6 @@ const StepSelectApplicationType: React.VFC<StepSelectApplicationTypeProps> =
217212
};
218213
}, []);
219214

220-
const hasNoAPIResources = (data?.resources?.totalCount ?? 0) === 0;
221-
222215
const options: IChoiceGroupOption[] = useMemo(() => {
223216
return [
224217
{
@@ -476,7 +469,8 @@ const StepAuthorizeResource: React.VFC<StepAuthorizeResourceProps> =
476469

477470
const CreateOAuthClientContent: React.VFC<CreateOAuthClientContentProps> =
478471
function CreateOAuthClientContent(props) {
479-
const { state, setState, save } = props.form;
472+
const { form, hasNoAPIResources } = props;
473+
const { state, setState, save } = form;
480474
const { appID } = useParams() as { appID: string };
481475
const navigate = useNavigate();
482476

@@ -554,14 +548,15 @@ const CreateOAuthClientContent: React.VFC<CreateOAuthClientContentProps> =
554548
{state.step === FormStep.SelectType ? (
555549
<StepSelectApplicationType
556550
client={client}
557-
form={props.form}
551+
form={form}
558552
onClickSave={onClickSave}
553+
hasNoAPIResources={hasNoAPIResources}
559554
/>
560555
) : null}
561556
{state.step === FormStep.AuthorizeResource ? (
562557
<StepAuthorizeResource
563558
client={client}
564-
form={props.form}
559+
form={form}
565560
onClickSave={onClickSave}
566561
/>
567562
) : null}
@@ -573,6 +568,16 @@ const CreateOAuthClientScreen: React.VFC = function CreateOAuthClientScreen() {
573568
const { appID } = useParams() as { appID: string };
574569
const [addResource] = useAddResourceToClientIdMutation();
575570

571+
const resourceCountQuery = useResourcesQueryQuery({
572+
variables: {
573+
first: 1,
574+
},
575+
fetchPolicy: "cache-and-network",
576+
});
577+
578+
const hasNoAPIResources =
579+
(resourceCountQuery.data?.resources?.totalCount ?? 0) === 0;
580+
576581
const form = useAppSecretConfigForm({
577582
appID,
578583
secretVisitToken: null,
@@ -600,8 +605,6 @@ const CreateOAuthClientScreen: React.VFC = function CreateOAuthClientScreen() {
600605
),
601606
});
602607

603-
const { isLoading, loadError, reload, updateError, isUpdating } = form;
604-
605608
const errorRules = useMemo(
606609
() => [
607610
makeValidationErrorMatchUnknownKindParseRule(
@@ -616,22 +619,31 @@ const CreateOAuthClientScreen: React.VFC = function CreateOAuthClientScreen() {
616619
[appID]
617620
);
618621

619-
if (isLoading) {
620-
return <ShowLoading />;
621-
}
622-
623-
if (loadError) {
624-
return <ShowError error={loadError} onRetry={reload} />;
625-
}
626-
627-
return (
628-
<FormProvider loading={isUpdating} error={updateError} rules={errorRules}>
629-
<FormErrorMessageBar />
630-
<div className="flex-1 overflow-y-auto flex flex-col">
631-
<CreateOAuthClientContent form={form} />
632-
</div>
633-
</FormProvider>
634-
);
622+
return useLoadableView({
623+
loadables: [
624+
form,
625+
{
626+
isLoading: resourceCountQuery.loading,
627+
loadError: resourceCountQuery.error,
628+
reload: resourceCountQuery.refetch,
629+
},
630+
] as const,
631+
render: ([form]) => (
632+
<FormProvider
633+
loading={form.isUpdating}
634+
error={form.updateError}
635+
rules={errorRules}
636+
>
637+
<FormErrorMessageBar />
638+
<div className="flex-1 overflow-y-auto flex flex-col">
639+
<CreateOAuthClientContent
640+
form={form}
641+
hasNoAPIResources={hasNoAPIResources}
642+
/>
643+
</div>
644+
</FormProvider>
645+
),
646+
});
635647
};
636648

637649
export default CreateOAuthClientScreen;

portal/src/graphql/portal/EditOAuthClientForm.tsx

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,20 @@ import { ensureNonEmptyString } from "../../util/misc";
2121
import { parseIntegerAllowLeadingZeros } from "../../util/input";
2222
import Toggle from "../../Toggle";
2323
import TextFieldWithCopyButton from "../../TextFieldWithCopyButton";
24-
import { useParams } from "react-router-dom";
24+
import { useParams, useNavigate } from "react-router-dom";
2525
import TextField from "../../TextField";
2626
import { Accordion } from "../../components/common/Accordion";
27-
import PrimaryButton from "../../PrimaryButton";
2827
import DefaultButton from "../../DefaultButton";
28+
import ButtonWithLoading from "../../ButtonWithLoading";
2929
import { ClientSecretsHook } from "../../hook/useClientSecrets";
3030
import { useSystemConfig } from "../../context/SystemConfigContext";
31+
import { useStartReauthentication } from "../../graphql/portal/Authenticated";
3132
import {
3233
DeleteClientSecretConfirmationDialog,
3334
DeleteClientSecretConfirmationDialogData,
3435
} from "../../components/applications/DeleteClientSecretConfirmationDialog";
3536
import Tooltip from "../../Tooltip";
37+
import { LocationState } from "./EditOAuthClientScreen";
3638

3739
const MASKED_SECRET = "***************";
3840

@@ -43,7 +45,6 @@ interface EditOAuthClientFormProps {
4345
customUIEnabled: boolean;
4446
app2appEnabled: boolean;
4547
onClientConfigChange: (newClientConfig: OAuthClientConfig) => void;
46-
onRevealSecret: () => void;
4748
clientSecretHook: ClientSecretsHook;
4849
}
4950

@@ -98,7 +99,6 @@ const EditOAuthClientForm: React.VFC<EditOAuthClientFormProps> =
9899
customUIEnabled,
99100
app2appEnabled,
100101
onClientConfigChange,
101-
onRevealSecret,
102102
clientSecretHook,
103103
} = props;
104104

@@ -108,6 +108,9 @@ const EditOAuthClientForm: React.VFC<EditOAuthClientFormProps> =
108108

109109
const { appID } = useParams() as { appID: string };
110110

111+
const { startReauthentication, isRevealing } =
112+
useStartReauthentication<LocationState>();
113+
111114
const [deleteClientSecretDialogData, setDeleteClientSecretDialogData] =
112115
useState<DeleteClientSecretConfirmationDialogData | null>(null);
113116

@@ -292,6 +295,11 @@ const EditOAuthClientForm: React.VFC<EditOAuthClientFormProps> =
292295
[]
293296
);
294297

298+
const navigate = useNavigate();
299+
const onRevealSecretClick = useCallback(() => {
300+
startReauthentication(navigate, { isClientSecretRevealed: true });
301+
}, [startReauthentication, navigate]);
302+
295303
const onConfirmDeleteClientSecret = useCallback(async () => {
296304
if (deleteClientSecretDialogData == null) {
297305
return;
@@ -602,10 +610,11 @@ const EditOAuthClientForm: React.VFC<EditOAuthClientFormProps> =
602610
</div>
603611
))}
604612
<div className="flex flex-row space-x-4">
605-
<PrimaryButton
606-
text={renderToString("reveal")}
607-
onClick={onRevealSecret}
613+
<ButtonWithLoading
614+
labelId="reveal"
615+
onClick={onRevealSecretClick}
608616
disabled={clientSecrets.every((item) => !!item.key)}
617+
loading={isRevealing}
609618
/>
610619
{clientSecrets.length < 2 ? (
611620
<DefaultButton

portal/src/graphql/portal/EditOAuthClientFormQuickStartContent.tsx

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ import { CodeField } from "../../components/common/CodeField";
2828
import { useCopyFeedback } from "../../hook/useCopyFeedback";
2929
import DefaultButton from "../../DefaultButton";
3030
import { useNavigate } from "react-router-dom";
31-
import { startReauthentication } from "../../graphql/portal/Authenticated";
32-
import PrimaryButton from "../../PrimaryButton";
33-
import { LocationState } from "./EditOAuthClientScreen";
31+
import { useStartReauthentication } from "../../graphql/portal/Authenticated";
32+
import ButtonWithLoading from "../../ButtonWithLoading";
3433
import { Resource } from "../adminapi/globalTypes.generated";
3534
import { useSearchParamsState } from "../../hook/useSearchParamsState";
35+
import { LocationState } from "./EditOAuthClientScreen";
3636

3737
interface EditOAuthClientFormQuickStartContentProps {
3838
className?: string;
@@ -89,13 +89,18 @@ function EditOAuthClientFormQuickStartContentLoaded(
8989
const { className, resources, appConfig, client, clientSecrets } = props;
9090
const { renderToString } = useContext(MessageContext);
9191
const navigate = useNavigate();
92+
const { startReauthentication, isRevealing } =
93+
useStartReauthentication<LocationState>();
9294
const firstClientSecret =
9395
(clientSecrets?.keys?.length ?? 0) > 0 ? clientSecrets!.keys![0] : null;
9496

9597
const isEmpty = resources.length === 0;
9698

9799
const [selectedResourceURI, setSelectedResourceURI] =
98-
useSearchParamsState<string>("resource", "");
100+
useSearchParamsState<string>(
101+
"resource",
102+
resources.length > 0 ? resources[0].resourceURI : ""
103+
);
99104
const [selectedCodeVariant, setSelectedCodeVariant] =
100105
useState<ExampleCodeVariant>(ExampleCodeVariant.CURL);
101106

@@ -130,13 +135,13 @@ function EditOAuthClientFormQuickStartContentLoaded(
130135
});
131136

132137
const revealSecrets = useCallback(() => {
133-
startReauthentication<LocationState>(navigate, {
138+
startReauthentication(navigate, {
134139
isClientSecretRevealed: true,
135140
}).catch((e) => {
136141
// Normally there should not be any error.
137142
console.error(e);
138143
});
139-
}, [navigate]);
144+
}, [navigate, startReauthentication]);
140145

141146
const resourceOptions = useMemo((): IDropdownOption[] => {
142147
return resources.map((resource) => {
@@ -218,10 +223,11 @@ function EditOAuthClientFormQuickStartContentLoaded(
218223
</Pivot>
219224
<CodeField className="mt-1">{exampleCode}</CodeField>
220225
<div className="mt-4 flex space-x-4">
221-
<PrimaryButton
222-
text={<FormattedMessage id="reveal" />}
226+
<ButtonWithLoading
227+
labelId="reveal"
223228
onClick={revealSecrets}
224229
disabled={!!firstClientSecret?.key}
230+
loading={isRevealing}
225231
/>
226232
<DefaultButton
227233
{...copyButtonProps}

portal/src/graphql/portal/EditOAuthClientScreen.tsx

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ import xamarinIconURL from "../../images/framework_xamarin.svg";
4141
import PrimaryButton from "../../PrimaryButton";
4242
import { useAppFeatureConfigQuery } from "./query/appFeatureConfigQuery";
4343
import { AppSecretKey } from "./globalTypes.generated";
44-
import { startReauthentication } from "./Authenticated";
4544
import { useLocationEffect } from "../../hook/useLocationEffect";
4645
import { useAppSecretVisitToken } from "./mutations/generateAppSecretVisitTokenMutation";
4746
import { useOAuthClientForm, FormState } from "../../hook/useOAuthClientForm";
@@ -376,8 +375,6 @@ const EditOAuthClientContent: React.VFC<EditOAuthClientContentProps> =
376375

377376
const { formTab, setFormTab } = useContext(FormTabContext);
378377

379-
const navigate = useNavigate();
380-
381378
const client =
382379
state.editedClient ?? state.clients.find((c) => c.client_id === clientID);
383380

@@ -405,16 +402,6 @@ const EditOAuthClientContent: React.VFC<EditOAuthClientContentProps> =
405402
[setState]
406403
);
407404

408-
const onRevealSecret = useCallback(() => {
409-
const state: LocationState = {
410-
isClientSecretRevealed: true,
411-
};
412-
startReauthentication(navigate, state).catch((e) => {
413-
// Normally there should not be any error.
414-
console.error(e);
415-
});
416-
}, [navigate]);
417-
418405
if (client == null) {
419406
return (
420407
<Text>
@@ -481,7 +468,6 @@ const EditOAuthClientContent: React.VFC<EditOAuthClientContentProps> =
481468
app2appEnabled={app2appEnabled}
482469
customUIEnabled={customUIEnabled}
483470
onClientConfigChange={onClientConfigChange}
484-
onRevealSecret={onRevealSecret}
485471
clientSecretHook={clientSecretHook}
486472
/>
487473
) : null}
@@ -514,7 +500,6 @@ interface OAuthClientSettingsFormProps {
514500
app2appEnabled: boolean;
515501
customUIEnabled: boolean;
516502
onClientConfigChange: (newClientConfig: OAuthClientConfig) => void;
517-
onRevealSecret: () => void;
518503
clientSecretHook: ClientSecretsHook;
519504
}
520505

@@ -524,7 +509,6 @@ function OAuthClientSettingsForm({
524509
app2appEnabled,
525510
customUIEnabled,
526511
onClientConfigChange,
527-
onRevealSecret,
528512
clientSecretHook,
529513
}: OAuthClientSettingsFormProps): React.ReactElement {
530514
const theme = useTheme();
@@ -550,7 +534,6 @@ function OAuthClientSettingsForm({
550534
customUIEnabled={customUIEnabled}
551535
app2appEnabled={app2appEnabled}
552536
onClientConfigChange={onClientConfigChange}
553-
onRevealSecret={onRevealSecret}
554537
clientSecretHook={clientSecretHook}
555538
/>
556539
</div>

0 commit comments

Comments
 (0)