Skip to content

Commit 41be764

Browse files
Custom form fields (#760)
* 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]>
1 parent 78dc12e commit 41be764

File tree

31 files changed

+1927
-687
lines changed

31 files changed

+1927
-687
lines changed

CHANGELOG.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,81 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
77

8+
## [0.35.7] - 2023-11-16
9+
10+
### Added
11+
12+
- EmailPassword and ThirdPartyEmailPassword recipe enhancements:
13+
- Introduced the capability to utilize custom components by exposing `inputComponent` types.
14+
- Allow setting default values in signup/signin form fields.
15+
- Made the `onChange` prop in `inputComponent` simpler by removing the need for an `id` attribute.
16+
- Added a feature to customize the "Field is not optional" error message for each form field with the `nonOptionalErrorMsg` prop.
17+
18+
Following is an example of how to use above features.
19+
20+
```tsx
21+
EmailPassword.init({
22+
signInAndUpFeature: {
23+
signUpForm: {
24+
formFields: [
25+
{
26+
id: "select-dropdown",
27+
label: "Select Option",
28+
getDefaultValue: () => "option 2",
29+
nonOptionalErrorMsg: "Select dropdown is required",
30+
inputComponent: ({ value, name, onChange }) => (
31+
<select
32+
value={value}
33+
name={name}
34+
onChange={(e) => onChange(e.target.value)}
35+
placeholder="Select Option">
36+
<option value="" disabled hidden>
37+
Select an option
38+
</option>
39+
<option value="option 1">Option 1</option>
40+
<option value="option 2">Option 2</option>
41+
<option value="option 3">Option 3</option>
42+
</select>
43+
),
44+
},
45+
],
46+
},
47+
},
48+
});
49+
50+
ThirdPartyEmailPassword.init({
51+
signInAndUpFeature: {
52+
signUpForm: {
53+
formFields: [
54+
{
55+
id: "terms",
56+
label: "",
57+
optional: false,
58+
getDefaultValue: () => "true",
59+
nonOptionalErrorMsg: "You must accept the terms and conditions",
60+
inputComponent: ({ name, onChange, value }) => (
61+
<div
62+
style={{
63+
display: "flex",
64+
alignItems: "center",
65+
justifyContent: "left",
66+
}}>
67+
<input
68+
value={value}
69+
checked={value === "true"}
70+
name={name}
71+
type="checkbox"
72+
onChange={(e) => onChange(e.target.checked.toString())}></input>
73+
<span style={{ marginLeft: 5 }}>I agree to the terms and conditions</span>
74+
</div>
75+
),
76+
},
77+
],
78+
},
79+
},
80+
});
81+
```
82+
883
## [0.35.6] - 2023-10-16
984

1085
### Test changes

examples/for-tests/src/App.js

Lines changed: 218 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,167 @@ const formFields = [
168168
},
169169
];
170170

171+
const formFieldsWithDefault = [
172+
{
173+
id: "country",
174+
label: "Your Country",
175+
placeholder: "Where do you live?",
176+
optional: true,
177+
getDefaultValue: () => "India",
178+
},
179+
{
180+
id: "select-dropdown",
181+
label: "Select Option",
182+
getDefaultValue: () => "option 2",
183+
inputComponent: ({ value, name, onChange }) => (
184+
<select value={value} name={name} onChange={(e) => onChange(e.target.value)}>
185+
<option value="" disabled hidden>
186+
Select an option
187+
</option>
188+
<option value="option 1">Option 1</option>
189+
<option value="option 2">Option 2</option>
190+
<option value="option 3">Option 3</option>
191+
</select>
192+
),
193+
optional: true,
194+
},
195+
{
196+
id: "terms",
197+
label: "",
198+
optional: false,
199+
getDefaultValue: () => "true",
200+
inputComponent: ({ name, onChange, value }) => (
201+
<div
202+
style={{
203+
display: "flex",
204+
alignItems: "center",
205+
justifyContent: "left",
206+
}}>
207+
<input
208+
value={value}
209+
checked={value === "true"}
210+
name={name}
211+
type="checkbox"
212+
onChange={(e) => onChange(e.target.checked.toString())}></input>
213+
<span style={{ marginLeft: 5 }}>I agree to the terms and conditions</span>
214+
</div>
215+
),
216+
validate: async (value) => {
217+
if (value === "true") {
218+
return undefined;
219+
}
220+
return "Please check Terms and conditions";
221+
},
222+
},
223+
{
224+
id: "email",
225+
label: "Email",
226+
getDefaultValue: () => "[email protected]",
227+
},
228+
{
229+
id: "password",
230+
label: "Password",
231+
getDefaultValue: () => "fakepassword123",
232+
},
233+
];
234+
235+
const incorrectFormFields = [
236+
{
237+
id: "country",
238+
label: "Your Country",
239+
placeholder: "Where do you live?",
240+
optional: true,
241+
getDefaultValue: () => 23, // return should be a string
242+
},
243+
{
244+
id: "select-dropdown",
245+
label: "Select Dropdown",
246+
getDefaultValue: "option 2", // should be function
247+
inputComponent: ({ value, name, onChange }) => (
248+
<select value={value} name={name} onChange={(e) => onChange(e.target.value)}>
249+
<option value="" disabled hidden>
250+
Select an option
251+
</option>
252+
<option value="option 1">Option 1</option>
253+
<option value="option 2">Option 2</option>
254+
<option value="option 3">Option 3</option>
255+
</select>
256+
),
257+
optional: true,
258+
},
259+
{
260+
// onChange accepts only string value, here we pass boolean
261+
id: "terms",
262+
label: "",
263+
optional: false,
264+
inputComponent: ({ name, onChange }) => (
265+
<div
266+
style={{
267+
display: "flex",
268+
alignItems: "center",
269+
justifyContent: "left",
270+
}}>
271+
<input name={name} type="checkbox" onChange={(e) => onChange(e.target.checked)}></input>
272+
<span style={{ marginLeft: 5 }}>I agree to the terms and conditions</span>
273+
</div>
274+
),
275+
validate: async (value) => {
276+
if (value === "true") {
277+
return undefined;
278+
}
279+
return "Please check Terms and conditions";
280+
},
281+
},
282+
{
283+
id: "city",
284+
label: "Your city",
285+
optional: false,
286+
nonOptionalErrorMsg: "", // empty string should throw error
287+
},
288+
];
289+
290+
const customFields = [
291+
{
292+
id: "select-dropdown",
293+
label: "Select Dropdown",
294+
nonOptionalErrorMsg: "Select dropdown is not an optional",
295+
inputComponent: ({ value, name, onChange }) => (
296+
<select value={value} name={name} onChange={(e) => onChange(e.target.value)}>
297+
<option value="" disabled hidden>
298+
Select an option
299+
</option>
300+
<option value="option 1">Option 1</option>
301+
<option value="option 2">Option 2</option>
302+
<option value="option 3">Option 3</option>
303+
</select>
304+
),
305+
optional: true,
306+
},
307+
{
308+
id: "terms",
309+
label: " ",
310+
optional: false,
311+
nonOptionalErrorMsg: "You must accept the terms and conditions",
312+
inputComponent: ({ name, onChange }) => (
313+
<div
314+
style={{
315+
display: "flex",
316+
alignItems: "center",
317+
justifyContent: "left",
318+
}}>
319+
<input name={name} type="checkbox" onChange={(e) => onChange(e.target.checked.toString())}></input>
320+
<span style={{ marginLeft: 5 }}>I agree to the terms and conditions</span>
321+
</div>
322+
),
323+
validate: async (value) => {
324+
if (value === "true") {
325+
return undefined;
326+
}
327+
return "Please check Terms and conditions";
328+
},
329+
},
330+
];
331+
171332
const testContext = getTestContext();
172333

173334
let recipeList = [
@@ -552,7 +713,51 @@ function getEmailVerificationConfigs({ disableDefaultUI }) {
552713
});
553714
}
554715

555-
function getEmailPasswordConfigs({ disableDefaultUI }) {
716+
function getSignUpFormFields(formType) {
717+
switch (formType) {
718+
case "INCORRECT_FIELDS":
719+
return incorrectFormFields;
720+
case "INCORRECT_ONCHANGE":
721+
return incorrectFormFields.filter(({ id }) => id === "terms");
722+
case "INCORRECT_NON_OPTIONAL_ERROR_MSG":
723+
return incorrectFormFields.filter(({ id }) => id === "city");
724+
case "INCORRECT_GETDEFAULT":
725+
return incorrectFormFields.filter(({ id }) => id === "country");
726+
case "CUSTOM_FIELDS_WITH_DEFAULT_VALUES":
727+
return formFieldsWithDefault;
728+
case "CUSTOM_FIELDS":
729+
return customFields;
730+
default:
731+
return formFields;
732+
}
733+
}
734+
735+
function getSignInFormFields(formType) {
736+
switch (formType) {
737+
case "DEFAULT_FIELDS":
738+
return [
739+
{
740+
id: "email",
741+
getDefaultValue: () => "[email protected]",
742+
},
743+
{
744+
id: "password",
745+
getDefaultValue: () => "fakepassword123",
746+
},
747+
];
748+
case "FIELDS_WITH_NON_OPTIONAL_ERROR_MESSAGE":
749+
return [
750+
{
751+
id: "email",
752+
nonOptionalErrorMsg: "Please add email",
753+
},
754+
];
755+
default:
756+
return;
757+
}
758+
}
759+
760+
function getEmailPasswordConfigs({ disableDefaultUI, formFieldType }) {
556761
return EmailPassword.init({
557762
style: `
558763
[data-supertokens~=container] {
@@ -632,12 +837,13 @@ function getEmailPasswordConfigs({ disableDefaultUI }) {
632837
defaultToSignUp,
633838
signInForm: {
634839
style: theme,
840+
formFields: getSignInFormFields(formFieldType.signIn),
635841
},
636842
signUpForm: {
637843
style: theme,
638844
privacyPolicyLink: "https://supertokens.com/legal/privacy-policy",
639845
termsOfServiceLink: "https://supertokens.com/legal/terms-and-conditions",
640-
formFields,
846+
formFields: getSignUpFormFields(formFieldType.signUp),
641847
},
642848
},
643849
});
@@ -981,7 +1187,12 @@ function getThirdPartyConfigs({ staticProviderList, disableDefaultUI, thirdParty
9811187
});
9821188
}
9831189

984-
function getThirdPartyEmailPasswordConfigs({ staticProviderList, disableDefaultUI, thirdPartyRedirectURL }) {
1190+
function getThirdPartyEmailPasswordConfigs({
1191+
staticProviderList,
1192+
disableDefaultUI,
1193+
thirdPartyRedirectURL,
1194+
formFieldType,
1195+
}) {
9851196
let providers = [
9861197
ThirdParty.Github.init(),
9871198
ThirdParty.Google.init(),
@@ -1160,9 +1371,11 @@ function getThirdPartyEmailPasswordConfigs({ staticProviderList, disableDefaultU
11601371
},
11611372
signInAndUpFeature: {
11621373
disableDefaultUI,
1163-
signInForm: {},
1374+
signInForm: {
1375+
formFields: getSignInFormFields(formFieldType.signIn),
1376+
},
11641377
signUpForm: {
1165-
formFields,
1378+
formFields: getSignUpFormFields(formFieldType.signUp),
11661379
privacyPolicyLink: "https://supertokens.com/legal/privacy-policy",
11671380
termsOfServiceLink: "https://supertokens.com/legal/terms-and-conditions",
11681381
},

examples/for-tests/src/testContext.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ export function getTestContext() {
1414
staticProviderList: localStorage.getItem("staticProviderList"),
1515
mockTenantId: localStorage.getItem("mockTenantId"),
1616
clientType: localStorage.getItem("clientType") || undefined,
17+
formFieldType: {
18+
signIn: localStorage.getItem("SIGNIN_SETTING_TYPE"),
19+
signUp: localStorage.getItem("SIGNUP_SETTING_TYPE"),
20+
},
1721
};
1822
return ret;
1923
}

lib/build/emailpassword-shared4.js

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)