/skybet-livefootball

Skybet senior engineer technical assessment

Primary LanguageJavaScript

Live Football Event App

Table of Contents

Choice of Tools

For this app, I originally chose redux due the constant live changing data, using the stream api in redux-toolkit, but felt redux was overkill and then switched to reatc contexts using a single page app.

I planned to use NextJS but decided to avoid to whole redux + SSR integration and stick with create-react-app, despite never ending up using Redux, oh well :).

I figured that while TailwindCSS or MUI would give me that easy design and native responsiveness, I decided to use styled-components for the flexibilty in custom components and lightweight touch.

Having used Websockets no where near as much as REST API's, I felt this would be trickiest thing for me to manage in a clean way, but sticking it in a context turned out pretty well, so I decided to combine this with the react-error-boundary package, which gave me a nice fallback mechanism on weird errors not just with the websocket, but with the app in general.

I did not want UX ruined by use of react-error-boundary each time a network issue happened with the websocket, so for the most part, a bit of standard checking for empty or undefined objects allows some conditional rendering for managing empty data, which you can see in some of the screen shots below.

For the testing framework, I stuck with Jest & react-testing-library, included are some snapchot tests and basic component tests.

How To Run

Starting the App

To start the app, first ensure you have the sbgtechtest/api docker container running with the relevant ports exposed.

You can run this using the following compose method:

version: '3'
services:
    api:
        image: sbgtechtest/api:latest
        ports:
            - '8888-8890:8888-8890'

Or the following docker run command:

docker run -it --rm --name sbg-tech-test-api -p 8888-8890:8888-8890 sbgtechtest/api:2.0.0

Ensure you are in the directory of the cloned web app:

cd <repo>

Install the relevant packages:

npm i

Then configure your environment variable by creating a .env file with the following content:

Be sure to replace the URL with the URL and port of your websocket

echo "REACT_APP_SOCKET_URL=<URL>" > .env

Then run the application with:

npm start

You can now view your app on port 3000 -> here.

Running the Tests

To enter the interactive test suite, run the following command:

npm t

UI Explained

Events Overview

The events overview screen loads as a custom table view like so:

full data

Events are filtered by displayable, and sorted into groups via the linkedEventTypeName property.

full data 2

By default, any selected events take visual priority in the top sticky panel.

Selected Events

As mentioned above, when an event is selected, it is presented to the top sticky panel.

When selected, it uses the already fetched event object from the getLiveEvents api. This way no new network calls are required until the user is sure they wish to fetch new detail.

Dynamic Loading

If the user wishes to see more information regarding an event, they can click the expand icon on the top right of the panel.

expanded markets

When exanded, the event markets will be displayed by first fetchig the market ID's via the getEvent api, and then querying the first 10 of these with the getMarket api.

These markets are horizontally scrollable and can be clicked.

If clicked, the outcomes related to that market will be loaded via the getOutcome api.

outcome loaded outcome loaded 2

Odds Display

The top right setting button contains the display settings, where the odds format can be changed.

odds format

Tasks Completed

Task One

✓ 1. Display Live Football event in an overview

✗ 2. Show primary markets for each event

✓ 3. Show outcomes for each market

✓ 4. Toggle odds display globally

Task Two

✓ 1. Browse full details of an event

✓ 2. Inform users of type, start time, and scores

✓ 3. Use the event payload to show all markets for available for the event

✓ 4. Show the outcomes for the first 10 markets only

✓ 5. Markets should be in displayOrder (ascending) and then name

✓ 6. Load outcomes for the market on demand

✓ 6. Use the displayable status to filter events

✓ 6. Use the displayable status to filter markets and outcomes

Task Three

✗ 1. Subscribe to events, market, and outcomes of interest

✓ 2. Group events by linkedEventTypeName, a missing value should cause the grouping to fallback to the typeName property

✗ 3. Add support for displaying markets with different types

✗ 4. Allow the user to click on outcomes to add them to a bet slip

✗ 5. Manage WebSocket subscriptions to allow the bet slip to listen for updates to selected outcomes and markets as and when they change, and invalidate selections as appropriate

Future Features

Primary Markets

At first, the primary markets were being loaded into the sticky panel instead of the full market set - this was a mistake on behalf when reading the task and upon correction, I ran out of time to add the primary market to each table cell via a toggle in the settings menu.

Contexts Optimisation

By design, contexts where choses to reduce the need for redux, and provide a simple way to pass event information between parallel components. However, contexts re-render all children upon updates to the value - meaning some performance hits.

For small apps such as this, its not much of an issue, but the impact of it can be reduced by using the useMemo hook on certain values.

Due to this, changing the odds format causes a re-render, and the state of the outcomes do not persist and a market has to be re-selected for the odds format change to take effect.

With more time, I would probably reload the currently selected outcomes in the useEffect hook to persist the UI state.

Subscriptions

Using the subcribe options on the websocket, I would have liked to have completed this with more time on my behalf.

I would have added a button in the sticky panel to subscribe to events and use basic states to update the UI.

Subscriptions

Again, lack of time on my behalf meant I did not attempt this part of the assesment.

Responsiveness

For time constraints, I did not attempt to make this application mobile responsive, but would have added simple media queries to the css if this was the case.

Logging

A package like loglevel would have been useful for production use and even development purposes.