From f8aded64452cb6fff0443631bf48078114d164f1 Mon Sep 17 00:00:00 2001 From: Brian Miller Date: Mon, 16 Oct 2023 13:50:05 -0500 Subject: [PATCH 01/23] Add progresive components and routes Initial port from profile components and routes as it will have to support the same variation of inputs. It does have added logic to only display one question at the moment based on array of configured ids. example: [ givenName, familyName, phone, 618a8a62934f8400ad4beb8f] It will only display on unanswered question in order at the moment. --- .../marko-web-identity-x/browser/index.js | 7 + .../browser/progressive.vue | 661 ++++++++++++++++++ .../components/form-progressive.marko | 41 ++ .../components/marko.json | 12 + packages/marko-web-identity-x/config.js | 7 + packages/marko-web-identity-x/routes/index.js | 2 + .../routes/progressive.js | 132 ++++ 7 files changed, 862 insertions(+) create mode 100644 packages/marko-web-identity-x/browser/progressive.vue create mode 100644 packages/marko-web-identity-x/components/form-progressive.marko create mode 100644 packages/marko-web-identity-x/routes/progressive.js diff --git a/packages/marko-web-identity-x/browser/index.js b/packages/marko-web-identity-x/browser/index.js index 454aadd0a..b4d07d165 100644 --- a/packages/marko-web-identity-x/browser/index.js +++ b/packages/marko-web-identity-x/browser/index.js @@ -9,6 +9,7 @@ const Download = () => import(/* webpackChunkName: "identity-x-download" */ './d const Logout = () => import(/* webpackChunkName: "identity-x-logout" */ './logout.vue'); const Login = () => import(/* webpackChunkName: "identity-x-login" */ './login.vue'); const Profile = () => import(/* webpackChunkName: "identity-x-profile" */ './profile.vue'); +const Progressive = () => import(/* webpackChunkName: "identity-x-progressive" */ './progressive.vue'); const CommentStream = () => import(/* webpackChunkName: "identity-x-comment-stream" */ './comments/stream.vue'); const $graphql = createGraphqlClient({ uri: '/__graphql' }); @@ -25,6 +26,7 @@ export default (Browser, { CustomLoginComponent, CustomLogoutComponent, CustomProfileComponent, + CustomProgressiveComponent, } = {}) => { const AccessComponent = CustomAccessComponent || Access; const AuthenticateComponent = CustomAuthenticateComponent || Authenticate; @@ -35,6 +37,7 @@ export default (Browser, { const LoginComponent = CustomLoginComponent || Login; const LogoutComponent = CustomLogoutComponent || Logout; const ProfileComponent = CustomProfileComponent || Profile; + const ProgressiveComponent = CustomProgressiveComponent || Progressive; window.IdentityX = new IdentityX(); @@ -48,6 +51,7 @@ export default (Browser, { Browser.register('IdentityXLogin', LoginComponent, { provide: { EventBus } }); Browser.register('IdentityXLogout', LogoutComponent, { provide: { EventBus } }); Browser.register('IdentityXProfile', ProfileComponent, { provide: { EventBus } }); + Browser.register('IdentityXProgressive', ProgressiveComponent, { provide: { EventBus } }); // Ensure the client-side IdX context is refreshed when the authentication event occurs EventBus.$on('identity-x-authenticated', () => window.IdentityX.refreshContext()); @@ -66,6 +70,7 @@ export default (Browser, { 'identity-x-login-mounted', 'identity-x-logout-mounted', 'identity-x-profile-mounted', + 'identity-x-progressive-mounted', // Actions/submissions 'identity-x-access-submitted', 'identity-x-authenticated', @@ -79,6 +84,7 @@ export default (Browser, { 'identity-x-login-link-sent', 'identity-x-logout', 'identity-x-profile-updated', + 'identity-x-progressive-updated', // Errors 'identity-x-access-errored', 'identity-x-authenticate-errored', @@ -90,6 +96,7 @@ export default (Browser, { 'identity-x-login-errored', 'identity-x-logout-errored', 'identity-x-profile-errored', + 'identity-x-progressive-errored', ].forEach((event) => { EventBus.$on(event, (args) => { if (!window.IdentityX) return; diff --git a/packages/marko-web-identity-x/browser/progressive.vue b/packages/marko-web-identity-x/browser/progressive.vue new file mode 100644 index 000000000..c949d9c3a --- /dev/null +++ b/packages/marko-web-identity-x/browser/progressive.vue @@ -0,0 +1,661 @@ + + + diff --git a/packages/marko-web-identity-x/components/form-progressive.marko b/packages/marko-web-identity-x/components/form-progressive.marko new file mode 100644 index 000000000..f445568b7 --- /dev/null +++ b/packages/marko-web-identity-x/components/form-progressive.marko @@ -0,0 +1,41 @@ +import { get } from "@parameter1/base-cms-object-path"; +import defaultValue from "@parameter1/base-cms-marko-core/utils/default-value"; + +$ const { req } = out.global; +$ const { identityX, query } = req; +$ const additionalEventData = defaultValue(input.additionalEventData, {}); + +$ console.log(identityX.config.getProgresiveQuestions()) + + + + $ const props = { + additionalEventData: additionalEventData, + loginSource: input.loginSource, + activeUser: user, + requiredServerFields: identityX.config.getRequiredServerFields(), + requiredClientFields: identityX.config.getRequiredClientFields(), + requiredCreateFields: identityX.config.getRequiredCreateFields(), + activeCustomFieldIds: identityX.config.getActiveCustomFieldIds(), + progressiveFields: identityX.config.getProgresiveQuestions(), + defaultFieldLabels: identityX.config.get("defaultFieldLabels"), + hiddenFields: identityX.config.getHiddenFields(), + defaultCountryCode: identityX.config.get('defaultCountryCode'), + booleanQuestionsLabel: identityX.config.get('booleanQuestionsLabel'), + callToAction: input.callToAction, + reloadPageOnSubmit: input.reloadPageOnSubmit, + buttonLabel: input.buttonLabel, + endpoints: identityX.config.getEndpoints(), + consentPolicy: get(application, "organization.consentPolicy"), + emailConsentRequest: get(application, "organization.emailConsentRequest"), + appContextId: identityX.config.get("appContextId"), + regionalConsentPolicies: get(application, "organization.regionalConsentPolicies"), + returnTo: query.returnTo, + returnToDelay: input.returnToDelay, + enableChangeEmail: identityX.config.get("enableChangeEmail"), + }; + + + + + diff --git a/packages/marko-web-identity-x/components/marko.json b/packages/marko-web-identity-x/components/marko.json index c09b165e8..32c5545aa 100644 --- a/packages/marko-web-identity-x/components/marko.json +++ b/packages/marko-web-identity-x/components/marko.json @@ -66,6 +66,18 @@ "@return-to-delay": "number", "@button-label": "string" }, + "": { + "template": "./form-progressive.marko", + "@additional-event-data": "object", + "@login-source": { + "type": "string", + "default-value": "default" + }, + "@call-to-action": "string", + "@reload-page-on-submit": "boolean", + "@return-to-delay": "number", + "@button-label": "string" + }, "": { "template": "./comment-stream.marko", "@additional-event-data": "object", diff --git a/packages/marko-web-identity-x/config.js b/packages/marko-web-identity-x/config.js index b0544cc67..1284edbf5 100644 --- a/packages/marko-web-identity-x/config.js +++ b/packages/marko-web-identity-x/config.js @@ -25,6 +25,7 @@ class IdentityXConfiguration { requiredClientFields = [], requiredCreateFields = [], activeCustomFieldIds = [], + progressiveQuestions = [], defaultFieldLabels = {}, hiddenFields = ['city', 'street', 'addressExtra', 'phoneNumber', 'mobileNumber'], defaultCountryCode, @@ -42,6 +43,7 @@ class IdentityXConfiguration { requiredClientFields, requiredCreateFields, activeCustomFieldIds, + progressiveQuestions, defaultFieldLabels, hiddenFields, defaultCountryCode, @@ -64,6 +66,7 @@ class IdentityXConfiguration { 'logout', 'register', 'profile', + 'progressive', ]; this.hooks = validHooks.reduce((o, name) => ({ ...o, [name]: [] }), {}); } @@ -139,6 +142,10 @@ class IdentityXConfiguration { return this.getAsArray('activeCustomFieldIds'); } + getProgresiveQuestions() { + return this.getAsArray('progressiveQuestions'); + } + get(path, def) { return get(this.options, path, def); } diff --git a/packages/marko-web-identity-x/routes/index.js b/packages/marko-web-identity-x/routes/index.js index 7810ac807..f0db11218 100644 --- a/packages/marko-web-identity-x/routes/index.js +++ b/packages/marko-web-identity-x/routes/index.js @@ -15,6 +15,7 @@ const login = require('./login'); const loginFields = require('./login-fields'); const logout = require('./logout'); const profile = require('./profile'); +const progressive = require('./progressive'); const regions = require('./regions'); const router = Router(); @@ -35,6 +36,7 @@ router.post('/login-fields', loginFields); router.post('/login', login); router.post('/logout', logout); router.post('/profile', profile); +router.post('/progressive', progressive); router.use(jsonErrorHandler()); module.exports = router; diff --git a/packages/marko-web-identity-x/routes/progressive.js b/packages/marko-web-identity-x/routes/progressive.js new file mode 100644 index 000000000..2faafd43d --- /dev/null +++ b/packages/marko-web-identity-x/routes/progressive.js @@ -0,0 +1,132 @@ +const gql = require('graphql-tag'); +const { asyncRoute } = require('@parameter1/base-cms-utils'); +const { getAsArray } = require('@parameter1/base-cms-object-path'); +const userFragment = require('../api/fragments/active-user'); +const callHooksFor = require('../utils/call-hooks-for'); + +const mutation = gql` + mutation UpdateUserProfile($input: UpdateOwnAppUserMutationInput!) { + updateOwnAppUser(input: $input) { + ...ActiveUserFragment + } + } + + ${userFragment} +`; + +const consentAnswers = gql` + mutation SetAppUserRegionalConsent($input: SetAppUserRegionalConsentMutationInput!) { + setAppUserRegionalConsent(input: $input) { + id + } + } +`; + +const customBooleanFieldsMutation = gql` + mutation SetAppUserCustomBooleanFields($input: UpdateOwnAppUserCustomBooleanAnswersMutationInput!) { + updateOwnAppUserCustomBooleanAnswers(input: $input) { + id + } + } +`; + +const customSelectFieldsMutation = gql` + mutation SetAppUserCustomSelectFields($input: UpdateOwnAppUserCustomSelectAnswersMutationInput!) { + updateOwnAppUserCustomSelectAnswers(input: $input) { + id + } + } +`; + +module.exports = asyncRoute(async (req, res) => { + const { identityX, body } = req; + const { + givenName, + familyName, + organization, + organizationTitle, + countryCode, + regionCode, + postalCode, + city, + street, + addressExtra, + phoneNumber, + receiveEmail, + regionalConsentAnswers, + customBooleanFieldAnswers, + customSelectFieldAnswers, + additionalEventData = {}, + } = body; + const input = { + givenName, + familyName, + organization, + organizationTitle, + countryCode, + regionCode, + postalCode, + city, + street, + addressExtra, + phoneNumber, + receiveEmail, + }; + + const activeCustomFieldIds = getAsArray(identityX, 'config.options.activeCustomFieldIds'); + + const answers = regionalConsentAnswers + .map((answer) => ({ policyId: answer.id, given: answer.given })); + + if (answers.length) { + await identityX.client.mutate({ mutation: consentAnswers, variables: { input: { answers } } }); + } + + if (customBooleanFieldAnswers.length) { + // only update custom questions when there some :) + const customBooleanFieldsInput = customBooleanFieldAnswers.map((fieldAnswer) => ({ + fieldId: fieldAnswer.field.id, + // can either be true, false or null. convert null to false. + // the form submit is effectively answers the question. + value: Boolean(fieldAnswer.answer), + })).filter( + activeCustomFieldIds.length > 0 + ? ({ fieldId }) => activeCustomFieldIds.includes(fieldId) + : () => true, + ); + await identityX.client.mutate({ + mutation: customBooleanFieldsMutation, + variables: { input: { answers: customBooleanFieldsInput } }, + }); + } + + if (customSelectFieldAnswers.length) { + // only update custom questions when there some :) + const customSelectFieldsInput = customSelectFieldAnswers.map((fieldAnswer) => ({ + fieldId: fieldAnswer.field.id, + optionIds: fieldAnswer.answers.map(({ id }) => id), + writeInValues: fieldAnswer.answers.reduce((arr, { id, writeInValue }) => ([ + ...arr, + ...(writeInValue ? [{ optionId: id, value: writeInValue }] : []), + ]), []), + })).filter( + activeCustomFieldIds.length > 0 + ? ({ fieldId }) => activeCustomFieldIds.includes(fieldId) + : () => true, + ); + await identityX.client.mutate({ + mutation: customSelectFieldsMutation, + variables: { input: { answers: customSelectFieldsInput } }, + }); + } + + const { data } = await identityX.client.mutate({ mutation, variables: { input } }); + const { updateOwnAppUser: user } = data; + await callHooksFor(identityX, 'onUserProfileUpdate', { + additionalEventData, + ...(additionalEventData || {}), + req, + user, + }); + res.json({ ok: true, user, additionalEventData }); +}); From 7c5dc279368043cbcf3fdba630a5d0b907772c93 Mon Sep 17 00:00:00 2001 From: Brian Miller Date: Fri, 20 Oct 2023 07:46:38 -0500 Subject: [PATCH 02/23] remove change email from progressive --- .../browser/progressive.vue | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/packages/marko-web-identity-x/browser/progressive.vue b/packages/marko-web-identity-x/browser/progressive.vue index c949d9c3a..6d3775c77 100644 --- a/packages/marko-web-identity-x/browser/progressive.vue +++ b/packages/marko-web-identity-x/browser/progressive.vue @@ -5,25 +5,6 @@

-
-
- -
- -
-
-
Date: Fri, 20 Oct 2023 07:47:03 -0500 Subject: [PATCH 03/23] Update CTA --- packages/marko-web-identity-x/browser/progressive.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/marko-web-identity-x/browser/progressive.vue b/packages/marko-web-identity-x/browser/progressive.vue index 6d3775c77..7109fd086 100644 --- a/packages/marko-web-identity-x/browser/progressive.vue +++ b/packages/marko-web-identity-x/browser/progressive.vue @@ -256,7 +256,7 @@ export default { }, callToAction: { type: String, - default: 'To complete your profile, please fill out the required fields.', + default: 'Please tell us a little more about yourself', }, requiredServerFields: { type: Array, From d85273ab0719f60ed911c0247a43e46e3faf57e2 Mon Sep 17 00:00:00 2001 From: Brian Miller Date: Fri, 20 Oct 2023 07:47:50 -0500 Subject: [PATCH 04/23] update verbiage --- .../marko-web-identity-x/browser/progressive.vue | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/packages/marko-web-identity-x/browser/progressive.vue b/packages/marko-web-identity-x/browser/progressive.vue index 7109fd086..a7d8c2411 100644 --- a/packages/marko-web-identity-x/browser/progressive.vue +++ b/packages/marko-web-identity-x/browser/progressive.vue @@ -151,20 +151,11 @@
- Your profile has been saved. + Your profile has been update.
-
+

- You will be automatically redirected in {{ returnToDelay / 1000 }} seconds. -

-

- To continue now, - click here. -

-
-
-

- To continue modifying your profile, + To finsih filling out your profile, . From 3f12a1071b9f368456c55f62afca4a6419acacb4 Mon Sep 17 00:00:00 2001 From: Brian Miller Date: Fri, 20 Oct 2023 07:48:25 -0500 Subject: [PATCH 05/23] remove concent & submit redirect logic --- .../browser/progressive.vue | 23 ++----------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/packages/marko-web-identity-x/browser/progressive.vue b/packages/marko-web-identity-x/browser/progressive.vue index a7d8c2411..4b066368a 100644 --- a/packages/marko-web-identity-x/browser/progressive.vue +++ b/packages/marko-web-identity-x/browser/progressive.vue @@ -128,7 +128,7 @@

- + /> -->
id); - console.warn('currentProgressiceIds: ', ids); + const addressDependent = ['postalCode']; const unAnsweredIds = ids.filter((id) => { if (this.activeUser[id]) return false; + if (addressDependent.includes(id)) return this.showAddressBlock; if (this.activeUser.customBooleanFieldAnswers && this.activeUser.customBooleanFieldAnswers .filter(({ id: answerId, hasAnswered }) => { - console.warn('filter: ', id, answerId, hasAnswered); if (id === answerId && !hasAnswered) return true; return false; }).length) return true; @@ -377,7 +376,6 @@ export default { }).length) return true; return true; }); - // console.warn('hittingCurrentPro: ', ids, this.user); return [unAnsweredIds[0]]; }, diff --git a/services/example-website/config/identity-x.js b/services/example-website/config/identity-x.js index 8e9d2603b..8c488282b 100644 --- a/services/example-website/config/identity-x.js +++ b/services/example-website/config/identity-x.js @@ -39,6 +39,15 @@ module.exports = new IdentityXConfiguration({ organization: 'Company Name', }, progressiveQuestions: [ + { + id: 'postalCode', + }, + { + id: 'street', + }, + { + id: 'countryCode', + }, { id: 'givenName', }, From fc9344d983d3dbb92000fb9a4ee47ac24c1b599e Mon Sep 17 00:00:00 2001 From: Brian Miller Date: Fri, 20 Oct 2023 10:49:08 -0500 Subject: [PATCH 10/23] use config to set delay by hours --- packages/marko-web-identity-x/config.js | 6 +++ services/example-website/config/identity-x.js | 1 + .../server/templates/user/progressive.marko | 48 ++----------------- 3 files changed, 12 insertions(+), 43 deletions(-) diff --git a/packages/marko-web-identity-x/config.js b/packages/marko-web-identity-x/config.js index 1284edbf5..4be68be7d 100644 --- a/packages/marko-web-identity-x/config.js +++ b/packages/marko-web-identity-x/config.js @@ -25,6 +25,7 @@ class IdentityXConfiguration { requiredClientFields = [], requiredCreateFields = [], activeCustomFieldIds = [], + progressiveDelay = 24, // hours progressiveQuestions = [], defaultFieldLabels = {}, hiddenFields = ['city', 'street', 'addressExtra', 'phoneNumber', 'mobileNumber'], @@ -43,6 +44,7 @@ class IdentityXConfiguration { requiredClientFields, requiredCreateFields, activeCustomFieldIds, + progressiveDelay, progressiveQuestions, defaultFieldLabels, hiddenFields, @@ -142,6 +144,10 @@ class IdentityXConfiguration { return this.getAsArray('activeCustomFieldIds'); } + getProgresiveDelay() { + return this.get('progressiveDelay'); + } + getProgresiveQuestions() { return this.getAsArray('progressiveQuestions'); } diff --git a/services/example-website/config/identity-x.js b/services/example-website/config/identity-x.js index 8c488282b..5f21576c5 100644 --- a/services/example-website/config/identity-x.js +++ b/services/example-website/config/identity-x.js @@ -38,6 +38,7 @@ module.exports = new IdentityXConfiguration({ phoneNumber: 'Mobile Phone', organization: 'Company Name', }, + progressiveDelay: 0.25, // hours progressiveQuestions: [ { id: 'postalCode', diff --git a/services/example-website/server/templates/user/progressive.marko b/services/example-website/server/templates/user/progressive.marko index 4b075d664..1dd59697b 100644 --- a/services/example-website/server/templates/user/progressive.marko +++ b/services/example-website/server/templates/user/progressive.marko @@ -7,9 +7,8 @@ $ const title = "Progressive Profile"; $ const description = `Tell ${config.siteName()} a little more about your self`; $ const now = new Date().getTime(); - -$ const days = 1 / (24 * 60); -$ const delay = days * 24 * 60 * 60 * 1000; +$ const hours = identityX.config.getProgresiveDelay(); +$ const delay = hours * 60 * 60 * 1000; $ const setDisplayNext = user => identityX.setAppUserCustomAttributes({ userId: user.id, @@ -25,11 +24,9 @@ $ const qIds = questions.map(({id}) => id); <@section>

${description}

- $ console.log(user); $ const customAttributes = getAsObject(user, 'customAttributes'); $ const { profileLastUpdated } = customAttributes; - $ console.log(customAttributes, profileLastUpdated, now, delay, profileLastUpdated - (now - delay)) $ const canAsk = profileLastUpdated ? profileLastUpdated < (now - delay) : true; id); $ const waitInMilliSeconds = profileLastUpdated - (now - delay); - $ const waitInSec = waitInMilliSeconds / 1000; - $ const customAttributes = { ...user.customAttributes, waitInMilliSeconds, waitInSec }; + $ const date = new Date(waitInMilliSeconds); + $ const waitInMin = `${date.getMinutes()}:${date.getSeconds()}`; + $ const customAttributes = { ...user.customAttributes, waitInMilliSeconds, waitInMin };
${JSON.stringify({ id: user.id, customAttributes }, null, 2)}
- From 2a48524edbd0c1a4d708381fde4246995c385f1c Mon Sep 17 00:00:00 2001 From: Brian Miller Date: Tue, 24 Oct 2023 10:26:54 -0500 Subject: [PATCH 11/23] Update field required/visibility logic Everything is required when visible. Also update address field logic so that only the postalCode & regionCode fields are dependent on countryCode bein answered. Also add visible logic to consent input in oreder to display when not answered. --- .../browser/form/consent.vue | 31 ++- .../browser/progressive.vue | 180 ++++++++++-------- 2 files changed, 130 insertions(+), 81 deletions(-) diff --git a/packages/marko-web-identity-x/browser/form/consent.vue b/packages/marko-web-identity-x/browser/form/consent.vue index 8e392dc31..5828c1b9b 100644 --- a/packages/marko-web-identity-x/browser/form/consent.vue +++ b/packages/marko-web-identity-x/browser/form/consent.vue @@ -1,5 +1,5 @@