Skip to content

Commit

Permalink
1853 id verification consent sms (#2094)
Browse files Browse the repository at this point in the history
  • Loading branch information
eastandwestwind authored Jan 3, 2023
1 parent 2ef39ad commit 65dbcfc
Show file tree
Hide file tree
Showing 9 changed files with 283 additions and 66 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ The types of changes are:
* Nav 2.0 - Replace form flow side navs with top tabs [#2037](https://github.com/ethyca/fides/pull/2050)
* Adds new erasure policy for complete user data masking [#1839](https://github.com/ethyca/fides/pull/1839)
* Added ability to use Mailgun templates when sending emails. [#2039](https://github.com/ethyca/fides/pull/2039)
* Adds SMS id verification for consent [#2094](https://github.com/ethyca/fides/pull/2094)

### Fixed

Expand Down
12 changes: 6 additions & 6 deletions clients/privacy-center/__tests__/RequestModal.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ describe("RequestModal", () => {
expect(
screen.getByPlaceholderText("test-email@example.com")
).toBeInTheDocument();
expect(screen.getByPlaceholderText("+1 000 000 0000")).toBeInTheDocument();
expect(screen.getByPlaceholderText("000 000 0000")).toBeInTheDocument();

unmount();

Expand All @@ -81,7 +81,7 @@ describe("RequestModal", () => {
expect(
screen.getByPlaceholderText("test-email@example.com")
).toBeInTheDocument();
expect(screen.queryByPlaceholderText("+1 000 000 0000")).toBeNull();
expect(screen.queryByPlaceholderText("000 000 0000")).toBeNull();

unmount();

Expand All @@ -94,7 +94,7 @@ describe("RequestModal", () => {

expect(screen.queryByPlaceholderText("Michael Brown")).toBeNull();
expect(screen.queryByPlaceholderText("test-email@example.com")).toBeNull();
expect(screen.getByPlaceholderText("+1 000 000 0000")).toBeInTheDocument();
expect(screen.getByPlaceholderText("000 000 0000")).toBeInTheDocument();

unmount();
});
Expand All @@ -116,7 +116,7 @@ describe("RequestModal", () => {
target: { value: "testing@ethyca.com" },
});

fireEvent.change(screen.getByPlaceholderText("+1 000 000 0000"), {
fireEvent.change(screen.getByPlaceholderText("000 000 0000"), {
target: { value: "0000000000" },
});
});
Expand Down Expand Up @@ -151,7 +151,7 @@ describe("RequestModal", () => {
target: { value: "testing@ethyca.com" },
});

fireEvent.change(screen.getByPlaceholderText("+1 000 000 0000"), {
fireEvent.change(screen.getByPlaceholderText("000 000 0000"), {
target: { value: "0000000000" },
});
});
Expand Down Expand Up @@ -189,7 +189,7 @@ describe("RequestModal", () => {
target: { value: "testing@ethyca.com" },
});

fireEvent.change(screen.getByPlaceholderText("+1 000 000 0000"), {
fireEvent.change(screen.getByPlaceholderText("000 000 0000"), {
target: { value: "0000000000" },
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
chakra,
FormControl,
FormErrorMessage,
FormLabel,
Input,
ModalBody,
ModalFooter,
Expand All @@ -20,9 +21,15 @@ import { ErrorToastOptions } from "~/common/toast-options";
import { Headers } from "headers-polyfill";
import { addCommonHeaders } from "~/common/CommonHeaders";

import { hostUrl } from "~/constants";
import { config, hostUrl } from "~/constants";
import dynamic from "next/dynamic";
import * as Yup from "yup";
import { ModalViews, VerificationType } from "../types";

const PhoneInput = dynamic(() => import("react-phone-number-input"), {
ssr: false,
});

const useConsentRequestForm = ({
onClose,
setCurrentView,
Expand All @@ -41,12 +48,14 @@ const useConsentRequestForm = ({
const formik = useFormik({
initialValues: {
email: "",
phone: "",
},
onSubmit: async (values) => {
setIsLoading(true);

const body = {
email: values.email,
phone_number: values.phone,
};
const handleError = ({
title,
Expand Down Expand Up @@ -102,18 +111,27 @@ const useConsentRequestForm = ({
handleError({ title: "An unhandled exception occurred." });
}
},
validate: (values) => {
const errors: {
email?: string;
phone?: string;
} = {};

if (!values.email) {
errors.email = "Required";
}

return errors;
},
validationSchema: Yup.object().shape({
email: (() => {
let validation = Yup.string();
if (config.consent?.identity_inputs?.email === "required") {
validation = validation
.email("Email is invalid")
.required("Email is required");
}
return validation;
})(),
phone: (() => {
let validation = Yup.string();
if (config.consent?.identity_inputs?.phone === "required") {
validation = validation
.required("Phone is required")
// E.164 international standard format
.matches(/^\+[1-9]\d{1,14}$/, "Phone is invalid");
}
return validation;
})(),
}),
});

return { ...formik, isLoading };
Expand Down Expand Up @@ -145,6 +163,7 @@ const ConsentRequestForm: React.FC<ConsentRequestFormProps> = ({
values,
isValid,
dirty,
setFieldValue,
resetForm,
} = useConsentRequestForm({
onClose,
Expand All @@ -165,28 +184,61 @@ const ConsentRequestForm: React.FC<ConsentRequestFormProps> = ({
<ModalBody>
{isVerificationRequired ? (
<Text fontSize="sm" color="gray.500" mb={4}>
We will email you a verification code.
We will send you a verification code.
</Text>
) : null}
<Stack spacing={3}>
<FormControl
id="email"
isInvalid={touched.email && Boolean(errors.email)}
>
<Input
{config.consent?.identity_inputs.email ? (
<FormControl
id="email"
name="email"
type="email"
focusBorderColor="primary.500"
placeholder="Email*"
isRequired
onChange={handleChange}
onBlur={handleBlur}
value={values.email}
isInvalid={touched.email && Boolean(errors.email)}
/>
<FormErrorMessage>{errors.email}</FormErrorMessage>
</FormControl>
>
<FormLabel>
{config.consent?.identity_inputs.email === "required"
? "Email*"
: "Email"}
</FormLabel>
<Input
id="email"
name="email"
type="email"
focusBorderColor="primary.500"
placeholder="test-email@example.com"
onChange={handleChange}
onBlur={handleBlur}
value={values.email}
isInvalid={touched.email && Boolean(errors.email)}
/>
<FormErrorMessage>{errors.email}</FormErrorMessage>
</FormControl>
) : null}
{config.consent?.identity_inputs.phone ? (
<FormControl
id="phone"
isInvalid={touched.phone && Boolean(errors.phone)}
>
<FormLabel>
{config.consent?.identity_inputs.phone === "required"
? "Phone*"
: "Phone"}
</FormLabel>
<Input
as={PhoneInput}
id="phone"
name="phone"
type="tel"
focusBorderColor="primary.500"
placeholder="000 000 0000"
defaultCountry="US"
onChange={(value) => {
setFieldValue("phone", value, true);
}}
onBlur={handleBlur}
value={values.phone}
/>
<FormErrorMessage>{errors.phone}</FormErrorMessage>
</FormControl>
) : null}
</Stack>
</ModalBody>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ const PrivacyRequestForm: React.FC<PrivacyRequestFormProps> = ({
name="phone"
type="tel"
focusBorderColor="primary.500"
placeholder="+1 000 000 0000"
placeholder="000 000 0000"
defaultCountry="US"
onChange={(value) => {
setFieldValue("phone", value, true);
Expand Down
4 changes: 4 additions & 0 deletions clients/privacy-center/config/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@
"icon_path": "/consent.svg",
"title": "Manage your consent",
"description": "Manage your consent preferences, including the option to select 'Do Not Sell My Personal Information'.",
"identity_inputs": {
"email": "required",
"phone": "optional"
},
"consentOptions": [
{
"fidesDataUseKey": "advertising",
Expand Down
1 change: 1 addition & 0 deletions clients/privacy-center/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export type Config = {
icon_path: string;
title: string;
description: string;
identity_inputs: Record<string, string>;
consentOptions: ConfigConsentOption[];
};
};
Expand Down
Loading

0 comments on commit 65dbcfc

Please sign in to comment.