@medplum/react 0.9.30 → 0.9.33

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.
Files changed (58) hide show
  1. package/dist/cjs/QuestionnaireForm.d.ts +1 -0
  2. package/dist/cjs/Scheduler.d.ts +2 -1
  3. package/dist/cjs/auth/AuthenticationForm.d.ts +16 -0
  4. package/dist/cjs/auth/ChooseProfileForm.d.ts +8 -0
  5. package/dist/cjs/auth/NewProjectForm.d.ts +7 -0
  6. package/dist/cjs/auth/NewUserForm.d.ts +10 -0
  7. package/dist/cjs/auth/RegisterForm.d.ts +12 -0
  8. package/dist/cjs/{SignInForm.d.ts → auth/SignInForm.d.ts} +3 -1
  9. package/dist/cjs/index.d.ts +2 -2
  10. package/dist/cjs/index.js +700 -678
  11. package/dist/cjs/index.js.map +1 -1
  12. package/dist/cjs/index.min.js +1 -1
  13. package/dist/cjs/index.min.js.map +1 -1
  14. package/dist/cjs/styles.css +197 -197
  15. package/dist/esm/MedplumLink.js +29 -14
  16. package/dist/esm/MedplumLink.js.map +1 -1
  17. package/dist/esm/QuestionnaireForm.d.ts +1 -0
  18. package/dist/esm/QuestionnaireForm.js +1 -1
  19. package/dist/esm/QuestionnaireForm.js.map +1 -1
  20. package/dist/esm/ResourceHistoryTable.js +15 -11
  21. package/dist/esm/ResourceHistoryTable.js.map +1 -1
  22. package/dist/esm/ResourceTimeline.js +3 -1
  23. package/dist/esm/ResourceTimeline.js.map +1 -1
  24. package/dist/esm/Scheduler.d.ts +2 -1
  25. package/dist/esm/Scheduler.js +18 -24
  26. package/dist/esm/Scheduler.js.map +1 -1
  27. package/dist/esm/ServiceRequestTimeline.js +3 -3
  28. package/dist/esm/ServiceRequestTimeline.js.map +1 -1
  29. package/dist/esm/auth/AuthenticationForm.d.ts +16 -0
  30. package/dist/esm/auth/AuthenticationForm.js +71 -0
  31. package/dist/esm/auth/AuthenticationForm.js.map +1 -0
  32. package/dist/esm/auth/ChooseProfileForm.d.ts +8 -0
  33. package/dist/esm/auth/ChooseProfileForm.js +32 -0
  34. package/dist/esm/auth/ChooseProfileForm.js.map +1 -0
  35. package/dist/esm/auth/NewProjectForm.d.ts +7 -0
  36. package/dist/esm/auth/NewProjectForm.js +42 -0
  37. package/dist/esm/auth/NewProjectForm.js.map +1 -0
  38. package/dist/esm/auth/NewUserForm.d.ts +10 -0
  39. package/dist/esm/auth/NewUserForm.js +87 -0
  40. package/dist/esm/auth/NewUserForm.js.map +1 -0
  41. package/dist/esm/auth/RegisterForm.d.ts +12 -0
  42. package/dist/esm/auth/RegisterForm.js +39 -0
  43. package/dist/esm/auth/RegisterForm.js.map +1 -0
  44. package/dist/esm/{SignInForm.d.ts → auth/SignInForm.d.ts} +3 -1
  45. package/dist/esm/auth/SignInForm.js +52 -0
  46. package/dist/esm/auth/SignInForm.js.map +1 -0
  47. package/dist/esm/index.d.ts +2 -2
  48. package/dist/esm/index.js +2 -2
  49. package/dist/esm/index.min.js +1 -1
  50. package/dist/esm/index.min.js.map +1 -1
  51. package/dist/esm/styles.css +197 -197
  52. package/package.json +21 -23
  53. package/dist/cjs/RegisterForm.d.ts +0 -18
  54. package/dist/esm/RegisterForm.d.ts +0 -18
  55. package/dist/esm/RegisterForm.js +0 -121
  56. package/dist/esm/RegisterForm.js.map +0 -1
  57. package/dist/esm/SignInForm.js +0 -167
  58. package/dist/esm/SignInForm.js.map +0 -1
package/dist/cjs/index.js CHANGED
@@ -329,6 +329,582 @@
329
329
  return React__default["default"].createElement(UploadButton, { onUpload: setValueWrapper });
330
330
  }
331
331
 
332
+ function Document(props) {
333
+ return (React__default["default"].createElement("main", { className: "medplum-document" },
334
+ React__default["default"].createElement("article", { style: { maxWidth: props.width } }, props.children)));
335
+ }
336
+
337
+ /******************************************************************************
338
+ Copyright (c) Microsoft Corporation.
339
+
340
+ Permission to use, copy, modify, and/or distribute this software for any
341
+ purpose with or without fee is hereby granted.
342
+
343
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
344
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
345
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
346
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
347
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
348
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
349
+ PERFORMANCE OF THIS SOFTWARE.
350
+ ***************************************************************************** */
351
+
352
+ function __awaiter(thisArg, _arguments, P, generator) {
353
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
354
+ return new (P || (P = Promise))(function (resolve, reject) {
355
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
356
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
357
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
358
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
359
+ });
360
+ }
361
+
362
+ /**
363
+ * Parses an HTML form and returns the result as a JavaScript object.
364
+ * @param form The HTML form element.
365
+ */
366
+ function parseForm(form) {
367
+ const result = {};
368
+ for (const element of Array.from(form.elements)) {
369
+ if (element instanceof HTMLInputElement) {
370
+ parseInputElement(result, element);
371
+ }
372
+ else if (element instanceof HTMLTextAreaElement) {
373
+ result[element.name] = element.value;
374
+ }
375
+ else if (element instanceof HTMLSelectElement) {
376
+ parseSelectElement(result, element);
377
+ }
378
+ }
379
+ return result;
380
+ }
381
+ /**
382
+ * Parses an HTML input element.
383
+ * Sets the name/value pair in the result,
384
+ * but only if the element is enabled and checked.
385
+ * @param el The input element.
386
+ * @param result The result builder.
387
+ */
388
+ function parseInputElement(result, el) {
389
+ if (el.disabled) {
390
+ // Ignore disabled elements
391
+ return;
392
+ }
393
+ if ((el.type === 'checkbox' || el.type === 'radio') && !el.checked) {
394
+ // Ignore unchecked radio or checkbox elements
395
+ return;
396
+ }
397
+ result[el.name] = el.value;
398
+ }
399
+ /**
400
+ * Parses an HTML select element.
401
+ * Sets the name/value pair if one is selected.
402
+ * @param result The result builder.
403
+ * @param el The select element.
404
+ */
405
+ function parseSelectElement(result, el) {
406
+ result[el.name] = el.value;
407
+ }
408
+
409
+ function Form(props) {
410
+ return (React__default["default"].createElement("form", { style: props.style, "data-testid": props.testid, onSubmit: (e) => {
411
+ e.preventDefault();
412
+ const formData = parseForm(e.target);
413
+ if (props.onSubmit) {
414
+ props.onSubmit(formData);
415
+ }
416
+ } }, props.children));
417
+ }
418
+
419
+ function FormSection(props) {
420
+ const issues = getIssuesForExpression(props.outcome, props.htmlFor);
421
+ const invalid = issues && issues.length > 0;
422
+ return (React__default["default"].createElement("fieldset", { className: "medplum-form-section" },
423
+ props.title && React__default["default"].createElement("label", { htmlFor: props.htmlFor }, props.title),
424
+ props.description && React__default["default"].createElement("p", null, props.description),
425
+ props.children,
426
+ invalid && (React__default["default"].createElement("div", { id: props.htmlFor + '-errors', className: "medplum-input-error" }, issues === null || issues === void 0 ? void 0 : issues.map((issue) => {
427
+ var _a, _b;
428
+ return (React__default["default"].createElement("div", { "data-testid": "text-field-error", key: (_a = issue.details) === null || _a === void 0 ? void 0 : _a.text }, (_b = issue.details) === null || _b === void 0 ? void 0 : _b.text));
429
+ })))));
430
+ }
431
+
432
+ function Logo(props) {
433
+ return (React__default["default"].createElement("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 491 491", style: { width: props.size, height: props.size } },
434
+ React__default["default"].createElement("title", null, "Medplum Logo"),
435
+ React__default["default"].createElement("path", { fill: props.fill || '#ad7136', d: "M282 67c6-16 16-29 29-40L289 0c-22 17-37 41-43 68l17 23 19-24z" }),
436
+ React__default["default"].createElement("path", { fill: props.fill || '#654b87', d: "M311 63c-17 0-33 4-48 11-16-7-32-11-49-11-87 0-158 96-158 214s71 214 158 214c17 0 33-4 49-11 15 7 31 11 48 11 87 0 158-96 158-214S398 63 311 63z" }),
437
+ React__default["default"].createElement("path", { fill: props.fill || '#463068', d: "M231 489l-17 2c-87 0-158-96-158-214S127 63 214 63l17 1c-39 12-70 102-70 213s31 201 70 212z" }),
438
+ React__default["default"].createElement("path", { fill: props.fill || '#70d65b', d: "M207 220a176 176 0 01-177 43A176 176 0 01251 43l1 5c17 59 2 125-45 172z" }),
439
+ React__default["default"].createElement("path", { fill: props.fill || '#58b741', d: "M252 48A421 421 0 0057 270l-27-7A176 176 0 01251 43l1 5z" })));
440
+ }
441
+
442
+ function NewProjectForm(props) {
443
+ const medplum = useMedplum();
444
+ const [outcome, setOutcome] = React.useState();
445
+ return (React__default["default"].createElement(Form, { style: { maxWidth: 400 }, onSubmit: (formData) => __awaiter(this, void 0, void 0, function* () {
446
+ try {
447
+ props.handleAuthResponse(yield medplum.startNewProject({
448
+ login: props.login,
449
+ projectName: formData.projectName,
450
+ }));
451
+ }
452
+ catch (err) {
453
+ setOutcome(err);
454
+ }
455
+ }) },
456
+ React__default["default"].createElement("div", { className: "medplum-center" },
457
+ React__default["default"].createElement(Logo, { size: 32 }),
458
+ React__default["default"].createElement("h1", null, "Create project")),
459
+ React__default["default"].createElement(FormSection, { title: "Project Name", htmlFor: "projectName", outcome: outcome },
460
+ React__default["default"].createElement(Input, { name: "projectName", type: "text", testid: "projectName", placeholder: "My Project", required: true, outcome: outcome })),
461
+ React__default["default"].createElement("p", { style: { fontSize: '12px', color: '#888' } },
462
+ "By clicking submit you agree to the Medplum ",
463
+ React__default["default"].createElement("a", { href: "https://www.medplum.com/privacy" }, "Privacy\u00A0Policy"),
464
+ ' and ',
465
+ React__default["default"].createElement("a", { href: "https://www.medplum.com/terms" }, "Terms\u00A0of\u00A0Service"),
466
+ "."),
467
+ React__default["default"].createElement("div", { className: "medplum-signin-buttons" },
468
+ React__default["default"].createElement("div", null),
469
+ React__default["default"].createElement("div", null,
470
+ React__default["default"].createElement(Button, { type: "submit", testid: "submit" }, "Create project")))));
471
+ }
472
+
473
+ /**
474
+ * Dynamically creates a script tag for the specified JavaScript file.
475
+ * @param src The JavaScript file URL.
476
+ */
477
+ function createScriptTag(src, onload) {
478
+ const head = document.getElementsByTagName('head')[0];
479
+ const script = document.createElement('script');
480
+ script.async = true;
481
+ script.src = src;
482
+ script.onload = onload || null;
483
+ head.appendChild(script);
484
+ }
485
+
486
+ function GoogleButton(props) {
487
+ const medplum = useMedplum();
488
+ const { googleClientId, handleGoogleCredential } = props;
489
+ const parentRef = React.useRef(null);
490
+ const [scriptLoaded, setScriptLoaded] = React.useState(typeof google !== 'undefined');
491
+ const [initialized, setInitialized] = React.useState(false);
492
+ const [buttonRendered, setButtonRendered] = React.useState(false);
493
+ React.useEffect(() => {
494
+ if (typeof google === 'undefined') {
495
+ createScriptTag('https://accounts.google.com/gsi/client', () => setScriptLoaded(true));
496
+ return;
497
+ }
498
+ if (!initialized) {
499
+ google.accounts.id.initialize({
500
+ client_id: googleClientId,
501
+ callback: handleGoogleCredential,
502
+ });
503
+ setInitialized(true);
504
+ }
505
+ if (parentRef.current && !buttonRendered) {
506
+ google.accounts.id.renderButton(parentRef.current, {});
507
+ setButtonRendered(true);
508
+ }
509
+ }, [medplum, googleClientId, initialized, scriptLoaded, parentRef, buttonRendered, handleGoogleCredential]);
510
+ if (!googleClientId) {
511
+ return null;
512
+ }
513
+ return React__default["default"].createElement("div", { ref: parentRef });
514
+ }
515
+ function getGoogleClientId(clientId) {
516
+ var _a, _b;
517
+ if (clientId) {
518
+ return clientId;
519
+ }
520
+ const origin = window.location.protocol + '//' + window.location.host;
521
+ const authorizedOrigins = (_b = (_a = "http://localhost:3000,http://127.0.0.1:3000,http://localhost:6006,http://127.0.0.1:6006,https://app.medplum.com,https://docs.medplum.com") === null || _a === void 0 ? void 0 : _a.split(',')) !== null && _b !== void 0 ? _b : [];
522
+ if (authorizedOrigins.includes(origin)) {
523
+ return "921088377005-3j1sa10vr6hj86jgmdfh2l53v3mp7lfi.apps.googleusercontent.com";
524
+ }
525
+ return undefined;
526
+ }
527
+
528
+ /**
529
+ * Dynamically loads the recaptcha script.
530
+ * We do not want to load the script on page load unless the user needs it.
531
+ * @param siteKey The reCAPTCHA site key, available from the reCAPTCHA admin page.
532
+ */
533
+ function initRecaptcha(siteKey) {
534
+ if (typeof grecaptcha === 'undefined') {
535
+ createScriptTag('https://www.google.com/recaptcha/api.js?render=' + siteKey);
536
+ }
537
+ }
538
+ /**
539
+ * Starts a request to generate a recapcha token.
540
+ * @param siteKey The reCAPTCHA site key, available from the reCAPTCHA admin page.
541
+ * @returns Promise to a recaptcha token for the current user.
542
+ */
543
+ function getRecaptcha(siteKey) {
544
+ return new Promise((resolve, reject) => {
545
+ grecaptcha.ready(() => __awaiter(this, void 0, void 0, function* () {
546
+ try {
547
+ resolve(yield grecaptcha.execute(siteKey, { action: 'submit' }));
548
+ }
549
+ catch (err) {
550
+ reject(err);
551
+ }
552
+ }));
553
+ });
554
+ }
555
+
556
+ function NewUserForm(props) {
557
+ const googleClientId = getGoogleClientId(props.googleClientId);
558
+ const recaptchaSiteKey = props.recaptchaSiteKey;
559
+ const medplum = useMedplum();
560
+ const [outcome, setOutcome] = React.useState();
561
+ const issues = getIssuesForExpression(outcome, undefined);
562
+ React.useEffect(() => initRecaptcha(recaptchaSiteKey), [recaptchaSiteKey]);
563
+ return (React__default["default"].createElement(Form, { style: { maxWidth: 400 }, onSubmit: (formData) => __awaiter(this, void 0, void 0, function* () {
564
+ try {
565
+ const recaptchaToken = yield getRecaptcha(recaptchaSiteKey);
566
+ props.handleAuthResponse(yield medplum.startNewUser({
567
+ projectId: props.projectId,
568
+ firstName: formData.firstName,
569
+ lastName: formData.lastName,
570
+ email: formData.email,
571
+ password: formData.password,
572
+ remember: formData.remember === 'true',
573
+ recaptchaSiteKey,
574
+ recaptchaToken,
575
+ }));
576
+ }
577
+ catch (err) {
578
+ setOutcome(err);
579
+ }
580
+ }) },
581
+ React__default["default"].createElement("div", { className: "medplum-center" }, props.children),
582
+ issues && (React__default["default"].createElement("div", { className: "medplum-input-error" }, issues.map((issue) => {
583
+ var _a, _b;
584
+ return (React__default["default"].createElement("div", { "data-testid": "text-field-error", key: (_a = issue.details) === null || _a === void 0 ? void 0 : _a.text }, (_b = issue.details) === null || _b === void 0 ? void 0 : _b.text));
585
+ }))),
586
+ googleClientId && (React__default["default"].createElement(React__default["default"].Fragment, null,
587
+ React__default["default"].createElement("div", { className: "medplum-signin-google-container" },
588
+ React__default["default"].createElement(GoogleButton, { googleClientId: googleClientId, handleGoogleCredential: (response) => __awaiter(this, void 0, void 0, function* () {
589
+ try {
590
+ props.handleAuthResponse(yield medplum.startGoogleLogin({
591
+ googleClientId: response.clientId,
592
+ googleCredential: response.credential,
593
+ createUser: true,
594
+ }));
595
+ }
596
+ catch (err) {
597
+ setOutcome(err);
598
+ }
599
+ }) })),
600
+ React__default["default"].createElement("div", { className: "medplum-signin-separator" }, "or"))),
601
+ React__default["default"].createElement(FormSection, { title: "First Name", htmlFor: "firstName", outcome: outcome },
602
+ React__default["default"].createElement(Input, { name: "firstName", type: "text", testid: "firstName", placeholder: "First name", required: true, autoFocus: true, outcome: outcome })),
603
+ React__default["default"].createElement(FormSection, { title: "Last Name", htmlFor: "lastName", outcome: outcome },
604
+ React__default["default"].createElement(Input, { name: "lastName", type: "text", testid: "lastName", placeholder: "Last name", required: true, outcome: outcome })),
605
+ React__default["default"].createElement(FormSection, { title: "Email", htmlFor: "email", outcome: outcome },
606
+ React__default["default"].createElement(Input, { name: "email", type: "email", testid: "email", placeholder: "name@domain.com", required: true, outcome: outcome })),
607
+ React__default["default"].createElement(FormSection, { title: "Password", htmlFor: "password", outcome: outcome },
608
+ React__default["default"].createElement(Input, { name: "password", type: "password", testid: "password", autoComplete: "off", required: true, outcome: outcome })),
609
+ React__default["default"].createElement("p", { style: { fontSize: '12px', color: '#888' } },
610
+ "By clicking submit you agree to the Medplum ",
611
+ React__default["default"].createElement("a", { href: "https://www.medplum.com/privacy" }, "Privacy\u00A0Policy"),
612
+ ' and ',
613
+ React__default["default"].createElement("a", { href: "https://www.medplum.com/terms" }, "Terms\u00A0of\u00A0Service"),
614
+ "."),
615
+ React__default["default"].createElement("p", { style: { fontSize: '12px', color: '#888' } },
616
+ "This site is protected by reCAPTCHA and the Google",
617
+ ' ',
618
+ React__default["default"].createElement("a", { href: "https://policies.google.com/privacy" }, "Privacy\u00A0Policy"),
619
+ ' and ',
620
+ React__default["default"].createElement("a", { href: "https://policies.google.com/terms" }, "Terms\u00A0of\u00A0Service"),
621
+ " apply."),
622
+ React__default["default"].createElement("div", { className: "medplum-signin-buttons" },
623
+ React__default["default"].createElement("div", null,
624
+ React__default["default"].createElement("input", { type: "checkbox", id: "remember", name: "remember", value: "true" }),
625
+ React__default["default"].createElement("label", { htmlFor: "remember" }, "Remember me")),
626
+ React__default["default"].createElement("div", null,
627
+ React__default["default"].createElement(Button, { type: "submit", testid: "submit" }, "Create account")))));
628
+ }
629
+
630
+ function RegisterForm(props) {
631
+ const { type, projectId, googleClientId, recaptchaSiteKey, onSuccess } = props;
632
+ const medplum = useMedplum();
633
+ const [login, setLogin] = React.useState(undefined);
634
+ const [outcome, setOutcome] = React.useState();
635
+ React.useEffect(() => {
636
+ if (type === 'patient' && login) {
637
+ medplum
638
+ .startNewPatient({ login, projectId: projectId })
639
+ .then((response) => medplum.processCode(response.code))
640
+ .then(() => onSuccess())
641
+ .catch((err) => setOutcome(err));
642
+ }
643
+ }, [medplum, type, projectId, login, onSuccess]);
644
+ function handleAuthResponse(response) {
645
+ if (response.code) {
646
+ medplum
647
+ .processCode(response.code)
648
+ .then(() => onSuccess())
649
+ .catch(console.log);
650
+ }
651
+ else if (response.login) {
652
+ setLogin(response.login);
653
+ }
654
+ }
655
+ return (React__default["default"].createElement(Document, { width: 450 },
656
+ outcome && React__default["default"].createElement("pre", null, JSON.stringify(outcome, null, 2)),
657
+ !login && (React__default["default"].createElement(NewUserForm, { projectId: projectId, googleClientId: googleClientId, recaptchaSiteKey: recaptchaSiteKey, handleAuthResponse: handleAuthResponse }, props.children)),
658
+ login && type === 'project' && React__default["default"].createElement(NewProjectForm, { login: login, handleAuthResponse: handleAuthResponse })));
659
+ }
660
+
661
+ function MedplumLink(props) {
662
+ const navigate = reactRouterDom.useNavigate();
663
+ let href = getHref(props.to);
664
+ if (props.suffix) {
665
+ href += '/' + props.suffix;
666
+ }
667
+ return (React__default["default"].createElement("a", { href: href, id: props.id, "aria-label": props.label, "data-testid": props.testid || 'link', className: props.className, onClick: (e) => {
668
+ killEvent(e);
669
+ if (props.onClick) {
670
+ props.onClick();
671
+ }
672
+ else if (props.to) {
673
+ navigate(href);
674
+ }
675
+ } }, props.children));
676
+ }
677
+ function getHref(to) {
678
+ if (to) {
679
+ if (typeof to === 'string') {
680
+ return getStringHref(to);
681
+ }
682
+ else if ('resourceType' in to) {
683
+ return getResourceHref(to);
684
+ }
685
+ else if ('reference' in to) {
686
+ return getReferenceHref(to);
687
+ }
688
+ }
689
+ return '#';
690
+ }
691
+ function getStringHref(to) {
692
+ if (to.startsWith('http://') || to.startsWith('https://') || to.startsWith('/')) {
693
+ return to;
694
+ }
695
+ return '/' + to;
696
+ }
697
+ function getResourceHref(to) {
698
+ return `/${to.resourceType}/${to.id}`;
699
+ }
700
+ function getReferenceHref(to) {
701
+ return `/${to.reference}`;
702
+ }
703
+
704
+ function AuthenticationForm(props) {
705
+ const medplum = useMedplum();
706
+ const googleClientId = getGoogleClientId(props.googleClientId);
707
+ const [outcome, setOutcome] = React.useState();
708
+ const issues = getIssuesForExpression(outcome, undefined);
709
+ return (React__default["default"].createElement(Form, { style: { maxWidth: 400 }, onSubmit: (formData) => {
710
+ medplum
711
+ .startLogin({
712
+ projectId: props.projectId,
713
+ clientId: props.clientId,
714
+ scope: props.scope,
715
+ nonce: props.nonce,
716
+ codeChallenge: props.codeChallenge,
717
+ codeChallengeMethod: props.codeChallengeMethod,
718
+ email: formData.email,
719
+ password: formData.password,
720
+ remember: formData.remember === 'true',
721
+ })
722
+ .then(props.handleAuthResponse)
723
+ .catch(setOutcome);
724
+ } },
725
+ React__default["default"].createElement("div", { className: "medplum-center" }, props.children),
726
+ issues && (React__default["default"].createElement("div", { className: "medplum-input-error" }, issues.map((issue) => {
727
+ var _a, _b;
728
+ return (React__default["default"].createElement("div", { "data-testid": "text-field-error", key: (_a = issue.details) === null || _a === void 0 ? void 0 : _a.text }, (_b = issue.details) === null || _b === void 0 ? void 0 : _b.text));
729
+ }))),
730
+ googleClientId && (React__default["default"].createElement(React__default["default"].Fragment, null,
731
+ React__default["default"].createElement("div", { className: "medplum-signin-google-container" },
732
+ React__default["default"].createElement(GoogleButton, { googleClientId: googleClientId, handleGoogleCredential: (response) => {
733
+ medplum
734
+ .startGoogleLogin({
735
+ projectId: props.projectId,
736
+ clientId: props.clientId,
737
+ scope: props.scope,
738
+ nonce: props.nonce,
739
+ codeChallenge: props.codeChallenge,
740
+ codeChallengeMethod: props.codeChallengeMethod,
741
+ googleClientId: response.clientId,
742
+ googleCredential: response.credential,
743
+ })
744
+ .then(props.handleAuthResponse)
745
+ .catch(setOutcome);
746
+ } })),
747
+ React__default["default"].createElement("div", { className: "medplum-signin-separator" }, "or"))),
748
+ React__default["default"].createElement(FormSection, { title: "Email", htmlFor: "email", outcome: outcome },
749
+ React__default["default"].createElement(Input, { name: "email", type: "email", testid: "email", required: true, autoFocus: true, outcome: outcome })),
750
+ React__default["default"].createElement(FormSection, { title: "Password", htmlFor: "password", outcome: outcome },
751
+ React__default["default"].createElement(Input, { name: "password", type: "password", testid: "password", autoComplete: "off", required: true, outcome: outcome })),
752
+ React__default["default"].createElement("div", { className: "medplum-signin-buttons" },
753
+ (props.onForgotPassword || props.onRegister) && (React__default["default"].createElement("div", null,
754
+ props.onForgotPassword && (React__default["default"].createElement(MedplumLink, { testid: "forgotpassword", onClick: props.onForgotPassword }, "Forgot password")),
755
+ props.onRegister && (React__default["default"].createElement(MedplumLink, { testid: "register", onClick: props.onRegister }, "Register")))),
756
+ React__default["default"].createElement("div", null,
757
+ React__default["default"].createElement("input", { type: "checkbox", id: "remember", name: "remember", value: "true" }),
758
+ React__default["default"].createElement("label", { htmlFor: "remember" }, "Remember me")),
759
+ React__default["default"].createElement("div", null,
760
+ React__default["default"].createElement(Button, { type: "submit", testid: "submit" }, "Sign in")))));
761
+ }
762
+
763
+ const system = {
764
+ resourceType: 'Device',
765
+ id: 'system',
766
+ deviceName: [
767
+ {
768
+ name: 'System',
769
+ },
770
+ ],
771
+ };
772
+ /**
773
+ * React Hook to use a FHIR reference.
774
+ * Handles the complexity of resolving references and caching resources.
775
+ * @param value The resource or reference to resource.
776
+ * @returns The resolved resource.
777
+ */
778
+ function useResource(value) {
779
+ const medplum = useMedplum();
780
+ const [resource, setResource] = React.useState(getInitialResource(medplum, value));
781
+ React.useEffect(() => {
782
+ let subscribed = true;
783
+ if (!resource && value && 'reference' in value && value.reference) {
784
+ medplum
785
+ .readReference(value)
786
+ .then((r) => {
787
+ if (subscribed) {
788
+ setResource(r);
789
+ }
790
+ })
791
+ .catch(() => setResource(undefined));
792
+ }
793
+ return (() => (subscribed = false));
794
+ }, [medplum, resource, value]);
795
+ return resource;
796
+ }
797
+ /**
798
+ * Returns the initial resource value based on the input value.
799
+ * If the input value is a resource, returns the resource.
800
+ * If the input value is a reference to system, returns the system resource.
801
+ * If the input value is a reference to a resource available in the cache, returns the resource.
802
+ * Otherwise, returns undefined.
803
+ * @param medplum The medplum client.
804
+ * @param value The resource or reference to resource.
805
+ * @returns An initial resource if available; undefined otherwise.
806
+ */
807
+ function getInitialResource(medplum, value) {
808
+ if (!value) {
809
+ return undefined;
810
+ }
811
+ if ('resourceType' in value) {
812
+ return value;
813
+ }
814
+ if ('reference' in value) {
815
+ if (value.reference === 'system') {
816
+ return system;
817
+ }
818
+ return medplum.getCachedReference(value);
819
+ }
820
+ return undefined;
821
+ }
822
+
823
+ function Avatar(props) {
824
+ var _a, _b;
825
+ const resource = useResource(props.value);
826
+ const className = props.size ? 'medplum-avatar ' + props.size : 'medplum-avatar';
827
+ const text = resource ? core.getDisplayString(resource) : (_a = props.alt) !== null && _a !== void 0 ? _a : '';
828
+ const initials = text && getInitials(text);
829
+ const imageUrl = (_b = (resource && core.getImageSrc(resource))) !== null && _b !== void 0 ? _b : props.src;
830
+ const innerContent = imageUrl ? React__default["default"].createElement("img", { src: imageUrl, alt: text }) : initials;
831
+ return (React__default["default"].createElement("div", { className: className, style: { backgroundColor: props.color }, "data-testid": "avatar" }, props.link && resource ? React__default["default"].createElement(MedplumLink, { to: resource }, innerContent) : innerContent));
832
+ }
833
+ function getInitials(text) {
834
+ return text
835
+ .split(' ')
836
+ .map((n) => n[0])
837
+ .join('');
838
+ }
839
+
840
+ function ChooseProfileForm(props) {
841
+ const medplum = useMedplum();
842
+ return (React__default["default"].createElement("div", null,
843
+ React__default["default"].createElement("div", { className: "medplum-center" },
844
+ React__default["default"].createElement(Logo, { size: 32 }),
845
+ React__default["default"].createElement("h1", null, "Choose profile")),
846
+ props.memberships.map((membership) => {
847
+ var _a, _b, _c;
848
+ return (React__default["default"].createElement("div", { className: "medplum-nav-menu-profile", key: membership.id, onClick: () => {
849
+ medplum
850
+ .post('auth/profile', {
851
+ login: props.login,
852
+ profile: membership.id,
853
+ })
854
+ .then(props.handleAuthResponse)
855
+ .catch(console.log);
856
+ } },
857
+ React__default["default"].createElement("div", { className: "medplum-nav-menu-profile-icon" },
858
+ React__default["default"].createElement(Avatar, { alt: (_a = membership.profile) === null || _a === void 0 ? void 0 : _a.display })),
859
+ React__default["default"].createElement("div", { className: "medplum-nav-menu-profile-label" }, (_b = membership.profile) === null || _b === void 0 ? void 0 :
860
+ _b.display,
861
+ React__default["default"].createElement("div", { className: "medplum-nav-menu-profile-help-text" }, (_c = membership.project) === null || _c === void 0 ? void 0 : _c.display))));
862
+ })));
863
+ }
864
+
865
+ function SignInForm(props) {
866
+ const medplum = useMedplum();
867
+ const [login, setLogin] = React.useState(undefined);
868
+ const [memberships, setMemberships] = React.useState(undefined);
869
+ function handleAuthResponse(response) {
870
+ if (response.login) {
871
+ setLogin(response.login);
872
+ }
873
+ if (response.memberships) {
874
+ setMemberships(response.memberships);
875
+ }
876
+ if (response.code) {
877
+ if (props.onCode) {
878
+ props.onCode(response.code);
879
+ }
880
+ else {
881
+ medplum
882
+ .processCode(response.code)
883
+ .then(() => {
884
+ if (props.onSuccess) {
885
+ props.onSuccess();
886
+ }
887
+ })
888
+ .catch(console.log);
889
+ }
890
+ }
891
+ }
892
+ return (React__default["default"].createElement(Document, { width: 450 }, (() => {
893
+ if (!login) {
894
+ return (React__default["default"].createElement(AuthenticationForm, { projectId: props.projectId, clientId: props.clientId, scope: props.scope, nonce: props.nonce, googleClientId: props.googleClientId, codeChallenge: props.codeChallenge, codeChallengeMethod: props.codeChallengeMethod, onForgotPassword: props.onForgotPassword, onRegister: props.onRegister, handleAuthResponse: handleAuthResponse }, props.children));
895
+ }
896
+ else if (memberships) {
897
+ return React__default["default"].createElement(ChooseProfileForm, { login: login, memberships: memberships, handleAuthResponse: handleAuthResponse });
898
+ }
899
+ else if (props.projectId === 'new') {
900
+ return React__default["default"].createElement(NewProjectForm, { login: login, handleAuthResponse: handleAuthResponse });
901
+ }
902
+ else {
903
+ return React__default["default"].createElement("div", null, "Success");
904
+ }
905
+ })()));
906
+ }
907
+
332
908
  function Autocomplete(props) {
333
909
  var _a, _b;
334
910
  const inputRef = React.useRef(null);
@@ -576,138 +1152,33 @@
576
1152
  * @param option The drop down option.
577
1153
  */
578
1154
  function handleDropDownClick(e, option) {
579
- killEvent(e);
580
- addOption(option);
581
- }
582
- /**
583
- * Dismisses the drop down menu after a slight delay.
584
- */
585
- function dismissOnDelay() {
586
- window.setTimeout(() => {
587
- setDropDownVisible(false);
588
- }, 200);
589
- }
590
- const baseClassName = (_b = props.className) !== null && _b !== void 0 ? _b : 'medplum-autocomplete-container';
591
- return (React__default["default"].createElement("div", { "data-testid": "autocomplete", className: baseClassName + (focused ? ' focused' : ''), onClick: () => handleClick() },
592
- React__default["default"].createElement("ul", { onClick: () => handleClick() },
593
- values.map((value) => (React__default["default"].createElement("li", { key: props.getId(value), "data-testid": "selected", className: "medplum-autocomplete-item choice" }, props.getDisplay(value)))),
594
- React__default["default"].createElement("li", { className: "medplum-autocomplete-item" },
595
- React__default["default"].createElement("input", { type: "text", autoFocus: props.autofocus, placeholder: values.length === 0 ? props.placeholder : undefined, autoComplete: "off", autoCapitalize: "off", spellCheck: "true", onFocus: () => handleFocus(), onBlur: () => handleBlur(), onChange: () => handleInput(), onInput: () => handleInput(), onKeyDown: (e) => handleKeyDown(e), ref: inputRef, "data-testid": "input-element" }))),
596
- dropDownVisible && (React__default["default"].createElement("div", { className: "medplum-autocomplete", "data-testid": "dropdown" },
597
- options.map((option, index) => (React__default["default"].createElement("div", { key: props.getId(option), className: index === selectedIndex
598
- ? 'medplum-autocomplete-row medplum-autocomplete-active'
599
- : 'medplum-autocomplete-row', onMouseOver: (e) => handleDropDownHover(e, index), onClick: (e) => handleDropDownClick(e, option) },
600
- props.getIcon && React__default["default"].createElement("div", { className: "medplum-autocomplete-icon" }, props.getIcon(option)),
601
- React__default["default"].createElement("div", { className: "medplum-autocomplete-label" },
602
- props.getDisplay(option),
603
- props.getHelpText && React__default["default"].createElement("div", { className: "medplum-autocomplete-help-text" }, props.getHelpText(option)))))),
604
- props.onCreateNew && (React__default["default"].createElement("div", { className: "medplum-autocomplete-row", onClick: props.onCreateNew },
605
- React__default["default"].createElement("div", { className: "medplum-autocomplete-label" }, "Create new...")))))));
606
- }
607
-
608
- function MedplumLink(props) {
609
- const navigate = reactRouterDom.useNavigate();
610
- let href = '#';
611
- if (props.to) {
612
- if (typeof props.to === 'string') {
613
- href = props.to;
614
- }
615
- else if ('resourceType' in props.to) {
616
- href = `/${props.to.resourceType}/${props.to.id}`;
617
- }
618
- else if ('reference' in props.to) {
619
- href = `/${props.to.reference}`;
620
- }
621
- if (props.suffix) {
622
- href += '/' + props.suffix;
623
- }
624
- }
625
- return (React__default["default"].createElement("a", { href: href, id: props.id, "aria-label": props.label, "data-testid": props.testid || 'link', className: props.className, onClick: (e) => {
626
- killEvent(e);
627
- if (props.onClick) {
628
- props.onClick();
629
- }
630
- else if (props.to) {
631
- navigate(href);
632
- }
633
- } }, props.children));
634
- }
635
-
636
- const system = {
637
- resourceType: 'Device',
638
- id: 'system',
639
- deviceName: [
640
- {
641
- name: 'System',
642
- },
643
- ],
644
- };
645
- /**
646
- * React Hook to use a FHIR reference.
647
- * Handles the complexity of resolving references and caching resources.
648
- * @param value The resource or reference to resource.
649
- * @returns The resolved resource.
650
- */
651
- function useResource(value) {
652
- const medplum = useMedplum();
653
- const [resource, setResource] = React.useState(getInitialResource(medplum, value));
654
- React.useEffect(() => {
655
- let subscribed = true;
656
- if (!resource && value && 'reference' in value && value.reference) {
657
- medplum
658
- .readReference(value)
659
- .then((r) => {
660
- if (subscribed) {
661
- setResource(r);
662
- }
663
- })
664
- .catch(() => setResource(undefined));
665
- }
666
- return (() => (subscribed = false));
667
- }, [medplum, resource, value]);
668
- return resource;
669
- }
670
- /**
671
- * Returns the initial resource value based on the input value.
672
- * If the input value is a resource, returns the resource.
673
- * If the input value is a reference to system, returns the system resource.
674
- * If the input value is a reference to a resource available in the cache, returns the resource.
675
- * Otherwise, returns undefined.
676
- * @param medplum The medplum client.
677
- * @param value The resource or reference to resource.
678
- * @returns An initial resource if available; undefined otherwise.
679
- */
680
- function getInitialResource(medplum, value) {
681
- if (!value) {
682
- return undefined;
683
- }
684
- if ('resourceType' in value) {
685
- return value;
686
- }
687
- if ('reference' in value) {
688
- if (value.reference === 'system') {
689
- return system;
690
- }
691
- return medplum.getCachedReference(value);
692
- }
693
- return undefined;
694
- }
695
-
696
- function Avatar(props) {
697
- var _a, _b;
698
- const resource = useResource(props.value);
699
- const className = props.size ? 'medplum-avatar ' + props.size : 'medplum-avatar';
700
- const text = resource ? core.getDisplayString(resource) : (_a = props.alt) !== null && _a !== void 0 ? _a : '';
701
- const initials = text && getInitials(text);
702
- const imageUrl = (_b = (resource && core.getImageSrc(resource))) !== null && _b !== void 0 ? _b : props.src;
703
- const innerContent = imageUrl ? React__default["default"].createElement("img", { src: imageUrl, alt: text }) : initials;
704
- return (React__default["default"].createElement("div", { className: className, style: { backgroundColor: props.color }, "data-testid": "avatar" }, props.link && resource ? React__default["default"].createElement(MedplumLink, { to: resource }, innerContent) : innerContent));
705
- }
706
- function getInitials(text) {
707
- return text
708
- .split(' ')
709
- .map((n) => n[0])
710
- .join('');
1155
+ killEvent(e);
1156
+ addOption(option);
1157
+ }
1158
+ /**
1159
+ * Dismisses the drop down menu after a slight delay.
1160
+ */
1161
+ function dismissOnDelay() {
1162
+ window.setTimeout(() => {
1163
+ setDropDownVisible(false);
1164
+ }, 200);
1165
+ }
1166
+ const baseClassName = (_b = props.className) !== null && _b !== void 0 ? _b : 'medplum-autocomplete-container';
1167
+ return (React__default["default"].createElement("div", { "data-testid": "autocomplete", className: baseClassName + (focused ? ' focused' : ''), onClick: () => handleClick() },
1168
+ React__default["default"].createElement("ul", { onClick: () => handleClick() },
1169
+ values.map((value) => (React__default["default"].createElement("li", { key: props.getId(value), "data-testid": "selected", className: "medplum-autocomplete-item choice" }, props.getDisplay(value)))),
1170
+ React__default["default"].createElement("li", { className: "medplum-autocomplete-item" },
1171
+ React__default["default"].createElement("input", { type: "text", autoFocus: props.autofocus, placeholder: values.length === 0 ? props.placeholder : undefined, autoComplete: "off", autoCapitalize: "off", spellCheck: "true", onFocus: () => handleFocus(), onBlur: () => handleBlur(), onChange: () => handleInput(), onInput: () => handleInput(), onKeyDown: (e) => handleKeyDown(e), ref: inputRef, "data-testid": "input-element" }))),
1172
+ dropDownVisible && (React__default["default"].createElement("div", { className: "medplum-autocomplete", "data-testid": "dropdown" },
1173
+ options.map((option, index) => (React__default["default"].createElement("div", { key: props.getId(option), className: index === selectedIndex
1174
+ ? 'medplum-autocomplete-row medplum-autocomplete-active'
1175
+ : 'medplum-autocomplete-row', onMouseOver: (e) => handleDropDownHover(e, index), onClick: (e) => handleDropDownClick(e, option) },
1176
+ props.getIcon && React__default["default"].createElement("div", { className: "medplum-autocomplete-icon" }, props.getIcon(option)),
1177
+ React__default["default"].createElement("div", { className: "medplum-autocomplete-label" },
1178
+ props.getDisplay(option),
1179
+ props.getHelpText && React__default["default"].createElement("div", { className: "medplum-autocomplete-help-text" }, props.getHelpText(option)))))),
1180
+ props.onCreateNew && (React__default["default"].createElement("div", { className: "medplum-autocomplete-row", onClick: props.onCreateNew },
1181
+ React__default["default"].createElement("div", { className: "medplum-autocomplete-label" }, "Create new...")))))));
711
1182
  }
712
1183
 
713
1184
  function CheckboxFormSection(props) {
@@ -728,19 +1199,6 @@
728
1199
  'modifierExtension',
729
1200
  ];
730
1201
 
731
- function FormSection(props) {
732
- const issues = getIssuesForExpression(props.outcome, props.htmlFor);
733
- const invalid = issues && issues.length > 0;
734
- return (React__default["default"].createElement("fieldset", { className: "medplum-form-section" },
735
- props.title && React__default["default"].createElement("label", { htmlFor: props.htmlFor }, props.title),
736
- props.description && React__default["default"].createElement("p", null, props.description),
737
- props.children,
738
- invalid && (React__default["default"].createElement("div", { id: props.htmlFor + '-errors', className: "medplum-input-error" }, issues === null || issues === void 0 ? void 0 : issues.map((issue) => {
739
- var _a, _b;
740
- return (React__default["default"].createElement("div", { "data-testid": "text-field-error", key: (_a = issue.details) === null || _a === void 0 ? void 0 : _a.text }, (_b = issue.details) === null || _b === void 0 ? void 0 : _b.text));
741
- })))));
742
- }
743
-
744
1202
  function ResourceForm(props) {
745
1203
  const medplum = useMedplum();
746
1204
  const defaultValue = useResource(props.defaultValue);
@@ -1473,31 +1931,6 @@
1473
1931
  React__default["default"].createElement(QuantityInput, { name: props.name + '-denominator', defaultValue: value === null || value === void 0 ? void 0 : value.denominator, onChange: (v) => setValueWrapper(Object.assign(Object.assign({}, value), { denominator: v })) })));
1474
1932
  }
1475
1933
 
1476
- /******************************************************************************
1477
- Copyright (c) Microsoft Corporation.
1478
-
1479
- Permission to use, copy, modify, and/or distribute this software for any
1480
- purpose with or without fee is hereby granted.
1481
-
1482
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
1483
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
1484
- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
1485
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
1486
- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
1487
- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
1488
- PERFORMANCE OF THIS SOFTWARE.
1489
- ***************************************************************************** */
1490
-
1491
- function __awaiter(thisArg, _arguments, P, generator) {
1492
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
1493
- return new (P || (P = Promise))(function (resolve, reject) {
1494
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
1495
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
1496
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
1497
- step((generator = generator.apply(thisArg, _arguments || [])).next());
1498
- });
1499
- }
1500
-
1501
1934
  function ResourceName(props) {
1502
1935
  const resource = useResource(props.value);
1503
1936
  if (!resource) {
@@ -1985,63 +2418,6 @@
1985
2418
  return React__default["default"].createElement(RangeDisplay, { value: range });
1986
2419
  }
1987
2420
 
1988
- /**
1989
- * Parses an HTML form and returns the result as a JavaScript object.
1990
- * @param form The HTML form element.
1991
- */
1992
- function parseForm(form) {
1993
- const result = {};
1994
- for (const element of Array.from(form.elements)) {
1995
- if (element instanceof HTMLInputElement) {
1996
- parseInputElement(result, element);
1997
- }
1998
- else if (element instanceof HTMLTextAreaElement) {
1999
- result[element.name] = element.value;
2000
- }
2001
- else if (element instanceof HTMLSelectElement) {
2002
- parseSelectElement(result, element);
2003
- }
2004
- }
2005
- return result;
2006
- }
2007
- /**
2008
- * Parses an HTML input element.
2009
- * Sets the name/value pair in the result,
2010
- * but only if the element is enabled and checked.
2011
- * @param el The input element.
2012
- * @param result The result builder.
2013
- */
2014
- function parseInputElement(result, el) {
2015
- if (el.disabled) {
2016
- // Ignore disabled elements
2017
- return;
2018
- }
2019
- if ((el.type === 'checkbox' || el.type === 'radio') && !el.checked) {
2020
- // Ignore unchecked radio or checkbox elements
2021
- return;
2022
- }
2023
- result[el.name] = el.value;
2024
- }
2025
- /**
2026
- * Parses an HTML select element.
2027
- * Sets the name/value pair if one is selected.
2028
- * @param result The result builder.
2029
- * @param el The select element.
2030
- */
2031
- function parseSelectElement(result, el) {
2032
- result[el.name] = el.value;
2033
- }
2034
-
2035
- function Form(props) {
2036
- return (React__default["default"].createElement("form", { style: props.style, "data-testid": props.testid, onSubmit: (e) => {
2037
- e.preventDefault();
2038
- const formData = parseForm(e.target);
2039
- if (props.onSubmit) {
2040
- props.onSubmit(formData);
2041
- }
2042
- } }, props.children));
2043
- }
2044
-
2045
2421
  function Loading() {
2046
2422
  return (React__default["default"].createElement("div", { role: "progressbar", "aria-busy": "true", className: "medplum-loading" },
2047
2423
  React__default["default"].createElement("div", { className: "medplum-loading-container" },
@@ -2331,7 +2707,9 @@
2331
2707
  }
2332
2708
  if (bundle.entry) {
2333
2709
  for (const entry of bundle.entry) {
2334
- newItems.push(entry.resource);
2710
+ if (entry.resource) {
2711
+ newItems.push(entry.resource);
2712
+ }
2335
2713
  }
2336
2714
  }
2337
2715
  }
@@ -2518,11 +2896,6 @@
2518
2896
  }) }));
2519
2897
  }
2520
2898
 
2521
- function Document(props) {
2522
- return (React__default["default"].createElement("main", { className: "medplum-document" },
2523
- React__default["default"].createElement("article", { style: { maxWidth: props.width } }, props.children)));
2524
- }
2525
-
2526
2899
  function EncounterTimeline(props) {
2527
2900
  return (React__default["default"].createElement(ResourceTimeline, { value: props.encounter, buildSearchRequests: (resource) => ({
2528
2901
  resourceType: 'Bundle',
@@ -3852,9 +4225,9 @@
3852
4225
  props.onBulk && (React__default["default"].createElement(Button, { size: "small", onClick: () => props.onBulk(Object.keys(state.selected)) }, "Bulk..."))),
3853
4226
  lastResult && (React__default["default"].createElement("div", null,
3854
4227
  React__default["default"].createElement("span", { className: "medplum-search-summary" },
3855
- getStart(search, lastResult.total),
4228
+ getStart$1(search, lastResult.total),
3856
4229
  "-",
3857
- getEnd(search, lastResult.total),
4230
+ getEnd$1(search, lastResult.total),
3858
4231
  " of",
3859
4232
  ' ', (_d = lastResult.total) === null || _d === void 0 ? void 0 :
3860
4233
  _d.toLocaleString()),
@@ -3921,11 +4294,11 @@
3921
4294
  return (React__default["default"].createElement("svg", { xmlns: "http://www.w3.org/2000/svg", className: "h-6 w-6", fill: "none", viewBox: "0 0 24 24", stroke: "rgba(0, 0, 0, 0.3)", strokeWidth: 2 },
3922
4295
  React__default["default"].createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M4 6h16M4 12h16m-7 6h7" })));
3923
4296
  }
3924
- function getStart(search, total) {
4297
+ function getStart$1(search, total) {
3925
4298
  var _a;
3926
4299
  return Math.min(total, ((_a = search.offset) !== null && _a !== void 0 ? _a : 0) + 1);
3927
4300
  }
3928
- function getEnd(search, total) {
4301
+ function getEnd$1(search, total) {
3929
4302
  var _a, _b;
3930
4303
  return Math.min(total, (((_a = search.offset) !== null && _a !== void 0 ? _a : 0) + 1) * ((_b = search.count) !== null && _b !== void 0 ? _b : core.DEFAULT_SEARCH_COUNT));
3931
4304
  }
@@ -4301,16 +4674,6 @@
4301
4674
  React__default["default"].createElement(MedplumLink, { to: "/changepassword" }, "Change password"))))));
4302
4675
  }
4303
4676
 
4304
- function Logo(props) {
4305
- return (React__default["default"].createElement("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 491 491", style: { width: props.size, height: props.size } },
4306
- React__default["default"].createElement("title", null, "Medplum Logo"),
4307
- React__default["default"].createElement("path", { fill: props.fill || '#ad7136', d: "M282 67c6-16 16-29 29-40L289 0c-22 17-37 41-43 68l17 23 19-24z" }),
4308
- React__default["default"].createElement("path", { fill: props.fill || '#654b87', d: "M311 63c-17 0-33 4-48 11-16-7-32-11-49-11-87 0-158 96-158 214s71 214 158 214c17 0 33-4 49-11 15 7 31 11 48 11 87 0 158-96 158-214S398 63 311 63z" }),
4309
- React__default["default"].createElement("path", { fill: props.fill || '#463068', d: "M231 489l-17 2c-87 0-158-96-158-214S127 63 214 63l17 1c-39 12-70 102-70 213s31 201 70 212z" }),
4310
- React__default["default"].createElement("path", { fill: props.fill || '#70d65b', d: "M207 220a176 176 0 01-177 43A176 176 0 01251 43l1 5c17 59 2 125-45 172z" }),
4311
- React__default["default"].createElement("path", { fill: props.fill || '#58b741', d: "M252 48A421 421 0 0057 270l-27-7A176 176 0 01251 43l1 5z" })));
4312
- }
4313
-
4314
4677
  const searches = [
4315
4678
  '$/_history',
4316
4679
  'Communication?subject=$',
@@ -4678,7 +5041,7 @@
4678
5041
  } },
4679
5042
  questionnaire.title && React__default["default"].createElement("h1", null, questionnaire.title),
4680
5043
  questionnaire.item && React__default["default"].createElement(QuestionnaireFormItemArray, { items: questionnaire.item, onChange: setItems }),
4681
- React__default["default"].createElement(Button, { type: "submit", size: "large" }, "OK")));
5044
+ React__default["default"].createElement(Button, { type: "submit", size: "large" }, props.submitButtonText || 'OK')));
4682
5045
  }
4683
5046
  function QuestionnaireFormItemArray(props) {
4684
5047
  const [responseItems, setResponseItems] = React.useState(buildInitialResponseItems(props.items));
@@ -4984,258 +5347,69 @@
4984
5347
  options.map((option) => {
4985
5348
  const [propertyValue, propertyType] = getValueAndType({ type: 'QuestionnaireItemAnswerOption', value: option }, 'value');
4986
5349
  return (React__default["default"].createElement("div", { key: option.id, style: {
4987
- display: 'flex',
4988
- flexDirection: 'row',
4989
- justifyContent: 'space-between',
4990
- alignItems: 'center',
4991
- width: '80%',
4992
- } },
4993
- React__default["default"].createElement("div", null,
4994
- React__default["default"].createElement(ResourcePropertyInput, { key: option.id, name: "value[x]", property: property, defaultPropertyType: propertyType, defaultValue: propertyValue, onChange: (newValue, propName) => {
4995
- const newOptions = [...options];
4996
- const index = newOptions.findIndex((o) => o.id === option.id);
4997
- newOptions[index] = { id: option.id, [propName]: newValue };
4998
- props.onChange(newOptions);
4999
- } })),
5000
- React__default["default"].createElement("div", null,
5001
- React__default["default"].createElement("a", { href: "#", onClick: (e) => {
5002
- killEvent(e);
5003
- props.onChange(options.filter((o) => o.id !== option.id));
5004
- } }, "Remove"))));
5005
- }),
5006
- React__default["default"].createElement("a", { href: "#", onClick: (e) => {
5007
- killEvent(e);
5008
- props.onChange([
5009
- ...options,
5010
- {
5011
- id: generateId(),
5012
- },
5013
- ]);
5014
- } }, "Add choice")));
5015
- }
5016
- let nextLinkId = 1;
5017
- let nextId = 1;
5018
- /**
5019
- * Generates a link ID for an item.
5020
- * Link IDs are required properties on QuestionnaireItem objects.
5021
- * @return A unique link ID.
5022
- */
5023
- function generateLinkId(prefix) {
5024
- return prefix + nextLinkId++;
5025
- }
5026
- /**
5027
- * Generates a unique ID.
5028
- * React needs unique IDs for components for rendering performance.
5029
- * All of the important components in the questionnaire builder have id properties for this:
5030
- * Questionnaire, QuestionnaireItem, and QuestionnaireItemAnswerOption.
5031
- * @return A unique key.
5032
- */
5033
- function generateId() {
5034
- return 'id-' + nextId++;
5035
- }
5036
- function ensureQuestionnaireKeys(questionnaire) {
5037
- return Object.assign(Object.assign({}, questionnaire), { id: questionnaire.id || generateId(), item: ensureQuestionnaireItemKeys(questionnaire.item) });
5038
- }
5039
- function ensureQuestionnaireItemKeys(items) {
5040
- if (!items) {
5041
- return undefined;
5042
- }
5043
- return items.map((item) => (Object.assign(Object.assign({}, item), { id: item.id || generateId(), item: ensureQuestionnaireItemKeys(item.item), answerOption: ensureQuestionnaireOptionKeys(item.answerOption) })));
5044
- }
5045
- function ensureQuestionnaireOptionKeys(options) {
5046
- if (!options) {
5047
- return undefined;
5048
- }
5049
- return options.map((option) => (Object.assign(Object.assign({}, option), { id: option.id || generateId() })));
5050
- }
5051
-
5052
- /**
5053
- * Dynamically creates a script tag for the specified JavaScript file.
5054
- * @param src The JavaScript file URL.
5055
- */
5056
- function createScriptTag(src, onload) {
5057
- const head = document.getElementsByTagName('head')[0];
5058
- const script = document.createElement('script');
5059
- script.async = true;
5060
- script.src = src;
5061
- script.onload = onload || null;
5062
- head.appendChild(script);
5063
- }
5064
-
5065
- function GoogleButton(props) {
5066
- const medplum = useMedplum();
5067
- const { googleClientId, handleGoogleCredential } = props;
5068
- const parentRef = React.useRef(null);
5069
- const [scriptLoaded, setScriptLoaded] = React.useState(typeof google !== 'undefined');
5070
- const [initialized, setInitialized] = React.useState(false);
5071
- const [buttonRendered, setButtonRendered] = React.useState(false);
5072
- React.useEffect(() => {
5073
- if (typeof google === 'undefined') {
5074
- createScriptTag('https://accounts.google.com/gsi/client', () => setScriptLoaded(true));
5075
- return;
5076
- }
5077
- if (!initialized) {
5078
- google.accounts.id.initialize({
5079
- client_id: googleClientId,
5080
- callback: handleGoogleCredential,
5081
- });
5082
- setInitialized(true);
5083
- }
5084
- if (parentRef.current && !buttonRendered) {
5085
- google.accounts.id.renderButton(parentRef.current, {});
5086
- setButtonRendered(true);
5087
- }
5088
- }, [medplum, googleClientId, initialized, scriptLoaded, parentRef, buttonRendered, handleGoogleCredential]);
5089
- if (!googleClientId) {
5090
- return null;
5091
- }
5092
- return React__default["default"].createElement("div", { ref: parentRef });
5093
- }
5094
- function getGoogleClientId(clientId) {
5095
- var _a, _b;
5096
- if (clientId) {
5097
- return clientId;
5098
- }
5099
- const origin = window.location.protocol + '//' + window.location.host;
5100
- const authorizedOrigins = (_b = (_a = "http://localhost:3000,http://127.0.0.1:3000,http://localhost:6006,http://127.0.0.1:6006,https://app.medplum.com,https://docs.medplum.com") === null || _a === void 0 ? void 0 : _a.split(',')) !== null && _b !== void 0 ? _b : [];
5101
- if (authorizedOrigins.includes(origin)) {
5102
- return "921088377005-3j1sa10vr6hj86jgmdfh2l53v3mp7lfi.apps.googleusercontent.com";
5103
- }
5104
- return undefined;
5350
+ display: 'flex',
5351
+ flexDirection: 'row',
5352
+ justifyContent: 'space-between',
5353
+ alignItems: 'center',
5354
+ width: '80%',
5355
+ } },
5356
+ React__default["default"].createElement("div", null,
5357
+ React__default["default"].createElement(ResourcePropertyInput, { key: option.id, name: "value[x]", property: property, defaultPropertyType: propertyType, defaultValue: propertyValue, onChange: (newValue, propName) => {
5358
+ const newOptions = [...options];
5359
+ const index = newOptions.findIndex((o) => o.id === option.id);
5360
+ newOptions[index] = { id: option.id, [propName]: newValue };
5361
+ props.onChange(newOptions);
5362
+ } })),
5363
+ React__default["default"].createElement("div", null,
5364
+ React__default["default"].createElement("a", { href: "#", onClick: (e) => {
5365
+ killEvent(e);
5366
+ props.onChange(options.filter((o) => o.id !== option.id));
5367
+ } }, "Remove"))));
5368
+ }),
5369
+ React__default["default"].createElement("a", { href: "#", onClick: (e) => {
5370
+ killEvent(e);
5371
+ props.onChange([
5372
+ ...options,
5373
+ {
5374
+ id: generateId(),
5375
+ },
5376
+ ]);
5377
+ } }, "Add choice")));
5105
5378
  }
5106
-
5379
+ let nextLinkId = 1;
5380
+ let nextId = 1;
5107
5381
  /**
5108
- * Dynamically loads the recaptcha script.
5109
- * We do not want to load the script on page load unless the user needs it.
5110
- * @param siteKey The reCAPTCHA site key, available from the reCAPTCHA admin page.
5382
+ * Generates a link ID for an item.
5383
+ * Link IDs are required properties on QuestionnaireItem objects.
5384
+ * @return A unique link ID.
5111
5385
  */
5112
- function initRecaptcha(siteKey) {
5113
- if (typeof grecaptcha === 'undefined') {
5114
- createScriptTag('https://www.google.com/recaptcha/api.js?render=' + siteKey);
5115
- }
5386
+ function generateLinkId(prefix) {
5387
+ return prefix + nextLinkId++;
5116
5388
  }
5117
5389
  /**
5118
- * Starts a request to generate a recapcha token.
5119
- * @param siteKey The reCAPTCHA site key, available from the reCAPTCHA admin page.
5120
- * @returns Promise to a recaptcha token for the current user.
5390
+ * Generates a unique ID.
5391
+ * React needs unique IDs for components for rendering performance.
5392
+ * All of the important components in the questionnaire builder have id properties for this:
5393
+ * Questionnaire, QuestionnaireItem, and QuestionnaireItemAnswerOption.
5394
+ * @return A unique key.
5121
5395
  */
5122
- function getRecaptcha(siteKey) {
5123
- return new Promise((resolve, reject) => {
5124
- grecaptcha.ready(() => __awaiter(this, void 0, void 0, function* () {
5125
- try {
5126
- resolve(yield grecaptcha.execute(siteKey, { action: 'submit' }));
5127
- }
5128
- catch (err) {
5129
- reject(err);
5130
- }
5131
- }));
5132
- });
5396
+ function generateId() {
5397
+ return 'id-' + nextId++;
5133
5398
  }
5134
-
5135
- function RegisterForm(props) {
5136
- const medplum = useMedplum();
5137
- const googleClientId = getGoogleClientId(props.googleClientId);
5138
- const recaptchaSiteKey = props.recaptchaSiteKey;
5139
- const [outcome, setOutcome] = React.useState();
5140
- const issues = getIssuesForExpression(outcome, undefined);
5141
- React.useEffect(() => initRecaptcha(recaptchaSiteKey), [recaptchaSiteKey]);
5142
- function handleAuthResponse(registerRequest, partialLogin) {
5143
- return __awaiter(this, void 0, void 0, function* () {
5144
- try {
5145
- let login;
5146
- if (props.type === 'patient') {
5147
- login = yield medplum.startNewPatient(registerRequest, partialLogin);
5148
- }
5149
- else {
5150
- login = yield medplum.startNewProject(registerRequest, partialLogin);
5151
- }
5152
- yield medplum.processCode(login.code);
5153
- props.onSuccess();
5154
- }
5155
- catch (err) {
5156
- setOutcome(err);
5157
- }
5158
- });
5399
+ function ensureQuestionnaireKeys(questionnaire) {
5400
+ return Object.assign(Object.assign({}, questionnaire), { id: questionnaire.id || generateId(), item: ensureQuestionnaireItemKeys(questionnaire.item) });
5401
+ }
5402
+ function ensureQuestionnaireItemKeys(items) {
5403
+ if (!items) {
5404
+ return undefined;
5159
5405
  }
5160
- return (React__default["default"].createElement(Document, { width: 450 },
5161
- React__default["default"].createElement(Form, { style: { maxWidth: 400 }, onSubmit: (formData) => __awaiter(this, void 0, void 0, function* () {
5162
- try {
5163
- const recaptchaToken = yield getRecaptcha(recaptchaSiteKey);
5164
- const registerRequest = {
5165
- projectId: props.projectId,
5166
- projectName: formData.projectName,
5167
- firstName: formData.firstName,
5168
- lastName: formData.lastName,
5169
- email: formData.email,
5170
- password: formData.password,
5171
- remember: formData.remember === 'true',
5172
- recaptchaSiteKey,
5173
- recaptchaToken,
5174
- };
5175
- const userLogin = yield medplum.startNewUser(registerRequest);
5176
- yield handleAuthResponse(registerRequest, userLogin);
5177
- }
5178
- catch (err) {
5179
- setOutcome(err);
5180
- }
5181
- }) },
5182
- React__default["default"].createElement("div", { className: "medplum-center" }, props.children),
5183
- issues && (React__default["default"].createElement("div", { className: "medplum-input-error" }, issues.map((issue) => {
5184
- var _a, _b;
5185
- return (React__default["default"].createElement("div", { "data-testid": "text-field-error", key: (_a = issue.details) === null || _a === void 0 ? void 0 : _a.text }, (_b = issue.details) === null || _b === void 0 ? void 0 : _b.text));
5186
- }))),
5187
- googleClientId && (React__default["default"].createElement(React__default["default"].Fragment, null,
5188
- React__default["default"].createElement("div", { className: "medplum-signin-google-container" },
5189
- React__default["default"].createElement(GoogleButton, { googleClientId: googleClientId, handleGoogleCredential: (response) => __awaiter(this, void 0, void 0, function* () {
5190
- try {
5191
- const loginRequest = {
5192
- googleClientId: response.clientId,
5193
- googleCredential: response.credential,
5194
- };
5195
- const userLogin = yield medplum.startGoogleLogin(loginRequest);
5196
- const googleClaims = core.parseJWTPayload(loginRequest.googleCredential);
5197
- const registerRequest = {
5198
- projectId: props.projectId,
5199
- firstName: googleClaims.given_name,
5200
- lastName: googleClaims.family_name,
5201
- email: googleClaims.email,
5202
- };
5203
- yield handleAuthResponse(registerRequest, userLogin);
5204
- }
5205
- catch (err) {
5206
- setOutcome(err);
5207
- }
5208
- }) })),
5209
- React__default["default"].createElement("div", { className: "medplum-signin-separator" }, "or"))),
5210
- React__default["default"].createElement(FormSection, { title: "First Name", htmlFor: "firstName", outcome: outcome },
5211
- React__default["default"].createElement(Input, { name: "firstName", type: "text", testid: "firstName", placeholder: "First name", required: true, autoFocus: true, outcome: outcome })),
5212
- React__default["default"].createElement(FormSection, { title: "Last Name", htmlFor: "lastName", outcome: outcome },
5213
- React__default["default"].createElement(Input, { name: "lastName", type: "text", testid: "lastName", placeholder: "Last name", required: true, outcome: outcome })),
5214
- props.type === 'project' && (React__default["default"].createElement(FormSection, { title: "Project Name", htmlFor: "projectName", outcome: outcome },
5215
- React__default["default"].createElement(Input, { name: "projectName", type: "text", testid: "projectName", placeholder: "My Project", required: true, outcome: outcome }))),
5216
- React__default["default"].createElement(FormSection, { title: "Email", htmlFor: "email", outcome: outcome },
5217
- React__default["default"].createElement(Input, { name: "email", type: "email", testid: "email", placeholder: "name@domain.com", required: true, outcome: outcome })),
5218
- React__default["default"].createElement(FormSection, { title: "Password", htmlFor: "password", outcome: outcome },
5219
- React__default["default"].createElement(Input, { name: "password", type: "password", testid: "password", autoComplete: "off", required: true, outcome: outcome })),
5220
- React__default["default"].createElement("p", { style: { fontSize: '12px', color: '#888' } },
5221
- "By clicking submit you agree to the Medplum ",
5222
- React__default["default"].createElement("a", { href: "https://www.medplum.com/privacy" }, "Privacy\u00A0Policy"),
5223
- ' and ',
5224
- React__default["default"].createElement("a", { href: "https://www.medplum.com/terms" }, "Terms\u00A0of\u00A0Service"),
5225
- "."),
5226
- React__default["default"].createElement("p", { style: { fontSize: '12px', color: '#888' } },
5227
- "This site is protected by reCAPTCHA and the Google",
5228
- ' ',
5229
- React__default["default"].createElement("a", { href: "https://policies.google.com/privacy" }, "Privacy\u00A0Policy"),
5230
- ' and ',
5231
- React__default["default"].createElement("a", { href: "https://policies.google.com/terms" }, "Terms\u00A0of\u00A0Service"),
5232
- " apply."),
5233
- React__default["default"].createElement("div", { className: "medplum-signin-buttons" },
5234
- React__default["default"].createElement("div", null,
5235
- React__default["default"].createElement("input", { type: "checkbox", id: "remember", name: "remember", value: "true" }),
5236
- React__default["default"].createElement("label", { htmlFor: "remember" }, "Remember me")),
5237
- React__default["default"].createElement("div", null,
5238
- React__default["default"].createElement(Button, { type: "submit", testid: "submit" }, "Create account"))))));
5406
+ return items.map((item) => (Object.assign(Object.assign({}, item), { id: item.id || generateId(), item: ensureQuestionnaireItemKeys(item.item), answerOption: ensureQuestionnaireOptionKeys(item.answerOption) })));
5407
+ }
5408
+ function ensureQuestionnaireOptionKeys(options) {
5409
+ if (!options) {
5410
+ return undefined;
5411
+ }
5412
+ return options.map((option) => (Object.assign(Object.assign({}, option), { id: option.id || generateId() })));
5239
5413
  }
5240
5414
 
5241
5415
  function StatusBadge(props) {
@@ -5588,19 +5762,23 @@
5588
5762
  React__default["default"].createElement("th", null, "Author"),
5589
5763
  React__default["default"].createElement("th", null, "Date"),
5590
5764
  React__default["default"].createElement("th", null, "Version"))),
5591
- React__default["default"].createElement("tbody", null, (_a = value.entry) === null || _a === void 0 ? void 0 : _a.map((entry) => {
5592
- var _a, _b;
5593
- return (React__default["default"].createElement(HistoryRow, { key: (_b = (_a = entry.resource) === null || _a === void 0 ? void 0 : _a.meta) === null || _b === void 0 ? void 0 : _b.versionId, version: entry.resource }));
5594
- }))));
5765
+ React__default["default"].createElement("tbody", null, (_a = value.entry) === null || _a === void 0 ? void 0 : _a.map((entry, index) => (React__default["default"].createElement(HistoryRow, { key: 'entry-' + index, entry: entry }))))));
5595
5766
  }
5596
5767
  function HistoryRow(props) {
5597
5768
  var _a, _b, _c;
5598
- return (React__default["default"].createElement("tr", null,
5599
- React__default["default"].createElement("td", null,
5600
- React__default["default"].createElement(ResourceBadge, { value: (_a = props.version.meta) === null || _a === void 0 ? void 0 : _a.author, link: true })),
5601
- React__default["default"].createElement("td", null, core.formatDateTime((_b = props.version.meta) === null || _b === void 0 ? void 0 : _b.lastUpdated)),
5602
- React__default["default"].createElement("td", null,
5603
- React__default["default"].createElement(MedplumLink, { to: getVersionUrl(props.version) }, (_c = props.version.meta) === null || _c === void 0 ? void 0 : _c.versionId))));
5769
+ const { response, resource } = props.entry;
5770
+ if (resource) {
5771
+ return (React__default["default"].createElement("tr", null,
5772
+ React__default["default"].createElement("td", null,
5773
+ React__default["default"].createElement(ResourceBadge, { value: (_a = resource.meta) === null || _a === void 0 ? void 0 : _a.author, link: true })),
5774
+ React__default["default"].createElement("td", null, core.formatDateTime((_b = resource.meta) === null || _b === void 0 ? void 0 : _b.lastUpdated)),
5775
+ React__default["default"].createElement("td", null,
5776
+ React__default["default"].createElement(MedplumLink, { to: getVersionUrl(resource) }, (_c = resource.meta) === null || _c === void 0 ? void 0 : _c.versionId))));
5777
+ }
5778
+ else {
5779
+ return (React__default["default"].createElement("tr", null,
5780
+ React__default["default"].createElement("td", { colSpan: 3 }, core.normalizeErrorString(response === null || response === void 0 ? void 0 : response.outcome))));
5781
+ }
5604
5782
  }
5605
5783
  function getVersionUrl(resource) {
5606
5784
  var _a;
@@ -5702,22 +5880,23 @@
5702
5880
  var _a;
5703
5881
  const medplum = useMedplum();
5704
5882
  const schedule = useResource(props.schedule);
5883
+ const questionnaire = useResource(props.questionnaire);
5705
5884
  const [slots, setSlots] = React.useState();
5706
5885
  const slotsRef = React.useRef();
5707
5886
  slotsRef.current = slots;
5708
5887
  const [month, setMonth] = React.useState(getStartMonth());
5709
5888
  const [date, setDate] = React.useState();
5710
5889
  const [slot, setSlot] = React.useState();
5711
- const [info, setInfo] = React.useState();
5712
- const [form, setForm] = React.useState();
5890
+ const [response, setResponse] = React.useState();
5713
5891
  React.useEffect(() => {
5714
5892
  if (schedule) {
5715
5893
  setSlots([]);
5716
5894
  medplum
5717
5895
  .searchResources('Slot', new URLSearchParams([
5896
+ ['_count', (30 * 24).toString()],
5718
5897
  ['schedule', core.getReferenceString(schedule)],
5719
- ['start', 'gt' + month.toISOString()],
5720
- ['start', 'lt' + new Date(month.getTime() + 31 * 24 * 60 * 60 * 1000).toISOString()],
5898
+ ['start', 'gt' + getStart(month)],
5899
+ ['start', 'lt' + getEnd(month)],
5721
5900
  ]))
5722
5901
  .then(setSlots)
5723
5902
  .catch(console.log);
@@ -5726,7 +5905,7 @@
5726
5905
  setSlots(undefined);
5727
5906
  }
5728
5907
  }, [medplum, schedule, month]);
5729
- if (!schedule || !slots) {
5908
+ if (!schedule || !slots || !questionnaire) {
5730
5909
  return null;
5731
5910
  }
5732
5911
  const actor = (_a = schedule.actor) === null || _a === void 0 ? void 0 : _a[0];
@@ -5750,26 +5929,20 @@
5750
5929
  slotStart.getTime() < date.getTime() + 24 * 3600 * 1000 && (React__default["default"].createElement("div", { key: s.id },
5751
5930
  React__default["default"].createElement(Button, { style: { width: 150 }, onClick: () => setSlot(s) }, formatTime(slotStart)))));
5752
5931
  }))),
5753
- date && slot && !info && (React__default["default"].createElement("div", null,
5754
- React__default["default"].createElement("h3", null, "Enter your info"),
5755
- React__default["default"].createElement(FormSection, { title: "Name", htmlFor: "name" },
5756
- React__default["default"].createElement(Input, { name: "name" })),
5757
- React__default["default"].createElement(FormSection, { title: "Email", htmlFor: "email" },
5758
- React__default["default"].createElement(Input, { name: "email" })),
5759
- React__default["default"].createElement(Button, { primary: true, onClick: () => setInfo('info') }, "Next"))),
5760
- date && slot && info && !form && (React__default["default"].createElement("div", null,
5761
- React__default["default"].createElement("h3", null, "Custom questions"),
5762
- React__default["default"].createElement(FormSection, { title: "Question 1", htmlFor: "q1" },
5763
- React__default["default"].createElement(Input, { name: "q1" })),
5764
- React__default["default"].createElement(FormSection, { title: "Question 2", htmlFor: "q2" },
5765
- React__default["default"].createElement(Input, { name: "email" })),
5766
- React__default["default"].createElement(FormSection, { title: "Question 3", htmlFor: "q3" },
5767
- React__default["default"].createElement(Input, { name: "email" })),
5768
- React__default["default"].createElement(Button, { primary: true, onClick: () => setForm('form') }, "Next"))),
5769
- date && slot && info && form && (React__default["default"].createElement("div", null,
5932
+ date && slot && !response && (React__default["default"].createElement(QuestionnaireForm, { questionnaire: questionnaire, submitButtonText: 'Next', onSubmit: setResponse })),
5933
+ date && slot && response && (React__default["default"].createElement("div", null,
5770
5934
  React__default["default"].createElement("h3", null, "You're all set!"),
5771
5935
  React__default["default"].createElement("p", null, "Check your email for a calendar invite."))))));
5772
5936
  }
5937
+ function getStart(month) {
5938
+ return formatSlotInstant(month.getTime());
5939
+ }
5940
+ function getEnd(month) {
5941
+ return formatSlotInstant(month.getTime() + 31 * 24 * 60 * 60 * 1000);
5942
+ }
5943
+ function formatSlotInstant(time) {
5944
+ return new Date(Math.max(Date.now(), time)).toISOString();
5945
+ }
5773
5946
  function formatTime(date) {
5774
5947
  return date.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' });
5775
5948
  }
@@ -5788,19 +5961,19 @@
5788
5961
  {
5789
5962
  request: {
5790
5963
  method: 'GET',
5791
- url: `Communication?based-on=${core.getReferenceString(resource)}`,
5964
+ url: `Communication?based-on=${core.getReferenceString(resource)}&_sort=-_lastUpdated`,
5792
5965
  },
5793
5966
  },
5794
5967
  {
5795
5968
  request: {
5796
5969
  method: 'GET',
5797
- url: `Media?_count=100&based-on=${core.getReferenceString(resource)}`,
5970
+ url: `Media?_count=100&based-on=${core.getReferenceString(resource)}&_sort=-_lastUpdated`,
5798
5971
  },
5799
5972
  },
5800
5973
  {
5801
5974
  request: {
5802
5975
  method: 'GET',
5803
- url: `DiagnosticReport?based-on=${core.getReferenceString(resource)}`,
5976
+ url: `DiagnosticReport?based-on=${core.getReferenceString(resource)}&_sort=-_lastUpdated`,
5804
5977
  },
5805
5978
  },
5806
5979
  ],
@@ -5821,157 +5994,6 @@
5821
5994
  }) }));
5822
5995
  }
5823
5996
 
5824
- function SignInForm(props) {
5825
- const medplum = useMedplum();
5826
- const [login, setLogin] = React.useState(undefined);
5827
- const [memberships, setMemberships] = React.useState(undefined);
5828
- function handleAuthResponse(response) {
5829
- if (response.login) {
5830
- setLogin(response.login);
5831
- }
5832
- if (response.memberships) {
5833
- setMemberships(response.memberships);
5834
- }
5835
- if (response.code) {
5836
- if (props.onCode) {
5837
- props.onCode(response.code);
5838
- }
5839
- else {
5840
- medplum
5841
- .processCode(response.code)
5842
- .then(() => {
5843
- if (props.onSuccess) {
5844
- props.onSuccess();
5845
- }
5846
- })
5847
- .catch(console.log);
5848
- }
5849
- }
5850
- }
5851
- return (React__default["default"].createElement(Document, { width: 450 }, (() => {
5852
- if (!login) {
5853
- return (React__default["default"].createElement(AuthenticationForm, { projectId: props.projectId, clientId: props.clientId, scope: props.scope, nonce: props.nonce, googleClientId: props.googleClientId, onForgotPassword: props.onForgotPassword, onRegister: props.onRegister, handleAuthResponse: handleAuthResponse }, props.children));
5854
- }
5855
- else if (memberships) {
5856
- return React__default["default"].createElement(ProfileForm, { login: login, memberships: memberships, handleAuthResponse: handleAuthResponse });
5857
- }
5858
- else if (props.projectId === 'new') {
5859
- return React__default["default"].createElement(NewProjectForm, { login: login, handleAuthResponse: handleAuthResponse });
5860
- }
5861
- else {
5862
- return React__default["default"].createElement("div", null, "Success");
5863
- }
5864
- })()));
5865
- }
5866
- function AuthenticationForm(props) {
5867
- const medplum = useMedplum();
5868
- const googleClientId = getGoogleClientId(props.googleClientId);
5869
- const [outcome, setOutcome] = React.useState();
5870
- const issues = getIssuesForExpression(outcome, undefined);
5871
- return (React__default["default"].createElement(Form, { style: { maxWidth: 400 }, onSubmit: (formData) => {
5872
- medplum
5873
- .startLogin({
5874
- projectId: props.projectId,
5875
- clientId: props.clientId,
5876
- scope: props.scope,
5877
- nonce: props.nonce,
5878
- email: formData.email,
5879
- password: formData.password,
5880
- remember: formData.remember === 'true',
5881
- })
5882
- .then(props.handleAuthResponse)
5883
- .catch(setOutcome);
5884
- } },
5885
- React__default["default"].createElement("div", { className: "medplum-center" }, props.children),
5886
- issues && (React__default["default"].createElement("div", { className: "medplum-input-error" }, issues.map((issue) => {
5887
- var _a, _b;
5888
- return (React__default["default"].createElement("div", { "data-testid": "text-field-error", key: (_a = issue.details) === null || _a === void 0 ? void 0 : _a.text }, (_b = issue.details) === null || _b === void 0 ? void 0 : _b.text));
5889
- }))),
5890
- googleClientId && (React__default["default"].createElement(React__default["default"].Fragment, null,
5891
- React__default["default"].createElement("div", { className: "medplum-signin-google-container" },
5892
- React__default["default"].createElement(GoogleButton, { googleClientId: googleClientId, handleGoogleCredential: (response) => {
5893
- medplum
5894
- .startGoogleLogin({
5895
- projectId: props.projectId,
5896
- clientId: props.clientId,
5897
- scope: props.scope,
5898
- nonce: props.nonce,
5899
- googleClientId: response.clientId,
5900
- googleCredential: response.credential,
5901
- })
5902
- .then(props.handleAuthResponse)
5903
- .catch(setOutcome);
5904
- } })),
5905
- React__default["default"].createElement("div", { className: "medplum-signin-separator" }, "or"))),
5906
- React__default["default"].createElement(FormSection, { title: "Email", htmlFor: "email", outcome: outcome },
5907
- React__default["default"].createElement(Input, { name: "email", type: "email", testid: "email", required: true, autoFocus: true, outcome: outcome })),
5908
- React__default["default"].createElement(FormSection, { title: "Password", htmlFor: "password", outcome: outcome },
5909
- React__default["default"].createElement(Input, { name: "password", type: "password", testid: "password", autoComplete: "off", required: true, outcome: outcome })),
5910
- React__default["default"].createElement("div", { className: "medplum-signin-buttons" },
5911
- (props.onForgotPassword || props.onRegister) && (React__default["default"].createElement("div", null,
5912
- props.onForgotPassword && (React__default["default"].createElement(MedplumLink, { testid: "forgotpassword", onClick: props.onForgotPassword }, "Forgot password")),
5913
- props.onRegister && (React__default["default"].createElement(MedplumLink, { testid: "register", onClick: props.onRegister }, "Register")))),
5914
- React__default["default"].createElement("div", null,
5915
- React__default["default"].createElement("input", { type: "checkbox", id: "remember", name: "remember", value: "true" }),
5916
- React__default["default"].createElement("label", { htmlFor: "remember" }, "Remember me")),
5917
- React__default["default"].createElement("div", null,
5918
- React__default["default"].createElement(Button, { type: "submit", testid: "submit" }, "Sign in")))));
5919
- }
5920
- function ProfileForm(props) {
5921
- const medplum = useMedplum();
5922
- return (React__default["default"].createElement("div", null,
5923
- React__default["default"].createElement("div", { className: "medplum-center" },
5924
- React__default["default"].createElement(Logo, { size: 32 }),
5925
- React__default["default"].createElement("h1", null, "Choose profile")),
5926
- props.memberships.map((membership) => {
5927
- var _a, _b, _c;
5928
- return (React__default["default"].createElement("div", { className: "medplum-nav-menu-profile", key: membership.id, onClick: () => {
5929
- medplum
5930
- .post('auth/profile', {
5931
- login: props.login,
5932
- profile: membership.id,
5933
- })
5934
- .then(props.handleAuthResponse)
5935
- .catch(console.log);
5936
- } },
5937
- React__default["default"].createElement("div", { className: "medplum-nav-menu-profile-icon" },
5938
- React__default["default"].createElement(Avatar, { alt: (_a = membership.profile) === null || _a === void 0 ? void 0 : _a.display })),
5939
- React__default["default"].createElement("div", { className: "medplum-nav-menu-profile-label" }, (_b = membership.profile) === null || _b === void 0 ? void 0 :
5940
- _b.display,
5941
- React__default["default"].createElement("div", { className: "medplum-nav-menu-profile-help-text" }, (_c = membership.project) === null || _c === void 0 ? void 0 : _c.display))));
5942
- })));
5943
- }
5944
- function NewProjectForm(props) {
5945
- const medplum = useMedplum();
5946
- const [outcome, setOutcome] = React.useState();
5947
- return (React__default["default"].createElement(Form, { style: { maxWidth: 400 }, onSubmit: (formData) => __awaiter(this, void 0, void 0, function* () {
5948
- try {
5949
- const registerRequest = { projectName: formData.projectName };
5950
- const partialLogin = { login: props.login };
5951
- const login = yield medplum.startNewProject(registerRequest, partialLogin);
5952
- props.handleAuthResponse(login);
5953
- }
5954
- catch (err) {
5955
- setOutcome(err);
5956
- }
5957
- }) },
5958
- React__default["default"].createElement("div", { className: "medplum-center" },
5959
- React__default["default"].createElement(Logo, { size: 32 }),
5960
- React__default["default"].createElement("h1", null, "Create project")),
5961
- React__default["default"].createElement(FormSection, { title: "Project Name", htmlFor: "projectName", outcome: outcome },
5962
- React__default["default"].createElement(Input, { name: "projectName", type: "text", testid: "projectName", placeholder: "My Project", required: true, outcome: outcome })),
5963
- React__default["default"].createElement("p", { style: { fontSize: '12px', color: '#888' } },
5964
- "By clicking submit you agree to the Medplum ",
5965
- React__default["default"].createElement("a", { href: "https://www.medplum.com/privacy" }, "Privacy\u00A0Policy"),
5966
- ' and ',
5967
- React__default["default"].createElement("a", { href: "https://www.medplum.com/terms" }, "Terms\u00A0of\u00A0Service"),
5968
- "."),
5969
- React__default["default"].createElement("div", { className: "medplum-signin-buttons" },
5970
- React__default["default"].createElement("div", null),
5971
- React__default["default"].createElement("div", null,
5972
- React__default["default"].createElement(Button, { type: "submit", testid: "submit" }, "Create project")))));
5973
- }
5974
-
5975
5997
  function Tab(props) {
5976
5998
  let className = 'medplum-tab';
5977
5999
  if (props.selected) {