@medplum/react 1.0.6 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/OperationOutcomeAlert/OperationOutcomeAlert.d.ts +6 -0
- package/dist/cjs/auth/AuthenticationForm.d.ts +15 -1
- package/dist/cjs/auth/SignInForm.d.ts +1 -0
- package/dist/cjs/index.cjs +96 -50
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.min.cjs +1 -1
- package/dist/esm/OperationOutcomeAlert/OperationOutcomeAlert.d.ts +6 -0
- package/dist/esm/OperationOutcomeAlert/OperationOutcomeAlert.mjs +13 -0
- package/dist/esm/OperationOutcomeAlert/OperationOutcomeAlert.mjs.map +1 -0
- package/dist/esm/auth/AuthenticationForm.d.ts +15 -1
- package/dist/esm/auth/AuthenticationForm.mjs +66 -34
- package/dist/esm/auth/AuthenticationForm.mjs.map +1 -1
- package/dist/esm/auth/NewUserForm.mjs +3 -4
- package/dist/esm/auth/NewUserForm.mjs.map +1 -1
- package/dist/esm/auth/SignInForm.d.ts +1 -0
- package/dist/esm/auth/SignInForm.mjs +27 -19
- package/dist/esm/auth/SignInForm.mjs.map +1 -1
- package/dist/esm/index.min.mjs +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { OperationOutcomeIssue } from '@medplum/fhirtypes';
|
|
3
|
+
export interface OperationOutcomeAlertProps {
|
|
4
|
+
issues?: OperationOutcomeIssue[];
|
|
5
|
+
}
|
|
6
|
+
export declare function OperationOutcomeAlert(props: OperationOutcomeAlertProps): JSX.Element | null;
|
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
import { BaseLoginRequest, LoginAuthenticationResponse } from '@medplum/core';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
export interface AuthenticationFormProps extends BaseLoginRequest {
|
|
4
|
-
readonly generatePkce?: boolean;
|
|
5
4
|
readonly onForgotPassword?: () => void;
|
|
6
5
|
readonly onRegister?: () => void;
|
|
7
6
|
readonly handleAuthResponse: (response: LoginAuthenticationResponse) => void;
|
|
8
7
|
readonly children?: React.ReactNode;
|
|
9
8
|
}
|
|
10
9
|
export declare function AuthenticationForm(props: AuthenticationFormProps): JSX.Element;
|
|
10
|
+
export interface EmailFormProps extends BaseLoginRequest {
|
|
11
|
+
readonly generatePkce?: boolean;
|
|
12
|
+
readonly onRegister?: () => void;
|
|
13
|
+
readonly handleAuthResponse: (response: LoginAuthenticationResponse) => void;
|
|
14
|
+
readonly setEmail: (email: string) => void;
|
|
15
|
+
readonly children?: React.ReactNode;
|
|
16
|
+
}
|
|
17
|
+
export declare function EmailForm(props: EmailFormProps): JSX.Element;
|
|
18
|
+
export interface PasswordFormProps extends BaseLoginRequest {
|
|
19
|
+
readonly email: string;
|
|
20
|
+
readonly onForgotPassword?: () => void;
|
|
21
|
+
readonly handleAuthResponse: (response: LoginAuthenticationResponse) => void;
|
|
22
|
+
readonly children?: React.ReactNode;
|
|
23
|
+
}
|
|
24
|
+
export declare function PasswordForm(props: PasswordFormProps): JSX.Element;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { BaseLoginRequest } from '@medplum/core';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
export interface SignInFormProps extends BaseLoginRequest {
|
|
4
|
+
readonly login?: string;
|
|
4
5
|
readonly chooseScopes?: boolean;
|
|
5
6
|
readonly onSuccess?: () => void;
|
|
6
7
|
readonly onForgotPassword?: () => void;
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -630,6 +630,13 @@
|
|
|
630
630
|
return undefined;
|
|
631
631
|
}
|
|
632
632
|
|
|
633
|
+
function OperationOutcomeAlert(props) {
|
|
634
|
+
if (!props.issues) {
|
|
635
|
+
return null;
|
|
636
|
+
}
|
|
637
|
+
return (React.createElement(core$1.Alert, { icon: React.createElement(icons.IconAlertCircle, { size: 16 }), color: "red" }, props.issues.map((issue) => (React.createElement("div", { "data-testid": "text-field-error", key: issue.details?.text }, issue.details?.text)))));
|
|
638
|
+
}
|
|
639
|
+
|
|
633
640
|
/**
|
|
634
641
|
* Dynamically loads the recaptcha script.
|
|
635
642
|
* We do not want to load the script on page load unless the user needs it.
|
|
@@ -684,12 +691,11 @@
|
|
|
684
691
|
}
|
|
685
692
|
} },
|
|
686
693
|
React.createElement(core$1.Center, { sx: { flexDirection: 'column' } }, props.children),
|
|
687
|
-
|
|
694
|
+
React.createElement(OperationOutcomeAlert, { issues: issues }),
|
|
688
695
|
googleClientId && (React.createElement(React.Fragment, null,
|
|
689
696
|
React.createElement(core$1.Group, { position: "center", p: "xl", style: { height: 70 } },
|
|
690
697
|
React.createElement(GoogleButton, { googleClientId: googleClientId, handleGoogleCredential: async (response) => {
|
|
691
698
|
try {
|
|
692
|
-
await medplum.startPkce();
|
|
693
699
|
props.handleAuthResponse(await medplum.startGoogleLogin({
|
|
694
700
|
googleClientId: response.clientId,
|
|
695
701
|
googleCredential: response.credential,
|
|
@@ -757,47 +763,79 @@
|
|
|
757
763
|
}
|
|
758
764
|
|
|
759
765
|
function AuthenticationForm(props) {
|
|
760
|
-
const
|
|
766
|
+
const [email, setEmail] = React.useState();
|
|
767
|
+
if (!email) {
|
|
768
|
+
return React.createElement(EmailForm, { setEmail: setEmail, ...props });
|
|
769
|
+
}
|
|
770
|
+
else {
|
|
771
|
+
return React.createElement(PasswordForm, { email: email, ...props });
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
function EmailForm(props) {
|
|
775
|
+
const { setEmail, onRegister, handleAuthResponse, children, ...baseLoginRequest } = props;
|
|
761
776
|
const medplum = useMedplum();
|
|
762
777
|
const googleClientId = getGoogleClientId(props.googleClientId);
|
|
763
|
-
const
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
if (generatePkce) {
|
|
767
|
-
await medplum.startPkce();
|
|
778
|
+
const isExternalAuth = React.useCallback(async (authMethod) => {
|
|
779
|
+
if (!authMethod.authorizeUrl) {
|
|
780
|
+
return false;
|
|
768
781
|
}
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
782
|
+
const state = JSON.stringify({
|
|
783
|
+
...(await medplum.ensureCodeChallenge(baseLoginRequest)),
|
|
784
|
+
domain: authMethod.domain,
|
|
785
|
+
});
|
|
786
|
+
const url = new URL(authMethod.authorizeUrl);
|
|
787
|
+
url.searchParams.set('state', state);
|
|
788
|
+
window.location.assign(url.toString());
|
|
789
|
+
return true;
|
|
790
|
+
}, [medplum, baseLoginRequest]);
|
|
791
|
+
const handleSubmit = React.useCallback(async (formData) => {
|
|
792
|
+
const authMethod = await medplum.post('auth/method', { email: formData.email });
|
|
793
|
+
if (!(await isExternalAuth(authMethod))) {
|
|
794
|
+
setEmail(formData.email);
|
|
795
|
+
}
|
|
796
|
+
}, [medplum, isExternalAuth, setEmail]);
|
|
797
|
+
const handleGoogleCredential = React.useCallback(async (response) => {
|
|
798
|
+
const authResponse = await medplum.startGoogleLogin({
|
|
799
|
+
...baseLoginRequest,
|
|
800
|
+
googleCredential: response.credential,
|
|
801
|
+
});
|
|
802
|
+
if (!(await isExternalAuth(authResponse))) {
|
|
803
|
+
handleAuthResponse(authResponse);
|
|
804
|
+
}
|
|
805
|
+
}, [medplum, baseLoginRequest, isExternalAuth, handleAuthResponse]);
|
|
806
|
+
return (React.createElement(Form, { style: { maxWidth: 400 }, onSubmit: handleSubmit },
|
|
781
807
|
React.createElement(core$1.Center, { sx: { flexDirection: 'column' } }, children),
|
|
782
|
-
issues && (React.createElement(core$1.Alert, { icon: React.createElement(icons.IconAlertCircle, { size: 16 }), color: "red" }, issues.map((issue) => (React.createElement("div", { "data-testid": "text-field-error", key: issue.details?.text }, issue.details?.text))))),
|
|
783
808
|
googleClientId && (React.createElement(React.Fragment, null,
|
|
784
809
|
React.createElement(core$1.Group, { position: "center", p: "xl", style: { height: 70 } },
|
|
785
|
-
React.createElement(GoogleButton, { googleClientId: googleClientId, handleGoogleCredential:
|
|
786
|
-
startPkce()
|
|
787
|
-
.then(() => medplum.startGoogleLogin({
|
|
788
|
-
...baseLoginRequest,
|
|
789
|
-
googleCredential: response.credential,
|
|
790
|
-
}))
|
|
791
|
-
.then(props.handleAuthResponse)
|
|
792
|
-
.catch(setOutcome);
|
|
793
|
-
} })),
|
|
810
|
+
React.createElement(GoogleButton, { googleClientId: googleClientId, handleGoogleCredential: handleGoogleCredential })),
|
|
794
811
|
React.createElement(core$1.Divider, { label: "or", labelPosition: "center", my: "lg" }))),
|
|
812
|
+
React.createElement(core$1.TextInput, { name: "email", type: "email", label: "Email", placeholder: "name@domain.com", required: true, autoFocus: true }),
|
|
813
|
+
React.createElement(core$1.Group, { position: "apart", mt: "xl", spacing: 0, noWrap: true },
|
|
814
|
+
onRegister && (React.createElement(core$1.Anchor, { component: "button", type: "button", color: "dimmed", onClick: onRegister, size: "xs" }, "Register")),
|
|
815
|
+
React.createElement(core$1.Button, { type: "submit" }, "Next"))));
|
|
816
|
+
}
|
|
817
|
+
function PasswordForm(props) {
|
|
818
|
+
const { onForgotPassword, handleAuthResponse, children, ...baseLoginRequest } = props;
|
|
819
|
+
const medplum = useMedplum();
|
|
820
|
+
const [outcome, setOutcome] = React.useState();
|
|
821
|
+
const issues = getIssuesForExpression(outcome, undefined);
|
|
822
|
+
const handleSubmit = React.useCallback((formData) => {
|
|
823
|
+
medplum
|
|
824
|
+
.startLogin({
|
|
825
|
+
...baseLoginRequest,
|
|
826
|
+
password: formData.password,
|
|
827
|
+
remember: formData.remember === 'on',
|
|
828
|
+
})
|
|
829
|
+
.then(handleAuthResponse)
|
|
830
|
+
.catch(setOutcome);
|
|
831
|
+
}, [medplum, baseLoginRequest, handleAuthResponse]);
|
|
832
|
+
return (React.createElement(Form, { style: { maxWidth: 400 }, onSubmit: handleSubmit },
|
|
833
|
+
React.createElement(core$1.Center, { sx: { flexDirection: 'column' } }, children),
|
|
834
|
+
React.createElement(OperationOutcomeAlert, { issues: issues }),
|
|
795
835
|
React.createElement(core$1.Stack, { spacing: "xl" },
|
|
796
|
-
React.createElement(core$1.TextInput, { name: "email", type: "email", label: "Email", placeholder: "name@domain.com", required: true, autoFocus: true, error: getErrorsForInput(outcome, 'email') }),
|
|
797
836
|
React.createElement(core$1.PasswordInput, { name: "password", type: "password", label: "Password", autoComplete: "off", required: true, error: getErrorsForInput(outcome, 'password') })),
|
|
798
837
|
React.createElement(core$1.Group, { position: "apart", mt: "xl", spacing: 0, noWrap: true },
|
|
799
838
|
onForgotPassword && (React.createElement(core$1.Anchor, { component: "button", type: "button", color: "dimmed", onClick: onForgotPassword, size: "xs" }, "Forgot password")),
|
|
800
|
-
onRegister && (React.createElement(core$1.Anchor, { component: "button", type: "button", color: "dimmed", onClick: onRegister, size: "xs" }, "Register")),
|
|
801
839
|
React.createElement(core$1.Checkbox, { id: "remember", name: "remember", label: "Remember me", size: "xs", sx: { lineHeight: 1 } }),
|
|
802
840
|
React.createElement(core$1.Button, { type: "submit" }, "Sign in"))));
|
|
803
841
|
}
|
|
@@ -884,7 +922,22 @@
|
|
|
884
922
|
const [login, setLogin] = React.useState(undefined);
|
|
885
923
|
const [mfaRequired, setAuthenticatorRequired] = React.useState(false);
|
|
886
924
|
const [memberships, setMemberships] = React.useState(undefined);
|
|
887
|
-
|
|
925
|
+
const handleCode = React.useCallback((code) => {
|
|
926
|
+
if (onCode) {
|
|
927
|
+
onCode(code);
|
|
928
|
+
}
|
|
929
|
+
else {
|
|
930
|
+
medplum
|
|
931
|
+
.processCode(code)
|
|
932
|
+
.then(() => {
|
|
933
|
+
if (onSuccess) {
|
|
934
|
+
onSuccess();
|
|
935
|
+
}
|
|
936
|
+
})
|
|
937
|
+
.catch(console.log);
|
|
938
|
+
}
|
|
939
|
+
}, [medplum, onCode, onSuccess]);
|
|
940
|
+
const handleAuthResponse = React.useCallback((response) => {
|
|
888
941
|
setAuthenticatorRequired(!!response.mfaRequired);
|
|
889
942
|
if (response.login) {
|
|
890
943
|
setLogin(response.login);
|
|
@@ -900,28 +953,21 @@
|
|
|
900
953
|
handleCode(response.code);
|
|
901
954
|
}
|
|
902
955
|
}
|
|
903
|
-
}
|
|
904
|
-
|
|
956
|
+
}, [chooseScopes, handleCode]);
|
|
957
|
+
const handleScopeResponse = React.useCallback((response) => {
|
|
905
958
|
handleCode(response.code);
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
if (
|
|
909
|
-
onCode(code);
|
|
910
|
-
}
|
|
911
|
-
else {
|
|
959
|
+
}, [handleCode]);
|
|
960
|
+
React.useEffect(() => {
|
|
961
|
+
if (props.login) {
|
|
912
962
|
medplum
|
|
913
|
-
.
|
|
914
|
-
.then(
|
|
915
|
-
|
|
916
|
-
onSuccess();
|
|
917
|
-
}
|
|
918
|
-
})
|
|
919
|
-
.catch(console.log);
|
|
963
|
+
.get('auth/login/' + props.login)
|
|
964
|
+
.then(handleAuthResponse)
|
|
965
|
+
.catch(console.error);
|
|
920
966
|
}
|
|
921
|
-
}
|
|
967
|
+
}, [medplum, props, handleAuthResponse]);
|
|
922
968
|
return (React.createElement(Document, { width: 450 }, (() => {
|
|
923
969
|
if (!login) {
|
|
924
|
-
return (React.createElement(AuthenticationForm, {
|
|
970
|
+
return (React.createElement(AuthenticationForm, { onForgotPassword: onForgotPassword, onRegister: onRegister, handleAuthResponse: handleAuthResponse, ...baseLoginRequest }, props.children));
|
|
925
971
|
}
|
|
926
972
|
else if (mfaRequired) {
|
|
927
973
|
return React.createElement(MfaForm, { login: login, handleAuthResponse: handleAuthResponse });
|