4.7.1-alpha.0 • Published 8 months ago

dynamic_step_core v4.7.1-alpha.0

Weekly downloads
-
License
-
Repository
-
Last release
8 months ago

Frontend

Frontend for developing core components for Dynamic Steps

Preparation

Node: 14.20.0

  1. git clone git@github.com:wkda/dynamic-step-frontend-core.git
  2. run yarn

DSB data workflow - 22.06.2017

Table of content

Frontend initial data state

Old Car Flow (AKA manual pricing)

Normal flow of Booking page

Normal flow of Booking Handover page

Normal flow of Booking Confirmation page

Data Layer - Booking Tracking

Cookies workflow

LocalStorage workflow

Return customer banner workflow

Quick Price

Rebooking Redirection

Short form request

marketing properties hoc

marketing property self eva hide online price

Frontend initial data state:

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

Email

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

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 is booking/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 is booking/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

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.jsx

  • When 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.