diff --git a/apps/mosaico/package.json b/apps/mosaico/package.json index 2245af639..6430edff8 100644 --- a/apps/mosaico/package.json +++ b/apps/mosaico/package.json @@ -8,7 +8,7 @@ "cheerio": "^1.0.0-rc.10", "parcel": "1.12.3", "parcel-bundler": "^1.12.3", - "persona": "https://github.com/KSF-Media/persona-javascript-client.git#40058b09b6d969390daaad6e58918b04129b1c16", + "persona": "https://github.com/KSF-Media/persona-javascript-client.git#d8876dc53c39e6c80cdff363a8230deb944c6e73", "react": ">=16.12", "react-date-picker": "^8.0.3", "react-dom": ">=16.12", diff --git a/packages/components/src/Persona.purs b/packages/components/src/Persona.purs index 363888ff7..6ef42b1ee 100644 --- a/packages/components/src/Persona.purs +++ b/packages/components/src/Persona.purs @@ -83,6 +83,7 @@ updateUser uuid update auth = do let body = case update of UpdateName names -> unsafeToForeign names UpdateEmail email -> unsafeToForeign email + UpdatePhone phone -> unsafeToForeign phone UpdateAddress { countryCode, zipCode, streetAddress, startDate } -> unsafeToForeign { address: @@ -96,6 +97,7 @@ updateUser uuid update auth = do unsafeToForeign { firstName: userInfo.firstName , lastName: userInfo.lastName + , phone: toNullable $ userInfo.phone , address: { streetAddress: userInfo.streetAddress , zipCode: userInfo.zipCode @@ -203,8 +205,9 @@ hasScope :: UUID -> AuthScope -> UserAuth -> Aff Number hasScope uuid authScope auth = do callApi usersApi "usersUuidScopeGet" [ unsafeToForeign uuid + , unsafeToForeign scope ] $ - Record.merge ( authHeaders uuid auth ) { scope } + ( authHeaders uuid auth ) where scope = case authScope of UserRead -> "UserRead" @@ -356,6 +359,7 @@ type LoginDataSso = data UserUpdate = UpdateName { firstName :: String, lastName :: String } | UpdateEmail { email :: String } + | UpdatePhone { phone :: String } | UpdateAddress { countryCode :: String , zipCode :: String , streetAddress :: String @@ -367,6 +371,7 @@ data UserUpdate , countryCode :: String , zipCode :: String , streetAddress :: String + , phone :: Maybe String , startDate :: Maybe Date } | DeletePendingAddressChanges @@ -479,6 +484,7 @@ type BaseUser = , firstName :: Nullable String , lastName :: Nullable String , address :: Nullable Address + , phone :: Nullable String , cusno :: Cusno , subs :: Array Subscription , consent :: Array GdprConsent diff --git a/packages/components/src/Profile/Component.purs b/packages/components/src/Profile/Component.purs index 3ccb4d839..61737638a 100644 --- a/packages/components/src/Profile/Component.purs +++ b/packages/components/src/Profile/Component.purs @@ -37,7 +37,7 @@ import KSF.Sentry as Sentry import KSF.User (User, UserError(UniqueViolation)) import KSF.User as User import KSF.User.Cusno as Cusno -import KSF.ValidatableForm (class ValidatableField, ValidatedForm, inputFieldErrorMessage, validateEmailAddress, validateEmptyField, validateField, validateFinnishZipCode, validateZipCode) +import KSF.ValidatableForm (class ValidatableField, ValidatedForm, inputFieldErrorMessage, validateEmailAddress, validateEmptyField, validateField, validatePhone, validateFinnishZipCode, validateZipCode) import React.Basic (JSX) import React.Basic.Classic (make) import React.Basic.Classic as React @@ -59,11 +59,13 @@ type State = { name :: Name , address :: Address , email :: Maybe String + , phone :: Maybe String , now :: Maybe Date , changeDate :: Maybe Date , editFields :: Set EditField , editName :: AsyncWrapper.Progress JSX , editEmail :: AsyncWrapper.Progress JSX + , editPhone :: AsyncWrapper.Progress JSX , editAddress :: AsyncWrapper.Progress JSX } @@ -79,7 +81,7 @@ type Address = , city :: Maybe String } -data EditField = EditAddress | EditEmail | EditName +data EditField = EditAddress | EditEmail | EditName | EditPhone derive instance eqEditField :: Eq EditField derive instance ordEditField :: Ord EditField @@ -113,6 +115,12 @@ instance validatableFieldEmailFormFields :: ValidatableField EmailFormFields whe validateField field value _serverErrors = case field of Email -> validateEmailAddress field value +data PhoneFormFields + = Phone +instance validatableFieldPhoneFormFields :: ValidatableField PhoneFormFields where + validateField field value _serverErrors = case field of + Phone -> validatePhone field value + derive instance eqEmailFormFields :: Eq EmailFormFields jsComponent :: React.Component Props @@ -126,12 +134,14 @@ profile = make component { initialState: { name: { firstName: Nothing, lastName: Nothing } , email: Nothing + , phone: Nothing , address: { zipCode: Nothing, countryCode: Nothing, streetAddress: Nothing, city: Nothing } , now: Nothing , changeDate: Nothing , editFields: Set.empty , editName: Ready , editEmail: Ready + , editPhone: Ready , editAddress: Ready } , render @@ -150,6 +160,7 @@ didMount self = do resetFields self EditEmail resetFields self EditAddress resetFields self EditName + resetFields self EditPhone render :: Self -> JSX render self@{ props: { profile: user } } = @@ -164,6 +175,7 @@ render self@{ props: { profile: user } } = [ DescriptionList.descriptionList { definitions: visiblePendingAddressChanges } ] } , profileEmail + , profilePhone , DOM.div { id: "profile--display" , children: @@ -220,6 +232,49 @@ render self@{ props: { profile: user } } = ] } + profilePhone = + AsyncWrapper.asyncWrapper + { wrapperState: self.state.editPhone + , readyView: profilePhoneReady + , editingView: \_ -> profilePhoneEditing + , loadingView: profilePhoneLoading + , successView: \_ -> profilePhoneReady + , errorView: editingError self EditPhone + } + where + profilePhoneReady = DOM.div + { className: "profile--profile-row" + , id: "profile--phone" + , children: + [ currentPhone + , changePhoneButton self + ] + } + profilePhoneEditing = DOM.div_ + [ DescriptionList.descriptionList + { definitions: + [ { term: "Telefonnummer:" + , description: [ editPhone self ] + } + ] + } + ] + profilePhoneLoading spinner = DOM.div + { className: "profile--profile-row" + , children: + [ currentPhone + , spinner + ] + } + currentPhone = + DescriptionList.descriptionList + { definitions: + [ { term: "Telefonnummer:" + , description: [ DOM.text $ fromMaybe "-" $ Nullable.toMaybe user.phone ] + } + ] + } + profileName = AsyncWrapper.asyncWrapper { wrapperState: self.state.editName @@ -329,6 +384,7 @@ editingError self fieldName errMessage = EditAddress -> self.setState _ { editAddress = AsyncWrapper.Ready } EditName -> self.setState _ { editName = AsyncWrapper.Ready } EditEmail -> self.setState _ { editEmail = AsyncWrapper.Ready } + EditPhone -> self.setState _ { editPhone = AsyncWrapper.Ready } } ] } @@ -502,6 +558,53 @@ editEmail self = Tracking.changeEmail self.props.profile.cusno "error: unexpected error when updating email" updateEmail _ = pure unit +editPhone :: Self -> JSX +editPhone self = + DOM.form + { className: "profile--edit-phone" + , children: + [ InputField.inputField + { type_: InputField.Text + , name: "phone" + , placeholder: "Telefonnummer" + , value: self.state.phone + , onChange: \newPhone -> self.setState _ { phone = newPhone } + , label: Just "Telefonnummer" + , validationError: inputFieldErrorMessage $ validateField Phone self.state.phone [] + } + , submitButton + , DOM.div { className: "profile--submit-buttons", children: [ iconClose self EditPhone ] } + ] + , onSubmit: Events.handler preventDefault $ \_ -> submitNewPhone $ validatePhoneForm self.state.phone + } + where + submitButton = iconSubmit $ isValid (validatePhoneForm self.state.phone) + + validatePhoneForm :: Maybe String -> ValidatedForm PhoneFormFields (Maybe String) + validatePhoneForm form = + validateField Phone form [] + + submitNewPhone :: ValidatedForm PhoneFormFields (Maybe String) -> Effect Unit + submitNewPhone = validation + (\_ -> Console.error "Could not submit phone.") + updatePhone + + updatePhone :: Maybe String -> Effect Unit + updatePhone (Just phone) = do + self.setState _ { editPhone = Loading mempty } + Aff.launchAff_ do + newUser <- User.updateUser self.props.profile.uuid $ User.UpdatePhone { phone } + case newUser of + Right u -> liftEffect do + self.props.onUpdate u + self.setState _ { editEmail = Success Nothing } + Tracking.changePhone self.props.profile.cusno "success" + Left err -> liftEffect do + self.props.logger.error $ Error.userError $ show err + self.setState _ { editPhone = AsyncWrapper.Error "Det gick inte att updatera telefonnummer. Vänligen tak kontakt med kundservice." } + Tracking.changePhone self.props.profile.cusno "error: unexpected error when updating phone" + updatePhone _ = pure unit + editName :: Self -> JSX editName self = DOM.form @@ -631,6 +734,9 @@ changeEmailButton self = changeAttributeButton self EditEmail changeNameButton :: Self -> JSX changeNameButton self = changeAttributeButton self EditName +changePhoneButton :: Self -> JSX +changePhoneButton self = changeAttributeButton self EditPhone + editButton :: String -> Self -> EditField -> JSX editButton buttonText self field = DOM.div @@ -657,6 +763,7 @@ switchEditProgress :: Self -> EditField -> AsyncWrapper.Progress JSX -> Effect U switchEditProgress self EditName progress = self.setState _ { editName = progress } switchEditProgress self EditEmail progress = self.setState _ { editEmail = progress } switchEditProgress self EditAddress progress = self.setState _ { editAddress = progress } +switchEditProgress self EditPhone progress = self.setState _ { editPhone = progress } isUpcomingPendingChange :: Maybe Date -> User.PendingAddressChange -> Boolean isUpcomingPendingChange Nothing _ = true @@ -688,6 +795,9 @@ resetFields self EditName = resetFields self EditEmail = self.setState _ { email = Just self.props.profile.email } +resetFields self EditPhone = + self.setState _ { phone = Nullable.toMaybe self.props.profile.phone } + formatAddress :: User.DeliveryAddress -> String formatAddress { temporaryName, streetAddress, zipcode, city } = (maybe "" (_ <> ", ") $ toMaybe temporaryName) <> diff --git a/packages/components/src/Tracking.js b/packages/components/src/Tracking.js index 4943f5155..0eb2c2308 100644 --- a/packages/components/src/Tracking.js +++ b/packages/components/src/Tracking.js @@ -84,6 +84,10 @@ exports.changeAddress_ = function (cusno, result) { dataLayer.push({ event: "changeAddress", cusno: cusno, result: result }); }; +exports.changePhone_ = function (cusno, result) { + dataLayer.push({ event: "changePhone", cusno: cusno, result: result }); +}; + exports.deletePendingAddressChanges_ = function (cusno, result) { dataLayer.push({ event: "deletePendingAddressChanges", cusno: cusno, result: result }); }; diff --git a/packages/components/src/Tracking.purs b/packages/components/src/Tracking.purs index 7f8b61fda..ee71b5a5a 100644 --- a/packages/components/src/Tracking.purs +++ b/packages/components/src/Tracking.purs @@ -24,6 +24,7 @@ foreign import updateCreditCard_ :: EffectFn5 Cusno Subsno CreditCard CreditCard foreign import changeName_ :: EffectFn2 Cusno Result Unit foreign import changeEmail_ :: EffectFn2 Cusno Result Unit foreign import changeAddress_ :: EffectFn2 Cusno Result Unit +foreign import changePhone_ :: EffectFn2 Cusno Result Unit foreign import deletePendingAddressChanges_ :: EffectFn2 Cusno Result Unit foreign import updateResetPassword_ :: EffectFn1 Result Unit @@ -94,6 +95,9 @@ changeEmail = runEffectFn2 changeEmail_ changeAddress :: Cusno -> Result -> Effect Unit changeAddress = runEffectFn2 changeAddress_ +changePhone :: Cusno -> Result -> Effect Unit +changePhone = runEffectFn2 changePhone_ + deletePendingAddressChanges :: Cusno -> Result -> Effect Unit deletePendingAddressChanges = runEffectFn2 deletePendingAddressChanges_ diff --git a/packages/user/package.json b/packages/user/package.json index 3e5d1d52a..340abf8ef 100644 --- a/packages/user/package.json +++ b/packages/user/package.json @@ -5,7 +5,7 @@ "main": "index.js", "license": "MIT", "dependencies": { - "persona": "https://github.com/KSF-Media/persona-javascript-client.git#40058b09b6d969390daaad6e58918b04129b1c16", + "persona": "https://github.com/KSF-Media/persona-javascript-client.git#d8876dc53c39e6c80cdff363a8230deb944c6e73", "react": "^16.12", "bottega": "https://github.com/KSF-Media/bottega-javascript-client.git#f58baa4ebf8a494347b212e3378b6168c5e2faff", "react-dom": "^16.12" diff --git a/packages/vetrina/package.json b/packages/vetrina/package.json index 0829f25f9..ae472f3bf 100644 --- a/packages/vetrina/package.json +++ b/packages/vetrina/package.json @@ -11,7 +11,7 @@ "@sentry/browser": "^6.2.1", "bottega": "https://github.com/KSF-Media/bottega-javascript-client.git#f58baa4ebf8a494347b212e3378b6168c5e2faff", "create-react-class": "^15.6.3", - "persona": "https://github.com/KSF-Media/persona-javascript-client.git#40058b09b6d969390daaad6e58918b04129b1c16", + "persona": "https://github.com/KSF-Media/persona-javascript-client.git#d8876dc53c39e6c80cdff363a8230deb944c6e73", "react": "^16.12.0", "react-dom": "^16.12.0", "uuid-validate": "^0.0.3" diff --git a/packages/vetrina/src/Vetrina/Purchase/AccountForm.purs b/packages/vetrina/src/Vetrina/Purchase/AccountForm.purs index 58ad03865..a4e17f270 100644 --- a/packages/vetrina/src/Vetrina/Purchase/AccountForm.purs +++ b/packages/vetrina/src/Vetrina/Purchase/AccountForm.purs @@ -182,7 +182,7 @@ render props self@{ state: { contactForm } } = fragment liftEffect $ props.setLoading (Just Spinner.Loading) Aff.finally (liftEffect $ props.setLoading Nothing) - do eitherUser <- User.updateUser props.user.uuid $ UpdateFull $ addr `merge` { startDate: Nothing } + do eitherUser <- User.updateUser props.user.uuid $ UpdateFull $ addr `merge` { startDate: Nothing, phone: Nothing } case eitherUser of Right user -> liftEffect $ props.retryPurchase user Left err -> liftEffect $ props.onError err