-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
THEME-738: Identity | Account Management (#1739)
* THEMES-738: copied in v1 account management code, began converting to v2. * THEMES-738: saving work, need to convert social sign on first. * THEMES-738: converted most of the account management block. * THEMES-738: updated components to use v2 social sign on block. * THEMES-768: updated styling for account management to better match mock ups. * THEMES-738: finished correcting styling for account management page. * THEMES-738: updated tests to use react testing library. * THEMES-738: fixed tests for editable form input. * THEMES-738: tiny edits to testing files so jest --changed-since can see those tests for the coverage report. * THEMES-738: added testing ignores for Identity SDK functionality. * THEMES-738: updated styling for paragraphs in the edit blocks. Also updated styles for the commerce theme. * Apply suggestions from code review --------- Co-authored-by: nschubach <[email protected]>
- Loading branch information
Showing
17 changed files
with
1,390 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
101 changes: 101 additions & 0 deletions
101
blocks/identity-block/components/editable-form-input/index.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import React, { useState, useRef } from "react"; | ||
import { Button, Paragraph } from "@wpmedia/arc-themes-components"; | ||
|
||
// handles submit and display of form | ||
// will toggle back to not editable upon successful submit | ||
export function ConditionalFormContainer({ showForm, children, onSubmit, setIsEditable }) { | ||
const formRef = useRef(); | ||
|
||
// handleSubmit from headline submit form | ||
const handleSubmit = (event) => { | ||
event.preventDefault(); | ||
|
||
const valid = formRef.current.checkValidity(); | ||
if (valid) { | ||
const namedFields = Array.from(formRef.current.elements) | ||
.filter((element) => element?.name && typeof element?.name !== "undefined") | ||
.reduce( | ||
(accumulator, element) => ({ | ||
...accumulator, | ||
[element.name]: element.value, | ||
}), | ||
{} | ||
); | ||
onSubmit(namedFields).then(() => setIsEditable(false)); | ||
} | ||
}; | ||
|
||
return showForm ? ( | ||
<form data-testid="conditional-form" onSubmit={handleSubmit} ref={formRef}> | ||
{children} | ||
</form> | ||
) : ( | ||
<>{children}</> | ||
); | ||
} | ||
|
||
function EditableFieldPresentational({ | ||
blockClassName, | ||
cancelEdit, | ||
cancelText, | ||
children, | ||
editText, | ||
formErrorText, | ||
initialValue, | ||
label, | ||
onSubmit, | ||
saveText, | ||
}) { | ||
const [isEditable, setIsEditable] = useState(!!formErrorText); | ||
return ( | ||
<section className={`${blockClassName}__edit`}> | ||
<ConditionalFormContainer | ||
onSubmit={onSubmit} | ||
showForm={isEditable} | ||
setIsEditable={setIsEditable} | ||
> | ||
<div> | ||
{isEditable ? ( | ||
<> | ||
{children} | ||
{formErrorText ? ( | ||
<section role="alert"> | ||
<Paragraph>{formErrorText}</Paragraph> | ||
</section> | ||
) : null} | ||
<div> | ||
<Button | ||
onClick={() => { | ||
if (cancelEdit) { | ||
cancelEdit(); | ||
} | ||
setIsEditable(false); | ||
}} | ||
size="small" | ||
type="submit" | ||
> | ||
<span>{cancelText}</span> | ||
</Button> | ||
<Button size="small" type="submit"> | ||
<span>{saveText}</span> | ||
</Button> | ||
</div> | ||
</> | ||
) : ( | ||
<> | ||
<div className={`${blockClassName}__edit-label`}> | ||
<Paragraph>{label}</Paragraph> | ||
<Button onClick={() => setIsEditable(true)} size="small" type="button"> | ||
<span>{editText}</span> | ||
</Button> | ||
</div> | ||
<Paragraph>{initialValue}</Paragraph> | ||
</> | ||
)} | ||
</div> | ||
</ConditionalFormContainer> | ||
</section> | ||
); | ||
} | ||
|
||
export default EditableFieldPresentational; |
32 changes: 32 additions & 0 deletions
32
blocks/identity-block/components/editable-form-input/index.story-ignore.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import React from "react"; | ||
|
||
import { FormInputField, FIELD_TYPES } from "@wpmedia/shared-styles"; | ||
import EditableFormInput from "."; | ||
|
||
export default { | ||
title: "Blocks/Identity/Components/EditableFormInput", | ||
parameters: { | ||
chromatic: { viewports: [320, 1200] }, | ||
}, | ||
}; | ||
|
||
export const emailField = () => ( | ||
<EditableFormInput | ||
initialValue="[email protected]" | ||
label="Email address" | ||
editText="Edit" | ||
saveText="Save" | ||
cancelText="Cancel" | ||
> | ||
<FormInputField | ||
type={FIELD_TYPES.EMAIL} | ||
label="Email address" | ||
defaultValue="[email protected]" | ||
showDefaultError={false} | ||
required | ||
autoComplete="email" | ||
name="email" | ||
validationErrorMessage="Please enter a valid email address" | ||
/> | ||
</EditableFormInput> | ||
); |
123 changes: 123 additions & 0 deletions
123
blocks/identity-block/components/editable-form-input/index.test.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
import React from "react"; | ||
import { fireEvent, render, screen } from "@testing-library/react"; | ||
|
||
import EditableFormInputField, { ConditionalFormContainer } from "."; | ||
|
||
describe("Editable form input field", () => { | ||
it("conditional form renders a form when show form elected", () => { | ||
render(<ConditionalFormContainer showForm />); | ||
expect(screen.getByTestId("conditional-form")).not.toBeNull(); | ||
}); | ||
|
||
it("conditional form does not render a form when show form not elected", () => { | ||
render(<ConditionalFormContainer showForm={false} />); | ||
expect(screen.queryByTestId("conditional-form")).toBeNull(); | ||
}); | ||
|
||
it("editable form field shows initial value, label, and edit button when not editable and hides children", () => { | ||
render( | ||
<EditableFormInputField | ||
initialValue="initial value" | ||
editText="edit text" | ||
label="label" | ||
onSubmit={() => {}} | ||
> | ||
<p id="test-child">Test child</p> | ||
</EditableFormInputField> | ||
); | ||
expect(screen.getByText("initial value")).not.toBeNull(); | ||
expect(screen.getByText("edit text")).not.toBeNull(); | ||
expect(screen.getByText("label")).not.toBeNull(); | ||
}); | ||
|
||
it("shows error text if passed in with formErrorText prop", () => { | ||
render( | ||
<EditableFormInputField | ||
initialValue="initial value" | ||
editText="edit text" | ||
label="label" | ||
onSubmit={() => {}} | ||
formErrorText="Error Text" | ||
> | ||
<p id="test-child">Test child</p> | ||
</EditableFormInputField> | ||
); | ||
|
||
expect(screen.getByText("Error Text")).not.toBeNull(); | ||
}); | ||
|
||
it("editable form field hides edit button when editable and shows children", async () => { | ||
render( | ||
<EditableFormInputField | ||
initialValue="initial value" | ||
editText="edit text" | ||
label="label" | ||
onSubmit={() => {}} | ||
> | ||
<p id="test-child">Test child</p> | ||
</EditableFormInputField> | ||
); | ||
|
||
fireEvent.click(screen.getByRole("button")); | ||
expect(screen.queryByText("initial value")).toBeNull(); | ||
expect(screen.queryByText("edit text")).toBeNull(); | ||
}); | ||
|
||
it("does not submit if the input is invalid", () => { | ||
const callback = jest.fn(() => Promise.resolve()); | ||
|
||
render( | ||
<ConditionalFormContainer onSubmit={callback} setIsEditable={() => {}} showForm> | ||
<input name="inputField" type="email" defaultValue="invalid" /> | ||
</ConditionalFormContainer> | ||
); | ||
|
||
fireEvent.submit(screen.getByTestId("conditional-form")); | ||
|
||
expect(callback).not.toHaveBeenCalled(); | ||
}); | ||
|
||
it("does submit if the input is valid", () => { | ||
const callback = jest.fn(() => Promise.resolve()); | ||
|
||
render( | ||
<ConditionalFormContainer onSubmit={callback} showForm setIsEditable={() => {}}> | ||
<input name="inputField" type="email" defaultValue="[email protected]" /> | ||
</ConditionalFormContainer> | ||
); | ||
|
||
fireEvent.submit(screen.getByTestId("conditional-form")); | ||
|
||
expect(callback).toHaveBeenCalledWith({ | ||
inputField: "[email protected]", | ||
}); | ||
}); | ||
|
||
it("calls passed in cancelEdit function when using cancel button", () => { | ||
const callback = jest.fn(); | ||
|
||
render( | ||
<EditableFormInputField | ||
cancelEdit={callback} | ||
cancelText="cancel change" | ||
formErrorText="Error" | ||
> | ||
<input name="inputField" type="email" defaultValue="invalid" /> | ||
</EditableFormInputField> | ||
); | ||
|
||
fireEvent.click(screen.getByText("cancel change")); | ||
|
||
expect(callback).toHaveBeenCalled(); | ||
}); | ||
|
||
it("calls passed in cancelEdit function when using cancel button", () => { | ||
render( | ||
<EditableFormInputField formErrorText="Error"> | ||
<input name="inputField" type="email" defaultValue="invalid" /> | ||
</EditableFormInputField> | ||
); | ||
|
||
expect(screen.getByText("Error")).not.toBeNull(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
65 changes: 65 additions & 0 deletions
65
blocks/identity-block/features/account-management/_children/EmailEditableFieldContainer.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import React, { useState } from "react"; | ||
import { useFusionContext } from "fusion:context"; | ||
import getProperties from "fusion:properties"; | ||
import getTranslatedPhrases from "fusion:intl"; | ||
import { Input } from "@wpmedia/arc-themes-components"; | ||
import useIdentity from "../../../components/identity"; | ||
import EditableFieldPresentational from "../../../components/editable-form-input"; | ||
|
||
function EmailEditableFieldContainer({ blockClassName, email, setEmail }) { | ||
const [formErrorText, setFormErrorText] = useState(null); | ||
|
||
const { arcSite } = useFusionContext(); | ||
const { locale } = getProperties(arcSite); | ||
const phrases = getTranslatedPhrases(locale); | ||
const { Identity } = useIdentity(); | ||
|
||
const formEmailLabel = phrases.t("identity-block.email"); | ||
const emailRequirements = phrases.t("identity-block.email-requirements"); | ||
const editText = phrases.t("identity-block.edit"); | ||
const saveText = phrases.t("identity-block.save"); | ||
const cancelText = phrases.t("identity-block.cancel"); | ||
const emailError = phrases.t("identity-block.update-email-error"); | ||
|
||
// istanbul ignore next | ||
const handleEmailUpdate = ({ email: newEmail }) => | ||
Identity.updateUserProfile({ email: newEmail }) | ||
.then((profileObject) => { | ||
setEmail(profileObject.email); | ||
}) | ||
.catch(() => { | ||
setFormErrorText(emailError); | ||
throw new Error(); | ||
}); | ||
|
||
const handleCancelEdit = () => { | ||
setFormErrorText(null); | ||
}; | ||
|
||
return ( | ||
<EditableFieldPresentational | ||
cancelEdit={handleCancelEdit} | ||
cancelText={cancelText} | ||
blockClassName={blockClassName} | ||
editText={editText} | ||
formErrorText={formErrorText} | ||
initialValue={email} | ||
label={formEmailLabel} | ||
onSubmit={handleEmailUpdate} | ||
saveText={saveText} | ||
> | ||
<Input | ||
type="email" | ||
label={formEmailLabel} | ||
defaultValue={email} | ||
showDefaultError={false} | ||
required | ||
autoComplete="email" | ||
name="email" | ||
validationErrorMessage={emailRequirements} | ||
/> | ||
</EditableFieldPresentational> | ||
); | ||
} | ||
|
||
export default EmailEditableFieldContainer; |
Oops, something went wrong.