Skip to content

Commit

Permalink
Custom form fields (#760)
Browse files Browse the repository at this point in the history
* feat: add input component as types, simplify event handlers - emailpassword recipe  (#752)

* Add inputComponent to exposed types

* Add inputComponent to normalised fields

* For testing only - use custom type definition for inputComponent

* Input component already present in  FormFieldThemeProps

* Testing if git package is getting installed correctly

* Run build for previous commits

* Remove inputComp from NormalizedFormField

* Add tests for custom fields

* Remove testing ele

* Move the custom fields tests into existing describe

* Update dropdown values to avoid confusion

* Add helper func to set dropdown, better test title, use existing describe hooks

* Use strict equal

* Update request

* A seperate func to fetch custom comp not required

* Move inputComponent to signup types

* Cleanup unwanted imports

* Move inputComponent to signup types

* Clean types

* Update build files

* Use explicit values in validate func

* Minor cleanup of types

* Better type names

* Props suggestions working for inputComponent

* Enforce strict string check on form values, now onChange function for fields only needs value, no need to supply name or id

* Update based on the new onChange func

* Ability to add default value with getDefaultValue prop

* Handle if getDefaultValue is not a function

* instead of form submit apply type test within onChange function itself

* Add tests for default value

* Remove unwanted abort

* Testing email-verification workflow

* Reverting onChange changes

* onChange function to accept only values

* Initialize fieldstates at the start

* Remove useEffect

* Fix race conditions when setting default value

* Add custom default fields to typescript example, plus add tests to show custom error message

* Add tests for incorrect default props in formFields

* Add tests for incorrect usage of onChange prop

* Add change log

* Wrap ternary opeators into seperate func for better readibility

* Wrap inputComponent in a serperate component to avoid unecessary rerenders

* Add change log feedbacks

* Better variable names, include formfields directly in typescript example

* Add more tests for default & onChange func, updated typescript file to show the latest changes

* Add more test, which intercepts request payload

* Cleanup comments

* Minor formatting

* Minor fix

* Clean up helper

* Update change log & versions

* feat: add getDefaultValue to signin & signup form fields   (#756)

* Add getDefaultValue to signin, hence shuffle the existing types

* Add tests for default signin feature

* Add default feature to typescript

* Fix failed test

* Reset password now supports getDefaultValue

* Add tests for resetPassword email field

* Revert "Add tests for resetPassword email field"

This reverts commit 363b575.

* Revert "Reset password now supports getDefaultValue"

This reverts commit be4c00a.

* feat: add nonOptionalErrorMsg props  (#757)

* Add nonOptionalErr types

* Now supports nonOptionalErrorMsg attribute

* Add tests and fix signin types to include nonOptionalMsg

* Enforce no api request are made on blank forms

* Clean up signup

* Throw error if invalid nonOptionalErrorMsg, add tests for the same

* Better error message

* Handle incorrect optional flag

* fixes redundant normalisation

---------

Co-authored-by: rishabhpoddar <[email protected]>

* Add test for thirdparty signup - new features

* Add tests for thirdparty signin - new features

* Run build-pretty

* Set correct flag

* fix: display required indicator only for non-empty labels and Improve test structure (#762)

* Show required sign only if label is valid

* Better func names & consistent return type

* Use assert instead of throw error

* Consistent tests description

* Remove unecessary code

* Add correct version number

* Update changelog, add thirdparty example

* Minor fox

* Read from testContext

* Refactor tests to ensure its easy to maintain different configurations

* Update third party tests

* Clean up

* Minor copy update

* Trim the label text

* Add build

* Handle if label is not supplied

* Highlight var in changelog, minor update

* Update custom payload to test for trimmed-version label

* Use page.select for changing dropdown values

* Handle react 16 tests for new features (#764)

---------

Co-authored-by: Amit Badala <[email protected]>
  • Loading branch information
rishabhpoddar and amitbadala authored Nov 23, 2023
1 parent 78dc12e commit 41be764
Show file tree
Hide file tree
Showing 31 changed files with 1,927 additions and 687 deletions.
75 changes: 75 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,81 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html)

## [0.35.7] - 2023-11-16

### Added

- EmailPassword and ThirdPartyEmailPassword recipe enhancements:
- Introduced the capability to utilize custom components by exposing `inputComponent` types.
- Allow setting default values in signup/signin form fields.
- Made the `onChange` prop in `inputComponent` simpler by removing the need for an `id` attribute.
- Added a feature to customize the "Field is not optional" error message for each form field with the `nonOptionalErrorMsg` prop.

Following is an example of how to use above features.

```tsx
EmailPassword.init({
signInAndUpFeature: {
signUpForm: {
formFields: [
{
id: "select-dropdown",
label: "Select Option",
getDefaultValue: () => "option 2",
nonOptionalErrorMsg: "Select dropdown is required",
inputComponent: ({ value, name, onChange }) => (
<select
value={value}
name={name}
onChange={(e) => onChange(e.target.value)}
placeholder="Select Option">
<option value="" disabled hidden>
Select an option
</option>
<option value="option 1">Option 1</option>
<option value="option 2">Option 2</option>
<option value="option 3">Option 3</option>
</select>
),
},
],
},
},
});

ThirdPartyEmailPassword.init({
signInAndUpFeature: {
signUpForm: {
formFields: [
{
id: "terms",
label: "",
optional: false,
getDefaultValue: () => "true",
nonOptionalErrorMsg: "You must accept the terms and conditions",
inputComponent: ({ name, onChange, value }) => (
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "left",
}}>
<input
value={value}
checked={value === "true"}
name={name}
type="checkbox"
onChange={(e) => onChange(e.target.checked.toString())}></input>
<span style={{ marginLeft: 5 }}>I agree to the terms and conditions</span>
</div>
),
},
],
},
},
});
```

## [0.35.6] - 2023-10-16

### Test changes
Expand Down
223 changes: 218 additions & 5 deletions examples/for-tests/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,167 @@ const formFields = [
},
];

const formFieldsWithDefault = [
{
id: "country",
label: "Your Country",
placeholder: "Where do you live?",
optional: true,
getDefaultValue: () => "India",
},
{
id: "select-dropdown",
label: "Select Option",
getDefaultValue: () => "option 2",
inputComponent: ({ value, name, onChange }) => (
<select value={value} name={name} onChange={(e) => onChange(e.target.value)}>
<option value="" disabled hidden>
Select an option
</option>
<option value="option 1">Option 1</option>
<option value="option 2">Option 2</option>
<option value="option 3">Option 3</option>
</select>
),
optional: true,
},
{
id: "terms",
label: "",
optional: false,
getDefaultValue: () => "true",
inputComponent: ({ name, onChange, value }) => (
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "left",
}}>
<input
value={value}
checked={value === "true"}
name={name}
type="checkbox"
onChange={(e) => onChange(e.target.checked.toString())}></input>
<span style={{ marginLeft: 5 }}>I agree to the terms and conditions</span>
</div>
),
validate: async (value) => {
if (value === "true") {
return undefined;
}
return "Please check Terms and conditions";
},
},
{
id: "email",
label: "Email",
getDefaultValue: () => "[email protected]",
},
{
id: "password",
label: "Password",
getDefaultValue: () => "fakepassword123",
},
];

const incorrectFormFields = [
{
id: "country",
label: "Your Country",
placeholder: "Where do you live?",
optional: true,
getDefaultValue: () => 23, // return should be a string
},
{
id: "select-dropdown",
label: "Select Dropdown",
getDefaultValue: "option 2", // should be function
inputComponent: ({ value, name, onChange }) => (
<select value={value} name={name} onChange={(e) => onChange(e.target.value)}>
<option value="" disabled hidden>
Select an option
</option>
<option value="option 1">Option 1</option>
<option value="option 2">Option 2</option>
<option value="option 3">Option 3</option>
</select>
),
optional: true,
},
{
// onChange accepts only string value, here we pass boolean
id: "terms",
label: "",
optional: false,
inputComponent: ({ name, onChange }) => (
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "left",
}}>
<input name={name} type="checkbox" onChange={(e) => onChange(e.target.checked)}></input>
<span style={{ marginLeft: 5 }}>I agree to the terms and conditions</span>
</div>
),
validate: async (value) => {
if (value === "true") {
return undefined;
}
return "Please check Terms and conditions";
},
},
{
id: "city",
label: "Your city",
optional: false,
nonOptionalErrorMsg: "", // empty string should throw error
},
];

const customFields = [
{
id: "select-dropdown",
label: "Select Dropdown",
nonOptionalErrorMsg: "Select dropdown is not an optional",
inputComponent: ({ value, name, onChange }) => (
<select value={value} name={name} onChange={(e) => onChange(e.target.value)}>
<option value="" disabled hidden>
Select an option
</option>
<option value="option 1">Option 1</option>
<option value="option 2">Option 2</option>
<option value="option 3">Option 3</option>
</select>
),
optional: true,
},
{
id: "terms",
label: " ",
optional: false,
nonOptionalErrorMsg: "You must accept the terms and conditions",
inputComponent: ({ name, onChange }) => (
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "left",
}}>
<input name={name} type="checkbox" onChange={(e) => onChange(e.target.checked.toString())}></input>
<span style={{ marginLeft: 5 }}>I agree to the terms and conditions</span>
</div>
),
validate: async (value) => {
if (value === "true") {
return undefined;
}
return "Please check Terms and conditions";
},
},
];

const testContext = getTestContext();

let recipeList = [
Expand Down Expand Up @@ -552,7 +713,51 @@ function getEmailVerificationConfigs({ disableDefaultUI }) {
});
}

function getEmailPasswordConfigs({ disableDefaultUI }) {
function getSignUpFormFields(formType) {
switch (formType) {
case "INCORRECT_FIELDS":
return incorrectFormFields;
case "INCORRECT_ONCHANGE":
return incorrectFormFields.filter(({ id }) => id === "terms");
case "INCORRECT_NON_OPTIONAL_ERROR_MSG":
return incorrectFormFields.filter(({ id }) => id === "city");
case "INCORRECT_GETDEFAULT":
return incorrectFormFields.filter(({ id }) => id === "country");
case "CUSTOM_FIELDS_WITH_DEFAULT_VALUES":
return formFieldsWithDefault;
case "CUSTOM_FIELDS":
return customFields;
default:
return formFields;
}
}

function getSignInFormFields(formType) {
switch (formType) {
case "DEFAULT_FIELDS":
return [
{
id: "email",
getDefaultValue: () => "[email protected]",
},
{
id: "password",
getDefaultValue: () => "fakepassword123",
},
];
case "FIELDS_WITH_NON_OPTIONAL_ERROR_MESSAGE":
return [
{
id: "email",
nonOptionalErrorMsg: "Please add email",
},
];
default:
return;
}
}

function getEmailPasswordConfigs({ disableDefaultUI, formFieldType }) {
return EmailPassword.init({
style: `
[data-supertokens~=container] {
Expand Down Expand Up @@ -632,12 +837,13 @@ function getEmailPasswordConfigs({ disableDefaultUI }) {
defaultToSignUp,
signInForm: {
style: theme,
formFields: getSignInFormFields(formFieldType.signIn),
},
signUpForm: {
style: theme,
privacyPolicyLink: "https://supertokens.com/legal/privacy-policy",
termsOfServiceLink: "https://supertokens.com/legal/terms-and-conditions",
formFields,
formFields: getSignUpFormFields(formFieldType.signUp),
},
},
});
Expand Down Expand Up @@ -981,7 +1187,12 @@ function getThirdPartyConfigs({ staticProviderList, disableDefaultUI, thirdParty
});
}

function getThirdPartyEmailPasswordConfigs({ staticProviderList, disableDefaultUI, thirdPartyRedirectURL }) {
function getThirdPartyEmailPasswordConfigs({
staticProviderList,
disableDefaultUI,
thirdPartyRedirectURL,
formFieldType,
}) {
let providers = [
ThirdParty.Github.init(),
ThirdParty.Google.init(),
Expand Down Expand Up @@ -1160,9 +1371,11 @@ function getThirdPartyEmailPasswordConfigs({ staticProviderList, disableDefaultU
},
signInAndUpFeature: {
disableDefaultUI,
signInForm: {},
signInForm: {
formFields: getSignInFormFields(formFieldType.signIn),
},
signUpForm: {
formFields,
formFields: getSignUpFormFields(formFieldType.signUp),
privacyPolicyLink: "https://supertokens.com/legal/privacy-policy",
termsOfServiceLink: "https://supertokens.com/legal/terms-and-conditions",
},
Expand Down
4 changes: 4 additions & 0 deletions examples/for-tests/src/testContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ export function getTestContext() {
staticProviderList: localStorage.getItem("staticProviderList"),
mockTenantId: localStorage.getItem("mockTenantId"),
clientType: localStorage.getItem("clientType") || undefined,
formFieldType: {
signIn: localStorage.getItem("SIGNIN_SETTING_TYPE"),
signUp: localStorage.getItem("SIGNUP_SETTING_TYPE"),
},
};
return ret;
}
Expand Down
7 changes: 7 additions & 0 deletions lib/build/emailpassword-shared4.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 41be764

Please sign in to comment.