Build a community-driven delivery app using Django, PostgreSQL & JavaScript - Part 2
Tags: webdev, django, javascript, tutorial
Published: 2022-02-01 12:03:19

---
date: "2020-12-09"
description: "A project-based microcourse on building applications with Django, PostgreSQL & JavaScript"
---

Welcome to the 2nd part of this series on using technology to foster sustainability in your community! In this tutorial, you'll continue building **Kartpool** — a community driven delivery platform for the ones who need it the most!

Be sure to read [Part 1](https://dev.to/egeninc/thinking-of-building-a-contact-tracing-application-what-to-do-instead-4pd3) of the tutorial series thoroughly and complete the exercises before you proceed with this tutorial!

## Table of Contents

1. [Problems in traditional delivery models](#problems-in-traditional-delivery-models)
2. [Local Search and Discovery platforms](#local-search-and-discovery-platforms)
3. [Kartpool app features](#features)
   * [Advantages](#advantages)
4. [Engineering the app](#engineering)
   * [Django](#django)
5. [Feature #1: Adding a wishlist](#feature-1-adding-a-wishlist)
6. [Feature #2: Listing nearby wishlists](#feature-2-listing-nearby-wishlists)
7. [Feature #3: Store navigation and info](#feature-3-store-navigation-and-info)
8. [Feature #4: Updating a wishlist](#feature-4-updating-a-wishlist)
9. [Next steps](#whats-next)
10. [Source code](#source-code)

---

**To recap, here's the list of features:**

**Feature #1:** A location-based store discovery service from where users could buy grocery and other essentials. You already built this in [Part 1](https://dev.to/egeninc/thinking-of-building-a-contact-tracing-application-what-to-do-instead-4pd3).

**Feature #2:** Users can select a store and add a **wishlist** of essentials that they intend to buy. This wishlist would be visible to other residents.

**Feature #3:** Any other resident can choose to accept this person's request and become a **wishmaster**. Then, they can purchase the items from the store on behalf of the requestor and deliver it to them.

**Feature #4:** Users can give **karma points** to runners via a recognition and appreciation system, for being good samaritans and helpful members of the community.

> Sounds similar to online grocery delivery platforms right? What's so different about this one, when so many others are already out there?

Fair question indeed! Let's look at some problems in the existing delivery business models, and what your platform will help solve:

## Problems in traditional delivery models

You may already be aware of several retail delivery platforms out there. [**Walmart**](https://www.walmart.com/grocery/), founded in 1962, operates a multinational chain of hypermarkets, grocery stores and discount department stores along with home delivery, and is arguably the largest retailer in the US in terms of revenue.

In June 2017, **Amazon** acquired **Whole Foods** for $13.7 Billion USD and amped up their retail delivery offerings as well. There's also [**Instacart**](https://www.instacart.com) — another grocery delivery and pick-up service in Canada and USA. Despite losing Whole Foods as a customer, Instacart holds a whopping [59%](https://progressivegrocer.com/how-instacart-has-outsmarted-amazon) of the delivery market. And **Kroger,** another American retail company, is the second largest retailer in the United States, just behind Walmart.

**These developments in the retail and delivery sectors have had various [impacts on local businesses](https://www.digitalcommerce360.com/2020/06/12/why-independent-grocers-should-avoid-instacart-and-similar-services/):**

* While these platforms offer convenience, there can be challenges in ensuring a consistently positive experience for customers who shop at local stores.
* All these platforms have also been at the center of a large number of controversies and lawsuits on issues involving low wages, poor working conditions, treatment of suppliers, and waste management. When local businesses are on-boarded onto these larger platforms, any bad press coverages and negative consequences tend to spill over into your store's reputation and reviews as well, for probably no fault of their own.
* Large companies slowly end up transforming into a monopoly — taking over smaller businesses and becoming the sole retailer and distribution chain in the area. Eventually your local businesses become very dependent on these platforms, which is a bad idea.
* There are labor costs, and service and delivery charges associated while utilizing larger monopolized platforms. Due to these, businesses would make lesser profits than they did if they were to sell the items directly. In-order to maintain their current profits or to grow, they would inevitably need to raise the prices of items — once again, bad news for both customers and grocers.

It seems like there are a lot of opportunities for innovation in the delivery model. Time for a fresh new approach!

## Local Search and Discovery Platforms

In the previous part, you learned to build a store discovery service that fetches all nearby stores in your neighborhood and displays them on a map.

Over the last decade, local search-and-discovery applications have been seeing a steady rise in usage and popularity. In 2009, **Foursquare** — a platform of nearly 50 million users — launched a platform that let users search for restaurants, nightlife spots, shops and other places in a location. In 2012, Facebook launched **Nearby**, Foursquare's competitor that pretty much did the same thing. And in 2017, **Google Maps** announced a similar feature that let users create lists of their favorite places to visit.

When you look at the user interfaces in several of these platforms, you observe a lot of similarities — especially on the layout on the home page that shows the places of interest:

![Screenshot of Foursquare's City guide](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g7xrhgdtrwupsowc4tv3.png)

Indeed, if you look at [Foursquare's city guide](https://foursquare.com/explore?mode=url&ne=41.998029%2C-87.505589&q=Shopping&sw=41.77464%2C-87.902126) — the user-interface consists of a small column on the left that displays a list of areas of interest, along with their locations to the right on a wide map. [Google Maps](https://www.google.com/maps/search/shops/@41.9698117,-87.6921986,15z) also has a similar interface:

![Screenshot of Google Maps](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tdg73i8any55ednj8vf5.png)

And here's AirBnb:

![Screenshot of AirBnb app](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8spmrmy110h14b9b6tzy.png)

Clicking on one of the items on the left makes the map fly to the associated location and zoom in to the marker icon. Sometimes, it also shows a popup on the marker with some useful information.

So needless to say, these user-interfaces are in vogue because its convenient to navigate through the list on the left, and look at their associated locations on the right on the map.

Deriving lessons from both the online grocery delivery models and local search and discovery applications, this platform that you'll build might just be what your community needs!

<figure>
<img
src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/phm5uget2xndawqxtx3b.png"
alt="Kartpool app screenshot">
<figcaption>
Kartpool app screenshot - what you'll build!
</figcaption>
</figure>

## Features

On the right-side you have a map where you'll type in the name of a location, which then displays stores in the area. You already did this in the previous tutorial.

The left column is a bit different — unlike Foursquare or Google Maps, you won't be displaying stores here, but **wishlists.** Clicking on one of the wishlist cards will make the map "fly" to the location of the store, where the items can be purchased from. These list of cards are arranged across 3 different tabs:

* The _1st tab_ displays all **nearby wishlists** created by users in the neighborhood. From here, you can accept a wishlist and it will be assigned to you to collect from a store nearby.
* Wishlists created by you will be visible on the _2nd tab_.
* The _3rd tab_ shows wishlists that you accept from the 1st tab. If you mark a wishlist as _accepted_, you become a _wishmaster_ for that user and it gets added to your trips. You can then make a trip to the store to purchase the items, and mark them as _fulfilled_ once your neighbor receives them.

To **create a wishlist**, you'll select a store icon from the map and add the items that you need by using the input-field on the bottom left.

> How are these features useful?

### Advantages

While most of the year 2020 was spent in lockdowns and quarantines, it also revealed many [heart-warming](https://nbc16.com/news/local/teens-organize-to-help-deliver-groceries-for-seniors-in-roseburg) [examples](https://shoppingangelsglobal.org/) of how powerful organized efforts and informed choices of individuals within a community can be.

![Examples of community spirit during a crisis](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m5knnvi50kt9bf5lybhf.png)

Providing a digital tool that leverages this power can create an immensely positive social and economic impact:

* You could foster virtually an endless shopping experience focused exclusively on local stores and businesses.
* User on-boarding becomes simpler.
* Enable massive reduction in delivery/service fees.
* The business model is socially driven and community-driven, which will foster a feeling of togetherness and readiness to help those in need.
* Not having to rely on middlemen and eliminating needless logistics and packaging would translate into drastic reductions in pollution and consumer waste, thus helping the planet stay green.

I hope you're excited. **Let's begin!**

## Engineering

### Django

A Django project consists of one or more [_applications_](https://docs.djangoproject.com/en/dev/ref/applications/#projects-and-applications). At the moment, your project root directory contains two applications — _stores_ and _home_. An application encapsulates a set of related features along with its own models, views, serializers and business logic.

It is useful to group your project logic in this way as it offers a lot of advantages:

* It gives you much better organization and structure of your project, and allows you to maintain separation of concerns.
* Flexible development — one developer could chose to work on features related to _stores,_ while another could chose to work on the _wishlists_ feature.
* Re-usability — you can easily reuse an app and migrate it to another project.

So in your current project, everything that is related to stores is in the _stores_ directory, and everything related to rendering the home page is in the _home_ directory. Similarly, you'll create a new Django app for the _wishlists_ feature. In your terminal type `python manage.py startapp wishlists`. This will create a new directory _wishlists_ with its structure similar to the _stores_ directory.

#### Wishlists

##### Step #1: Create the database model for storing wishlists

Open _wishlists/model.py_ and add the following code:

```python
from django.db import models  
from django.contrib.postgres.fields import ArrayField

# Create your models here.

WISHLIST_STATUSES = [  
    ("PENDING", "PENDING"),  
    ("ACCEPTED", "ACCEPTED"),  
    ("FULFILLED", "FULFILLED")  
]

class Wishlist(models.Model):  
    created_at = models.DateTimeField(auto_now_add=True)  
    buyer = models.CharField(max_length=100)  
    wishmaster = models.CharField(max_length=100)  
    items = ArrayField(models.CharField(max_length=100))  
    status = models.CharField(  
        choices=WISHLIST_STATUSES,  
        default="PENDING",  
        max_length=10  
    )  
    store = models.ForeignKey(  
        "stores.Store",  
        related_name="wishlists",  
        on_delete=models.SET_NULL,  
        null=True  
    )  

```

* Each wishlist can have one of three statuses, with the default `status` being `PENDING`at the time of creation.
* A `buyer`is that user who creates the wishlist, while the `wishmaster`is the user that makes the trip to the store and collects the items on behalf of the buyer.
* Each wishlist also has a foreign key that's associated with a valid store-id from the `stores` model that you implemented in the previous tutorial.

Now you'll run `python manage.py makemigrations` followed by `python manage.py migrate` . Django's ORM will create the table with the defined schema in the database!

##### Step #2: Add a serializer

In _wishlists/serializers.py_, add the following:

```python
from rest_framework import serializers  
from .models import Wishlist

class WishlistSerializer(serializers.ModelSerializer):  
    class Meta:  
        model = Wishlist  
        fields = [  
            'id', 'created_at', 'buyer', 'wishmaster', 'items',  
            'status', 'store'  
        ]  

```

##### Step #3: Define the View Class

Add the following in _wishlists/views.py_:

```python
from rest_framework import viewsets  
from rest_framework.response import Responsefrom .models import Wishlist  
from .serializers import WishlistSerializer

# Create your views here.
class WishlistView(viewsets.ModelViewSet):  
    queryset = Wishlist.objects.all()  
    serializer_class = WishlistSerializer  

```

You'll add the controller logic for creating, listing and updating wishlists within this class.

##### Step #4: Define the API service

Add the URL for your wishlists service in _kartpool/urls.py_:

```python
from wishlists import views as wishlists_viewsrouter.register(r'wishlists', wishlists_views.WishlistView, basename='wishlists')
```

Any request made to the endpoint _/wishlists/_ will execute the relevant controller within your `WishlistView` class.

Now you're ready to begin developing the wishlist feature for your app.

**Note:** Some helper methods have already been provided for you in the code, so that you may devote most of your time towards writing the core logic:
* **helpers.js**: Contains methods to render wishlists.
* **api.js**: Has functions for making network requests to the `/stores/` and `/wishlists/` endpoints.

## Feature #1: Adding a Wishlist

### Backend

Create a new file _services.py_ in the _wishlists_ directory.

Here, you'll write a function that takes in 3 arguments — a `buyer`, an `items` array, and a `store`. This function will create a new `Wishlist`, save it in the table, and return it.

```python
from django.core.exceptions import ObjectDoesNotExist  
from .models import Wishlist  
from stores.models import Store

def create_wishlist(buyer: str, items: list, store: Store):  
    wishlist = Wishlist(  
        buyer=buyer,  
        items=items,  
        store_id=store  
    )
    
    wishlist.save()
    
    return wishlist
```

Next, you'll import this function in _wishlist/views.py_ and add the controller logic in the `WishlistView`class.

```python
def create(self, request):  
    buyer = self.request.data.get('buyer')  
    items = self.request.data.get('items')  
    store = int(self.request.data.get('store'))
    
    wishlist = create_wishlist(buyer, items, store)  
    wishlist_data = WishlistSerializer(wishlist, many=False)
    
    return Response(wishlist_data.data)
```

When when someone makes a POST request to the _/wishlists/_ endpoint, it'll run the `create` method, extract the values for the `buyer`, `items` and the `store` id, and pass them to `create_wishlist` to create a new wishlist in the db.

### Front-end

In order to add a wishlist on the front-end, you'll need to click on a store marker on the map, and add items on the input-box`#wishlist-items` separated by commas. Then when you click on the "_Add a wishlist_" button, it will make a POSt request to _/wishlists/_ with the required data.

Open _wishlists.js_ and add the following:

```javascript
async function createWishlist() {  
    const wishlistInput = document.getElementById("wishlist-items").value.trim();  
    if (USERNAME && SELECTED_sTORE_ID && wishlistInput) {  
        addWishlist(USERNAME, wishlistInput.split(","), STORE);  
    }  
}
```

This function extracts the value from the input-field, converts it into an array, and passes these values to the method `addWishlist`, which will make the _POST_ request to add the wishlist into the database!

You'll now need to run this function upon clicking the _Add a wishlist_ button. Let's define the event handler for this in _index.js_:

```javascript
document.getElementById("add-wishlist").onclick = function(e) {  
    createWishlist();  
}
```

Run `python manage.py runserver` and head over to localhost:8000/?username=YOURNAME. Try adding your first wishlist and some sample wishlists for a few other users as well. You should be able to see them in your database.

![Screenshot of my psql terminal](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ue68jwpmdae7s7bwxl4t.png)


Next, you'll build the service to fetch nearby wishlists and display them in the UI.

## Feature #2: Listing nearby wishlists

### Backend

For retriving nearby wishlists, you'll define a function `get_wishlists` in _wishlists/services.py_, that accepts 3 arguments — a `latitude`, a `longitude`, and an optional `options`dictionary.

```python
from stores.services import get_nearby_stores_within

def get_wishlists(latitude: float, longitude: float, options: dict):  
    return Wishlist.objects.filter(  
        **options,  
        store__in=get_nearby_stores_within(  
            latitude=latitude,  
            longitude=longitude,  
            km=10,  
            limit=100  
        )  
    ).order_by(  
        'created_at'  
    )
```

Using the `get_nearby_stores_within` function that you wrote in Part 1, we can use the foreign key `store` and retrieve only those wishlists for which their associated stores are near the given pair of coordinates. That way in the UI, you'll never have a wishlist for which its store isn't visible on the map! _Makes sense?_

With the `get_wishlists` method, you can retrieve the required data for all 3 tabs for the left column using the `options` argument:

![Screenshot of the Wishlists tab](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ybmr4wdehzo7j8492c02.png)


* If you wish to return your own requests, you just need to retrieve those wishlists for which you're the _buyer_. So you'd pass in `{buyer=ashwin}` in the `options` argument.
* Likewise, for retrieving your trips, you'll just need to retrieve those wishlists for which you're the _wishmaster_, by providing `{wishmaster=ashwin}`.

Next, you'll import the above function and add the controller logic in _wishlists/views.py_:

```python
def list(self, request):
    latitude = self.request.query_params.get('lat')
    longitude = self.request.query_params.get('lng')
    options = {}
    for key in ('buyer', 'wishmaster'):
        value = self.request.query_params.get(key)
        if value:
            options[key] = value

    wishlist = get_wishlists(
        float(latitude),
        float(longitude),
        options
    )
    
    wishlist_data = WishlistSerializer(wishlist, many=True)
    return Response(wishlist_data.data)
```

### Frontend

Inside _wishlists.js_, you'll have 3 functions:

* `displayNearbyWishlists`: To show all nearby wishlists in the 1st tab.
* `displayMyRequests`: To show wishlists that you created in the 2nd tab.
* `displayMyTrips`: To show the wishlists that you accepted in the 3rd Tab.

```javascript
export async function displayNearbyWishlists(latitude, longitude) {
    try {
        const nearbyWishlists = await fetchNearbyWishlists(latitude, longitude);
        renderWishlists('nearby-wishlists', nearbyWishlists);
    } catch (error) {
        console.error(error);
    }
}
```

`fetchNearbyWishlists` makes an HTTP _GET_ request with the given pair of coordinates to the endpoint `/wishlists/`. Once the wishlists are fetched, you'll render it inside the tab section with the id `nearby-wishlists`, using the helper method `renderWishlists`.

Likewise, add the other two functions as well:

```javascript
export async function displayMyRequests(latitude, longitude) {  
    try {  
        const myWishlists = await fetchNearbyWishlists(latitude, longitude, {buyer: USERNAME});  
        renderWishlists('my-wishlists', myWishlists);  
    } catch(error) {  
        console.error(error);  
    }  
}export async function displayMyTrips(latitude, longitude) {  
    try {  
        const myTrips = await fetchNearbyWishlists(latitude, longitude, {wishmaster: USERNAME});  
        renderWishlists('my-trips', myTrips);  
    } catch(error) {  
        console.error(error);  
    }  
}
```

Refresh the page and try it out!

## Feature #3: Store Navigation and Info

Displaying wishlists is great, but how do you know which store to collect it from?

That's where the foreign key `store` on our `Store` model comes in handy, which is present in the JSON response when you make the request to fetch wishlists:

![Screenshot of Google Chrome network tab showing the request response JSON](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pilw1krxfgygr6rwvsx2.png)

On the DOM, each wishlist card has a data-attribute with the value of the associated store-id:

![Screenshot of Google Chrome Elements Inspector showing the attributes on the DOM element for a wishlist](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/02rupvnrdfbltd0bg9rg.png)


Inside _stores.js_, add a function `setStoreNavigation` that takes in 2 arguments — `map` and `storesGeoJson` . The function will loop over all the wishlist elements and add a _click_ event listener on all of them. Upon a click,

* Fetch the wishlist's associated store-id from the `data-store-id` attribute.
* Then using the store-id, find the relevant store's GeoJSON information (that also contains the latitude and longitude information) from `storesGeoJson`.
* Using the store's coordinates, you can now programmatically make _Mapbox_ zoom into the store's location.

```javascript
export function setStoreNavigation(map, storesGeoJson) {  
    const wishlistElements = document.getElementsByClassName('wishlist');
    
    for (let i=0; i<wishlistElements.length; i++) {  
        wishlistElements[i].onclick = (event) => {  
            const storeId = event.currentTarget.getAttribute('data-store-id');
            
            for (let point of storesGeoJson.features) {  
                if (storeId === point.properties.id) {  
                    flyToStore(map, point);  
                    displayStoreDetails(map, point);  
                    updateSelectedStore(storeId);  
                    break;  
                }  
            }  
        }  
    }  
}
```

Next, add the function `flyToStore` that zooms the map into a given pair of coordinates within _map.js_:

```javascript
export function flyToStore(map, point) {  
    map.flyTo({  
        center: point.geometry.coordinates,  
        zoom: 20  
    });  
}
```

Refresh the page, type in a location in which you created the wishlists in the previous step. Once the wishlists show up, click on one of them and watch the map zoom in to the store marker!

**But we're not nearly done yet.**

### Accessibility

In the previous tutorial, you added a `title` attribute to each marker that shows you the store information when you hover your cursor over a store icon. Though it gets the job done, it isn't nearly good in terms of **accessibility**.

Along with flying to the store location, what would really be good is to also show a popup on the marker. Lucky for you, _Mapbox_ has a neat little API that does the job!

Add the following function within _map.js_, and call it inside `setStoreNavigation`, right after you fly to the store:

```javascript
export function displayStoreDetails(map, point) {  
    const popUps = document.getElementsByClassName('mapboxgl-popup');  
    /** Check if there is already a popup on the map and if so, remove it */  
    if (popUps[0]){  
        popUps[0].remove();  
    } const popup = new mapboxgl.Popup({ closeOnClick: false })  
        .setLngLat(point.geometry.coordinates)  
        .setHTML(`  
            <details>  
                <summary><h2>${point.properties.name}</h2></summary>  
                <dl>  
                    <dt>Distance</dt>  
                    <dd>Approximately <strong>${point.properties.distance.toFixed(2)} km</strong> away</dd>
                    
                    <dt>Address</dt>  
                    <dd>${point.properties.address || 'N/A'}</dd>
                    
                    <dt>Phone</dt>  
                    <dd>${point.properties.phone || 'N/A'}</dd>
                    
                    <dt>Rating</dt>  
                    <dd>${point.properties.rating || 'N/A'}</dd>  
                </dl>  
            </details>  
        `)  
        .addTo(map);  
    return popup;  
}
```

Moving on to our final set of feature in this tutorial:

## Feature #4: Updating a wishlist

So far, you've managed to build stuff that adds quite some oomph factor on the UI. But your app isn't usable yet.

The real fun begins when a user can pick up one of the wishlists created by someone in the neighborhood. This is where the true value of the application lies — the community aspect that makes it possible for neighbors to help each other and be good samaritans during times of need!

When a wishlist item is first created on the platform, it isn't assigned to any _wishmaster_ yet, and the default `status` is set to `PENDING`. So this is how the card looks like on the UI:


![Screenshot of a wishlist item on the Kartpool app in the pending status](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hn4zopuiw3bb7x2ejg03.png)

**In-order to accept a wishlist:**

* Click on the little grey icon on the right-side of the card. This icon has a class value `accept`in the DOM.
* Upon clicking the icon, the app will make a `PATCH` request to the _/wishlists/_ endpoint.
* On the backend, update the wishlist item's `status` to `ACCEPTED`, and also update the `wishmaster` field to the current user.
* Finally in the UI, _accepted_ wishlists will be indicated by a little green shopper icon with an `accepted`class, like this:

![Screenshot of a wishlist item on the Kartpool app in the accepted status](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1owfhv866abzv0nzpzs3.png)

Once the items have been picked up by the _wishmaster_ and handed over to the _buyer_, they can then click on the green icon and mark it as `FULFILLED` with a similar `PATCH` request, after which it'll look like this:

![Screenshot of a wishlist item on the Kartpool app in the fulfilled status](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pv1eqbeqod8kmb1x300a.png)

### Backend

Create a function `update_wishlist` inside _wishlists/services.py_. This function will require 3 arguments — the primary key of the wishlist `pk`, the `wishmaster`, and `status`:

```python
def update_wishlist(pk: str, wishmaster: str=None, status: str="ACCEPTED"):  
    try:  
        wishlist = Wishlist.objects.get(pk=pk)
        
        wishlist.wishmaster = wishmaster  
        wishlist.status = status
        
        wishlist.save(update_fields=['wishmaster', 'status'])  
        return wishlist  
    except ObjectDoesNotExist:  
        print("Wishlist does not exist")
```

You'll call this method upon receiving a `PATCH` request to the /wishlists/ endpoint. For `PATCH` requests, the controller logic needs to be written within the `partial_update` inside your Class View.

Import the above method inside _wishlists/views.py_ and add the following code inside the `WishlistView` class:

```python
def partial_update(self, request, pk):  
    wishlist = update_wishlist(  
        pk=pk,  
        wishmaster=self.request.data.get('wishmaster'),  
        status=self.request.data.get('status')  
    )
    
    wishlist_data = WishlistSerializer(wishlist, many=False)  
    return Response(wishlist_data.data)
```

That's all you need for the backend!

### Frontend

First you'll register an event listener for any click events that occur on the _wishlists_ container elements. Add the following code inside _index.js_:

```javascript
const wishlists = document.getElementsByClassName('wishlists');  
    for (let i=0; i<wishlists.length; i++) {  
        wishlists[i].addEventListener('click', updateWishlistStatus);  
    }  
}
```

This is how the markup of the card looks like:

![Screenshot of Google Chrome Elements Inspector that shows the markup structure of a wishlist card in the DOM](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ycmknytqgsob03p5h580.png)

In _wishlists.js_, you'll define a function `updateWishlistStatus` that runs whenever a click occurs anywhere within the three wishlist container elements. Within this function:

1. First check whether the click occurred on any of the icons on the right-side of the card. If it did, then
2. Grab the wishlist's primary key (id) from the `data-id` field.
3. Determine the right `status` value to be set, using the class-name of the icon.
4. Finally call the `updateWishlist` function from _api.js_ to make the `PATCH` request and update the wishlist!

```javascript
export async function updateWishlistStatus(event) {  
    switch(event.target.className) {  
        case 'accept':  
            event.preventDefault();  
            updateWishlist(  
                event.target.getAttribute('data-id'),  
                {  
                    status: 'ACCEPTED',  
                    wishmaster: USERNAME  
                }  
            ).then((result) => {  
                updateWishlistNode(event.target, 'ACCEPTED');  
            }).catch(error => console.error(error));
            
            break;  
        case 'accepted':  
            event.preventDefault();  
            updateWishlist(  
                event.target.getAttribute('data-id'),  
                {  
                    status: 'FULFILLED',  
                    wishmaster: USERNAME  
                }  
            ).then((result) => {  
                updateWishlistNode(event.target, 'FULFILLED');  
            }).catch(error => console.error(error));
            
            break;  
    }  
}
```

**And you're done. Refresh the page, play around with your app and watch it in action!**

## What's next?

Congrats on successfully building a Minimum Viable Product. As an exercise, I leave it to you implement the _karma points_ feature. Don't hesitate to leave a comment if you need any help!

Once you finish developing all the essential features, it's time for you to speak to your neighbors, demonstrate to them the usefulness of this service, and get some real active users on the platform. Alone, you can do little — but together, you can do so much more!

> Technology is best when it brings people together — [Matt Mullenweg](https://ma.tt/)

Speaking to members within your community will help you receive valuable feedback for your platform. Here are some nice-to-have features that'll make your app even more powerful:

* Add the ability for users to sign-up and create accounts in the platform.
* A community hall-of-fame page that displays _Samaritans of the month._
* Show a store's inventory so that users may know beforehand whether they can buy a certain item from a store. You will need to connect with your local businesses for this one!
* Continuously refreshing the page after adding or updating a wishlist is annoying. Why don't you try adding [web-sockets](https://channels.readthedocs.io/en/stable/)?
* Implement payment integration so that users can make payments directly to the store from within the app.
* Build a Progressive Web App or a native mobile application UI.

## Conclusion

Crisis are part of life. Everybody has to face them, and it doesn't make any difference what the crisis is. Most hardships are opportunities to either advance or stay where you are.

That being said —

> You shouldn't necessarily have to wait until you're in a crisis to come up with a crisis plan.

The utility of Kartpool will extend beyond emergencies. With big players and large retail chains already eating up local businesses and killing most competition, platforms like these will give the small guys a fighting chance. Your local economy and community will thrive, adapt and grow together in the ever-changing e-commerce landscape and become sustainable!

I leave you with this:

> Thou shalt treat thy neighbor as thyself

### Source code

Here's the [github repository](https://github.com/booleanhunter-tech-blog/kartpool) for reference. In case you have any questions regarding the tutorial, please leave a comment below!