diff --git a/src/apps/create-patron-user-info/CreatePatron.dev.tsx b/src/apps/create-patron-user-info/CreatePatron.dev.tsx index 5f8d1e3a69..8d8cf39b65 100644 --- a/src/apps/create-patron-user-info/CreatePatron.dev.tsx +++ b/src/apps/create-patron-user-info/CreatePatron.dev.tsx @@ -23,14 +23,10 @@ export default { '[\n {\n "branchId":"DK-775120",\n "title":"Højbjerg"\n },\n {\n "branchId":"DK-775122",\n "title":"Beder-Malling"\n },\n {\n "branchId":"DK-775144",\n "title":"Gellerup"\n },\n {\n "branchId":"DK-775167",\n "title":"Lystrup"\n },\n {\n "branchId":"DK-775146",\n "title":"Harlev"\n },\n {\n "branchId":"DK-775168",\n "title":"Skødstrup"\n },\n {\n "branchId":"FBS-751010",\n "title":"Arresten"\n },\n {\n "branchId":"DK-775147",\n "title":"Hasle"\n },\n {\n "branchId":"FBS-751032",\n "title":"Må ikke benyttes"\n },\n {\n "branchId":"FBS-751031",\n "title":"Fjernlager 1"\n },\n {\n "branchId":"DK-775126",\n "title":"Solbjerg"\n },\n {\n "branchId":"FBS-751030",\n "title":"ITK"\n },\n {\n "branchId":"DK-775149",\n "title":"Sabro"\n },\n {\n "branchId":"DK-775127",\n "title":"Tranbjerg"\n },\n {\n "branchId":"DK-775160",\n "title":"Risskov"\n },\n {\n "branchId":"DK-775162",\n "title":"Hjortshøj"\n },\n {\n "branchId":"DK-775140",\n "title":"Åby"\n },\n {\n "branchId":"FBS-751009",\n "title":"Fjernlager 2"\n },\n {\n "branchId":"FBS-751029",\n "title":"Stadsarkivet"\n },\n {\n "branchId":"FBS-751027",\n "title":"Intern"\n },\n {\n "branchId":"FBS-751026",\n "title":"Fælles undervejs"\n },\n {\n "branchId":"FBS-751025",\n "title":"Fællessekretariatet"\n },\n {\n "branchId":"DK-775133",\n "title":"Bavnehøj"\n },\n {\n "branchId":"FBS-751024",\n "title":"Fjernlånte materialer"\n },\n {\n "branchId":"DK-775100",\n "title":"Hovedbiblioteket"\n },\n {\n "branchId":"DK-775170",\n "title":"Trige"\n },\n {\n "branchId":"DK-775150",\n "title":"Tilst"\n },\n {\n "branchId":"DK-775130",\n "title":"Viby"\n },\n {\n "branchId":"DK-775164",\n "title":"Egå"\n }\n]', control: { type: "text" } }, - loginUrl: { + userinfoUrl: { defaultValue: "https://login.bib.dk/userinfo", control: { type: "text" } }, - userToken: { - defaultValue: "", - control: { type: "text" } - }, textNotificationsEnabledConfig: { defaultValue: "1", control: { type: "text" } diff --git a/src/apps/create-patron-user-info/CreatePatron.entry.tsx b/src/apps/create-patron-user-info/CreatePatron.entry.tsx index bccc2d157d..76c4f8332d 100644 --- a/src/apps/create-patron-user-info/CreatePatron.entry.tsx +++ b/src/apps/create-patron-user-info/CreatePatron.entry.tsx @@ -3,6 +3,7 @@ import { withConfig } from "../../core/utils/config"; import { withText } from "../../core/utils/text"; import { withUrls } from "../../core/utils/url"; import CreatePatron from "./CreatePatron"; +import { getToken, hasToken } from "../../core/token"; interface CreatePatronConfigProps { pincodeLengthMinConfig: string; @@ -46,11 +47,17 @@ interface CreatePatronTextProps { export interface CreatePatronProps extends CreatePatronConfigProps, CreatePatronUrlProps, - CreatePatronTextProps { - userToken: string; -} + CreatePatronTextProps {} + +const CreatePatronEntry: FC = () => { + const userToken = hasToken("user") ? getToken("user") : null; + + // The application using this app should handle the case where the user is not logged in. + // Eg by returning 403 Forbidden or redirecting to the login page. + if (!userToken) { + return null; + } -const CreatePatronEntry: FC = ({ userToken }) => { return ; }; diff --git a/src/apps/create-patron-user-info/CreatePatron.tsx b/src/apps/create-patron-user-info/CreatePatron.tsx index 01382ffacd..ae67dca4bd 100644 --- a/src/apps/create-patron-user-info/CreatePatron.tsx +++ b/src/apps/create-patron-user-info/CreatePatron.tsx @@ -1,4 +1,4 @@ -import React, { useState, FC } from "react"; +import React, { useState, FC, useEffect } from "react"; import UserInfo from "./UserInfo"; import { useUrls } from "../../core/utils/url"; @@ -8,18 +8,24 @@ interface CreatePatronProps { const CreatePatron: FC = ({ userToken }) => { const [cpr, setCpr] = useState(null); - const { loginUrl } = useUrls(); + const { userinfoUrl } = useUrls(); - fetch(String(loginUrl), { - method: "get", - headers: { Authorization: `Bearer ${userToken}` } - }) - .then((response) => response.json()) - .then((data) => { - if (data?.attributes?.cpr) { - setCpr(data.attributes.cpr); - } - }); + if (!userinfoUrl) { + throw new Error("userinfoUrl is not defined"); + } + + useEffect(() => { + fetch(String(userinfoUrl), { + method: "get", + headers: { Authorization: `Bearer ${userToken}` } + }) + .then((response) => response.json()) + .then((data) => { + if (data?.attributes?.cpr) { + setCpr(data.attributes.cpr); + } + }); + }, [userToken, userinfoUrl]); if (cpr === null) return null; diff --git a/src/apps/create-patron-user-info/UserInfo.tsx b/src/apps/create-patron-user-info/UserInfo.tsx index beb0598c7e..6e7cfee8d5 100644 --- a/src/apps/create-patron-user-info/UserInfo.tsx +++ b/src/apps/create-patron-user-info/UserInfo.tsx @@ -76,17 +76,17 @@ const UserInfo: FC = ({ cpr }) => { changePatron={changePatron} patron={patron} /> + {t("createPatronChangePickupHeaderText") && ( -

+

{t("createPatronChangePickupHeaderText")}

)} {t("createPatronChangePickupBodyText") && ( -

+

{t("createPatronChangePickupBodyText")}

)} -
= ({ + className = "", + patron, + changePatron, + showCheckboxes, + isRequired = false +}) => { + const t = useText(); + return ( + <> + changePatron(newEmail, "emailAddress")} + value={patron?.emailAddress} + label={t("patronContactEmailLabelText")} + /> + {showCheckboxes && ( + + changePatron(newReceiveEmail, "receiveEmail") + } + id="email-messages" + selected={patron?.receiveEmail} + disabled={false} + label={t("patronContactEmailCheckboxText")} + /> + )} + + ); +}; + +export default ContactInfoEmail; diff --git a/src/components/contact-info-section/ContactInfoInputs.tsx b/src/components/contact-info-section/ContactInfoInputs.tsx new file mode 100644 index 0000000000..89f4b214f9 --- /dev/null +++ b/src/components/contact-info-section/ContactInfoInputs.tsx @@ -0,0 +1,46 @@ +import clsx from "clsx"; +import * as React from "react"; +import { FC } from "react"; + +export interface ContactInfoInputsProps { + isInline: boolean; + children: React.ReactNode; + dataCy?: string; + className?: string; +} + +// This component wraps the input fields for the contact info section +// depending on the isInline prop. +const ContactInfoInputs: FC = ({ + isInline, + children, + dataCy = "contact-info-input", + className = undefined +}) => { + if (!isInline) { + return ( +
+ {children} +
+ ); + } + + const renderableChildren = React.Children.toArray(children); + return ( +
+ {renderableChildren.map((child, i) => { + const childClassName = clsx("patron__input--desktop", { + "mr-16": i < renderableChildren.length - 1 + }); + return
{child}
; + })} +
+ ); +}; + +export default ContactInfoInputs; diff --git a/src/components/contact-info-section/ContactInfoPhone.tsx b/src/components/contact-info-section/ContactInfoPhone.tsx new file mode 100644 index 0000000000..cebc0e27d7 --- /dev/null +++ b/src/components/contact-info-section/ContactInfoPhone.tsx @@ -0,0 +1,54 @@ +import * as React from "react"; +import { FC } from "react"; +import TextInput from "../atoms/input/TextInput"; +import { PatronSettingsV3, PatronV5 } from "../../core/fbs/model"; +import CheckBox from "../checkbox/Checkbox"; +import { useText } from "../../core/utils/text"; +import { ChangePatronProps } from "./types"; + +export interface ContactInfoPhoneProps { + patron: PatronV5 | PatronSettingsV3 | null; + changePatron: ChangePatronProps; + showCheckboxes: boolean; + className?: string; + isRequired?: boolean; +} + +const ContactInfoPhone: FC = ({ + patron, + changePatron, + showCheckboxes, + className = "", + isRequired = false +}) => { + const t = useText(); + return ( + <> + + changePatron(newPhoneNumber, "phoneNumber") + } + value={patron?.phoneNumber} + label={t("patronContactPhoneLabelText")} + /> + {showCheckboxes && ( + + changePatron(newReceiveSms, "receiveSms") + } + id="phone-messages" + selected={patron?.receiveSms} + disabled={false} + label={t("patronContactPhoneCheckboxText")} + /> + )} + + ); +}; + +export default ContactInfoPhone; diff --git a/src/components/contact-info-section/ContactInfoSection.tsx b/src/components/contact-info-section/ContactInfoSection.tsx index 7565d697a8..1969da85c9 100644 --- a/src/components/contact-info-section/ContactInfoSection.tsx +++ b/src/components/contact-info-section/ContactInfoSection.tsx @@ -1,14 +1,12 @@ import React, { FC } from "react"; import clsx from "clsx"; import { PatronV5, PatronSettingsV3 } from "../../core/fbs/model"; -import TextInput from "../atoms/input/TextInput"; -import CheckBox from "../checkbox/Checkbox"; import { useText } from "../../core/utils/text"; import { useConfig } from "../../core/utils/config"; - -export interface ChangePatronProps { - (newValue: string | boolean, key: string): void; -} +import ContactInfoInputs from "./ContactInfoInputs"; +import ContactInfoPhone from "./ContactInfoPhone"; +import ContactInfoEmail from "./ContactInfoEmail"; +import { ChangePatronProps } from "./types"; interface ContactInfoSectionProps { patron: PatronV5 | PatronSettingsV3 | null; @@ -26,64 +24,9 @@ const ContactInfoSection: FC = ({ const t = useText(); const inputsClass = clsx("dpl-input", { input__desktop: inLine }); const config = useConfig(); - const textNotificationsEnabled = + const textNotificationsEnabledConfig = config("textNotificationsEnabledConfig") === "1"; - const phoneNode = ( - <> - - changePatron(newPhoneNumber, "phoneNumber") - } - value={patron?.phoneNumber} - label={t("patronContactPhoneLabelText")} - /> - {showCheckboxes && textNotificationsEnabled && ( - - changePatron(newReceiveSms, "receiveSms") - } - id="phone-messages" - selected={patron?.receiveSms} - disabled={false} - label={t("patronContactPhoneCheckboxText")} - /> - )} - - ); - const emailNode = ( - <> - changePatron(newEmail, "emailAddress")} - value={patron?.emailAddress} - label={t("patronContactEmailLabelText")} - /> - {showCheckboxes && ( - - changePatron(newReceiveEmail, "receiveEmail") - } - id="email-messages" - selected={patron?.receiveEmail} - disabled={false} - label={t("patronContactEmailCheckboxText")} - /> - )} - - ); - return (

@@ -94,17 +37,23 @@ const ContactInfoSection: FC = ({ {t("patronContactInfoBodyText")}

)} - {inLine && ( -
-
{phoneNode}
-
{emailNode}
-
- )} - {!inLine && ( - <> - {phoneNode} {emailNode} - - )} + + + +

); }; diff --git a/src/components/contact-info-section/__snapshots__/ContactInfoInputs.tsx.snap b/src/components/contact-info-section/__snapshots__/ContactInfoInputs.tsx.snap new file mode 100644 index 0000000000..c2591cf8f7 --- /dev/null +++ b/src/components/contact-info-section/__snapshots__/ContactInfoInputs.tsx.snap @@ -0,0 +1,36 @@ +// Vitest Snapshot v1 + +exports[`ContactInfoInputs > Should NOT wrap the input fields if it is NOT inline 1`] = ` +
+

+ One input component +

+

+ Another input component +

+
+`; + +exports[`ContactInfoInputs > Should wrap the input fields if it is inline 1`] = ` +
+
+

+ One input component +

+
+
+

+ Another input component +

+
+
+`; diff --git a/src/components/contact-info-section/types.ts b/src/components/contact-info-section/types.ts new file mode 100644 index 0000000000..ec855d2c17 --- /dev/null +++ b/src/components/contact-info-section/types.ts @@ -0,0 +1,3 @@ +export interface ChangePatronProps { + (newValue: string | boolean, key: string): void; +} diff --git a/src/tests/unit/__snapshots__/contact-info.test.tsx.snap b/src/tests/unit/__snapshots__/contact-info.test.tsx.snap new file mode 100644 index 0000000000..42e2dda746 --- /dev/null +++ b/src/tests/unit/__snapshots__/contact-info.test.tsx.snap @@ -0,0 +1,36 @@ +// Vitest Snapshot v1 + +exports[`ContactInfoInputs > Should NOT wrap the input fields if it is NOT inline 1`] = ` +
+

+ One input component +

+

+ Another input component +

+
+`; + +exports[`ContactInfoInputs > Should wrap the input fields if it is inline 1`] = ` +
+
+

+ One input component +

+
+
+

+ Another input component +

+
+
+`; diff --git a/src/tests/unit/contact-info.test.tsx b/src/tests/unit/contact-info.test.tsx new file mode 100644 index 0000000000..145b513ff5 --- /dev/null +++ b/src/tests/unit/contact-info.test.tsx @@ -0,0 +1,36 @@ +import { describe, expect, it } from "vitest"; +import React from "react"; +import { render } from "@testing-library/react"; +import { configure } from "@testing-library/dom"; +import ContactInfoInputs from "../../components/contact-info-section/ContactInfoInputs"; + +configure({ + testIdAttribute: "data-cy" +}); + +describe("ContactInfoInputs", () => { + it("Should wrap the input fields if it is inline", async () => { + const { getByTestId } = render( + +

One input component

+

Another input component

+
+ ); + + const contactInfoInputs = getByTestId("contact-info-input-inline"); + + expect(contactInfoInputs).toMatchSnapshot(); + }); + it("Should NOT wrap the input fields if it is NOT inline", async () => { + const { getByTestId } = render( + +

One input component

+

Another input component

+
+ ); + + const contactInfoInputs = getByTestId("contact-info-input"); + + expect(contactInfoInputs).toMatchSnapshot(); + }); +});