dynamic_step_core v4.7.1-alpha.0
Frontend
Frontend for developing core components for Dynamic Steps
Preparation
Node: 14.20.0
- git clone git@github.com:wkda/dynamic-step-frontend-core.git
- run yarn
DSB data workflow - 22.06.2017
Table of content
- Manufacturer
- Model
- Built year
- Submit Step1
- Body type
- Model details
- Model sub details
- Mileage
- Submit Step2 and StepCompound
Old Car Flow (AKA manual pricing)
- initialization
- Double Opt In
- Branch
- Date and Time
- First Name
- last-name
- Phone number and codes
- submit-booking
Normal flow of Booking Handover page
- initialization handover
- Branch handover
- Date and Time handover
- Full name
- Phone number and codes handover
- Submit Booking Handover
Normal flow of Booking Confirmation page
- Booking Tracking Initialization
- Booking tracking Usage
- Booking branch tracking
- Booking date and time tracking
- Booking first name tracking
- Booking last name tracking
- Booking telephone tracking
- Booking Selling Decision tracking
- Booking submit tracking
Return customer banner workflow
marketing property self eva hide online price
Frontend initial data state:
- mileage data comes from translations - path: src/reducers/mileage.js
- phoneCodes data is hard coded inside phoneCodes reducer - path: src/reducers/data/phoneCodes.js
- funnel config values are initialised at start after being merged with default config - paths: initial config: src/reducers/config.js, update: src/store/configureStore.js
Normal flow of StepCompound and Step1 & Step2 pages (first time user - without localStorage and cookies)
In StepCompound and in Step2 we fetch location endpoint (/papi/v1/geolocation/search
) when mounted to be submitted with the form to create the car lead. In Step1 we don't fetch anything.
- we receive the response (Example:
{"country":"DE", "city":"Berlin", "lat":52.516693, "lng":13.399994}
) - and we insert it as it is to the data store - path: src/reducers/location.js
Manufacturer
In both pages, SelectMake component fetches the manufacturer (Makes) data from this endpoint (/papi/v1/car-types/manufacturer
) - path: src/components/composed/steps/SelectMake.jsx
- Response example:
{"page":0,"pageSize":2147483647,"totalPageCount":1,"wkda":{"020":"Abarth","040":"Alfa Romeo","042":"Alpina","057":"Aston Martin"...}}
- Normalize the data and sort it by ascending order - path: src/reducers/manufacturer.js **
when the user selects a manufacturer from the list, we store the value to the store and fetch models data from this endpoint (/papi/v1/car-types/main-types?manufacturer=<manufacturer_value>
) - path: src/components/composed/steps/SelectMake.jsx
- Response (Example:
{"page":0,"pageSize":2147483647,"totalPageCount":1,"wkda":{"124 Spider":"124 Spider","500":"500","Grande Punto":"Grande Punto","Punto":"Punto","Ritmo":"Ritmo","Stilo":"Stilo"}}
) - Normalize the data and sort it by ascending order - path: src/reducers/models.js **
Model
when the user selects a model from the list, we store the value to the store and fetch built years data from this endpoint (/papi/v1/car-types/built-dates?manufacturer=<manufacturer_value>&main-type=<model_value>
) - path: src/components/composed/steps/SelectModel.jsx
Response (Example: {"wkda":{"2014":"2014","2015":"2015","2016":"2016","2017":"2017"}}
)
Normalize the data and sort it by descending order - path: src/reducers/builtYear.js **
Built year
when the user selects a built year, we check the value is in our list and store the value to the store - path: src/components/composed/steps/SelectBuiltYear.jsx
** Normalize - meaning that we turn the "wkda" object to an array of objects with the keys as values and the values as labels.
** Sorting by ascending or descending order is sorted by Alphabet of the labels - path: src/helpers/normalization/normalizeWkdaResponse.js
Submit Step1
In Step1 the next action is to submit button which activates the form validation in which we check that all values are selected - path: src/pageContainers/Step1.jsx
- after validation we redirect to the redirectAfterStep1 variable from the config which by default is "step-2" (the data is already stored by that point in the LocaleStorage) - path: src/reducers/config.js
- there the user will continue the steps
In StepCompound the SelectBuiltYear component will call body types api endpoint (/papi/v1/car-types/body-types?manufacturer=<manufacturer_value>&main-type=<model_value>&built-date=<build_year>
) - path: src/components/composed/steps/SelectBuiltYear.jsx
- then unselect the rest of the fields ahead of the build year step, to support when user changes the value In Step2 the SelectBodyType component will call body types api endpoint when mounted with values which we will get from localStorage (manufacturer, model and build_year) - path: src/components/composed/steps/SelectBodyType.jsx
Body type
when the user selects a body type from the list, we store the value to the store and fetch model details data from this endpoint (/papi/v1/car-types/main-types-details?manufacturer=<manufacturer_value>&main-type=<model_value>&built-date=<build_year>&body-type=<body_type>
) - path: src/components/composed/steps/SelectBodyType.jsx
- Response (Example:
{"wkda":{"1.4:76.00":"Alfa 146 1.4 (76 kW \/ 103 PS)","1.6:88.00":"Alfa 146 1.6 (88 kW \/ 120 PS)","1.8:103.00":"Alfa 146 1.8 (103 kW \/ 140 PS)"...}}
) - Normalize the data and store it in the store - path: src/reducers/modelDetails.js **
Model details
when the user selects a model details from the list, we store the value to the store and fetch model sub details data from this endpoint (/papi/v1/car-types/main-types-sub?manufacturer=<manufacturer_value>&main-type=<model_value>&built-date=<build_year>&body-type=<body_type>&main-type-detail=1.4%3A76.00
) - path: src/components/composed/steps/SelectModelDetails.jsx
- Response (Example:
{"wkda":{"010400080110001":"Alfa 146 T.Spark Junior Schaltgetriebe 5 T\u00fcrer","010400080120001":"Alfa 146 T.Spark Schaltgetriebe 5 T\u00fcrer","010400080130001":"Alfa 146 T.Spark L Schaltgetriebe 5 T\u00fcrer"}}
) - Normalize the data and store it in the store - path: src/reducers/modelDetailsSub.js **
Model sub details
when the user selects a model sub details from the list, we store the value to the store - path: src/components/composed/steps/SelectModelDetailsSub.jsx
Mileage
when the user selects a mileage from the list, we store the value to the store - path: src/components/composed/steps/SelectMileage.jsx
when the user enters an email we validate it and store the value to the store - path: src/components/composed/steps/StepEmail.jsx
Submit Step2 and StepCompound
when the user the user clicks the submit button on Step2 or StepCompound we validate it - path: src/helpers/logic/carLead.js
- after validation we submit the form to
/consumer/carlead/
- path: post request: src/reducers/carLead.js, config: src/reducers/config.js - the body request we build before with the values from the store (Example:
{"carLead":{"builtYear":"1998","datTypeId":"010400080110001","email":"test@auto1.com","km":"40000","mobile":null,"subscribed":true},"location":{"ipAddress":"87.128.11.187"},"options":{"pricingScope":"price-ranges"}}
) - path: src/helpers/logic/carLead.js
Old Car Flow, AKA manual pricing
Initial data for Old Steps
- ownership data comes from translation key
old-steps.ownershipStatus
- path: src/reducers/manualPricing/ownership.js - doorCount data comes from translation key
old-steps.doorCount
- path: src/reducers/manualPricing/doorCount.js - old body type data comes from translation key
old-steps.bodyType
- path: src/reducers/manualPricing/bodyTypeOld.js - condition data comes from translation key
old-steps.carCondition
- path: src/reducers/manualPricing/condition.js - fuel type data comes from translation key
old-steps.fuelType
- path: src/reducers/manualPricing/fuelType.js - gear type data comes from translation key
old-steps.gearType
- path: src/reducers/manualPricing/gearType.js
In the old car flow Step1 is the first step still and before the user picks built year (if oldCarFlow is equal true
in the config which is false
by default) then we add another option with a negative value - path: src/reducers/builtYear.js
that says older than <the last year from the list>
which by choosing this option will lead the user to the Old Car Flow - path: src/pageContainers/Step1.jsx
When submitOldCarFlowUrl variable is empty from the config then submitting Step1 with old car flow will redirect to variable redirectToOldStep2 from the config which contains step-2-old
by default - path: src/pageContainers/Step1.jsx
Old Step2
path: src/pageContainers/OldStep2.jsx
when the user fills in the InitialYear we pass the value and the built year the user picked in Step1 to set the initial year - path: src/components/composed/steps/StepInitialYear.jsx
then we validate that the year is above or equal to 1800 and that its less or equal to the minimum year we had in Step1 and then place the value and if its validated in the store (if the value is not a number we will get not validated) - path: src/reducers/manualPricing/initialYear.js
when the user selects a body type from the given list, we pass the value to set it in the store - path: component - src/components/composed/steps/SelectAllBodyType.jsx, reducer - src/reducers/manualPricing/bodyTypeOld.js
when the user fills in the exact type (MTypeDetail) we pass the value to set it in the store with validation that it contains only numbers and characters (Alphabet) - path: component - src/components/composed/steps/StepMTypeDetail.jsx, reducer - src/reducers/manualPricing/mtypeDetail.js
when the user fills in the horse power we pass the value to set it in the store with validation that it contains only numbers - path: component - src/components/composed/steps/StepHorsePower.jsx, reducer - src/reducers/manualPricing/horsePower.js
When the user clicks on the Submit button of the old step2 we validate the values from the store and redirect to redirectAfterOldStep2 value from the config which contains step-3
by default - path: src/pageContainers/OldStep2.jsx
Old Step3
path: src/pageContainers/OldStep3.jsx
when the user selects a door count from the given list, we pass the value to set it in the store - path: component - src/components/composed/steps/SelectDoorCount.jsx, reducer - src/reducers/manualPricing/doorCount.js
when the user selects a fuel type from the given list, we pass the value to set it in the store - path: component - src/components/composed/steps/SelectFuelType.jsx, reducer - src/reducers/manualPricing/fuelType.js
when the user selects a gear type from the given list, we pass the value to set it in the store - path: component - src/components/composed/steps/SelectGearType.jsx, reducer - src/reducers/manualPricing/gearType.js
when the user selects a AC type from the given list, we pass the value to set it in the store - path: component - src/components/composed/steps/SelectACType.jsx, reducer - src/reducers/manualPricing/acType.js
When the user clicks on the Submit button of the old step3 we validate the values from the store and redirect to redirectAfterOldStep3 value from the config which contains step-4
by default - path: src/pageContainers/OldStep2.jsx
Old Step4
path: src/pageContainers/OldStep4.jsx
when the user selects a mileage (Kilometer) from the given list, we pass the value to set it in the store - path: component - src/components/composed/steps/SelectMileageOld.jsx, reducer - src/reducers/mileage.js when the user selects a condition from the given list, we pass the value to set it in the store - path: component - src/components/composed/steps/SelectCarCondition.jsx, reducer - src/reducers/manualPricing/condition.js when the user selects if the car is salvageable (salvage), we pass the value to set it in the store - path: component - src/components/composed/steps/StepSalvage.jsx, reducer - src/reducers/manualPricing/salvage.js when the user selects a ownership from the given list, we pass the value to set it in the store - path: component - src/components/composed/steps/SelectOwnershipStatus.jsx, reducer - src/reducers/manualPricing/ownership.js when the user fills in the email we pass the value to set it in the store with validation that it is correct - path: component - src/components/composed/steps/StepEmailOld.jsx, validating action - src/actions/actions.js, reducer - src/reducers/email.js
Old Steps, Old Step4 Submit
we check that all the fields are validated and then we build the request body - path: src/helpers/logic/oldCarLead.js
Example request body: {"carLead":{"builtYear":"2000","email":"test@auto1.com","km":"10000","subscribed":true,"oldCar":true,"mobile":null,"statusId":1},"location":{"ipAddress":"87.128.11.187"},"manualPricing":{"manufacturer":"020","maintype":"124 Spider","bodyType":"1025","model":"deadas asdas 23423","horsepower":"133","doorCount":"1146","fuelType":"1039","gearType":"1138","acType":"1049","condition":"1133","ownershipStatus":"1135","damage":"false"}}
which we will send to /consumer/carlead/
- path: src/pageContainers/OldStep4.jsx
and if the status of the response is not 400 and we have no carLead errors and we have a hash, we will redirect to //, redirectAfterStep2 variable comes from the config and is 'booking' by default - path: src/pageContainers/OldStep4.jsx
Normal flow of Booking page
initialization
On initialization the Booking page makes several requests to the api to get initial data - path: src/pageContainers/Booking.jsx
- car lead data (Example:
{"manufacturer":"Alfa Romeo","maintype":"Alfa 146","bodytype":1025, ...}
)/papi/v1/consumer/carlead/hash/<hash_from_url>/detail
- booking details data (Example:
{"carlead":{"id":405874944, ... "email":"test@auto1.com","mobile":{"fullNumber":null, ...},"subscribed":true,....},"customer":{"mobile":{"fullNumber":null,"prefix":null,"number":null}},"evaluation":{"onsitePrice":0, ... ,"painPriceCurrency":"EUR"},"branch":null,"dataLayer":{"event":"leadCreated","leadData":{"unique":false,"daysSinceFirstVisit":0,...}}}
)/papi/v1/booking/<hash_from_url>
- marketing properties data (Example:
{"carleadId":405874944,"carProperties":[]}
)/papi/v1/car-details/marketing-property/<hash_from_url>
Double Opt In
Then if booking details carlead object subscribed property is true we call car-crm endpoint
(Example response: {"doubleOptIn":true,"doubleOptInConfirmedOn":null,"paused":false,"pausedOn":null,"quarantined":false, ... "createdOn":"2017-06-26T10:41:01.000Z","createdBy":null}
) - /papi/v1/car-crm/<hash>
and check if response has doubleOptIn set to true and if doubleOptInConfirmedOn is set to null (empty)
then we'll show the doubleOptIn Modal (popup) and then make an additional call to the api (Example response: {"wkda":{"aid":144799,"transport":"experian","category":"DEFAULT"}}
) - /papi/crm/template/<hash>/double_opt_in
path: src/pageContainers/Booking.jsx
Branch
BookingMultiBranch component makes a request for the branches data when mounted from api endpoint and places it in the store (/papi/v1/branch?hash=<hash_from_url>&showPartners=<showPartnersBranches_value_from_config_default_false>
)
Example response: [{"id":29,"lat":50.766164,"lng":6.126049,"name":"Aachen-Brand","street":"Trierer Stra\u00dfe 170","zipcode":"52078","city":"Aachen","area":null,"partner":null},...]
when user selects location suggestion we calculate the distance of the branches from that location - path: component - src/components/composed/booking/BookingMultiBranch.jsx service - src/externalModules/geo.js reducer - src/reducers/multiBranchesWidget.js
and then we pick the closest branch automatically, when branch is selected date and time values are emptied.
Date and Time
then SelectAppointment component (date & time) will call api endpoint for open slots /papi/v1/appointment/opendatetimes/<branch_id>/car/<hash>?dayOffset=7&email=<email>
- path: src/components/composed/booking/SelectAppointment.jsx
Example response: {"availableDates":{"2017-06-26":{"slots":[{"start":"2017-06-26T11:00:00.00+0000","end":"2017-06-26T11:30:00.00+0000"},...]}}}
First slot (date & time) is being chosen automatically - path: src/components/composed/booking/SelectAppointment.jsx
First Name
when the user inserts First name, we store the value to the store - path: src/components/composed/booking/BookingFirstName.jsx
Last Name
when the user inserts Last name, we store the value to the store - path: src/components/composed/booking/BookingLastName.jsx
Phone number and codes
In BookingTelephone the default value for the phone code (country code) is being taken from the config value named callingCode
- path: src/components/composed/booking/BookingTelephone.jsx
when the user selects a phone code from the list, we store the value to the store.
when the user inserts phone number, we store the value to the store - path: src/components/composed/booking/BookingTelephone.jsx
Submit Booking
when user clicks the submit button - path: src/pageContainers/Booking.jsx
- we validate each field from the store
- build the request body with the values from the store
- we call the POST request to
/papi/v1/appointment/<hash>?email=<email>
with request body, Example:{"event":"booking.create","appointment":{"branchId":52,"start":"2017-06-26T16:00:00.00+0000","end":"2017-06-26T16:30:00.00+0000","firstName":"test","lastName":"test","mobile":"+4917633344555","hash":"6d0445dd8e8d4c52bd0f1a66ed37f992","type":"1"}}
- after the call we check for status 400 and if we get 400, we check the response for errors inside the appointment object to direct it to the right input through the store
- if the status is not 400 we redirect
<bookingConfirmationUrl>/<hash>/
bookingConfirmationUrl variable comes from the config of the funnel and it isbooking/confirmed
by default - if the user doesn't have a "best appointment" and is not in double booking, we redirect
<doubleBookingUrl>/<hash>/
bookingConfirmationUrl variable comes from the config of the funnel and it isbooking/second
by default
Normal flow of Booking Handover page
initialization handover
Same as normal Booking page except that a handover page triggers setHandoverCollapsed to be called with true
to the store which changes the handoverCollapsed value to true in the multiBranchesWidget reducer - src/reducers/multiBranchesWidget.js
Branch handover
BookingMultiBranch component behaves the same except for the selectBranch function and its child component MultiBranch
- MultiBranch doesn't render geo suggestions when handoverCollapsed is set to
true
- path: src/components/MultiBranch.jsx - if no selected branch was found or we couldn't find close branches we would render info message with no branches found. otherwise we would show only the first branch on the list. - path: src/components/MultiBranch.jsx In the BookingMultiBranch selectBranch function, if the partner name is 'TÜV' we will set handoverCollapsed to false in the store and then show the normal branch flow - path: src/components/composed/booking/BookingMultiBranch.jsx so normally with handover the user doesn't have the choice of the branch.
Date and Time handover
works the same as normal Booking page
Full name
in handover the user can't insert name since we just show first and last name together from the customer data we get from the api as Full name - path: src/components/composed/booking/BookingFullName.jsx
Phone number and codes handover
In handover the user doesn't get an input to edit/insert phone by default. instead we show his phone number from the customer data we get and give him an edit button to toggle editing mode - path: src/components/composed/bookingConfirmed/CustomerTelephone.jsx
Submit Booking Handover
the differences in handover when user clicks the submit button - path: src/pageContainers/Booking.jsx
- we don't validate First Name, Last Name and Telephone number and we count on the api error for telephone validation - path: src/pageContainers/Booking.jsx
- we populate the First Name, Last Name and Telephone fields from the customer data in the request body - path: src/pageContainers/Booking.jsx
Normal flow of Booking Confirmation page
When BookingConfirmed page is mounted:
- We get the hash from the url - path: src/pageContainers/BookingConfirmed.jsx
- Then we make several requests to api - path: src/pageContainers/BookingConfirmed.jsx
- get Booking (
/papi/v1/booking/<hash>
) Example response:{"carlead":{"id":405877084,"stockNumber":"EU47487","statusId":12,"branchId":52,"locationId":null,"carTypeId":null,"datTypeId":"010570030020002","km":10000,"builtYear":2014,"oldCar":false,"manualPricing":false,"email":"test@auto1.com","mobile":{"fullNumber":"+4917633344555","prefix":"+49","number":"17633344555"},"subscribed":true,"unsubscribedDatetime":null,...},"customer":{"id":405877084,"firstName":"test","lastName":"test","mobile":{"fullNumber":"+4917633344555","prefix":"+49","number":"17633344555"}},"evaluation":{"id":405877084,"appointment":{"start":"2017-06-26T16:00:00.00+0000","end":"2017-06-26T16:30:00.00+0000","firstBookDateTime":"2017-06-26T15:50:12.00+0000","bookDateTime":"2017-06-26T15:50:12.00+0000"},...},"branch":{"id":52,"status":1,"type":1,"lat":52.485014,"lng":13.456136,...},"dataLayer":{"event":"revisit","leadData":{"unique":false,"daysSinceFirstVisit":0,"cidChannels":[],"hash":"6d0445dd8e8d4c52bd0f1a66ed37f992","stockNumber":"EU47487","daysSinceLeadCreated":0}}}
- get Appointment (
/papi/v1/appointment/car/<hash>/latest?appointmentType=1%2C2
) Example response:{"id":36045187,"carleadId":405877084,"start":"2017-06-26T16:00:00.00+0000","end":"2017-06-26T16:30:00.00+0000","type":1,"status":2,...l}
- the if bookingConfirmedFull value from the config is true (which is false by default - path: src/reducers/config.js) we call getCarDetails (
/papi/v1/carlead/hash/<hash>/detail
) Example response:{"manufacturer":"Aston Martin","maintype":"DB 9","bodytype":1007,"modelDescription":"DB 9 5.9 V12 Volante","datHst":"057","datFza":1,"datHt":"3","datUt":"2","subtype":"5.9 V12","subtypeExtra":"Volante","factoryCode":null,"specialSubType":0,"line":null,"buildStartDate":"2004-11-01","buildEndDate":"2015-12-31","motor":"V12 KAT 335 kW","doorCount":2,"ccm":5935,"kw":335,"horsepower":455,"gearType":1139,"bodyForm":1177,"fuelType":1039,"cylinderCount":12,"driveType":1241,"wheelBase":"","hsn":null,"tsn":null,"soldCars":22}
When the user clicks the Edit Booking link we redirect him to the booking page with the hash - path: src/pageContainers/BookingConfirmed.jsx
Data Layer - Booking Tracking
Booking Tracking Initialization
we initiate the dataLayer array if not found in the window object - path: src/helpers/render.js
before we render a page we push (insert) the locale from the funnel config into dataLayer - path: src/helpers/render.js
When the booking page mounts and calls getBooking api (/papi/v1/booking/<hash>
) we put the dataLayer object from the response inside the global dataLayer object - path: src/reducers/carLead.js
Booking tracking Usage
all Booking tracking goes through one action that builds the request body and sends it to the api endpoint (/papi/v1/car-details/marketing-property/<hash>
) - path: src/actions/actions.js
! this functionality is disabled if the main funnel config doesn't have the enabledBookingTracking variable set to true
(by default its false
)
unless the action is being called with another true
boolean called forcePush which should override the config option in that specific action - path: src/actions/actions.js
Booking branch tracking
When a user selects a location suggestion (and then the first branch is selected by default) and when a user picks a different branch from the previous selected branch in the store
then we call the Booking tracking action with the hash from the url, branch
and the new branch id. Then it builds the request body (Example: { propertyId: 3402, value: <branch_id> }
) and sends the request - path: src/components/composed/booking/BookingMultiBranch.jsx
Booking date and time tracking
When a user selects a different date from the previous selected date in the store then we call the Booking tracking action with the hash from the url,
date
and the new date value. Then it builds the request body (Example:{ propertyId: 3403, value: <date_value> }
) and sends the request - path: src/components/composed/booking/SelectAppointment.jsxWhen a user selects a different time from the previous selected time in the store then we call the Booking tracking action with the hash from the url,
time
and the new time value. Then it builds the request body (Example:{ propertyId: 3404, value: <time_value> }
) and sends the request - path: src/components/composed/booking/SelectAppointment.jsx
Booking first name tracking
When a user inserts a different first name from the previous selected name in the store
then we call the Booking tracking action with the hash from the url, firstName
and the number 1 if there's a value and 0 if not. Then it builds the request body (Example: { propertyId: 3405, value: <1 if there's a value and 0 if not (empty value)> }
) and sends the request - path: src/components/composed/booking/BookingFirstName.jsx
Booking last name tracking
When a user inserts a different last name from the previous selected name in the store
then we call the Booking tracking action with the hash from the url, lastName
and the number 1 if there's a value and 0 if not. Then it builds the request body (Example: { propertyId: 3406, value: <1 if there's a value and 0 if not (empty value)> }
) and sends the request - path: src/components/composed/booking/BookingLastName.jsx
Booking telephone tracking
When a user inserts a phone number then we call the Booking tracking action with the hash from the url, telephone
and the number 1 if there's a value and 0 if not. Then it builds the request body (Example: { propertyId: 3407, value: <1 if there's a value and 0 if not (empty value)> }
) and sends the request - path: src/components/composed/booking/BookingTelephone.jsx
Booking Selling Decision tracking
When a user selects a selling decision then we call the Booking tracking action with the hash from the url, sellingDecision
, the value of the decision and true
(to force tracking even when enabledBookingTracking is set to false
. Then it builds the request body (Example: { propertyId: 3408, value: <decision_value> }
) and sends the request - path: src/components/composed/booking/BookingSellingDecision.jsx
Booking submit tracking
when the user submits the booking form, after we validate the form and before we send the post request for the booking, if there's a selling decision we call the Booking tracking action with the hash from the url, sellingDecision
, the value of the decision and true
(to force tracking even when enabledBookingTracking is set to false
. Then it builds the request body (Example: { propertyId: 3408, value: <decision_value> }
) and sends the request - path: src/pageContainers/Booking.jsx
Cookies workflow
Stored - one Cookie is being saved at the mounting of the Booking page named wkdaLead
with the hash inside - src/pageContainers/Booking.jsx
Loaded - BannerReturnCustomer component loads the cookie when mounted for the hash (as it is not in the url) - src/components/composed/banners/BannerReturnCustomer/BannerReturnCustomer.jsx
LocalStorage workflow
localStorage key structure - dsb-core_<core_version> (Example: dsb-core_de-DE_IT-887_2.10.3
)
in order to make it unique for each funnel, locale and core version.
- We get the store from the localStorage if exists - path: src/store/configureStore.js
- Our store is being synced and saved to local storage at any action that being sent to the store without the "loading" params - path: src/store/configureStore.js
- This allows the user to refresh at any point and continue from where he left off and reduce the number of api calls
LocalStorage risks
- old and non relevant data being reused instead of updated data from api which could break the workflow of each of the pages.
- corrupted data from a user aware of his localStorage and the ability to change it and insert malicious data/scripts
- shows out internal app data to the customer (for one that knows about the local storage)
Return customer banner workflow
path - src/components/composed/banners/BannerReturnCustomer/BannerReturnCustomer.jsx
- On Mounting BannerReturnCustomer component loads the cookie value of
wkdaLead
for the user hash - path: src/components/composed/banners/BannerReturnCustomer/BannerReturnCustomer.jsx - If a hash exist, the component will make several calls to the api with the hash. - path: src/components/composed/banners/BannerReturnCustomer/BannerReturnCustomer.jsx
- car lead data (Example:
{"manufacturer":"Alfa Romeo","maintype":"Alfa 146","bodytype":1025, ...}
)/papi/v1/carlead/hash/<hash>/detail
- booking details data (Example:
{"carlead":{"id":405874944, ... "email":"test@auto1.com","mobile":{"fullNumber":null, ...},"subscribed":true,....},"customer":{"mobile":{"fullNumber":null,"prefix":null,"number":null}},"evaluation":{"onsitePrice":0, ... ,"painPriceCurrency":"EUR"},"branch":null,"dataLayer":{"event":"leadCreated","leadData":{"unique":false,"daysSinceFirstVisit":0,...}}}
)/papi/v1/booking/<hash>
- marketing properties data (Example:
{"carleadId":405874944,"carProperties":[]}
)/papi/v1/car-details/marketing-property/<hash>
- appointment data (Example:
{"id":36044624,"carleadId":405874944,"start":"2017-06-30T06:00:00.00+0000","end":"2017-06-30T06:30:00.00+0000","type":1,"status":2,... ,"bookedBranch":{"id":16,"status":1,"type":1,"lat":51.307208,"lng":9.522554,"navigationInfo":"Neben stop+go",...}
)/papi/v1/appointment/car/<hash>/latest?appointmentType=1%2C2
Banner name is being chosen in the following order:
- L2A - lead to appointment is being chosen by default
- if an evaluation onsitePrice exists then we check the onsiteDateTime and if its bigger than 5 days we put
**onsite**-expired
and if more than**onsite**-valid
- if branch exists but onsitePrice doesn't, we check the evaluation.appointment.start date, if its has past we place
**no-show**
and if not yet then we place**appointment-reminder**
as the banner name
Then a banner component is picked according to the name from the components given in builder and the data for the specific banner is constructed to pass to it. BannerReturnCustomer risk - we depend on the builder side
- BannerReturnCustomer should be initiated with all types of banners in builder otherwise we could have an empty banner and we will lose the banner functionality
Quick price
path - src/components/QuickPrice/QuickPrice.jsx
A connected HOC that expose the quick price value for selected cities explicitly marked in the funnel configuration file:
Exposed properties
{
quickPrice: number | undefined;
selectedCity: string;
carHash: string;
quickPriceLoading: boolean;
quickPriceAvailable: boolean;
quickPriceAvailableCities: Array<string>;
updateSpecialSellingOptionType: (selection: string) => void;
updateSpecialSellingOptionEnabled: (selection: string) => void;
updateSpecialSellingOptionPrice: (price: string) => void;
}
note: quickPriceAvailable
is true when the funnel has the quickPriceCities
array configured, the selected city is available and there is a price in the marketingProperty response.
Funnel config
{
quickPriceCities: ['berlin'];
}
carHash
carHash is picked from the url path, so it's always available.
Rebooking Redirection
path - src/components/DirectBookingRedirection/DirectBookingRedirection.jsx
Render prop component that allows to show a Direct booking form component or a component with options for redirections depending on an appointment status.
This are the properties that this component receives, most of them come from the redux store, children
, redirectionView
, formView
, loadingView
are optional and they need to be supplied when using this component:
interface IDirectBookingRedirectionProps {
loadingMarketingProperties: boolean;
loadedMarketingProperties: boolean;
hasAppointment: boolean;
loadingCarlead: boolean;
loadedCarlead: boolean;
appointment: { start: string; end: string };
appointmentIsDirectBooking: boolean;
hash: string;
now: number;
dlp: string;
locale: string;
children?: (props: IRenderProps) => React.Node;
redirectionView?: (props: IRenderProps) => React.node;
formView?: (props: IRenderProps) => React.node;
loadingView?: (props: IRenderProps) => React.node;
}
Possible actions that can be done on when a booking exist:
enum redirectActions {
edit,
create,
confirm,
}
All the function render properties receive a IRenderProps
with details of the current appointment if any and actions needed for redirection.
redirectionView: a function that returns a component that is shown when there is an appointment, it doesn't matter if it's in the past, the component returned by this function should show a form with options to navigate to the different url based on the state of the appointment
formView: this is the actual Direct Booking form, works as before
loadingView: a Loading component that will be rendered on the server and when loading marketingProperties and the carlead
children: if you decide to implement the children render prop, the redirectionView
, formView
, loadingView
will never be rendered so you must apply the logic to show and hide components as needed based on the IRenderProps
that his function receives, good for playing and experiment.
type selectedAction = 'edit' | 'create' | 'confirm';
interface IRenderProps {
loadingMarketingProperties: boolean;
loadedMarketingProperties: boolean;
hasAppointment: boolean;
loadingCarlead: boolean;
loadedCarlead: boolean;
appointment: { start: string; end: string };
appointmentIsDirectBooking: boolean;
hash: string;
now: number;
locale: string;
showBookingForm: boolean;
isAppointmentInThePast: boolean;
onNextHandler: () => void;
selectedAction: selectedAction;
onSelectedChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
}
Short form request
Set the shortFormRequest
and set it to true
on a funnel configuration in order to add {'short-form': true}
query param to the getModelDetails
and getModelDetailsSub
requests.
marketing properties hoc
A Higher order component to expose parsed marketing properties and loading/loaded metadata
using the HOC
import the withMarketingProperties
function from 'dsb/module/components/hoc/withMarketingProperties'
and wrap yor component with it:
class YourComponent extends React.Component{...}
export default contextAware(withMarketingProperties(YourComponent));
exposed props
All properties are parsed if needed and camelized, properties that have a path as a propertyName
(special_selling_option.enabled
), the property will render an object with the nested values:
interface marketingProperties {
loading: boolean;
loaded: boolean;
properties: object;
}
example response
{
"loading": false,
"loaded": true,
"properties": {
"zipCodeLocation": {
"label": "Friedrichstraße 123, Berlin, Deutschland",
"lat": 52.5263532,
"lng": 13.386930699999994,
"city": "Berlin"
},
"specialSellingOption": {
"enabled": 1,
"type": "premium_fast",
"price": 21130
}
}
}
Self eva hide online price
To enable this feature a selfEvaHideOnlinePriceBucket
config key needs to be added to the funnel config with possible values 0
and 1
, this will send the marketing property 8764
with the value mentioned before
Disable booking time slot preselection
To disable booking time slot preselection a enableTimeSlotPreselect
config key must be set to false
. Default value is true
.
Use virtual booking slots
To enable this feature a virtualSlots
config key should be set to true
. Default value is false
.
8 months ago