Preliminary Assignment for frontend internships. Welcome! We are delighted to see you applying. Now it's your time to shine.
Note that frontend internship roles are only available in 🇫🇮 and 🇪🇪.
Please take your time, use the entire time available to complete this to the best of your ability - we do not prioritise submissions by speed!
The goal of the assignment is to showcase your coding skills and ability to develop realistic features. This is a highly important part of the hiring process, so it's crucial to put effort into this without making it too bloated. Based on the results of the assignment review, we will make the decision on whether to proceed to the technical interview.
Your task is to implement the Delivery Order Price Calculator UI, or DOPC for short!
DOPC is an imaginary frontend which is capable of calculating the total price and price breakdown of a delivery order.
DOPC integrates with the Home Assignment API to fetch venue related data required to calculate the prices.
The term venue refers to any kind of restaurant / shop / store that's in Wolt.
Build a delivery order price calculator web app using React and TypeScript, which calculates delivery fees and surcharges based on user input and shows the calculated total price to the user.
The following fields are what the user interacts with in the web application:
Field | Type | data-test-id | Description | Example value |
---|---|---|---|---|
Venue slug | text input | venueSlug | The venue slug, for which to calculate delivery pricing. | home-assignment-venue-helsinki |
Cart value | text or number input | cartValue | Value of the shopping cart, in Euro (EUR). For simplicity, consider inputs as having an optional decimal separator of . , and no hundreds/thousands separators. |
10, 10.55, 100.55 (EUR) |
User latitude | text or number input | userLatitude | The user's latitude. | 60.17094 |
User longitude | text or number input | userLongitude | The user's longitude. | 24.93087 |
Get location | button | getLocation | On activation, gets the user's current location, and populates the latitude and longitude fields | N/A |
Calculate delivery price | button | calculateDeliveryPrice | On activation, prompts the calculation of the delivery price | N/A |
For the "text or number" inputs, we recognise that there are multiple ways to implement them in HTML, and leave the exact attributes to your judgement.
Inputs should be validated, and feedback should be provided to the user, as appropriate.
When creating web interfaces, it's important to ensure accessibility and ease of testing.
For each Field
in the table above, ensure that the root element containing the value, typically an input
element, includes a data-test-id
attribute.
This attribute is used for identifying elements during automated testing. The value of data-test-id
should be the camelCase version of the Field
listed in the table. For example, the field Cart value
in the table should correspond to data-test-id="cartValue"
.
The core algorithm for calculating the price produces the following output for the UI:
Label | Description | Example value |
---|---|---|
Cart value | The cart value, in EUR, formatted. | 10 EUR |
Small order surcharge | The calculated small order surcharge, in EUR, formatted. | 0 EUR |
Delivery fee | The calculated delivery fee, formatted. | 1.90 EUR |
Delivery distance | The calculated delivery distance, in meters, formatted | 177 m |
Total price | The calculated total price, in EUR, formatted. | 11.90 EUR |
The display/output formatting is open-ended; for example, you can consider the user's locale when displaying currency and distance. However, we do not expect you to implement support for other currencies than euro.
To facilitate testing, the wrapper of a formatted value should contain the "raw" value in a data-raw-value
attribute, such as <span data-raw-value="1055">10.55 EUR</span>
or <span data-raw-value="700">700 m</span>
. The money related "raw" values should be in cents and the delivery distance in meters.
Feel free to design and implement the user interface how you want. Below is an example of what it could look like.
When reviewing the assignment, we are focusing on the code quality and structure of your app. However, good UX & design will definitely give you bonus points. We also encourage you to consider accessibility in your implementation, as that topic is close to our hearts here at Wolt. Crafting an accessible design and writing accessible code are the kind of good development practices we value.
Let's look at how to produce the described output from the input values.
Assuming some parsing of the fields, and an internal representation of:
{
"venueSlug": "home-assignment-venue-helsinki",
"cartValue": 1000,
"userLatitude": 60.17094,
"userLongitude": 24.93087
}
The algorithm described here would produce:
{
"cartValue": 1000,
"smallOrderSurcharge": 0,
"deliveryFee": 190,
"deliveryDistance": 177,
"totalPrice": 1190
}
In order to calculate the values needed for the response, DOPC should request data from Home Assignment API which is an imaginary backend service. Fortunately, it's already implemented, so you can use it right away! It provides two JSON endpoints:
- Static information about a venue:
https://consumer-api.development.dev.woltapi.com/home-assignment-api/v1/venues/<VENUE SLUG>/static
, examples: - Dynamic information about a venue:
https://consumer-api.development.dev.woltapi.com/home-assignment-api/v1/venues/<VENUE SLUG>/dynamic
, examples:
Feel free to use any of these venue slugs during development:
- home-assignment-venue-helsinki
- home-assignment-venue-tallinn
However, note that in real world there could be thousands of different venues so your implementation should work in general case.
If you open the examples in the browser, you can see that both of the endpoints return quite a bit of data. But don't worry, we only care about a couple of the fields in the scope of this assignment; you can ignore the rest. Here are the relevant fields:
Endpoint | Location of the important field in the response JSON payload | Explanation |
---|---|---|
/static | venue_raw -> location -> coordinates | Location of the venue: [longitude, latitude] |
/dynamic | venue_raw -> delivery_specs -> order_minimum_no_surcharge | The minimum cart value to avoid small order surcharge |
/dynamic | venue_raw -> delivery_specs -> delivery_pricing -> base_price | The base price for delivery fee |
/dynamic | venue_raw -> delivery_specs -> delivery_pricing -> distance_ranges | The distance ranges for calculating distance based component for the delivery fee. More about this below. |
You can assume that all the fields mentioned above are always present in the response payload of the corresponding endpoint if the response status code is 200.
The structure of distance_ranges
looks something like this:
"distance_ranges": [
{
"min": 0,
"max": 500,
"a": 0,
"b": 0,
"flag": null
},
{
"min": 500,
"max": 1000,
"a": 100,
"b": 1,
"flag": null
},
{
"min": 1000,
"max": 0,
"a": 0,
"b": 0,
"flag": null
}
]
Each object inside distance_ranges
list contains the following:
min
: The lower (inclusive) bound for the distance range in metersmax
: The upper (exclusive) bound for the distance range in meters."max": 0
means that the delivery is not available for delivery distances equal or longer the value ofmin
in that object.a
: A constant amount to be added to the delivery fee on top of the base priceb
: Multiplier to be used for calculating distance based component of the delivery fee.b
is factored in to the delivery fee by addingb * distance / 10
(rounded to the nearest integer value). For example, if the delivery distance is 1000 meters and the value ofb
is 2, we'd add 200 (2 * 1000 / 10
) to the delivery fee.flag
: You can ignore this field
You can assume that the order of the objects inside distance_ranges
is sorted by min
.
You can also assume that the value for min
is the same as the value for max
in the previous object in the list.
Also, the first object in the list always has "min": 0
and the last object has "max": 0
.
For example, given the above distance_ranges
example, if the delivery distance were 600 meters and the base_price
were 199, the delivery fee would be 359 (base_price + a + b * distance / 10 == 199 + 100 + 1 * 600 / 10 == 359).
Another example: if the delivery distance were 1000 meters or more, the delivery would not be possible.
All the money related information (prices, fees, etc) are in the lowest denomination of the local currency. In euro countries they are in cents.
Here's some guidance for getting the logic and calculations right:
smallOrderSurcharge
is the difference betweenorder_minimum_no_surcharge
(as received from the Home Assignment API) and the cart value. For example, if the cart value is 800 andorder_minimum_no_surcharge
is 1000, then thesmallOrderSurcharge
is 200.smallOrderSurcharge
can't be negative.- Delivery distance is the straight line distance between the user's and venue's locations. Note that it's straight line distance, you don't need to figure out what's the distance via public roads. The exact algorithm doesn't matter as long as it's a decent approximation of a straight line distance.
- Delivery fee can be calculated with:
base_price + a + (b * distance / 10)
. Please read carefully the details above in the "Home Assignment API" section. - Total price is the sum of cart value, small order surcharge, and delivery fee.
- If the delivery is not possible, for example if the delivery distance is too long, the UI should show an error, with explanatory information.
We expect you to:
- Implement the UI in TypeScript and React
- Use frameworks and libraries of your choice
- Follow the specification described above
- Ensure that you have implemented
data-test-id
anddata-raw-value
, to facilitate automated testing.
- Ensure that you have implemented
- Implement tests for your solution
- Document the installation and running instructions
- Consider that this could be a real world project so the code quality should be on the level that you'd be happy to contribute in our real projects
- Use your own judgement in case you discover an edge case which is not explicitly documented in the specification above
We do not expect you to:
- Implement any additional features which are not described in this assignment description
- Implement support for different currencies
- Introduce authentication or monitoring or continuous integration or any kind of persistence (e.g. database)
- Deploy your solution
Bundle your project into a Zip archive and upload it to Google Drive, Dropbox or similar and send a link to the recruiter. Remember to check permissions! If we cannot access the file, we cannot review your code. Please don't store your solution in a public GitHub repository.
A good check before sending your solution is to unzip the Zip archive into a new folder and check that building and running the project works, using the steps you had written in the README.md of your project.
We will keep adding common questions and answers here as they come up.
Can I use frameworks like Next.js, or styling libraries like tailwindcss or material-ui? Should I avoid them?
You can use any libraries and frameworks that you are familiar with, and that you feel would best showcase your work.
For example, if you find that a manual implementation of input styles or component APIs is your strong suit, and want to show it, then you might decide to skip the UI libraries. Similarly, if you are more excited or familiar with other parts of the implementation, consider using libraries to avoid getting bogged down in details.
If you use a framework, please make extra sure that your submission's README file contains clear instructions for how to run it.