Hey Team Zania,
This is a frontend-focused assignment. Let me walk you through it.
This is what the app looks like:
The app is sprinkled with synthetic delays to show the loaders.
It’s a web app which contains cards.
These cards are displayed in a 3 column grid layout.
User can drag and drop these cards to change their position.
Every card has a thumbnail attached to it. Clicking on a card, opens up a modal in which the thumbnail image is shown in enlarged view. User can close this modal by either of the following ways:
- Click cross icon on the modal
- Click on the overlay outside the modal
- Press
Esc
The handling of modal closing is covered later in the README.
Since, this is a frontend-focused assignment, I have used msw
library to mock api calls to the server. I have used local-storage as the persistence layer.
When the user opens the app for the first time, the app makes a GET
request to mock endpoint to get the data to render the cards. The GET
handler checks if the data exists in local storage. If it does, it sends the data from the local storage, else, it send the default static data.
When user reorders the cards around, a POST
request is made to the mock endpoint with newly ordered data as a part of the request body. The POST
handler persists this in local storage. Hence, if user revisits the website again, he/she sees the latest version of the changes that have been saved.
It is important to note that not every reorder action gets saved. Instead, the client saves data every 5 seconds if there are any unsaved changes. And while saving, the client shows a saving indicator in the header and the time passed since the last save action. Details of this mechanism are explained in detail further down.
We add a global keydown listener on window
in the top component of the app. And in this keydown listener, we check for Esc
key press, as a result of which we control the closing of the modal using the state.
We maintain a few states in the component to achieve these, namely:
hasUnsavedChanges
- (boolean) - gets set to true after reordering. Gets set to false after saving the changesisSaving
- (boolean) - gets set to true while the changes are being saved. Gets set to false after the changes have been saved.lastSavedTime
- (number) - increments every 1000 ms. Resets to 0 after latest changes have been saved.
Along with the above mentioned states, we create 2 intervals
in useEffect
hooks such as the following:
- The
saveData
interval runs every 5000 ms and calls the save action ifhasUnsavedChanges
is true - The
lastSavedTime
interval runs every 1000 ms to increment the counter which denotes the time elapsed since last saved.
The following describes the API endpoints for performing add, remove, and update operations on cards resource.
POST /api/v1/data
This endpoint is used to add a new card.
-
Method:
POST
-
URL:
/api/v1/data
-
Headers:
Content-Type: application/json
Authorization: Bearer <token>
-
Body (JSON):
{ "title": "Card Title", "type": "Card type", "thumbnail": "url" }
-
Status:
201 Created
-
Body (JSON):
{ "id": 123, "title": "Card Title", "type": "Card type", "thumbnail": "url" "createdAt": "timestamp" }
DELETE /api/v1/data/:id
This endpoint is used to remove an existing card by its id
.
- Method:
DELETE
- URL:
/api/v1/data/{id}
- Headers:
Authorization: Bearer <token>
-
Status:
200 OK
(if the resource was successfully deleted) -
Body (JSON):
{ "message": "Card successfully deleted", "id": 123 }
-
Status:
404 Not Found
(if the resource with the given ID does not exist) -
Body (JSON):
{ "error": "Card not found" }
PUT /api/v1/data/:id
This endpoint is used to update an existing card's information.
-
Method:
PUT
-
URL:
/api/v1/data/{id}
-
Headers:
Content-Type: application/json
Authorization: Bearer <token>
-
Body (JSON):
{ "title": "Card Title", "type": "Card type", "thumbnail": "url" }
-
Status:
200 OK
-
Body (JSON):
{ "id": 123, "title": "Updated Card Title", "description": "Updated description of the card", "thumbnail": "updated url", "updatedAt": "timestamp" }
-
Status:
404 Not Found
(if the card with the given ID does not exist) -
Body (JSON):
{ "error": "card not found" }