Your changes were saved.
+ }
+ type="positive"
+ sx={{ marginTop: "m" }}
+ />
+ )}
+
+
+
+ >
)
}
From 1f1565eb87fe08558cc1e1f1ccd11149c5df5c19 Mon Sep 17 00:00:00 2001
From: Emma Mansell <73774046+7emansell@users.noreply.github.com>
Date: Fri, 4 Oct 2024 14:08:35 -0400
Subject: [PATCH 03/86] Unit tests for email form
---
.../MyAccount/NewSettings/EmailForm.test.tsx | 135 ++++++++++++++++++
1 file changed, 135 insertions(+)
create mode 100644 src/components/MyAccount/NewSettings/EmailForm.test.tsx
diff --git a/src/components/MyAccount/NewSettings/EmailForm.test.tsx b/src/components/MyAccount/NewSettings/EmailForm.test.tsx
new file mode 100644
index 000000000..5c10d6397
--- /dev/null
+++ b/src/components/MyAccount/NewSettings/EmailForm.test.tsx
@@ -0,0 +1,135 @@
+import { render, screen, fireEvent, waitFor } from "@testing-library/react"
+import EmailForm from "./EmailForm"
+import { filteredPickupLocations } from "../../../../__test__/fixtures/processedMyAccountData"
+import { PatronDataProvider } from "../../../context/PatronDataContext"
+import { processedPatron } from "../../../../__test__/fixtures/processedMyAccountData"
+
+describe("email form", () => {
+ const mockSetIsLoading = jest.fn()
+ const mockSetIsSuccess = jest.fn()
+
+ beforeEach(() => {
+ jest.clearAllMocks()
+ global.fetch = jest.fn().mockResolvedValue({
+ json: async () => {
+ console.log("Updated")
+ },
+ status: 200,
+ } as Response)
+ })
+
+ const component = (
+
+
+
+ )
+
+ it("renders correctly with initial email", () => {
+ render(component)
+
+ expect(screen.getByText("streganonna@gmail.com")).toBeInTheDocument()
+
+ expect(screen.getByRole("button", { name: /edit/i })).toBeInTheDocument()
+ })
+
+ it("allows editing when edit button is clicked", () => {
+ render(component)
+ fireEvent.click(screen.getByRole("button", { name: /edit/i }))
+
+ expect(screen.getAllByLabelText("Update email")[0]).toBeInTheDocument()
+ expect(
+ screen.getByDisplayValue("streganonna@gmail.com")
+ ).toBeInTheDocument()
+
+ expect(screen.getByRole("button", { name: /cancel/i })).toBeInTheDocument()
+ expect(
+ screen.getByRole("button", { name: /save changes/i })
+ ).toBeInTheDocument()
+ })
+
+ it("validates email input correctly", () => {
+ render(component)
+
+ fireEvent.click(screen.getByRole("button", { name: /edit/i }))
+
+ const input = screen.getAllByLabelText("Update email")[0]
+ fireEvent.change(input, { target: { value: "invalid-email" } })
+
+ expect(
+ screen.getByText("Please enter a valid email address.")
+ ).toBeInTheDocument()
+
+ expect(screen.getByRole("button", { name: /save changes/i })).toBeDisabled()
+ })
+
+ it("allows adding a new email field", () => {
+ render(component)
+
+ fireEvent.click(screen.getByRole("button", { name: /edit/i }))
+
+ fireEvent.click(
+ screen.getByRole("button", { name: /\+ add an email address/i })
+ )
+
+ expect(screen.getAllByLabelText("Update email").length).toBe(3)
+ })
+
+ it("removes an email when delete icon is clicked", () => {
+ render(component)
+
+ fireEvent.click(screen.getByRole("button", { name: /edit/i }))
+
+ fireEvent.click(screen.getByLabelText("Remove email"))
+
+ expect(
+ screen.queryByDisplayValue("spaghettigrandma@gmail.com")
+ ).not.toBeInTheDocument()
+ })
+
+ it("calls submitEmails with valid data", async () => {
+ render(component)
+
+ fireEvent.click(screen.getByRole("button", { name: /edit/i }))
+
+ const input = screen.getAllByLabelText("Update email")[0]
+ fireEvent.change(input, { target: { value: "newemail@example.com" } })
+
+ fireEvent.click(screen.getByRole("button", { name: /save changes/i }))
+
+ await waitFor(() => expect(fetch).toHaveBeenCalledTimes(1))
+
+ expect(fetch).toHaveBeenCalledWith(
+ "/research/research-catalog/api/account/settings/6742743",
+ expect.objectContaining({
+ body: '{"emails":["newemail@example.com","spaghettigrandma@gmail.com"]}',
+ headers: { "Content-Type": "application/json" },
+ method: "PUT",
+ })
+ )
+ })
+
+ it("cancels editing and reverts state", () => {
+ render(component)
+
+ fireEvent.click(screen.getByRole("button", { name: /edit/i }))
+
+ const input = screen.getAllByLabelText("Update email")[0]
+ fireEvent.change(input, { target: { value: "modified@example.com" } })
+
+ fireEvent.click(screen.getByRole("button", { name: /cancel/i }))
+
+ expect(screen.getByText("streganonna@gmail.com")).toBeInTheDocument()
+ expect(
+ screen.queryByDisplayValue("modified@example.com")
+ ).not.toBeInTheDocument()
+ })
+})
From 06e1343ab0988c4d9c8b07dee5619d445b7ca399 Mon Sep 17 00:00:00 2001
From: Emma Mansell <73774046+7emansell@users.noreply.github.com>
Date: Mon, 7 Oct 2024 17:30:11 -0400
Subject: [PATCH 04/86] With no js form validation
---
pages/account/[[...index]].tsx | 50 ++++++++++++--
pages/api/account/settings/[id].ts | 36 ++++++++++
.../NewSettings/NewAccountSettingsTab.tsx | 22 +++++--
.../MyAccount/NewSettings/NoJsEmailForm.tsx | 65 +++++++++++++++++++
4 files changed, 162 insertions(+), 11 deletions(-)
create mode 100644 src/components/MyAccount/NewSettings/NoJsEmailForm.tsx
diff --git a/pages/account/[[...index]].tsx b/pages/account/[[...index]].tsx
index 094a92085..cb8415405 100644
--- a/pages/account/[[...index]].tsx
+++ b/pages/account/[[...index]].tsx
@@ -1,4 +1,4 @@
-import { Text } from "@nypl/design-system-react-components"
+import { Banner, Text } from "@nypl/design-system-react-components"
import Head from "next/head"
import Layout from "../../src/components/Layout/Layout"
@@ -18,6 +18,7 @@ interface MyAccountPropsType {
isAuthenticated: boolean
tabsPath?: string
renderAuthServerError?: boolean
+ storedQueryString?
}
export default function MyAccount({
@@ -25,6 +26,7 @@ export default function MyAccount({
accountData,
isAuthenticated,
tabsPath,
+ storedQueryString,
}: MyAccountPropsType) {
const errorRetrievingPatronData = !accountData.patron
@@ -50,6 +52,31 @@ export default function MyAccount({
assistance.
)
+
+ function formValidationMessage(storedQueryString) {
+ const queryString = decodeURIComponent(storedQueryString)
+ const type = queryString.split("=")[0] || ""
+ const message = queryString.split("=")[1] || ""
+ if (type === "success") {
+ return (
+
+ )
+ } else {
+ return (
+
+ )
+ }
+ }
useEffect(() => {
resetCountdown()
// to avoid a reference error on document in the modal, wait to render it
@@ -76,6 +103,7 @@ export default function MyAccount({
serverError
) : (
+ {storedQueryString && formValidationMessage(storedQueryString)}
)}
@@ -111,16 +139,25 @@ export async function getServerSideProps({ req, res }) {
},
}
}
- // Parsing path from url to pass to ProfileTabs.
+
+ // Parsing path and query from URL
const tabsPathRegex = /\/account\/(.+)/
const match = req.url.match(tabsPathRegex)
+ const queryString = req.url.split("?")[1] || "" // Extract query string
const tabsPath = match ? match[1] : null
const id = patronTokenResponse.decodedPatron.sub
+
try {
const { checkouts, holds, patron, fines, pickupLocations } =
await getPatronData(id)
- /* Redirecting invalid paths (including /overdues if user has none) and
- // cleaning extra parts off valid paths. */
+ // Collect query string from non-JS form submission.
+ if (queryString) {
+ res.setHeader(
+ "Set-Cookie",
+ `queryParams=${encodeURIComponent(queryString)}; Path=/; HttpOnly`
+ )
+ }
+ // Redirecting invalid paths and cleaning extra parts off valid paths.
if (tabsPath) {
const allowedPaths = ["items", "requests", "overdues", "settings"]
if (
@@ -137,7 +174,7 @@ export async function getServerSideProps({ req, res }) {
const matchedPath = allowedPaths.find((path) =>
tabsPath.startsWith(path)
)
- if (tabsPath != matchedPath) {
+ if (tabsPath !== matchedPath) {
return {
redirect: {
destination: "/account/" + matchedPath,
@@ -147,11 +184,14 @@ export async function getServerSideProps({ req, res }) {
}
}
}
+
+ const storedQueryString = req.cookies.queryParams || ""
return {
props: {
accountData: { checkouts, holds, patron, fines, pickupLocations },
tabsPath,
isAuthenticated,
+ storedQueryString,
renderAuthServerError: !redirectBasedOnNyplAccountRedirects,
},
}
diff --git a/pages/api/account/settings/[id].ts b/pages/api/account/settings/[id].ts
index f6c4af046..c1ef37087 100644
--- a/pages/api/account/settings/[id].ts
+++ b/pages/api/account/settings/[id].ts
@@ -22,6 +22,42 @@ export default async function handler(
if (req.method == "GET") {
responseMessage = "Please make a PUT request to this endpoint."
}
+ if (req.method === "POST" && req.body._method === "PUT") {
+ const patronId = req.query.id as string
+ const patronData = req.body.emails
+
+ const emailRegex = /^[^@]+@[^@]+\.[^@]+$/
+ const invalidEmails = patronData.filter((email) => !emailRegex.test(email))
+
+ if (invalidEmails.length > 0) {
+ const errorMessage = `Please enter a valid email. Invalid emails: ${invalidEmails.join(
+ ", "
+ )}`
+ res.writeHead(302, {
+ Location: `/research/research-catalog/account/settings?error=${encodeURIComponent(
+ errorMessage
+ )}`,
+ })
+ res.end()
+ return
+ }
+
+ if (patronId == cookiePatronId) {
+ const response = await updatePatronSettings(patronId, {
+ emails: patronData,
+ })
+ responseStatus = response.status
+ responseMessage = response.message
+ } else {
+ responseStatus = 403
+ responseMessage = "Authenticated patron does not match request"
+ }
+ res.writeHead(302, {
+ Location: "/research/research-catalog/account/settings?success=true",
+ })
+ res.end()
+ return
+ }
if (req.method == "PUT") {
/** We get the patron id from the request: */
const patronId = req.query.id as string
diff --git a/src/components/MyAccount/NewSettings/NewAccountSettingsTab.tsx b/src/components/MyAccount/NewSettings/NewAccountSettingsTab.tsx
index a7baa0fa4..9f71623fc 100644
--- a/src/components/MyAccount/NewSettings/NewAccountSettingsTab.tsx
+++ b/src/components/MyAccount/NewSettings/NewAccountSettingsTab.tsx
@@ -3,9 +3,10 @@ import {
Flex,
Banner,
} from "@nypl/design-system-react-components"
-import { useContext, useState } from "react"
+import { useContext, useEffect, useState } from "react"
import { PatronDataContext } from "../../../context/PatronDataContext"
import EmailForm from "./EmailForm"
+import NoJsEmailForm from "./NoJsEmailForm"
const NewAccountSettingsTab = () => {
const {
@@ -13,6 +14,11 @@ const NewAccountSettingsTab = () => {
} = useContext(PatronDataContext)
const [isLoading, setIsLoading] = useState(false)
const [isSuccess, setIsSuccess] = useState(false)
+ const [isJsEnabled, setIsJsEnabled] = useState(false)
+
+ useEffect(() => {
+ setIsJsEnabled(true)
+ }, [])
return isLoading ? (
@@ -29,11 +35,15 @@ const NewAccountSettingsTab = () => {
/>
)}
-
+ {isJsEnabled ? (
+
+ ) : (
+
+ )}
>
)
diff --git a/src/components/MyAccount/NewSettings/NoJsEmailForm.tsx b/src/components/MyAccount/NewSettings/NoJsEmailForm.tsx
new file mode 100644
index 000000000..168146b5c
--- /dev/null
+++ b/src/components/MyAccount/NewSettings/NoJsEmailForm.tsx
@@ -0,0 +1,65 @@
+import { Button, Icon, TextInput } from "@nypl/design-system-react-components"
+
+const NoJsEmailForm = ({ patronData }) => {
+ const tempEmails = patronData?.emails || []
+
+ return (
+ <>
+
+ >
+ )
+}
+
+export default NoJsEmailForm
From 54e668bebbea5bffedbbb6e71149006959d236f0 Mon Sep 17 00:00:00 2001
From: Emma Mansell <73774046+7emansell@users.noreply.github.com>
Date: Tue, 8 Oct 2024 16:26:21 -0400
Subject: [PATCH 05/86] Clean up components
---
pages/account/[[...index]].tsx | 34 ++-----
pages/api/account/settings/[id].ts | 67 ++++++++++---
.../MyAccount/NewSettings/EmailForm.tsx | 7 +-
.../MyAccount/NewSettings/NoJsEmailForm.tsx | 94 ++++++++++++++-----
.../NewSettings/NoJsFormValidationMessage.tsx | 26 +++++
5 files changed, 163 insertions(+), 65 deletions(-)
create mode 100644 src/components/MyAccount/NewSettings/NoJsFormValidationMessage.tsx
diff --git a/pages/account/[[...index]].tsx b/pages/account/[[...index]].tsx
index cb8415405..fca3eec1a 100644
--- a/pages/account/[[...index]].tsx
+++ b/pages/account/[[...index]].tsx
@@ -13,6 +13,7 @@ import TimedLogoutModal from "../../src/components/MyAccount/TimedLogoutModal"
import { getIncrementedTime } from "../../src/utils/cookieUtils"
import { useEffect, useState } from "react"
import { getPatronData } from "../api/account/[id]"
+import FormValidationMessage from "../../src/components/MyAccount/NewSettings/NoJsFormValidationMessage"
interface MyAccountPropsType {
accountData: MyAccountPatronData
isAuthenticated: boolean
@@ -53,30 +54,6 @@ export default function MyAccount({
)
- function formValidationMessage(storedQueryString) {
- const queryString = decodeURIComponent(storedQueryString)
- const type = queryString.split("=")[0] || ""
- const message = queryString.split("=")[1] || ""
- if (type === "success") {
- return (
-
- )
- } else {
- return (
-
- )
- }
- }
useEffect(() => {
resetCountdown()
// to avoid a reference error on document in the modal, wait to render it
@@ -103,7 +80,9 @@ export default function MyAccount({
serverError
) : (
- {storedQueryString && formValidationMessage(storedQueryString)}
+ {storedQueryString && (
+
+ )}
)}
@@ -143,7 +122,8 @@ export async function getServerSideProps({ req, res }) {
// Parsing path and query from URL
const tabsPathRegex = /\/account\/(.+)/
const match = req.url.match(tabsPathRegex)
- const queryString = req.url.split("?")[1] || "" // Extract query string
+ const queryString = req.url.split("?")[1] || null
+ console.log(queryString)
const tabsPath = match ? match[1] : null
const id = patronTokenResponse.decodedPatron.sub
@@ -186,6 +166,8 @@ export async function getServerSideProps({ req, res }) {
}
const storedQueryString = req.cookies.queryParams || ""
+ // Removing query string cookie so it doesn't persist on reload.
+ res.setHeader("Set-Cookie", "queryParams=; Path=/; HttpOnly; Max-Age=0")
return {
props: {
accountData: { checkouts, holds, patron, fines, pickupLocations },
diff --git a/pages/api/account/settings/[id].ts b/pages/api/account/settings/[id].ts
index c1ef37087..4be582bf4 100644
--- a/pages/api/account/settings/[id].ts
+++ b/pages/api/account/settings/[id].ts
@@ -22,13 +22,27 @@ export default async function handler(
if (req.method == "GET") {
responseMessage = "Please make a PUT request to this endpoint."
}
- if (req.method === "POST" && req.body._method === "PUT") {
+
+ // Account settings form submission without JS.
+ if (req.method === "POST") {
+ const action = req.body.delete ? "delete" : "save"
const patronId = req.query.id as string
- const patronData = req.body.emails
+ const patronData = req.body.emails.filter((email) => email.trim() !== "")
+ if (action === "delete") {
+ const indexToDelete = parseInt(req.body.delete, 10)
+ if (indexToDelete >= 0) {
+ patronData.splice(indexToDelete, 1)
+ }
+ }
const emailRegex = /^[^@]+@[^@]+\.[^@]+$/
- const invalidEmails = patronData.filter((email) => !emailRegex.test(email))
+ // Filter out invalid emails
+ const invalidEmails = patronData.filter(
+ (email) => email && !emailRegex.test(email)
+ )
+
+ // If there are invalid emails, redirect back with an error message
if (invalidEmails.length > 0) {
const errorMessage = `Please enter a valid email. Invalid emails: ${invalidEmails.join(
", "
@@ -42,22 +56,47 @@ export default async function handler(
return
}
- if (patronId == cookiePatronId) {
- const response = await updatePatronSettings(patronId, {
- emails: patronData,
+ // Remove duplicate emails while keeping the first
+ const uniqueEmails = patronData.reduce((acc, email) => {
+ const normalizedEmail = email.toLowerCase()
+ if (!acc.includes(normalizedEmail)) {
+ acc.push(normalizedEmail)
+ }
+ return acc
+ }, [])
+
+ const duplicateEmails = patronData.length !== uniqueEmails.length
+
+ if (duplicateEmails) {
+ const errorMessage = "Cannot use duplicate emails."
+ res.writeHead(302, {
+ Location: `/research/research-catalog/account/settings?error=${encodeURIComponent(
+ errorMessage
+ )}`,
})
- responseStatus = response.status
- responseMessage = response.message
- } else {
- responseStatus = 403
- responseMessage = "Authenticated patron does not match request"
+ res.end()
+ return
}
- res.writeHead(302, {
- Location: "/research/research-catalog/account/settings?success=true",
+
+ const response = await updatePatronSettings(patronId, {
+ emails: patronData.filter((email) => email !== ""),
})
+ if (response.status === 200) {
+ res.writeHead(302, {
+ Location: "/research/research-catalog/account/settings?success=true",
+ })
+ } else {
+ const errorMessage = `Error updating emails: ${response.message}`
+ res.writeHead(302, {
+ Location: `/research/research-catalog/account/settings?error=${encodeURIComponent(
+ errorMessage
+ )}`,
+ })
+ }
+
res.end()
- return
}
+
if (req.method == "PUT") {
/** We get the patron id from the request: */
const patronId = req.query.id as string
diff --git a/src/components/MyAccount/NewSettings/EmailForm.tsx b/src/components/MyAccount/NewSettings/EmailForm.tsx
index e41141a06..bb1dd5c77 100644
--- a/src/components/MyAccount/NewSettings/EmailForm.tsx
+++ b/src/components/MyAccount/NewSettings/EmailForm.tsx
@@ -149,7 +149,12 @@ const EmailForm = ({ patronData, setIsLoading, setIsSuccess }) => {
id="add-button"
buttonType="text"
onClick={addEmailField}
- sx={{ justifyContent: "flex-start", width: "300px" }}
+ size="large"
+ sx={{
+ justifyContent: "flex-start",
+ width: "300px",
+ padding: "xxs",
+ }}
>
+ Add an email address
diff --git a/src/components/MyAccount/NewSettings/NoJsEmailForm.tsx b/src/components/MyAccount/NewSettings/NoJsEmailForm.tsx
index 168146b5c..285553418 100644
--- a/src/components/MyAccount/NewSettings/NoJsEmailForm.tsx
+++ b/src/components/MyAccount/NewSettings/NoJsEmailForm.tsx
@@ -1,4 +1,9 @@
-import { Button, Icon, TextInput } from "@nypl/design-system-react-components"
+import {
+ Button,
+ Icon,
+ TextInput,
+ Text,
+} from "@nypl/design-system-react-components"
const NoJsEmailForm = ({ patronData }) => {
const tempEmails = patronData?.emails || []
@@ -10,52 +15,93 @@ const NoJsEmailForm = ({ patronData }) => {
method="POST"
style={{ width: "100%" }}
>
-
-
- Email
-
+
+ Email
+
+
{tempEmails.map((email, index) => (
-
+ style={{
+ display: "flex",
+ flexDirection: "row",
+ alignItems: "center",
+ gap: "8px",
+ }}
+ >
+
+ {index === 0 && (P) }
+ {index > 0 && (
+
+
+
+ )}
+
))}
+
+ {" "}
+ Add an email address:{" "}
+
+
+
+
+
+ Save changes
+
-
- Save changes
-
>
diff --git a/src/components/MyAccount/NewSettings/NoJsFormValidationMessage.tsx b/src/components/MyAccount/NewSettings/NoJsFormValidationMessage.tsx
new file mode 100644
index 000000000..215ea8746
--- /dev/null
+++ b/src/components/MyAccount/NewSettings/NoJsFormValidationMessage.tsx
@@ -0,0 +1,26 @@
+import { Banner } from "@nypl/design-system-react-components"
+
+const FormValidationMessage = ({ storedQueryString }) => {
+ const queryString = decodeURIComponent(storedQueryString)
+ const type = queryString.split("=")[0] || ""
+ const message = queryString.split("=")[1] || ""
+ if (type === "success") {
+ return (
+
+ )
+ } else {
+ return (
+
+ )
+ }
+}
+
+export default FormValidationMessage
From 36dd68b1343c47525324b2ba3b422360a561f183 Mon Sep 17 00:00:00 2001
From: Emma Mansell <73774046+7emansell@users.noreply.github.com>
Date: Tue, 8 Oct 2024 16:27:35 -0400
Subject: [PATCH 06/86] Add the logout modal back in
---
src/components/MyAccount/TimedLogoutModal.tsx | 44 +++++++++----------
1 file changed, 22 insertions(+), 22 deletions(-)
diff --git a/src/components/MyAccount/TimedLogoutModal.tsx b/src/components/MyAccount/TimedLogoutModal.tsx
index 1d5a4ab9a..6850462b1 100644
--- a/src/components/MyAccount/TimedLogoutModal.tsx
+++ b/src/components/MyAccount/TimedLogoutModal.tsx
@@ -41,30 +41,30 @@ const TimedLogoutModal = ({
buildTimeLeft(expirationTime)
)
- // if (
- // typeof document !== "undefined" &&
- // !document.cookie.includes("accountPageExp")
- // ) {
- // logOutAndRedirect()
- // }
+ if (
+ typeof document !== "undefined" &&
+ !document.cookie.includes("accountPageExp")
+ ) {
+ logOutAndRedirect()
+ }
- // useEffect(() => {
- // const timeout = setTimeout(() => {
- // const { minutes, seconds } = buildTimeLeft(expirationTime)
- // if (minutes < timeoutWindow) setOpen(true)
- // setTimeUntilExpiration({
- // minutes,
- // seconds,
- // })
- // }, 1000)
- // return () => {
- // clearTimeout(timeout)
- // }
- // })
+ useEffect(() => {
+ const timeout = setTimeout(() => {
+ const { minutes, seconds } = buildTimeLeft(expirationTime)
+ if (minutes < timeoutWindow) setOpen(true)
+ setTimeUntilExpiration({
+ minutes,
+ seconds,
+ })
+ }, 1000)
+ return () => {
+ clearTimeout(timeout)
+ }
+ })
- // if (timeUntilExpiration.minutes <= 0 && timeUntilExpiration.seconds <= 0) {
- // logOutAndRedirect()
- // }
+ if (timeUntilExpiration.minutes <= 0 && timeUntilExpiration.seconds <= 0) {
+ logOutAndRedirect()
+ }
if (!open) return null
return (
From 441110fab3438ef38c8dbd449d9c96691f395fb5 Mon Sep 17 00:00:00 2001
From: Emma Mansell <73774046+7emansell@users.noreply.github.com>
Date: Tue, 8 Oct 2024 16:46:28 -0400
Subject: [PATCH 07/86] Cleaning up again for readability
---
pages/account/[[...index]].tsx | 7 ++-
pages/api/account/settings/[id].ts | 6 +--
.../MyAccount/NewSettings/EmailForm.tsx | 50 +++++++++----------
.../MyAccount/NewSettings/NoJsEmailForm.tsx | 4 +-
.../NewSettings/NoJsFormValidationMessage.tsx | 24 +++------
.../MyAccount/Settings/AccountSettingsTab.tsx | 2 -
6 files changed, 39 insertions(+), 54 deletions(-)
diff --git a/pages/account/[[...index]].tsx b/pages/account/[[...index]].tsx
index fca3eec1a..2a8c96290 100644
--- a/pages/account/[[...index]].tsx
+++ b/pages/account/[[...index]].tsx
@@ -1,4 +1,4 @@
-import { Banner, Text } from "@nypl/design-system-react-components"
+import { Text } from "@nypl/design-system-react-components"
import Head from "next/head"
import Layout from "../../src/components/Layout/Layout"
@@ -123,7 +123,6 @@ export async function getServerSideProps({ req, res }) {
const tabsPathRegex = /\/account\/(.+)/
const match = req.url.match(tabsPathRegex)
const queryString = req.url.split("?")[1] || null
- console.log(queryString)
const tabsPath = match ? match[1] : null
const id = patronTokenResponse.decodedPatron.sub
@@ -154,7 +153,7 @@ export async function getServerSideProps({ req, res }) {
const matchedPath = allowedPaths.find((path) =>
tabsPath.startsWith(path)
)
- if (tabsPath !== matchedPath) {
+ if (tabsPath != matchedPath) {
return {
redirect: {
destination: "/account/" + matchedPath,
@@ -165,8 +164,8 @@ export async function getServerSideProps({ req, res }) {
}
}
+ // Saving the string and removing the cookie so it doesn't persist on reload.
const storedQueryString = req.cookies.queryParams || ""
- // Removing query string cookie so it doesn't persist on reload.
res.setHeader("Set-Cookie", "queryParams=; Path=/; HttpOnly; Max-Age=0")
return {
props: {
diff --git a/pages/api/account/settings/[id].ts b/pages/api/account/settings/[id].ts
index 4be582bf4..7d54fdba7 100644
--- a/pages/api/account/settings/[id].ts
+++ b/pages/api/account/settings/[id].ts
@@ -58,9 +58,9 @@ export default async function handler(
// Remove duplicate emails while keeping the first
const uniqueEmails = patronData.reduce((acc, email) => {
- const normalizedEmail = email.toLowerCase()
- if (!acc.includes(normalizedEmail)) {
- acc.push(normalizedEmail)
+ const lowercaseEmail = email.toLowerCase()
+ if (!acc.includes(lowercaseEmail)) {
+ acc.push(lowercaseEmail)
}
return acc
}, [])
diff --git a/src/components/MyAccount/NewSettings/EmailForm.tsx b/src/components/MyAccount/NewSettings/EmailForm.tsx
index bb1dd5c77..2fcc01134 100644
--- a/src/components/MyAccount/NewSettings/EmailForm.tsx
+++ b/src/components/MyAccount/NewSettings/EmailForm.tsx
@@ -21,15 +21,6 @@ const EmailForm = ({ patronData, setIsLoading, setIsSuccess }) => {
return emailRegex.test(email)
}
- const handleRemoveEmail = (index) => {
- const updatedEmails = tempEmails.filter((_, i) => i !== index)
- setTempEmails(updatedEmails)
-
- // Immediately revalidate remaining emails.
- const hasInvalidEmail = updatedEmails.some((email) => !validateEmail(email))
- setError(hasInvalidEmail)
- }
-
const handleInputChange = (e, index) => {
const { value } = e.target
const updatedEmails = [...tempEmails]
@@ -47,6 +38,24 @@ const EmailForm = ({ patronData, setIsLoading, setIsSuccess }) => {
}
}
+ const handleRemoveEmail = (index) => {
+ const updatedEmails = tempEmails.filter((_, i) => i !== index)
+ setTempEmails(updatedEmails)
+
+ // Immediately revalidate remaining emails.
+ const hasInvalidEmail = updatedEmails.some((email) => !validateEmail(email))
+ setError(hasInvalidEmail)
+ }
+
+ const handleAddEmail = () => {
+ const updatedEmails = [...tempEmails, ""]
+ setTempEmails(updatedEmails)
+
+ // Immediately revalidate all emails.
+ const hasInvalidEmail = updatedEmails.some((email) => !validateEmail(email))
+ setError(hasInvalidEmail)
+ }
+
const handleClearableCallback = (index) => {
const updatedEmails = [...tempEmails]
updatedEmails[index] = ""
@@ -54,6 +63,12 @@ const EmailForm = ({ patronData, setIsLoading, setIsSuccess }) => {
setError(true)
}
+ const cancelEditing = () => {
+ setTempEmails([...emails])
+ setIsEditing(false)
+ setError(false)
+ }
+
const submitEmails = async () => {
setIsLoading(true)
const validEmails = tempEmails.filter((email) => validateEmail(email))
@@ -84,21 +99,6 @@ const EmailForm = ({ patronData, setIsLoading, setIsSuccess }) => {
}
}
- const cancelEditing = () => {
- setTempEmails([...emails])
- setIsEditing(false)
- setError(false)
- }
-
- const addEmailField = () => {
- const updatedEmails = [...tempEmails, ""]
- setTempEmails(updatedEmails)
-
- // Immediately revalidate all emails.
- const hasInvalidEmail = updatedEmails.some((email) => !validateEmail(email))
- setError(hasInvalidEmail)
- }
-
return (
@@ -148,7 +148,7 @@ const EmailForm = ({ patronData, setIsLoading, setIsSuccess }) => {
{
const tempEmails = patronData?.emails || []
-
return (
<>
{
+const SettingsLabel = ({ icon, text }) => {
return (
@@ -18,4 +18,4 @@ const SettingsFormLabel = ({ icon, text }) => {
)
}
-export default SettingsFormLabel
+export default SettingsLabel
From 6622f503b3c943f347ef708c4328f5721a200daf Mon Sep 17 00:00:00 2001
From: Emma Mansell <73774046+7emansell@users.noreply.github.com>
Date: Wed, 16 Oct 2024 11:40:13 -0400
Subject: [PATCH 19/86] Wrapping in form component
---
.../MyAccount/NewSettings/EmailForm.tsx | 71 ++++++++++---------
1 file changed, 36 insertions(+), 35 deletions(-)
diff --git a/src/components/MyAccount/NewSettings/EmailForm.tsx b/src/components/MyAccount/NewSettings/EmailForm.tsx
index f5fbededa..5c434c14c 100644
--- a/src/components/MyAccount/NewSettings/EmailForm.tsx
+++ b/src/components/MyAccount/NewSettings/EmailForm.tsx
@@ -5,6 +5,7 @@ import {
Flex,
Button,
SkeletonLoader,
+ Form,
} from "@nypl/design-system-react-components"
import { useContext, useState } from "react"
import { PatronDataContext } from "../../../context/PatronDataContext"
@@ -123,41 +124,41 @@ const EmailForm = ({ patronData, setIsSuccess, setIsFailure }) => {
width="-webkit-fill-available"
>
{tempEmails.map((email, index) => (
-
- handleInputChange(e, index)}
- isRequired
- isClearable
- isClearableCallback={() => handleClearableCallback(index)}
- />
- {index !== 0 && (
- handleRemoveEmail(index)}
- >
- {" "}
-
-
- )}
-
+
+
+ handleInputChange(e, index)}
+ isRequired
+ isClearable
+ isClearableCallback={() => handleClearableCallback(index)}
+ />
+ {index !== 0 && (
+ handleRemoveEmail(index)}
+ >
+ {" "}
+
+
+ )}
+
+
))}
Date: Wed, 16 Oct 2024 12:11:25 -0400
Subject: [PATCH 20/86] Uniqueness....
---
.../MyAccount/NewSettings/EmailForm.tsx | 26 ++++++++++++-------
1 file changed, 17 insertions(+), 9 deletions(-)
diff --git a/src/components/MyAccount/NewSettings/EmailForm.tsx b/src/components/MyAccount/NewSettings/EmailForm.tsx
index 5c434c14c..cc09d32b6 100644
--- a/src/components/MyAccount/NewSettings/EmailForm.tsx
+++ b/src/components/MyAccount/NewSettings/EmailForm.tsx
@@ -21,9 +21,11 @@ const EmailForm = ({ patronData, setIsSuccess, setIsFailure }) => {
const [tempEmails, setTempEmails] = useState([...emails])
- const validateEmail = (email) => {
+ const validateEmail = (currentEmail, emails) => {
const emailRegex = /^[^@]+@[^@]+\.[^@]+$/
- return emailRegex.test(email)
+ const isEmailUnique =
+ emails.filter((email) => email === currentEmail).length === 1
+ return emailRegex.test(currentEmail) && isEmailUnique
}
const handleInputChange = (e, index) => {
@@ -34,11 +36,11 @@ const EmailForm = ({ patronData, setIsSuccess, setIsFailure }) => {
const firstEmailEmpty = index === 0
- if (firstEmailEmpty && (!value || !validateEmail(value))) {
+ if (firstEmailEmpty && (!value || !validateEmail(value, updatedEmails))) {
setError(true)
} else {
const hasInvalidEmail = updatedEmails.some(
- (email) => !validateEmail(email)
+ (email) => !validateEmail(email, updatedEmails)
)
setError(hasInvalidEmail)
}
@@ -49,7 +51,9 @@ const EmailForm = ({ patronData, setIsSuccess, setIsFailure }) => {
setTempEmails(updatedEmails)
// Immediately revalidate remaining emails.
- const hasInvalidEmail = updatedEmails.some((email) => !validateEmail(email))
+ const hasInvalidEmail = updatedEmails.some(
+ (email) => !validateEmail(email, updatedEmails)
+ )
setError(hasInvalidEmail)
}
@@ -58,7 +62,9 @@ const EmailForm = ({ patronData, setIsSuccess, setIsFailure }) => {
setTempEmails(updatedEmails)
// Immediately revalidate all emails.
- const hasInvalidEmail = updatedEmails.some((email) => !validateEmail(email))
+ const hasInvalidEmail = updatedEmails.some(
+ (email) => !validateEmail(email, updatedEmails)
+ )
setError(hasInvalidEmail)
}
@@ -78,7 +84,9 @@ const EmailForm = ({ patronData, setIsSuccess, setIsFailure }) => {
const submitEmails = async () => {
setIsLoading(true)
setIsEditing(false)
- const validEmails = tempEmails.filter((email) => validateEmail(email))
+ const validEmails = tempEmails.filter((email) =>
+ validateEmail(email, tempEmails)
+ )
try {
const response = await fetch(
`/research/research-catalog/api/account/settings/${patronData.id}`,
@@ -139,8 +147,8 @@ const EmailForm = ({ patronData, setIsSuccess, setIsFailure }) => {
key={index}
labelText="Update email"
showLabel={false}
- isInvalid={error && !validateEmail(email)}
- invalidText="Please enter a valid email address."
+ isInvalid={error && !validateEmail(email, tempEmails)}
+ invalidText={"Please enter a valid email address."}
onChange={(e) => handleInputChange(e, index)}
isRequired
isClearable
From 09acc0c4bf7082a2a8e7808d274dc2f9bac14547 Mon Sep 17 00:00:00 2001
From: Emma Mansell <73774046+7emansell@users.noreply.github.com>
Date: Wed, 16 Oct 2024 13:32:19 -0400
Subject: [PATCH 21/86] Updating label
---
src/components/MyAccount/NewSettings/EmailForm.tsx | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/components/MyAccount/NewSettings/EmailForm.tsx b/src/components/MyAccount/NewSettings/EmailForm.tsx
index cc09d32b6..1efd3ccf5 100644
--- a/src/components/MyAccount/NewSettings/EmailForm.tsx
+++ b/src/components/MyAccount/NewSettings/EmailForm.tsx
@@ -148,7 +148,9 @@ const EmailForm = ({ patronData, setIsSuccess, setIsFailure }) => {
labelText="Update email"
showLabel={false}
isInvalid={error && !validateEmail(email, tempEmails)}
- invalidText={"Please enter a valid email address."}
+ invalidText={
+ "Please enter a valid and unique email address."
+ }
onChange={(e) => handleInputChange(e, index)}
isRequired
isClearable
From b27bcbbcd392a1bb0a4c62e38127e6aa2df3d35b Mon Sep 17 00:00:00 2001
From: Emma Mansell <73774046+7emansell@users.noreply.github.com>
Date: Wed, 16 Oct 2024 16:06:27 -0400
Subject: [PATCH 22/86] Correcting tests
---
src/components/MyAccount/NewSettings/EmailForm.test.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/MyAccount/NewSettings/EmailForm.test.tsx b/src/components/MyAccount/NewSettings/EmailForm.test.tsx
index b7073c6aa..71a00a605 100644
--- a/src/components/MyAccount/NewSettings/EmailForm.test.tsx
+++ b/src/components/MyAccount/NewSettings/EmailForm.test.tsx
@@ -66,7 +66,7 @@ describe("email form", () => {
fireEvent.change(input, { target: { value: "invalid-email" } })
expect(
- screen.getByText("Please enter a valid email address.")
+ screen.getByText("Please enter a valid and unique email address.")
).toBeInTheDocument()
expect(screen.getByRole("button", { name: /save changes/i })).toBeDisabled()
From f7c92318b1bea1b2af740e68dd4f716ec3e5e76f Mon Sep 17 00:00:00 2001
From: Emma Mansell <73774046+7emansell@users.noreply.github.com>
Date: Tue, 22 Oct 2024 13:13:07 -0400
Subject: [PATCH 23/86] Pulling out form component and adding phone logic
---
.../NewSettings/NewAccountSettingsTab.tsx | 13 +-
.../MyAccount/NewSettings/PhoneEmailForm.tsx | 306 ++++++++++++++++++
.../NewSettings/SaveCancelButtons.tsx | 6 +-
.../MyAccount/NewSettings/SettingsLabel.tsx | 8 +-
4 files changed, 327 insertions(+), 6 deletions(-)
create mode 100644 src/components/MyAccount/NewSettings/PhoneEmailForm.tsx
diff --git a/src/components/MyAccount/NewSettings/NewAccountSettingsTab.tsx b/src/components/MyAccount/NewSettings/NewAccountSettingsTab.tsx
index 61072a1a4..e1dc90f9d 100644
--- a/src/components/MyAccount/NewSettings/NewAccountSettingsTab.tsx
+++ b/src/components/MyAccount/NewSettings/NewAccountSettingsTab.tsx
@@ -1,7 +1,7 @@
import { Flex, Banner } from "@nypl/design-system-react-components"
import { useContext, useState } from "react"
import { PatronDataContext } from "../../../context/PatronDataContext"
-import EmailForm from "./EmailForm"
+import PhoneEmailForm from "./PhoneEmailForm"
const NewAccountSettingsTab = () => {
const {
@@ -26,11 +26,18 @@ const NewAccountSettingsTab = () => {
sx={{ marginTop: "m" }}
/>
)}
-
-
+
+
>
diff --git a/src/components/MyAccount/NewSettings/PhoneEmailForm.tsx b/src/components/MyAccount/NewSettings/PhoneEmailForm.tsx
new file mode 100644
index 000000000..67005a33a
--- /dev/null
+++ b/src/components/MyAccount/NewSettings/PhoneEmailForm.tsx
@@ -0,0 +1,306 @@
+import {
+ Icon,
+ TextInput,
+ Text,
+ Flex,
+ Button,
+ SkeletonLoader,
+ Form,
+} from "@nypl/design-system-react-components"
+import { useContext, useState } from "react"
+import { PatronDataContext } from "../../../context/PatronDataContext"
+import SaveCancelButtons from "./SaveCancelButtons"
+import SettingsLabel from "./SettingsLabel"
+import type { Patron } from "../../../types/myAccountTypes"
+
+interface PhoneEmailFormProps {
+ patronData: Patron
+ setIsSuccess
+ setIsFailure
+ inputType: "phones" | "emails"
+}
+
+const PhoneEmailForm = ({
+ patronData,
+ setIsSuccess,
+ setIsFailure,
+ inputType,
+}: PhoneEmailFormProps) => {
+ const isEmail = inputType === "emails"
+ const { getMostUpdatedSierraAccountData } = useContext(PatronDataContext)
+ const [inputs, setInputs] = useState(
+ isEmail
+ ? patronData[inputType]
+ : patronData[inputType]?.map((phone) => phone.number) || []
+ )
+ const [isLoading, setIsLoading] = useState(false)
+ const [isEditing, setIsEditing] = useState(false)
+ const [error, setError] = useState(false)
+
+ const [tempInputs, setTempInputs] = useState([...inputs])
+
+ const validateInput = (currentInput, inputs) => {
+ const isInputUnique =
+ inputs.filter((input) => input === currentInput).length === 1
+ if (isEmail) {
+ const emailRegex = /^[^@]+@[^@]+\.[^@]+$/
+ return emailRegex.test(currentInput) && isInputUnique
+ } else {
+ const phoneRegex = /^\+?[1-9]\d{1,14}$/
+ return phoneRegex.test(currentInput) && isInputUnique
+ }
+ }
+
+ const handleInputChange = (e, index) => {
+ const { value } = e.target
+ const updatedInputs = [...tempInputs]
+ updatedInputs[index] = value
+ setTempInputs(updatedInputs)
+
+ const firstInputEmpty = index === 0
+
+ if (
+ isEmail &&
+ firstInputEmpty &&
+ (!value || !validateInput(value, updatedInputs))
+ ) {
+ setError(true)
+ } else {
+ const hasInvalidInput = updatedInputs.some(
+ (input) => !validateInput(input, updatedInputs)
+ )
+ setError(hasInvalidInput)
+ }
+ }
+
+ const handleRemove = (index) => {
+ const updatedInputs = tempInputs.filter((_, i) => i !== index)
+ setTempInputs(updatedInputs)
+
+ // Immediately revalidate remaining inputs.
+ const hasInvalidInput = updatedInputs.some(
+ (input) => !validateInput(input, updatedInputs)
+ )
+ setError(hasInvalidInput)
+ }
+
+ const handleAdd = () => {
+ const updatedInputs = [...tempInputs, ""]
+ setTempInputs(updatedInputs)
+
+ // Immediately revalidate remaining inputs.
+ const hasInvalidInput = updatedInputs.some(
+ (input) => !validateInput(input, updatedInputs)
+ )
+ setError(hasInvalidInput)
+ }
+
+ const handleClearableCallback = (index) => {
+ const updatedInputs = [...tempInputs]
+ updatedInputs[index] = ""
+ setTempInputs(updatedInputs)
+ setError(true)
+ }
+
+ const cancelEditing = () => {
+ setTempInputs([...inputs])
+ setIsEditing(false)
+ setError(false)
+ }
+
+ const submitInputs = async () => {
+ setIsLoading(true)
+ setIsEditing(false)
+ const validInputs = tempInputs.filter((input) =>
+ validateInput(input, tempInputs)
+ )
+ const body = isEmail
+ ? JSON.stringify({ [inputType]: validInputs })
+ : JSON.stringify({
+ [inputType]: validInputs.map((input) => ({
+ number: input,
+ type: "t",
+ })),
+ })
+ try {
+ const response = await fetch(
+ `/research/research-catalog/api/account/settings/${patronData.id}`,
+ {
+ method: "PUT",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: body,
+ }
+ )
+
+ if (response.status === 200) {
+ await getMostUpdatedSierraAccountData()
+ setIsSuccess(true)
+ setInputs([...validInputs])
+ setTempInputs([...validInputs])
+ } else {
+ setIsFailure(true)
+ setTempInputs([...inputs])
+ }
+ } catch (error) {
+ console.error("Error submitting", inputType, error)
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ return (
+ <>
+ {isLoading ? (
+
+ ) : (
+
+
+ {isEditing ? (
+
+ {tempInputs.map((input, index) => (
+
+
+ handleInputChange(e, index)}
+ isRequired
+ isClearable
+ isClearableCallback={() => handleClearableCallback(index)}
+ />
+ {(!isEmail || index !== 0) && (
+ handleRemove(index)}
+ >
+ {" "}
+
+
+ )}
+
+
+ ))}
+
+ {isEmail ? "+ Add an email address" : "+ Add a phone number"}
+
+
+ ) : isEmail || tempInputs.length != 0 ? (
+
+
+ {tempInputs.map((input, index) => (
+
+ {input}{" "}
+ {index === 0 && inputs.length > 1 && (
+
+ (P)
+
+ )}
+
+ ))}
+
+ {
+ setTempInputs([...inputs])
+ setIsEditing(true)
+ }}
+ sx={{
+ paddingLeft: "xs",
+ paddingRight: "xs",
+ marginLeft: "xxl",
+ }}
+ >
+
+ Edit
+
+
+ ) : (
+ {
+ setIsEditing(true)
+ handleAdd()
+ }}
+ size="large"
+ sx={{
+ justifyContent: "flex-start",
+ width: { base: "87%", md: "300px" },
+ paddingLeft: { base: "m", md: "unset" },
+ }}
+ >
+ + Add a phone number
+
+ )}
+
+ {isEditing && (
+
+ )}
+
+ )}
+ >
+ )
+}
+
+export default PhoneEmailForm
diff --git a/src/components/MyAccount/NewSettings/SaveCancelButtons.tsx b/src/components/MyAccount/NewSettings/SaveCancelButtons.tsx
index 589e8a0f7..fbc471d3c 100644
--- a/src/components/MyAccount/NewSettings/SaveCancelButtons.tsx
+++ b/src/components/MyAccount/NewSettings/SaveCancelButtons.tsx
@@ -4,12 +4,14 @@ type SaveCancelButtonProps = {
isDisabled: boolean
onCancel: () => void
onSave: () => void
+ inputType: "phones" | "emails"
}
const SaveCancelButtons = ({
isDisabled,
onCancel,
onSave,
+ inputType,
}: SaveCancelButtonProps) => {
return (
Cancel
{
return (
-
+
Date: Tue, 22 Oct 2024 13:13:23 -0400
Subject: [PATCH 24/86] Phone form test
---
.../MyAccount/NewSettings/EmailForm.test.tsx | 15 +-
.../MyAccount/NewSettings/EmailForm.tsx | 243 ------------------
.../MyAccount/NewSettings/PhoneForm.test.tsx | 157 +++++++++++
3 files changed, 165 insertions(+), 250 deletions(-)
delete mode 100644 src/components/MyAccount/NewSettings/EmailForm.tsx
create mode 100644 src/components/MyAccount/NewSettings/PhoneForm.test.tsx
diff --git a/src/components/MyAccount/NewSettings/EmailForm.test.tsx b/src/components/MyAccount/NewSettings/EmailForm.test.tsx
index 71a00a605..fe92ec10b 100644
--- a/src/components/MyAccount/NewSettings/EmailForm.test.tsx
+++ b/src/components/MyAccount/NewSettings/EmailForm.test.tsx
@@ -1,8 +1,8 @@
import { render, screen, fireEvent, waitFor } from "@testing-library/react"
-import EmailForm from "./EmailForm"
import { filteredPickupLocations } from "../../../../__test__/fixtures/processedMyAccountData"
import { PatronDataProvider } from "../../../context/PatronDataContext"
import { processedPatron } from "../../../../__test__/fixtures/processedMyAccountData"
+import PhoneEmailForm from "./PhoneEmailForm"
describe("email form", () => {
const mockSetIsSuccess = jest.fn()
@@ -25,10 +25,11 @@ describe("email form", () => {
pickupLocations: filteredPickupLocations,
}}
>
-
)
@@ -45,7 +46,7 @@ describe("email form", () => {
render(component)
fireEvent.click(screen.getByRole("button", { name: /edit/i }))
- expect(screen.getAllByLabelText("Update email")[0]).toBeInTheDocument()
+ expect(screen.getAllByLabelText("Update emails")[0]).toBeInTheDocument()
expect(
screen.getByDisplayValue("streganonna@gmail.com")
).toBeInTheDocument()
@@ -62,7 +63,7 @@ describe("email form", () => {
fireEvent.click(screen.getByRole("button", { name: /edit/i }))
- const input = screen.getAllByLabelText("Update email")[0]
+ const input = screen.getAllByLabelText("Update emails")[0]
fireEvent.change(input, { target: { value: "invalid-email" } })
expect(
@@ -81,7 +82,7 @@ describe("email form", () => {
screen.getByRole("button", { name: /\+ add an email address/i })
)
- expect(screen.getAllByLabelText("Update email").length).toBe(
+ expect(screen.getAllByLabelText("Update emails").length).toBe(
processedPatron.emails.length + 1
)
})
@@ -103,7 +104,7 @@ describe("email form", () => {
fireEvent.click(screen.getByRole("button", { name: /edit/i }))
- const input = screen.getAllByLabelText("Update email")[0]
+ const input = screen.getAllByLabelText("Update emails")[0]
fireEvent.change(input, { target: { value: "newemail@example.com" } })
fireEvent.click(screen.getByRole("button", { name: /save changes/i }))
@@ -125,7 +126,7 @@ describe("email form", () => {
fireEvent.click(screen.getByRole("button", { name: /edit/i }))
- const input = screen.getAllByLabelText("Update email")[0]
+ const input = screen.getAllByLabelText("Update emails")[0]
fireEvent.change(input, { target: { value: "modified@example.com" } })
fireEvent.click(screen.getByRole("button", { name: /cancel/i }))
diff --git a/src/components/MyAccount/NewSettings/EmailForm.tsx b/src/components/MyAccount/NewSettings/EmailForm.tsx
deleted file mode 100644
index 1efd3ccf5..000000000
--- a/src/components/MyAccount/NewSettings/EmailForm.tsx
+++ /dev/null
@@ -1,243 +0,0 @@
-import {
- Icon,
- TextInput,
- Text,
- Flex,
- Button,
- SkeletonLoader,
- Form,
-} from "@nypl/design-system-react-components"
-import { useContext, useState } from "react"
-import { PatronDataContext } from "../../../context/PatronDataContext"
-import SaveCancelButtons from "./SaveCancelButtons"
-import SettingsLabel from "./SettingsLabel"
-
-const EmailForm = ({ patronData, setIsSuccess, setIsFailure }) => {
- const { getMostUpdatedSierraAccountData } = useContext(PatronDataContext)
- const [emails, setEmails] = useState(patronData?.emails || [])
- const [isLoading, setIsLoading] = useState(false)
- const [isEditing, setIsEditing] = useState(false)
- const [error, setError] = useState(false)
-
- const [tempEmails, setTempEmails] = useState([...emails])
-
- const validateEmail = (currentEmail, emails) => {
- const emailRegex = /^[^@]+@[^@]+\.[^@]+$/
- const isEmailUnique =
- emails.filter((email) => email === currentEmail).length === 1
- return emailRegex.test(currentEmail) && isEmailUnique
- }
-
- const handleInputChange = (e, index) => {
- const { value } = e.target
- const updatedEmails = [...tempEmails]
- updatedEmails[index] = value
- setTempEmails(updatedEmails)
-
- const firstEmailEmpty = index === 0
-
- if (firstEmailEmpty && (!value || !validateEmail(value, updatedEmails))) {
- setError(true)
- } else {
- const hasInvalidEmail = updatedEmails.some(
- (email) => !validateEmail(email, updatedEmails)
- )
- setError(hasInvalidEmail)
- }
- }
-
- const handleRemoveEmail = (index) => {
- const updatedEmails = tempEmails.filter((_, i) => i !== index)
- setTempEmails(updatedEmails)
-
- // Immediately revalidate remaining emails.
- const hasInvalidEmail = updatedEmails.some(
- (email) => !validateEmail(email, updatedEmails)
- )
- setError(hasInvalidEmail)
- }
-
- const handleAddEmail = () => {
- const updatedEmails = [...tempEmails, ""]
- setTempEmails(updatedEmails)
-
- // Immediately revalidate all emails.
- const hasInvalidEmail = updatedEmails.some(
- (email) => !validateEmail(email, updatedEmails)
- )
- setError(hasInvalidEmail)
- }
-
- const handleClearableCallback = (index) => {
- const updatedEmails = [...tempEmails]
- updatedEmails[index] = ""
- setTempEmails(updatedEmails)
- setError(true)
- }
-
- const cancelEditing = () => {
- setTempEmails([...emails])
- setIsEditing(false)
- setError(false)
- }
-
- const submitEmails = async () => {
- setIsLoading(true)
- setIsEditing(false)
- const validEmails = tempEmails.filter((email) =>
- validateEmail(email, tempEmails)
- )
- try {
- const response = await fetch(
- `/research/research-catalog/api/account/settings/${patronData.id}`,
- {
- method: "PUT",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify({ emails: validEmails }),
- }
- )
-
- if (response.status === 200) {
- await getMostUpdatedSierraAccountData()
- setIsSuccess(true)
- } else {
- setIsFailure(true)
- setTempEmails([...emails])
- }
- } catch (error) {
- console.error("Error submitting emails:", error)
- } finally {
- setIsLoading(false)
- }
- }
-
- return (
- <>
- {isLoading ? (
-
- ) : (
-
-
- {isEditing ? (
-
- {tempEmails.map((email, index) => (
-
-
- handleInputChange(e, index)}
- isRequired
- isClearable
- isClearableCallback={() => handleClearableCallback(index)}
- />
- {index !== 0 && (
- handleRemoveEmail(index)}
- >
- {" "}
-
-
- )}
-
-
- ))}
-
- + Add an email address
-
-
- ) : (
-
-
- {tempEmails.map((email, index) => (
-
- {email}{" "}
- {index === 0 && emails.length > 1 && (
-
- (P)
-
- )}
-
- ))}
-
- setIsEditing(true)}
- sx={{
- paddingLeft: "xs",
- paddingRight: "xs",
- marginLeft: "xxl",
- }}
- >
-
- Edit
-
-
- )}
- {isEditing && (
-
- )}
-
- )}
- >
- )
-}
-
-export default EmailForm
diff --git a/src/components/MyAccount/NewSettings/PhoneForm.test.tsx b/src/components/MyAccount/NewSettings/PhoneForm.test.tsx
new file mode 100644
index 000000000..ddc1d9297
--- /dev/null
+++ b/src/components/MyAccount/NewSettings/PhoneForm.test.tsx
@@ -0,0 +1,157 @@
+import { render, screen, fireEvent, waitFor } from "@testing-library/react"
+import { filteredPickupLocations } from "../../../../__test__/fixtures/processedMyAccountData"
+import { PatronDataProvider } from "../../../context/PatronDataContext"
+import { processedPatron } from "../../../../__test__/fixtures/processedMyAccountData"
+import PhoneEmailForm from "./PhoneEmailForm"
+
+describe("phone form", () => {
+ const mockSetIsSuccess = jest.fn()
+ const mockSetIsFailure = jest.fn()
+
+ beforeEach(() => {
+ jest.clearAllMocks()
+ global.fetch = jest.fn().mockResolvedValue({
+ json: async () => {
+ console.log("Updated")
+ },
+ status: 200,
+ } as Response)
+ })
+
+ const component = (
+
+
+
+ )
+
+ it("renders correctly with initial phone", () => {
+ render(component)
+
+ expect(
+ screen.getByText(processedPatron.phones[0].number)
+ ).toBeInTheDocument()
+
+ expect(screen.getByRole("button", { name: /edit/i })).toBeInTheDocument()
+ })
+
+ it("allows editing when edit button is clicked", () => {
+ render(component)
+ fireEvent.click(screen.getByRole("button", { name: /edit/i }))
+
+ expect(screen.getAllByLabelText("Update phones")[0]).toBeInTheDocument()
+ expect(
+ screen.getByDisplayValue(processedPatron.phones[0].number)
+ ).toBeInTheDocument()
+
+ expect(screen.getByRole("button", { name: /cancel/i })).toBeInTheDocument()
+ expect(
+ screen.getByRole("button", { name: /save changes/i })
+ ).toBeInTheDocument()
+ expect(screen.queryByText(/edit/)).not.toBeInTheDocument()
+ })
+
+ it("validates phone input correctly", () => {
+ render(component)
+
+ fireEvent.click(screen.getByRole("button", { name: /edit/i }))
+
+ const input = screen.getAllByLabelText("Update phones")[0]
+ fireEvent.change(input, { target: { value: "invalid-phone" } })
+
+ expect(
+ screen.getByText("Please enter a valid and unique phone number.")
+ ).toBeInTheDocument()
+
+ expect(screen.getByRole("button", { name: /save changes/i })).toBeDisabled()
+ })
+
+ it("allows adding a new phone field", () => {
+ render(component)
+
+ fireEvent.click(screen.getByRole("button", { name: /edit/i }))
+
+ fireEvent.click(
+ screen.getByRole("button", { name: /\+ add a phone number/i })
+ )
+
+ expect(screen.getAllByLabelText("Update phones").length).toBe(
+ processedPatron.phones.length + 1
+ )
+ })
+
+ it("removes a phone when delete icon is clicked", () => {
+ render(component)
+
+ fireEvent.click(screen.getByRole("button", { name: /edit/i }))
+
+ fireEvent.click(screen.getByLabelText("Remove phone"))
+
+ expect(
+ screen.queryByDisplayValue(processedPatron.phones[0].number)
+ ).not.toBeInTheDocument()
+ })
+
+ it.skip("calls submit with valid data", async () => {
+ render(component)
+
+ fireEvent.click(screen.getByRole("button", { name: /edit/i }))
+
+ const input = screen.getAllByLabelText("Update phones")[0]
+ fireEvent.change(input, { target: { value: "1234" } })
+
+ fireEvent.click(screen.getByRole("button", { name: /save changes/i }))
+
+ await waitFor(() => expect(fetch).toHaveBeenCalledTimes(1))
+
+ expect(fetch).toHaveBeenCalledWith(
+ "/research/research-catalog/api/account/settings/6742743",
+ expect.objectContaining({
+ body: '{"phones":[{number:"1234", type: "t"}]}',
+ headers: { "Content-Type": "application/json" },
+ method: "PUT",
+ })
+ )
+ })
+
+ it("cancels editing and reverts state", () => {
+ render(component)
+
+ fireEvent.click(screen.getByRole("button", { name: /edit/i }))
+
+ const input = screen.getAllByLabelText("Update phones")[0]
+ fireEvent.change(input, { target: { value: "4534" } })
+
+ fireEvent.click(screen.getByRole("button", { name: /cancel/i }))
+
+ expect(screen.getByText("123-456-7890")).toBeInTheDocument()
+ expect(screen.queryByDisplayValue("4534")).not.toBeInTheDocument()
+ })
+
+ it("shows add phone number when there's no more phone numbers", async () => {
+ render(component)
+
+ fireEvent.click(screen.getByRole("button", { name: /edit/i }))
+
+ fireEvent.click(screen.getByLabelText("Remove phone"))
+
+ fireEvent.click(screen.getByRole("button", { name: /save changes/i }))
+
+ await expect(
+ screen.queryByText(processedPatron.phones[0].number)
+ ).not.toBeInTheDocument()
+
+ await waitFor(() =>
+ expect(screen.getByText("+ Add a phone number")).toBeInTheDocument()
+ )
+ })
+})
From b99c329ff772a9e894dcdb95e3be086bc94b5bc7 Mon Sep 17 00:00:00 2001
From: Emma Mansell <73774046+7emansell@users.noreply.github.com>
Date: Tue, 22 Oct 2024 12:58:16 -0700
Subject: [PATCH 25/86] Fixing nested p tag
---
src/components/MyAccount/NewSettings/PhoneEmailForm.tsx | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/components/MyAccount/NewSettings/PhoneEmailForm.tsx b/src/components/MyAccount/NewSettings/PhoneEmailForm.tsx
index 67005a33a..9cb3c5b6f 100644
--- a/src/components/MyAccount/NewSettings/PhoneEmailForm.tsx
+++ b/src/components/MyAccount/NewSettings/PhoneEmailForm.tsx
@@ -239,16 +239,16 @@ const PhoneEmailForm = ({
>
{input}{" "}
{index === 0 && inputs.length > 1 && (
-
(P)
-
+
)}
))}
From 2271fa7986cf14499adaacc46efa7786e60dd59d Mon Sep 17 00:00:00 2001
From: Emma Mansell <73774046+7emansell@users.noreply.github.com>
Date: Tue, 29 Oct 2024 14:53:13 -0400
Subject: [PATCH 26/86] Clean up
---
src/components/MyAccount/NewSettings/PhoneEmailForm.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/components/MyAccount/NewSettings/PhoneEmailForm.tsx b/src/components/MyAccount/NewSettings/PhoneEmailForm.tsx
index 9cb3c5b6f..8f56dbe1d 100644
--- a/src/components/MyAccount/NewSettings/PhoneEmailForm.tsx
+++ b/src/components/MyAccount/NewSettings/PhoneEmailForm.tsx
@@ -15,8 +15,8 @@ import type { Patron } from "../../../types/myAccountTypes"
interface PhoneEmailFormProps {
patronData: Patron
- setIsSuccess
- setIsFailure
+ setIsSuccess: (boolean) => void
+ setIsFailure: (boolean) => void
inputType: "phones" | "emails"
}
From 116ef9b488d21f23d75eab5b24a43561333b268d Mon Sep 17 00:00:00 2001
From: Emma Mansell <73774046+7emansell@users.noreply.github.com>
Date: Thu, 31 Oct 2024 12:01:38 -0400
Subject: [PATCH 27/86] inline checks moved into formUtils
---
.../MyAccount/NewSettings/PhoneEmailForm.tsx | 43 ++++++++-----------
1 file changed, 19 insertions(+), 24 deletions(-)
diff --git a/src/components/MyAccount/NewSettings/PhoneEmailForm.tsx b/src/components/MyAccount/NewSettings/PhoneEmailForm.tsx
index 8f56dbe1d..6d0b58822 100644
--- a/src/components/MyAccount/NewSettings/PhoneEmailForm.tsx
+++ b/src/components/MyAccount/NewSettings/PhoneEmailForm.tsx
@@ -39,16 +39,21 @@ const PhoneEmailForm = ({
const [tempInputs, setTempInputs] = useState([...inputs])
+ const formUtils = {
+ regex: isEmail ? /^[^@]+@[^@]+\.[^@]+$/ : /^\+?[1-9]\d{1,14}$/,
+ labelText: `Update ${inputType}`,
+ addButtonLabel: isEmail ? "+ Add an email address" : "+ Add a phone number",
+ errorMessage: `Please enter a valid and unique ${
+ isEmail ? "email address" : "phone number"
+ }.`,
+ icon: `communication${isEmail ? "Email" : "Call"}`,
+ inputLabel: isEmail ? "Email" : "Phone",
+ }
+
const validateInput = (currentInput, inputs) => {
const isInputUnique =
inputs.filter((input) => input === currentInput).length === 1
- if (isEmail) {
- const emailRegex = /^[^@]+@[^@]+\.[^@]+$/
- return emailRegex.test(currentInput) && isInputUnique
- } else {
- const phoneRegex = /^\+?[1-9]\d{1,14}$/
- return phoneRegex.test(currentInput) && isInputUnique
- }
+ return formUtils.regex.test(currentInput) && isInputUnique
}
const handleInputChange = (e, index) => {
@@ -76,8 +81,6 @@ const PhoneEmailForm = ({
const handleRemove = (index) => {
const updatedInputs = tempInputs.filter((_, i) => i !== index)
setTempInputs(updatedInputs)
-
- // Immediately revalidate remaining inputs.
const hasInvalidInput = updatedInputs.some(
(input) => !validateInput(input, updatedInputs)
)
@@ -87,8 +90,6 @@ const PhoneEmailForm = ({
const handleAdd = () => {
const updatedInputs = [...tempInputs, ""]
setTempInputs(updatedInputs)
-
- // Immediately revalidate remaining inputs.
const hasInvalidInput = updatedInputs.some(
(input) => !validateInput(input, updatedInputs)
)
@@ -160,10 +161,7 @@ const PhoneEmailForm = ({
alignItems="flex-start"
width="100%"
>
-
+
{isEditing ? (
handleInputChange(e, index)}
isRequired
isClearable
@@ -199,12 +195,12 @@ const PhoneEmailForm = ({
/>
{(!isEmail || index !== 0) && (
handleRemove(index)}
>
- {" "}
+ {""}
)}
@@ -222,7 +218,7 @@ const PhoneEmailForm = ({
padding: "xxs",
}}
>
- {isEmail ? "+ Add an email address" : "+ Add a phone number"}
+ {formUtils.addButtonLabel}
) : isEmail || tempInputs.length != 0 ? (
@@ -285,7 +281,7 @@ const PhoneEmailForm = ({
paddingLeft: { base: "m", md: "unset" },
}}
>
- + Add a phone number
+ {formUtils.addButtonLabel}
)}
@@ -302,5 +298,4 @@ const PhoneEmailForm = ({
>
)
}
-
export default PhoneEmailForm
From dbf459820e7a974557def13b06138ebae3dc234d Mon Sep 17 00:00:00 2001
From: Emma Mansell <73774046+7emansell@users.noreply.github.com>
Date: Thu, 31 Oct 2024 12:03:18 -0400
Subject: [PATCH 28/86] Recommenting for clarity
---
src/components/MyAccount/NewSettings/PhoneEmailForm.tsx | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/src/components/MyAccount/NewSettings/PhoneEmailForm.tsx b/src/components/MyAccount/NewSettings/PhoneEmailForm.tsx
index 6d0b58822..8ccfc74cf 100644
--- a/src/components/MyAccount/NewSettings/PhoneEmailForm.tsx
+++ b/src/components/MyAccount/NewSettings/PhoneEmailForm.tsx
@@ -81,6 +81,8 @@ const PhoneEmailForm = ({
const handleRemove = (index) => {
const updatedInputs = tempInputs.filter((_, i) => i !== index)
setTempInputs(updatedInputs)
+
+ // Immediately revalidate remaining inputs.
const hasInvalidInput = updatedInputs.some(
(input) => !validateInput(input, updatedInputs)
)
@@ -90,6 +92,8 @@ const PhoneEmailForm = ({
const handleAdd = () => {
const updatedInputs = [...tempInputs, ""]
setTempInputs(updatedInputs)
+
+ // Immediately revalidate remaining inputs.
const hasInvalidInput = updatedInputs.some(
(input) => !validateInput(input, updatedInputs)
)
From ac68ff8527312bc7cb305edaa1772d85f77771d4 Mon Sep 17 00:00:00 2001
From: Emma Mansell <73774046+7emansell@users.noreply.github.com>
Date: Fri, 1 Nov 2024 13:42:57 -0400
Subject: [PATCH 29/86] Home library and notification form component, plus
tests
---
.../NewSettings/HomeLibraryForm.test.tsx | 106 ++++++++++
.../HomeLibraryNotificationForm.tsx | 189 ++++++++++++++++++
.../NewSettings/NewAccountSettingsTab.tsx | 17 +-
.../NewSettings/NotificationForm.test.tsx | 112 +++++++++++
4 files changed, 423 insertions(+), 1 deletion(-)
create mode 100644 src/components/MyAccount/NewSettings/HomeLibraryForm.test.tsx
create mode 100644 src/components/MyAccount/NewSettings/HomeLibraryNotificationForm.tsx
create mode 100644 src/components/MyAccount/NewSettings/NotificationForm.test.tsx
diff --git a/src/components/MyAccount/NewSettings/HomeLibraryForm.test.tsx b/src/components/MyAccount/NewSettings/HomeLibraryForm.test.tsx
new file mode 100644
index 000000000..e8e78725d
--- /dev/null
+++ b/src/components/MyAccount/NewSettings/HomeLibraryForm.test.tsx
@@ -0,0 +1,106 @@
+import { render, screen, fireEvent, waitFor } from "@testing-library/react"
+import { filteredPickupLocations } from "../../../../__test__/fixtures/processedMyAccountData"
+import { PatronDataProvider } from "../../../context/PatronDataContext"
+import { processedPatron } from "../../../../__test__/fixtures/processedMyAccountData"
+import { pickupLocations } from "../../../../__test__/fixtures/rawSierraAccountData"
+import HomeLibraryNotificationForm from "./HomeLibraryNotificationForm"
+
+describe("home library form", () => {
+ const mockSetIsSuccess = jest.fn()
+ const mockSetIsFailure = jest.fn()
+
+ beforeEach(() => {
+ jest.clearAllMocks()
+ global.fetch = jest.fn().mockResolvedValue({
+ json: async () => {
+ console.log("Updated")
+ },
+ status: 200,
+ } as Response)
+ })
+
+ const component = (
+
+
+
+ )
+
+ it("renders correctly with initial location", () => {
+ render(component)
+
+ expect(
+ screen.getByText(processedPatron.homeLibrary.name)
+ ).toBeInTheDocument()
+
+ expect(screen.getByRole("button", { name: /edit/i })).toBeInTheDocument()
+ })
+
+ it("allows editing when edit button is clicked", () => {
+ render(component)
+ fireEvent.click(screen.getByRole("button", { name: /edit/i }))
+
+ expect(screen.getByLabelText("Update home library")).toBeInTheDocument()
+ expect(
+ screen.getByDisplayValue(processedPatron.homeLibrary.name)
+ ).toBeInTheDocument()
+
+ expect(screen.getByRole("button", { name: /cancel/i })).toBeInTheDocument()
+ expect(
+ screen.getByRole("button", { name: /save changes/i })
+ ).toBeInTheDocument()
+ expect(screen.queryByText(/edit/)).not.toBeInTheDocument()
+ })
+
+ it("calls submit with valid data", async () => {
+ render(component)
+
+ fireEvent.click(screen.getByRole("button", { name: /edit/i }))
+
+ const input = screen.getByLabelText("Update home library")
+ fireEvent.change(input, {
+ target: { value: "Belmont" },
+ })
+
+ fireEvent.click(screen.getByRole("button", { name: /save changes/i }))
+
+ await waitFor(() => expect(fetch).toHaveBeenCalledTimes(1))
+
+ expect(fetch).toHaveBeenCalledWith(
+ "/research/research-catalog/api/account/settings/6742743",
+ {
+ body: '{"homeLibraryCode":"be "}',
+ headers: { "Content-Type": "application/json" },
+ method: "PUT",
+ }
+ )
+ })
+
+ it("cancels editing and reverts state", () => {
+ render(component)
+
+ fireEvent.click(screen.getByRole("button", { name: /edit/i }))
+
+ const input = screen.getByLabelText("Update home library")
+ fireEvent.change(input, {
+ target: { value: "Belmont" },
+ })
+
+ fireEvent.click(screen.getByRole("button", { name: /cancel/i }))
+
+ expect(
+ screen.getByText("SNFL (formerly Mid-Manhattan)")
+ ).toBeInTheDocument()
+ expect(screen.queryByDisplayValue("Belmont")).not.toBeInTheDocument()
+ })
+})
diff --git a/src/components/MyAccount/NewSettings/HomeLibraryNotificationForm.tsx b/src/components/MyAccount/NewSettings/HomeLibraryNotificationForm.tsx
new file mode 100644
index 000000000..101d439cd
--- /dev/null
+++ b/src/components/MyAccount/NewSettings/HomeLibraryNotificationForm.tsx
@@ -0,0 +1,189 @@
+import { useContext, useState } from "react"
+import { PatronDataContext } from "../../../context/PatronDataContext"
+import {
+ Button,
+ Flex,
+ Icon,
+ Select,
+ SkeletonLoader,
+ Text,
+} from "@nypl/design-system-react-components"
+import SettingsLabel from "./SettingsLabel"
+import SaveCancelButtons from "./SaveCancelButtons"
+import type { Patron, SierraCodeName } from "../../../types/myAccountTypes"
+
+interface HomeLibraryNotificationFormProps {
+ type: "library" | "notification"
+ patronData: Patron
+ setIsSuccess: (boolean) => void
+ setIsFailure: (boolean) => void
+ pickupLocations: SierraCodeName[]
+}
+
+const HomeLibraryNotificationForm = ({
+ type,
+ patronData,
+ setIsSuccess,
+ setIsFailure,
+ pickupLocations,
+}: HomeLibraryNotificationFormProps) => {
+ const { getMostUpdatedSierraAccountData } = useContext(PatronDataContext)
+ const [isLoading, setIsLoading] = useState(false)
+ const [isEditing, setIsEditing] = useState(false)
+
+ const notificationPreferenceMap = [
+ { code: "z", name: "Email" },
+ { code: "p", name: "Phone" },
+ { code: "-", name: "None" },
+ ]
+
+ const sortedPickupLocations = [
+ patronData.homeLibrary,
+ ...pickupLocations.filter(
+ (loc) => loc.code.trim() !== patronData.homeLibrary.code.trim()
+ ),
+ ]
+
+ const options =
+ type === "notification" ? notificationPreferenceMap : sortedPickupLocations
+
+ const formUtils = {
+ icon: type === "notification" ? "communicationChatBubble" : "actionHome",
+ label: type === "notification" ? "Notification preference" : "Home library",
+ selectorId:
+ type === "notification"
+ ? "notification-preference-selector"
+ : "update-home-library-selector",
+ body: (code) =>
+ type === "notification"
+ ? {
+ fixedFields: { "268": { label: "Notice Preference", value: code } },
+ }
+ : { homeLibraryCode: `${code}` },
+ }
+
+ const [selection, setSelection] = useState(
+ type === "notification"
+ ? notificationPreferenceMap.find(
+ (pref) => pref.code === patronData.notificationPreference
+ )?.name
+ : patronData.homeLibrary.name
+ )
+ const [tempSelection, setTempSelection] = useState(selection)
+
+ const handleSelectChange = (event) => {
+ setTempSelection(event.target.value)
+ }
+
+ const cancelEditing = () => {
+ setIsEditing(false)
+ }
+
+ const submitSelection = async () => {
+ setIsLoading(true)
+ setIsEditing(false)
+
+ const code =
+ type === "notification"
+ ? notificationPreferenceMap.find((pref) => pref.name === tempSelection)
+ ?.code
+ : pickupLocations.find((loc) => loc.name === tempSelection)?.code
+
+ const body = formUtils.body(code)
+
+ try {
+ const response = await fetch(
+ `/research/research-catalog/api/account/settings/${patronData.id}`,
+ {
+ method: "PUT",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(body),
+ }
+ )
+
+ if (response.status === 200) {
+ await getMostUpdatedSierraAccountData()
+ setIsSuccess(true)
+ setSelection(tempSelection)
+ setTempSelection(tempSelection)
+ } else {
+ setIsFailure(true)
+ setTempSelection(tempSelection)
+ }
+ } catch (error) {
+ console.error("Error submitting", error)
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ return (
+ <>
+ {isLoading ? (
+
+ ) : (
+
+
+ {isEditing ? (
+
+
+ {options.map((option, index) => (
+
+ {option.name}
+
+ ))}
+
+
+ ) : (
+
+
+ {selection}
+
+ setIsEditing(true)}
+ sx={{
+ paddingLeft: "xs",
+ paddingRight: "xs",
+ marginLeft: "xxl",
+ }}
+ >
+
+ Edit
+
+
+ )}
+ {isEditing && (
+
+ )}
+
+ )}
+ >
+ )
+}
+
+export default HomeLibraryNotificationForm
diff --git a/src/components/MyAccount/NewSettings/NewAccountSettingsTab.tsx b/src/components/MyAccount/NewSettings/NewAccountSettingsTab.tsx
index e1dc90f9d..342208a17 100644
--- a/src/components/MyAccount/NewSettings/NewAccountSettingsTab.tsx
+++ b/src/components/MyAccount/NewSettings/NewAccountSettingsTab.tsx
@@ -2,10 +2,11 @@ import { Flex, Banner } from "@nypl/design-system-react-components"
import { useContext, useState } from "react"
import { PatronDataContext } from "../../../context/PatronDataContext"
import PhoneEmailForm from "./PhoneEmailForm"
+import HomeLibraryNotificationForm from "./HomeLibraryNotificationForm"
const NewAccountSettingsTab = () => {
const {
- updatedAccountData: { patron },
+ updatedAccountData: { patron, pickupLocations },
} = useContext(PatronDataContext)
const [isSuccess, setIsSuccess] = useState(false)
const [isFailure, setIsFailure] = useState(false)
@@ -39,6 +40,20 @@ const NewAccountSettingsTab = () => {
setIsFailure={setIsFailure}
inputType="emails"
/>
+
+
>
)
diff --git a/src/components/MyAccount/NewSettings/NotificationForm.test.tsx b/src/components/MyAccount/NewSettings/NotificationForm.test.tsx
new file mode 100644
index 000000000..eda29fc16
--- /dev/null
+++ b/src/components/MyAccount/NewSettings/NotificationForm.test.tsx
@@ -0,0 +1,112 @@
+import { render, screen, fireEvent, waitFor } from "@testing-library/react"
+import { filteredPickupLocations } from "../../../../__test__/fixtures/processedMyAccountData"
+import { PatronDataProvider } from "../../../context/PatronDataContext"
+import { processedPatron } from "../../../../__test__/fixtures/processedMyAccountData"
+import { pickupLocations } from "../../../../__test__/fixtures/rawSierraAccountData"
+import HomeLibraryNotificationForm from "./HomeLibraryNotificationForm"
+
+describe("notification preference form", () => {
+ const mockSetIsSuccess = jest.fn()
+ const mockSetIsFailure = jest.fn()
+
+ beforeEach(() => {
+ jest.clearAllMocks()
+ global.fetch = jest.fn().mockResolvedValue({
+ json: async () => {
+ console.log("Updated")
+ },
+ status: 200,
+ } as Response)
+ })
+
+ const notificationPreferenceMap = [
+ { code: "z", name: "Email" },
+ { code: "p", name: "Phone" },
+ { code: "-", name: "None" },
+ ]
+
+ const processedPatronPref = notificationPreferenceMap.find(
+ (pref) => pref.code === processedPatron.notificationPreference
+ )?.name
+
+ const component = (
+
+
+
+ )
+
+ it("renders correctly with initial preference", () => {
+ render(component)
+
+ expect(screen.getByText(processedPatronPref)).toBeInTheDocument()
+
+ expect(screen.getByRole("button", { name: /edit/i })).toBeInTheDocument()
+ })
+
+ it("allows editing when edit button is clicked", () => {
+ render(component)
+ fireEvent.click(screen.getByRole("button", { name: /edit/i }))
+
+ expect(
+ screen.getByLabelText("Update notification preference")
+ ).toBeInTheDocument()
+ expect(screen.getByDisplayValue(processedPatronPref)).toBeInTheDocument()
+
+ expect(screen.getByRole("button", { name: /cancel/i })).toBeInTheDocument()
+ expect(
+ screen.getByRole("button", { name: /save changes/i })
+ ).toBeInTheDocument()
+ expect(screen.queryByText(/edit/)).not.toBeInTheDocument()
+ })
+
+ it("calls submit with valid data", async () => {
+ render(component)
+
+ fireEvent.click(screen.getByRole("button", { name: /edit/i }))
+
+ const input = screen.getByLabelText("Update notification preference")
+ fireEvent.change(input, {
+ target: { value: "None" },
+ })
+
+ fireEvent.click(screen.getByRole("button", { name: /save changes/i }))
+
+ await waitFor(() => expect(fetch).toHaveBeenCalledTimes(1))
+
+ expect(fetch).toHaveBeenCalledWith(
+ "/research/research-catalog/api/account/settings/6742743",
+ {
+ body: '{"fixedFields":{"268":{"label":"Notice Preference","value":"-"}}}',
+ headers: { "Content-Type": "application/json" },
+ method: "PUT",
+ }
+ )
+ })
+
+ it("cancels editing and reverts state", () => {
+ render(component)
+
+ fireEvent.click(screen.getByRole("button", { name: /edit/i }))
+
+ const input = screen.getByLabelText("Update notification preference")
+ fireEvent.change(input, {
+ target: { value: "None" },
+ })
+
+ fireEvent.click(screen.getByRole("button", { name: /cancel/i }))
+
+ expect(screen.getByText(processedPatronPref)).toBeInTheDocument()
+ expect(screen.queryByDisplayValue("None")).not.toBeInTheDocument()
+ })
+})
From 420f93f4c787fa23f584e273f75ba365385ce530 Mon Sep 17 00:00:00 2001
From: Emma Mansell <73774046+7emansell@users.noreply.github.com>
Date: Fri, 1 Nov 2024 13:43:11 -0400
Subject: [PATCH 30/86] Styling and readme corrections
---
accountREADME.md | 2 +-
src/components/MyAccount/NewSettings/PhoneEmailForm.tsx | 6 +++++-
src/components/MyAccount/NewSettings/PhoneForm.test.tsx | 8 ++++----
.../MyAccount/NewSettings/SaveCancelButtons.tsx | 4 ++--
src/components/MyAccount/NewSettings/SettingsLabel.tsx | 3 +--
5 files changed, 13 insertions(+), 10 deletions(-)
diff --git a/accountREADME.md b/accountREADME.md
index c88db4eb2..e91a8b8f6 100644
--- a/accountREADME.md
+++ b/accountREADME.md
@@ -41,7 +41,7 @@ Route parameter is the hold ID. Request body requires the **patron ID**, and can
exampleBody: {
patronId: '123456',
freeze: true,
- pickupLocation: 'sn',
+ homeLibraryCode: 'sn',
},
```
diff --git a/src/components/MyAccount/NewSettings/PhoneEmailForm.tsx b/src/components/MyAccount/NewSettings/PhoneEmailForm.tsx
index 8ccfc74cf..88189b7cd 100644
--- a/src/components/MyAccount/NewSettings/PhoneEmailForm.tsx
+++ b/src/components/MyAccount/NewSettings/PhoneEmailForm.tsx
@@ -235,7 +235,11 @@ const PhoneEmailForm = ({
{tempInputs.map((input, index) => (
{input}{" "}
{index === 0 && inputs.length > 1 && (
diff --git a/src/components/MyAccount/NewSettings/PhoneForm.test.tsx b/src/components/MyAccount/NewSettings/PhoneForm.test.tsx
index ddc1d9297..55fa9c985 100644
--- a/src/components/MyAccount/NewSettings/PhoneForm.test.tsx
+++ b/src/components/MyAccount/NewSettings/PhoneForm.test.tsx
@@ -101,7 +101,7 @@ describe("phone form", () => {
).not.toBeInTheDocument()
})
- it.skip("calls submit with valid data", async () => {
+ it("calls submit with valid data", async () => {
render(component)
fireEvent.click(screen.getByRole("button", { name: /edit/i }))
@@ -115,11 +115,11 @@ describe("phone form", () => {
expect(fetch).toHaveBeenCalledWith(
"/research/research-catalog/api/account/settings/6742743",
- expect.objectContaining({
- body: '{"phones":[{number:"1234", type: "t"}]}',
+ {
+ body: '{"phones":[{"number":"1234","type":"t"}]}',
headers: { "Content-Type": "application/json" },
method: "PUT",
- })
+ }
)
})
diff --git a/src/components/MyAccount/NewSettings/SaveCancelButtons.tsx b/src/components/MyAccount/NewSettings/SaveCancelButtons.tsx
index fbc471d3c..e6bd00711 100644
--- a/src/components/MyAccount/NewSettings/SaveCancelButtons.tsx
+++ b/src/components/MyAccount/NewSettings/SaveCancelButtons.tsx
@@ -1,10 +1,10 @@
import { ButtonGroup, Button } from "@nypl/design-system-react-components"
type SaveCancelButtonProps = {
- isDisabled: boolean
+ isDisabled?: boolean
onCancel: () => void
onSave: () => void
- inputType: "phones" | "emails"
+ inputType?: "phones" | "emails"
}
const SaveCancelButtons = ({
diff --git a/src/components/MyAccount/NewSettings/SettingsLabel.tsx b/src/components/MyAccount/NewSettings/SettingsLabel.tsx
index fd4a2df74..9595accf0 100644
--- a/src/components/MyAccount/NewSettings/SettingsLabel.tsx
+++ b/src/components/MyAccount/NewSettings/SettingsLabel.tsx
@@ -5,7 +5,7 @@ const SettingsLabel = ({ icon, text }) => {
@@ -15,7 +15,6 @@ const SettingsLabel = ({ icon, text }) => {
sx={{
fontWeight: "500",
marginBottom: 0,
- marginRight: { base: "l", lg: "200px" },
}}
>
{text}
From d099abdd4bbde340ffd1a423957205451014efea Mon Sep 17 00:00:00 2001
From: Emma Mansell <73774046+7emansell@users.noreply.github.com>
Date: Mon, 4 Nov 2024 16:01:58 -0500
Subject: [PATCH 31/86] Renaming form components
---
.../MyAccount/NewSettings/EmailForm.test.tsx | 2 +-
.../MyAccount/NewSettings/HomeLibraryForm.test.tsx | 2 +-
.../MyAccount/NewSettings/NewAccountSettingsTab.tsx | 10 ++++++++--
.../MyAccount/NewSettings/NotificationForm.test.tsx | 2 +-
.../MyAccount/NewSettings/PhoneForm.test.tsx | 2 +-
.../{PhoneEmailForm.tsx => SettingsInputForm.tsx} | 0
...raryNotificationForm.tsx => SettingsSelectForm.tsx} | 1 +
7 files changed, 13 insertions(+), 6 deletions(-)
rename src/components/MyAccount/NewSettings/{PhoneEmailForm.tsx => SettingsInputForm.tsx} (100%)
rename src/components/MyAccount/NewSettings/{HomeLibraryNotificationForm.tsx => SettingsSelectForm.tsx} (99%)
diff --git a/src/components/MyAccount/NewSettings/EmailForm.test.tsx b/src/components/MyAccount/NewSettings/EmailForm.test.tsx
index fe92ec10b..9e4e99d6a 100644
--- a/src/components/MyAccount/NewSettings/EmailForm.test.tsx
+++ b/src/components/MyAccount/NewSettings/EmailForm.test.tsx
@@ -2,7 +2,7 @@ import { render, screen, fireEvent, waitFor } from "@testing-library/react"
import { filteredPickupLocations } from "../../../../__test__/fixtures/processedMyAccountData"
import { PatronDataProvider } from "../../../context/PatronDataContext"
import { processedPatron } from "../../../../__test__/fixtures/processedMyAccountData"
-import PhoneEmailForm from "./PhoneEmailForm"
+import PhoneEmailForm from "./SettingsInputForm"
describe("email form", () => {
const mockSetIsSuccess = jest.fn()
diff --git a/src/components/MyAccount/NewSettings/HomeLibraryForm.test.tsx b/src/components/MyAccount/NewSettings/HomeLibraryForm.test.tsx
index e8e78725d..4228960a2 100644
--- a/src/components/MyAccount/NewSettings/HomeLibraryForm.test.tsx
+++ b/src/components/MyAccount/NewSettings/HomeLibraryForm.test.tsx
@@ -3,7 +3,7 @@ import { filteredPickupLocations } from "../../../../__test__/fixtures/processed
import { PatronDataProvider } from "../../../context/PatronDataContext"
import { processedPatron } from "../../../../__test__/fixtures/processedMyAccountData"
import { pickupLocations } from "../../../../__test__/fixtures/rawSierraAccountData"
-import HomeLibraryNotificationForm from "./HomeLibraryNotificationForm"
+import HomeLibraryNotificationForm from "./SettingsSelectForm"
describe("home library form", () => {
const mockSetIsSuccess = jest.fn()
diff --git a/src/components/MyAccount/NewSettings/NewAccountSettingsTab.tsx b/src/components/MyAccount/NewSettings/NewAccountSettingsTab.tsx
index 342208a17..7cc45e426 100644
--- a/src/components/MyAccount/NewSettings/NewAccountSettingsTab.tsx
+++ b/src/components/MyAccount/NewSettings/NewAccountSettingsTab.tsx
@@ -1,8 +1,9 @@
import { Flex, Banner } from "@nypl/design-system-react-components"
import { useContext, useState } from "react"
import { PatronDataContext } from "../../../context/PatronDataContext"
-import PhoneEmailForm from "./PhoneEmailForm"
-import HomeLibraryNotificationForm from "./HomeLibraryNotificationForm"
+import PhoneEmailForm from "./SettingsInputForm"
+import HomeLibraryNotificationForm from "./SettingsSelectForm"
+import PasswordForm from "./PasswordForm"
const NewAccountSettingsTab = () => {
const {
@@ -54,6 +55,11 @@ const NewAccountSettingsTab = () => {
setIsFailure={setIsFailure}
type="notification"
/>
+
>
)
diff --git a/src/components/MyAccount/NewSettings/NotificationForm.test.tsx b/src/components/MyAccount/NewSettings/NotificationForm.test.tsx
index eda29fc16..39f9f6d9c 100644
--- a/src/components/MyAccount/NewSettings/NotificationForm.test.tsx
+++ b/src/components/MyAccount/NewSettings/NotificationForm.test.tsx
@@ -3,7 +3,7 @@ import { filteredPickupLocations } from "../../../../__test__/fixtures/processed
import { PatronDataProvider } from "../../../context/PatronDataContext"
import { processedPatron } from "../../../../__test__/fixtures/processedMyAccountData"
import { pickupLocations } from "../../../../__test__/fixtures/rawSierraAccountData"
-import HomeLibraryNotificationForm from "./HomeLibraryNotificationForm"
+import HomeLibraryNotificationForm from "./SettingsSelectForm"
describe("notification preference form", () => {
const mockSetIsSuccess = jest.fn()
diff --git a/src/components/MyAccount/NewSettings/PhoneForm.test.tsx b/src/components/MyAccount/NewSettings/PhoneForm.test.tsx
index 55fa9c985..12b215e45 100644
--- a/src/components/MyAccount/NewSettings/PhoneForm.test.tsx
+++ b/src/components/MyAccount/NewSettings/PhoneForm.test.tsx
@@ -2,7 +2,7 @@ import { render, screen, fireEvent, waitFor } from "@testing-library/react"
import { filteredPickupLocations } from "../../../../__test__/fixtures/processedMyAccountData"
import { PatronDataProvider } from "../../../context/PatronDataContext"
import { processedPatron } from "../../../../__test__/fixtures/processedMyAccountData"
-import PhoneEmailForm from "./PhoneEmailForm"
+import PhoneEmailForm from "./SettingsInputForm"
describe("phone form", () => {
const mockSetIsSuccess = jest.fn()
diff --git a/src/components/MyAccount/NewSettings/PhoneEmailForm.tsx b/src/components/MyAccount/NewSettings/SettingsInputForm.tsx
similarity index 100%
rename from src/components/MyAccount/NewSettings/PhoneEmailForm.tsx
rename to src/components/MyAccount/NewSettings/SettingsInputForm.tsx
diff --git a/src/components/MyAccount/NewSettings/HomeLibraryNotificationForm.tsx b/src/components/MyAccount/NewSettings/SettingsSelectForm.tsx
similarity index 99%
rename from src/components/MyAccount/NewSettings/HomeLibraryNotificationForm.tsx
rename to src/components/MyAccount/NewSettings/SettingsSelectForm.tsx
index 101d439cd..921d3d3a0 100644
--- a/src/components/MyAccount/NewSettings/HomeLibraryNotificationForm.tsx
+++ b/src/components/MyAccount/NewSettings/SettingsSelectForm.tsx
@@ -133,6 +133,7 @@ const HomeLibraryNotificationForm = ({
sx={{ marginTop: "xs", marginLeft: { base: "l", lg: "unset" } }}
>
Date: Mon, 4 Nov 2024 16:02:16 -0500
Subject: [PATCH 32/86] Password form, lacking styling
---
.../MyAccount/NewSettings/PasswordForm.tsx | 253 ++++++++++++++++++
.../MyAccount/NewSettings/SettingsLabel.tsx | 4 +-
2 files changed, 254 insertions(+), 3 deletions(-)
create mode 100644 src/components/MyAccount/NewSettings/PasswordForm.tsx
diff --git a/src/components/MyAccount/NewSettings/PasswordForm.tsx b/src/components/MyAccount/NewSettings/PasswordForm.tsx
new file mode 100644
index 000000000..6eb2cd2e0
--- /dev/null
+++ b/src/components/MyAccount/NewSettings/PasswordForm.tsx
@@ -0,0 +1,253 @@
+import { useContext, useState } from "react"
+import { PatronDataContext } from "../../../context/PatronDataContext"
+import {
+ Banner,
+ Button,
+ Flex,
+ FormField,
+ Icon,
+ SkeletonLoader,
+ Text,
+ TextInput,
+} from "@nypl/design-system-react-components"
+import SettingsLabel from "./SettingsLabel"
+import SaveCancelButtons from "./SaveCancelButtons"
+import type { Patron } from "../../../types/myAccountTypes"
+import { patron } from "../../../../__test__/fixtures/rawSierraAccountData"
+import { BASE_URL } from "../../../config/constants"
+
+interface PasswordFormProps {
+ patronData: Patron
+ setIsSuccess: (boolean) => void
+ setIsFailure: (boolean) => void
+}
+
+const PasswordForm = ({
+ patronData,
+ setIsSuccess,
+ setIsFailure,
+}: PasswordFormProps) => {
+ const { getMostUpdatedSierraAccountData } = useContext(PatronDataContext)
+ const [isLoading, setIsLoading] = useState(false)
+ const [isEditing, setIsEditing] = useState(false)
+ const [formData, setFormData] = useState({
+ oldPassword: "",
+ newPassword: "",
+ confirmPassword: "",
+ passwordsMatch: true,
+ })
+
+ const cancelEditing = () => {
+ setIsEditing(false)
+ }
+
+ const submitForm = async () => {
+ setIsLoading(true)
+ setIsEditing(false)
+ setIsSuccess(false)
+ setIsFailure(false)
+ try {
+ const response = await fetch(
+ `${BASE_URL}/api/account/update-pin/${patronData.id}`,
+ {
+ method: "PUT",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ oldPin: formData.oldPassword,
+ newPin: formData.newPassword,
+ barcode: patronData.barcode,
+ }),
+ }
+ )
+
+ if (response.status === 200) {
+ await getMostUpdatedSierraAccountData()
+ setIsSuccess(true)
+ } else {
+ setIsFailure(true)
+ }
+ } catch (error) {
+ console.error("Error submitting", error)
+ } finally {
+ setIsLoading(false)
+ }
+ }
+ const validateForm =
+ formData.oldPassword !== "" &&
+ formData.newPassword !== "" &&
+ formData.confirmPassword !== "" &&
+ formData.passwordsMatch
+
+ const handleInputChange = (e) => {
+ const { name, value } = e.target
+ let updatedFormData = { ...formData }
+
+ if (name === "confirmPassword") {
+ updatedFormData = {
+ ...updatedFormData,
+ confirmPassword: value,
+ passwordsMatch: updatedFormData.newPassword === value,
+ }
+ } else if (name === "newPassword") {
+ updatedFormData = {
+ ...updatedFormData,
+ newPassword: value,
+ passwordsMatch: updatedFormData.confirmPassword === value,
+ }
+ } else {
+ updatedFormData = {
+ ...updatedFormData,
+ [name]: value,
+ }
+ }
+ setFormData(updatedFormData)
+ }
+
+ return (
+ <>
+ {isLoading ? (
+
+ ) : isEditing ? (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Use a strong PIN/PASSWORD to protect your security and
+ identity.
+
+
+ You have the option of creating a standard PIN (4 characters
+ in length) or the more secure option of creating a PASSWORD
+ up to 32 characters long. You can create a
+ PIN/PASSWORD that includes upper or lower case characters
+ (a-z, A-Z), numbers (0-9), and/or special characters limited
+ to the following: ~ ! ? @ # $ % ^ & * ( )
+ PINs or PASSWORDS must not contain common patterns, for
+ example: a character that is repeated 3 or more times (0001,
+ aaaa, aaaatf54, x7gp3333), or four characters repeated two
+ or more times (1212, abab, abcabc, ababx7gp, x7gp3434).{" "}
+
+ PINs and PASSWORDS must NOT contain a period.
+
+ >
+ }
+ />
+
+
+
+ ) : (
+
+
+
+
+ ****
+
+ setIsEditing(true)}
+ sx={{
+ paddingLeft: "xs",
+ paddingRight: "xs",
+ marginLeft: "xxl",
+ }}
+ >
+
+ Edit
+
+
+
+ )}
+ >
+ )
+}
+
+export default PasswordForm
diff --git a/src/components/MyAccount/NewSettings/SettingsLabel.tsx b/src/components/MyAccount/NewSettings/SettingsLabel.tsx
index 9595accf0..36379ef72 100644
--- a/src/components/MyAccount/NewSettings/SettingsLabel.tsx
+++ b/src/components/MyAccount/NewSettings/SettingsLabel.tsx
@@ -5,9 +5,7 @@ const SettingsLabel = ({ icon, text }) => {
Date: Tue, 5 Nov 2024 10:30:09 -0500
Subject: [PATCH 33/86] With settingsState as a chunk
---
.../MyAccount/NewSettings/EditButton.tsx | 26 +++++++++++++
.../NewSettings/NewAccountSettingsTab.tsx | 26 ++++++-------
.../MyAccount/NewSettings/PasswordForm.tsx | 35 ++++++++---------
.../NewSettings/SettingsInputForm.tsx | 38 +++++++++----------
.../NewSettings/SettingsSelectForm.tsx | 34 ++++++++---------
5 files changed, 89 insertions(+), 70 deletions(-)
create mode 100644 src/components/MyAccount/NewSettings/EditButton.tsx
diff --git a/src/components/MyAccount/NewSettings/EditButton.tsx b/src/components/MyAccount/NewSettings/EditButton.tsx
new file mode 100644
index 000000000..edff21264
--- /dev/null
+++ b/src/components/MyAccount/NewSettings/EditButton.tsx
@@ -0,0 +1,26 @@
+import { Button, Icon } from "@nypl/design-system-react-components"
+
+type EditButtonProps = {
+ buttonId: string
+ onClick: () => void
+}
+
+const EditButton = ({ buttonId, onClick }: EditButtonProps) => {
+ return (
+
+
+ Edit
+
+ )
+}
+
+export default EditButton
diff --git a/src/components/MyAccount/NewSettings/NewAccountSettingsTab.tsx b/src/components/MyAccount/NewSettings/NewAccountSettingsTab.tsx
index 7cc45e426..46b8d71de 100644
--- a/src/components/MyAccount/NewSettings/NewAccountSettingsTab.tsx
+++ b/src/components/MyAccount/NewSettings/NewAccountSettingsTab.tsx
@@ -11,6 +11,14 @@ const NewAccountSettingsTab = () => {
} = useContext(PatronDataContext)
const [isSuccess, setIsSuccess] = useState(false)
const [isFailure, setIsFailure] = useState(false)
+ const [isOtherEditing, setIsOtherEditing] = useState(false)
+
+ const settingsState = {
+ setIsSuccess,
+ setIsFailure,
+ isOtherEditing,
+ setIsOtherEditing,
+ }
return (
<>
@@ -31,35 +39,27 @@ const NewAccountSettingsTab = () => {
-
+
>
)
diff --git a/src/components/MyAccount/NewSettings/PasswordForm.tsx b/src/components/MyAccount/NewSettings/PasswordForm.tsx
index 6eb2cd2e0..da2c68546 100644
--- a/src/components/MyAccount/NewSettings/PasswordForm.tsx
+++ b/src/components/MyAccount/NewSettings/PasswordForm.tsx
@@ -15,18 +15,14 @@ import SaveCancelButtons from "./SaveCancelButtons"
import type { Patron } from "../../../types/myAccountTypes"
import { patron } from "../../../../__test__/fixtures/rawSierraAccountData"
import { BASE_URL } from "../../../config/constants"
+import EditButton from "./EditButton"
interface PasswordFormProps {
patronData: Patron
- setIsSuccess: (boolean) => void
- setIsFailure: (boolean) => void
+ settingsState
}
-const PasswordForm = ({
- patronData,
- setIsSuccess,
- setIsFailure,
-}: PasswordFormProps) => {
+const PasswordForm = ({ patronData, settingsState }: PasswordFormProps) => {
const { getMostUpdatedSierraAccountData } = useContext(PatronDataContext)
const [isLoading, setIsLoading] = useState(false)
const [isEditing, setIsEditing] = useState(false)
@@ -36,9 +32,12 @@ const PasswordForm = ({
confirmPassword: "",
passwordsMatch: true,
})
+ const { setIsSuccess, setIsFailure, isOtherEditing, setIsOtherEditing } =
+ settingsState
const cancelEditing = () => {
setIsEditing(false)
+ setIsOtherEditing(false)
}
const submitForm = async () => {
@@ -230,19 +229,15 @@ const PasswordForm = ({
>
****
- setIsEditing(true)}
- sx={{
- paddingLeft: "xs",
- paddingRight: "xs",
- marginLeft: "xxl",
- }}
- >
-
- Edit
-
+ {!isOtherEditing && (
+ {
+ setIsEditing(true)
+ setIsOtherEditing(true)
+ }}
+ />
+ )}
)}
diff --git a/src/components/MyAccount/NewSettings/SettingsInputForm.tsx b/src/components/MyAccount/NewSettings/SettingsInputForm.tsx
index 88189b7cd..c55912c6a 100644
--- a/src/components/MyAccount/NewSettings/SettingsInputForm.tsx
+++ b/src/components/MyAccount/NewSettings/SettingsInputForm.tsx
@@ -12,18 +12,17 @@ import { PatronDataContext } from "../../../context/PatronDataContext"
import SaveCancelButtons from "./SaveCancelButtons"
import SettingsLabel from "./SettingsLabel"
import type { Patron } from "../../../types/myAccountTypes"
+import EditButton from "./EditButton"
interface PhoneEmailFormProps {
patronData: Patron
- setIsSuccess: (boolean) => void
- setIsFailure: (boolean) => void
+ settingsState
inputType: "phones" | "emails"
}
const PhoneEmailForm = ({
patronData,
- setIsSuccess,
- setIsFailure,
+ settingsState,
inputType,
}: PhoneEmailFormProps) => {
const isEmail = inputType === "emails"
@@ -37,6 +36,9 @@ const PhoneEmailForm = ({
const [isEditing, setIsEditing] = useState(false)
const [error, setError] = useState(false)
+ const { setIsSuccess, setIsFailure, isOtherEditing, setIsOtherEditing } =
+ settingsState
+
const [tempInputs, setTempInputs] = useState([...inputs])
const formUtils = {
@@ -110,6 +112,7 @@ const PhoneEmailForm = ({
const cancelEditing = () => {
setTempInputs([...inputs])
setIsEditing(false)
+ setIsOtherEditing(false)
setError(false)
}
@@ -257,22 +260,16 @@ const PhoneEmailForm = ({
))}