Mega Mart
Tags: devchallenge, wixstudiochallenge, webdev, javascript
Published: 2024-07-15 05:57:58

*This is a submission for the [Wix Studio Challenge](https://dev.to/challenges/wix).*

## Background
Why follow outdated methods when **AI** is transforming the business landscape? I created this ecommerce because nowadays AI is the trend for companies. Mega Mart is a platform that not only offers a wide variety of products but also enhances the UI/UX through advanced features made in **Wix Studio**.

---

## What I Built
I developed an **advanced AI search options** to solve the issue of inefficient product discovery, having the goal of creating a more **intuitive and enjoyable** shopping experience for users.

Have you ever seen someone wearing a t-shirt you like but didn’t want to ask them the brand? With Mega Market, simply **take a photo and upload it to our platform**; we’ll search for it in our product database.

Why go to the mall when you can search for the product you want just as you would ask to a store employee? Simply **write your query in natural language**, and we’ll show you examples of the products you're looking for. For example, try typing `I need a winter jacket for women` or `I would like a black smartphone with camera`.

The best part is that **the site doesn't requires for a Wix subscription to finish the checkout process** because it's entirely built using Wix elements and Velo APIs.

---

## How to buy at Mega Market
### Create an account (or not...)
At Mega Mart, you have the option to **purchase products with or without an account**.
Creating an account offers several **benefits**:
- During checkout, your **personal information and previous addresses are automatically filled in**, saving you time.
- Additionally, you receive **instant cashback** that can be used for future purchases.
- Love a product but don’t want to buy it yet? Your account includes a **wishlist** feature, allowing you to save products for later.

### Search product items
As mentioned earlier, you can search for products by **uploading images** from your device, **using AI**, or browsing all items to select the one you like the most.

### Verify Your Cart
Before checking out, ensure you have added all the desired products to your cart. **Adjust the quantity** of items if needed or **delete** any mistakes. Additionally, review your total and the estimated delivery date.

### Checkout
If you are logged in, your personal information will be **automatically** filled out. You can choose one of your **previously saved addresses** or enter a new one, which will be added to your account for future orders.
Enter your credit or debit card information, then click the final button to complete the checkout.

### Your order is completed!
You will be redirected to the order's page, where you can view the details of your purchase and its **status**: whether it is being processed, in transit for delivery, or already delivered. You will also receive an email confirmation of your order.

---

## Mega Mart Benefits
### Passwordless
Who wants to remember passwords these days? At Mega Mart, you don't have to worry about them; each time you want to log in to your account, you will receive a **one-time digit code** via email, SMS, or call.
### Cashback
Who doesn't love getting rewarded for shopping? Each time you purchase at Mega Mart, you will receive **1% cashback**, which you can use for paying future orders.
### Wishlist
Ever found a product you love but aren’t ready to buy yet? At Mega Mart, you can **save it to your wishlist** for a future purchase.
### Order status
You don't need to switch between pages to see the status of your shipment. From the order's page, you can easily check whether it's in process, in transit, or already delivered.
### Full responsive
Ever wonder if a website can be just as smooth on your computer, tablet, or phone? At Mega Mart, our site is fully responsive across all devices.
### Save your addresses
Curious about how convenient shopping can be? At Mega Mart, your addresses are saved for future purchases, and you can manage them easily from your account page.
### No account needed
While many ecommerce sites require account creation to shop, at Mega Mart, you can purchase as a guest. However, remember all the benefits you can have as a Mega Mart's frequent customer.

---

## Development Journey
Utilizing Wix Studio has helped me accelerating frontend development, significantly reducing time while ensuring a fully responsive design across all devices. JavaScript plays an important role in enhancing user interaction with Wix Studio elements, enabling dynamic features seamlessly. Moreover, our site employs robust security measures, including database's, web methods', and router's security, to safeguard user data and ensure a secure browsing experience.

> Before diving into how I created Mega Mart, it's important to say that I did not install any apps from the Wix Market. While I used `Wix Stores` for saving products to the cart and then retrived them in some pages, I opted to build the product pages, cart, and checkout pages entirely from scratch using Wix Elements.

> **Disclaimer**
> Since this is a demo for the contest, I have only saved 29 products in the database. To get the most out of the AI image product recognition and AI search methods, please first familiarize yourself with the available products in the store. For example, searching for a printer or a hammer might not yield results, as these items are not in the demo database. However, if this project were scaled up for large companies like Home Depot or Walmart, which have thousands of products in their databases, the AI functionalities would perform comprehensive searches and provide a wider range of results.

---

## Try it out!
Curious to see what Mega Mart can bring to your shopping experience? Click to explore and discover the possibilities.
[Lets go!](https://deapco.wixstudio.io/ecommerce)

---

## How I built it
Developing this ecommerce site wasn't easy, especially given the tight timeline, but I'm proud to have built it with all its functionality within just 2 weeks.

### Home
The first page I developed was the home page, where I aimed to incorporate features commonly found in other ecommerce sites such as showcasing best sellers, new arrivals, and highlighting the benefits of shopping at the store.
Below is an example of how I retrieved data for best sellers and newest products in the code.
```javascript
import wixData from 'wix-data';
import wixWindowFrontend from 'wix-window-frontend';
import wixLocationFrontend from 'wix-location-frontend';

const limit = wixWindowFrontend.formFactor === "Mobile" ? 6 : 5

const bestSellers = (await wixData.query("Products").descending("rating").limit(limit).find()).items
$w('#repeater2').data = bestSellers

$w('#repeater2').onItemReady(($item, itemData, index)=>{
    $item('#imageX9').src = itemData.imageUrl[0].src
    $item('#imageX9').alt = itemData.name

    $item('#text12').text = itemData.name
    $item('#text13').text = "$" + itemData.price

    $item('#box15').onClick(() => {
        wixLocationFrontend.to(`/product/${itemData._id}`)
    })
})

const newProucts = (await wixData.query("Products").descending("_createdDate").limit(limit).find()).items
$w('#repeater3').data = newProucts

$w('#repeater3').onItemReady(($item, itemData, index)=>{
    $item('#imageX10').src = itemData.imageUrl[0].src
    $item('#imageX10').alt = itemData.name

    $item('#text15').text = itemData.name
    $item('#text14').text = "$" + itemData.price

    $item('#box19').onClick(() => {
        wixLocationFrontend.to(`/product/${itemData._id}`)
    })
})
```
As you can see, the code also includes logic to detect whether the user is browsing on a mobile device or not. In mobile view, the repeaters are displayed in two columns, while in tablet and computer modes, they are arranged in a single row with five columns.

### Login & Register
I believe that the register and login pages should have an intuitive UI/UX interface. If either processes are difficult or frustrating for users, they may leave the site, resulting in a lost sale.
To prevent this, Mega Mart uses a passwordless system. Each time you want to log in, a one-time code will be sent via SMS, call, or email, eliminating the need to remember multiple passwords.

#### Register

```javascript
import { verify, sendVerification } from "backend/twilio.web"
import { register } from "backend/members.web"

$w('#button1').onClick(async () => {
    $w('#imageX9').show()
    $w('#button1').disable()
    if ($w('#box37').isVisible) {
        if ($w('#input5').valid) {

            $w('#text35').collapse()

            const verifyEmailResponse = await verify($w('#input3').value, $w('#input5').value)
            const verifyPhoneResponse = await verify($w('#input4').value, $w('#input5').value)

            if (verifyEmailResponse || verifyPhoneResponse) {
                try {
                    const sessionToken = await register($w('#input1').value, $w('#input2').value, $w('#input3').value, $w('#input4').value)
                    await authentication.applySessionToken(sessionToken)
                } catch (error) {
                    $w('#text35').text = "Error registering: " + new Error(error).message
                    $w('#text35').expand()
                }

            } else {
                $w('#text35').text = "Wrong verification code."
                $w('#text35').expand()
            }
        }
    } else {
        const sendEmailResponse = await sendVerification("email", $w('#input3').value)
        const sendSMSResponse = await sendVerification("sms", $w('#input4').value)
        if (sendEmailResponse && sendSMSResponse) {
            $w('#input1,#input2,#box36').collapse()
            $w('#box37').expand()
            $w('#button1').label = "Register"
            $w('#text37').hide()
        }
    }
    $w('#imageX9').hide()
    $w('#button1').enable()
})
```
This code handles the registration process when the button is clicked. If the `verification code input box` is visible and the verification code input is valid, it verifies the user's email or phone number with the provided verification code. If either verification is successful, it attempts to register the user and then apply a session token for authentication. If the verification fails, an error message is displayed.
If the box is not visible, it sends verification codes to the user's email and phone, then updates the UI to allow the user to complete the registration.

#### Login

**Sending verification code to user**
```javascript
import { sendVerification } from "backend/twilio.web"
import { verifyEmailOrPhoneNumber} from "backend/members.web"

$w('#button2,#button3').onClick(async (rest) => {
    if ($w('#input7').valid || $w('#input8').valid) {
        $w('#imageX10').show()
        $w('#text34').collapse()
        $w('#button2,#button3').disable()

        let toSearch = $w('#input7').isVisible ? $w('#input7').value : $w('#input8').value
        if (!(await verifyEmailOrPhoneNumber(toSearch))) {
            $w('#text34').text = "There isn't an account with that email or phone number."
            $w('#text34').expand()
        } else {
            switch (rest.target) {
            case $w('#button2'):
                let response = false
                if ($w('#input7').isVisible) {
                    response = await sendVerification("email", $w('#input7').value)
                    verificationTypeAtLogin = "email"
                    $w('#text31').text = "Please check you email. It may be at junk mails."
                } else {
                    response = await sendVerification("sms", $w('#input8').value)
                    verificationTypeAtLogin = "sms"
                    $w('#text31').text = "Please check you SMS."
                }

                if (response) {
                    $w('#text33,#box41,#box47').collapse()
                    $w('#box46,#box48').expand()
                    $w('#text36').hide()
                } else {
                    $w('#text34').text = "We couldn't send the verification code. Try using another method."
                    $w('#text34').expand()
                }
                break;
            case $w('#button3'):
                verificationTypeAtLogin = "call"
                $w('#text31').text = "You will receive a phone call in a few moments."

                if (await sendVerification("call", $w('#input8').value)) {
                    $w('#text33,#box41,#box47').collapse()
                    $w('#box46,#box48').expand()
                    $w('#text36').hide()
                } else {
                    $w('#text34').text = "We couldn't send the verification code. Try using another method."
                    $w('#text34').expand()
                }
                break;
            }
        }
        $w('#imageX10').hide()
        $w('#button2,#button3').enable()
    }
})
```
It first verifies the existence of the entered email or phone number.
Depending on which button is clicked, it sends a verification code via email, SMS, or call. Then it expands `verification code input` for the user to write the received code.
If verification fails, an appropriate error message is displayed. Finally, the loading image is hidden, and the buttons are re-enabled.

**Verifying verification code**
```javascript
import { verify } from "backend/twilio.web"
import { login } from "backend/members.web"

if ($w('#input9').valid) {
    $w('#imageX11').show()
    $w('#text34').collapse()
    $w('#button4').disable()

    let verifyResponse = false
    if (verificationTypeAtLogin === "email") verifyResponse = await verify($w('#input7').value, $w('#input9').value)
    else verifyResponse = await verify($w('#input8').value, $w('#input9').value)

    if (verifyResponse) {
        try {
            const sessionToken = await login(verificationTypeAtLogin === "email" ? $w('#input7').value : "", verificationTypeAtLogin !== "email" ? $w('#input8').value : "")
            await authentication.applySessionToken(sessionToken)
        } catch (error) {
            $w('#text34').text = "Error registering: " + new Error(error).message
            $w('#text34').expand()
            $w('#imageX11').hide()
            $w('#button4').enable()
        }

    } else {
        $w('#text34').text = "Wrong verification code."
        $w('#text34').expand()
        $w('#imageX11').hide()
        $w('#button4').enable()
    }

}
```
It verifies the provided code based on the verification type (email or phone). If verification is successful, it attempts to log the user in and apply the session token for authentication.

#### Redirecting the user
Whether the user is logging in or registering, after a successful process, they will be redirected to the last page they visited.
```javascript
import { authentication } from "wix-members"
import wixLocationFrontend from 'wix-location-frontend'

authentication.onLogin(() => {
    wixLocationFrontend.to(wixLocationFrontend.query.redirect)
})
```

### Search bar window
In order to allow users to search for products from any page, I added the search bar inside a lightbox.

#### Search type
First check what search type the user selected.
```javascript
import wixWindowFrontend from 'wix-window-frontend';

$w('#imageX7').hide()
$w('#button2,#input1,#searchByImage,#searchByAI').enable()
const type = wixWindowFrontend.lightbox.getContext()

let instructions = ""
switch (type) {
case "text":
    $w('#searchByTextMobile').show("fade", { duration: 500 })
    instructions = "Enter keywords or product names into the search bar."
    break;
case "image":
    $w('#searchByImage').show("fade", { duration: 500 })
    instructions = "Upload an image of the product you are looking for."
    break;
case "AI":
    $w('#searchByAI').show("fade", { duration: 500 })
    instructions = "Type your query in natural language (e.g., 'I need a winter jacket for women')."
    break;
}

$w('#text21').text = `Search by ${type}`
$w('#text22').text = instructions
```

#### Handle search type

**By image**
```javascript
import { getImageLabels } from "backend/googleVision.web"

$w('#searchByImage').onChange(async () => {
    $w('#searchByImage').disable()
    $w('#imageX7').show()
    const file = await $w('#searchByImage').uploadFiles()
    const labels = await getImageLabels(file[0].fileUrl)

    sendTo("tags", labels)
})
```
First, the image is uploaded to the Media Manager to obtain its URL. Then, the URL is sent to Google Vision to identify tags related to the image.

**By AI**
```javascript
$w('#searchByAI').onKeyPress(async (rest) => {
    if ($w('#searchByAI').valid && rest.key === "Enter") {
        $w('#searchByAI').disable()
        $w('#imageX7').show()

        const labels = await getTextLabels($w('#searchByAI').value)
        sendTo("tags", labels)
    }
})
```
If the user presses `Enter`, the input text is tokenized to extract nouns.

#### Search redirection
Once one of the methods is completed, the user is redirected to the `search router page` with the queries included in the URL.
```javascript
import wixLocationFrontend from 'wix-location-frontend';

function sendTo(type = "", value = []) {
    wixLocationFrontend.to(`/search?type=${type}&value=${JSON.stringify(value)}`)
}
```

### Search page
This is a `Wix Router page` that helped me show the different types of search, and also the wishlist of the products the user saved.

#### Repeater display
```javascript
import wixWindowFrontend from 'wix-window-frontend';
import { cart } from "wix-stores-frontend";

let originalItem = wixWindowFrontend.getRouterData()
let items = wixWindowFrontend.getRouterData()
initRepeater(items)

$w('#repeater1').onItemReady(async ($item, itemData, index) => {
    $item('#imageX9').src = itemData.imageUrl[0].src
    $item('#text12').text = itemData.name
    $item('#text13').text = "$" + itemData.price
    $item('#ratingsDisplay1').rating = itemData.rating

    $item('#button1').onClick(async () => {
        const product = [{
            productId: itemData._id,
            quantity: 1,
        }]

        await cart.addProducts(product)
        await showAlert("add")
    })

    $item('#box38').onClick(() => {
        wixLocationFrontend.to(`/product/${itemData._id}`)
    })

    if (authentication.loggedIn()) {
        const userID = await currentMember.getMember({ fieldsets: ["FULL"] })
        const item = await wixData.query("Wishlist").eq("product", itemData._id).eq("_owner", userID._id).find()
        if (item.totalCount === 0) $item('#button2').expand()
        else {
            itemData.wishList = item.items[0]._id
            $item('#button3').expand()
        }
    } else $item('#button2').expand()

    $item('#button2').onClick(async () => {
        if (authentication.loggedIn()) {
            const itemWishlist = await wixData.insert("Wishlist", { product: itemData._id })
            itemData.wishList = itemWishlist._id
            await showAlert("save")
            $item('#button2').collapse().then(() => {
                $item('#button3').expand()
            })
        } else wixWindowFrontend.openLightbox("loginToWishlist")
    })

    $item('#button3').onClick(async () => {
        await wixData.remove("Wishlist", itemData.wishList)
        await showAlert("remove")
        $item('#button3').collapse().then(() => {
            $item('#button2').expand()
        })
    })
})

function initRepeater(items = [], changeFilter = true) {
    $w('#repeater1').data = items

    if ($w('#repeater1').data.length === 0) {
        if (wixLocationFrontend.query.type === "wishlist") {
            $w('#text27').text = "You haven't add any item to your wishlist. Add your first product!"
        } else {
            $w('#text27').text = "Try searching with another method or include more details if you are using AI."
        }
        $w('#section5').collapse()
        $w('#section6').expand()
    } else {
        const htmlLinks = items[0].tags.slice(0, 5).map(word => `<span style="font-size: 10px;"><a href="/search?type=tags&value=[%22${word}%22]">${word}</a></span>`).join(' - ');
        $w('#text22').html = htmlLinks
        $w('#section6').collapse()
        $w('#section5').expand()

        $w('#switch1').checked = false

        if (changeFilter) {
            const maxPrice = sortByPrice(items, "desc")[0]

            $w('#slider1').max = maxPrice.price
            $w('#slider2').max = maxPrice.price

            $w('#slider2').value = maxPrice.price
        }

    }

}
```
As you can see, this includes the `import { cart } from "wix-stores-frontend"` statement, which allows the product to be added to the cart when the user clicks the button inside the repeater.

#### Sort products
```javascript
$w('#switch1').onChange(() => {
    $w('#repeater1').data = []

    items = sortByPrice(items, $w('#switch1').checked ? "desc" : "asc")
    $w('#repeater1').data = items
})

function sortByPrice(products = [{}], order = 'asc') {
    return products.sort((a, b) => {
        if (order === 'asc') {
            return a.price - b.price;
        } else if (order === 'desc') {
            return b.price - a.price;
        } else {
            throw new Error("Order must be either 'asc' or 'desc'");
        }
    });
}
```

#### Filter products
```javascript
$w('#slider1,#slider2').onChange(() => {
    items = filterByPriceRange(originalItem, $w('#slider1').value, $w('#slider2').value)
    initRepeater(items, false)
})

function filterByPriceRange(products, minPrice, maxPrice) {
    return products.filter(product => product.price >= minPrice && product.price <= maxPrice);
}
```

#### Wixlocation.onChange()
Since searching for new products redirects the user to the same page `/search` and only changes the URL queries, I needed to handle this with `wixLocationFrontend.onChange()` to update the repeater items.

### Product page
This is also a router page `/product/${productID}` that fetches product information from the backend and displays it using `wixWindowFrontend.getRouterData()`.

```javascript
import wixWindowFrontend from 'wix-window-frontend';

const product = wixWindowFrontend.getRouterData()

$w('#gallery1').items = product.imageUrl
$w('#features').text = product.features
$w('#title').text = product.name
$w('#brand').text = product.brand
$w('#color').text = product.color
$w('#price').text = String(product.price)
$w('#description').text = product.description
$w('#ratings').rating = product.rating
$w('#stock').text = `${product.stock} available`
```
Since you can add the product to your cart from here, with the option to set a quantity, I used the following code to achieve this.
```javascript
import { cart } from "wix-stores-frontend"

$w('#addCart').onClick(async () => {
    const productToSave = [{
        productId: product._id,
        quantity: Number($w('#inputQuantity').value),
    }]

    await cart.addProducts(productToSave)
    await showAlert("add")

})

$w('#quantityLess,#quantityMore').onClick((rest) => {
    let value = Number($w('#inputQuantity').value)

    switch (rest.target) {
    case $w('#quantityLess'):
        if (value > 1) value--
        break;
    case $w('#quantityMore'):
        value++
        break;
    }

    $w('#inputQuantity').value = String(value)
})
```
Also having the option to add the product to the wishlist or delete it.
```javascript
$w('#addWishList').onClick(async () => {
    if (authentication.loggedIn()) {
        await wixData.insert("Wishlist", { product: product._id })
        await showAlert("save")
        $w('#addWishList').collapse().then(() => {
            $w('#deleteWishlist').expand()
        })
    } else wixWindowFrontend.openLightbox("loginToWishlist")
})

$w('#deleteWishlist').onClick(async () => {
    await wixData.remove("Wishlist", product.wishList)
    await showAlert("remove")

    $w('#deleteWishlist').collapse().then(() => {
        $w('#addWishList').expand()
    })
})
```

### Cart page
Need to review your cart? This page allows you to check if you have added the desired quantities of the products you want to buy and also lets you delete any items if needed.
```javascript
import { cart } from "wix-stores-frontend"

$w('#repeater1').onItemReady(($item, itemData, index) => {
$item('#quantityLess,#quantityMore').onClick(async (rest) => {
        $item('#quantityLess,#quantityMore,#button3').disable()
        let value = Number($item('#inputQuantity').value)

        switch (rest.target) {
        case $item('#quantityLess'):
            if (value > 1) value--
            break;
        case $item('#quantityMore'):
            value++
            break;
        }

        $item('#inputQuantity').value = String(value)
        $item('#text22').text = `$${(itemData.price * value).toFixed(2)}`

        const updadedCart = await cart.updateLineItemQuantity(Number(itemData._id), value)
        $item('#quantityLess,#quantityMore,#button3').enable()

        $w('#text25').text = `Total: $${updadedCart.totals.total.toFixed(2)}`
    })

    $item('#button3').onClick(async () => {
        $item('#quantityLess,#quantityMore,#button3').disable()
        await cart.removeProduct(Number(itemData._id))
        await initCart()
    })
})
```
Clicking the buttons `#quantityLess` and `#quantityMore` triggers an event to edit the product quantity. Clicking the `#button3` removes the product from the cart.

### Checkout
This is the final step for purchasing at Mega Market.
If the user is a Site Member, it retrieves their saved addresses and accumulated cashback from their account.
```javascript
import { authentication, currentMember } from "wix-members-frontend"

if (authentication.loggedIn()) {
    $w('#text25').collapse()

    const user = await currentMember.getMember({ fieldsets: ["FULL"] })

    $w('#input1').value = user.contactDetails.firstName
    $w('#input2').value = user.contactDetails.lastName
    $w('#input3').value = user.loginEmail
    $w('#input4').value = user.contactDetails.phones[0]

    const addresses = await getUserAdderess(user._id)

    if (addresses.length === 0) {
        $w('#dropdown1').options = [{ value: "No addresses founded", label: "No addresses founded" }]
        $w('#dropdown1').value = "No addresses founded"
        $w('#dropdown1').disable()
    } else {
        const addressesOptions = addresses.map(address => ({
            label: `${address.addressLine} ${address.state}, ${address.country}`,
            value: JSON.stringify(address)
        }));

        $w('#dropdown1').options = addressesOptions
    }

    const totalCashback = await getTotalCashback(user._id)

    if (totalCashback === 0) {
        $w('#switch1').disable()
        $w('#switch1').checked = false
        $w('#text28').text = `You will earn $${Number(currentCart.totals.total * 0.01).toFixed(2)} in cashback with this order.`
    } else {
        cashback = totalCashback > currentCart.totals.total ? currentCart.totals.total : totalCashback;
        $w('#switch1').enable()
        $w('#text28').text = `Use my cashback: $${Number(cashback).toFixed(2)}`
    }

}
```
It also validates that the card number of the user is a Master Card, Visa or AMEX.
```javascript
$w('#input10').onCustomValidation((value, reject) => {
        const cardsInfo = {
            "Unknown": {
                "image": "https://static.wixstatic.com/media/4c0a11_600a3b93f607463296a7c9149268800e~mv2.gif",
                "regex": /^\d+$/,
                "length": 16
            },
            "Master Credit": {
                "image": "https://www.mercadopago.com/org-img/MP3/API/logos/master.gif",
                "regex": ,
                "length": 16
            },
            "Visa Credit": {
                "image": "https://www.mercadopago.com/org-img/MP3/API/logos/visa.gif",
                "regex": ,
                "length": 16
            },
            "AMEX": {
                "image": "https://www.mercadopago.com/org-img/MP3/API/logos/amex.gif",
                "regex": ,
                "length": 15
            },
            "Visa Debit": {
                "image": "https://www.mercadopago.com/org-img/MP3/API/logos/debvisa.gif",
                "regex": ,
                "length": 16
            },
            "Master Debit": {
                "image": "https://www.mercadopago.com/org-img/MP3/API/logos/debmaster.gif",
                "regex": ,
                "length": 16
            }
        }

        const cardCategory = identifyCardCategory(value)
        $w('#imageX8').src = cardsInfo[cardCategory].image
        $w('#input10').maxLength = cardsInfo[cardCategory].length
        if (!cardsInfo[cardCategory].regex.test(value)) {
            reject("Invalid card number")
        }
    })

function identifyCardCategory(cardNumber) {
    const patterns = {
        masterCredit: ,
        visaCredit: ,
        amex: ,
        visaDebit: ,
        masterDebit: 
    };

    if (patterns.masterCredit.test(cardNumber)) return "Master Credit";
    if (patterns.visaCredit.test(cardNumber)) return "Visa Credit";
    if (patterns.amex.test(cardNumber)) return "AMEX";
    if (patterns.visaDebit.test(cardNumber)) return "Visa Debit";
    if (patterns.masterDebit.test(cardNumber)) return "Master Debit";

    return "Unknown";
}
```
After the user inputs all the required information, it's time to process the order.
```javascript
$w('#button2').onClick(async () => {
    if ($w('#input1').valid && $w('#input2').valid && $w('#input3').valid && $w('#input4').valid && $w('#input5').valid && $w('#input6').valid && $w('#input7').valid && $w('#input8').valid && $w('#input9').valid && $w('#input10').valid && $w('#dropdown2').valid && $w('#dropdown3').valid && $w('#dropdown4').valid) {
        $w('TextInput,Dropdown').disable()
        $w('#box53').show()

        const productArray = currentCart.lineItems.map(item => ({
            quantity: item.quantity,
            name: item.name,
            priceData: {
                price: item.price,
            },
            mediaItem: {
                src: item.mediaItem.src
            }
        }));

        const insertedItem = await wixData.insert("Orders", {
            lineItems: productArray,
            totals: {
                subtotal: subTotal,
                total: total,
            },
            shippingInfo: {
                shipmentDetails: {
                    address: {
                        city: $w('#dropdown4').value,
                        country: $w('#dropdown2').value,
                        addressLine: $w('#input5').value,
                        postalCode: $w('#input6').value,
                        subdivision: $w('#dropdown3').value,
                    }
                }
            },
            billingInfo: {
                address: {
                    city: $w('#dropdown4').value,
                    country: $w('#dropdown2').value,
                    addressLine: $w('#input5').value,
                    postalCode: $w('#input6').value,
                    subdivision: $w('#dropdown3').value,
                },
                lastName: $w('#input2').value,
                firstName: $w('#input1').value,
                email: $w('#input3').value,
                phone: $w('#input4').value
            },
            cashback: $w('#switch1').checked
        })

        for (let lineItem of currentCart.lineItems) {
            await cart.removeProduct(lineItem.id)
        }

        wixLocationFrontend.to(`/order/${insertedItem._id}`)
    }
})
```
This `wixData.insert()` triggers a data hook that creates the order using `wixStoresBackend.createOrder()` which I'll talk of it later.

### Order page
This page displays the order information passed though the `wixWindowFrontend.getRouterData()`.
```javascript
import wixWindowFrontend from 'wix-window-frontend';

const orderInfo = wixWindowFrontend.getRouterData()
const contact = orderInfo.billingInfo
const shipping = contact.address

$w('#text21').text = `Order #${orderInfo.orderNumber}`
$w('#text48').text = `Amount paid: $${orderInfo.totals.total}`
$w('#text47').text = `Subtotal: $${Number(orderInfo.totals.subtotal).toFixed(2)} | Discounts: $${Number(orderInfo.totals.subtotal - orderInfo.totals.total).toFixed(2)}`

$w('#text26').text = contact.firstName
$w('#text27').text = contact.lastName
$w('#text28').text = contact.phone
$w('#text29').text = contact.email

$w('#text30').text = shipping.addressLine
$w('#text31').text = shipping.city
$w('#text32').text = shipping.postalCode
$w('#text33').text = `${shipping.subdivision}, ${shipping.country}`
```

---

## APIs
### Velo APIs
| API | Method | Location in Project |
|---|---|---|
| Wix Data | **1.** query() **2.** insert() **3.** remove() **4.** get() | **1.** Home page **2.** Search page **3.** Checkout |
| Wix Window Frontend | **1.** formFactor **2.** lightbox.getContext() **3.** getRouterData() **4.** openLightbox() | **1.** Home page **2.** Login **3.** Search bar **4.** Search page **5.** Product page |
| Wix Location Frontend | **1.** to() **2.** onChange() **3.** query | **1.** Home page **2.** Login **3.** Search bar **4.** Search page **5.** Cart page **6.** Checkout **7.** Order page |
| {authentication} from Wix Members | **1.** applySessionToken() **2.** onLogin() | **1.** Login **2.** Checkout | 
| { cart } from Wix Stores | **1.** addProducts() | **1.** Search page **2.** Product page **3.** Cart page **4.** Checkout |
| {currentMember} from Wix Members | **1.** getMember() | **1.** Product page **2.** Checkout |
| Wix Secrets | getSecret() | Backend |

### External services
#### country-state-city
Helped to get the countries, states of countries and cities of states.
```javascript
import { Permissions, webMethod } from "wix-web-module";

export const getAllCountries = webMethod(
    Permissions.Anyone,
    async () => {
        const countries = await require('country-state-city').Country.getAllCountries();

        const toReturn = countries.map(state => ({
            label: state.name,
            value: state.isoCode
        }));

        return toReturn;
    }
);

export const getStatesOfCountry = webMethod(
    Permissions.Anyone,
    async (country) => {
        const states = await require('country-state-city').State.getStatesOfCountry(country);

        const toReturn = states.map(state => ({
            label: state.name,
            value: state.isoCode
        }));

        return toReturn;
    }
);

export const getCitiesOfStates = webMethod(
    Permissions.Anyone,
    async (country, state) => {
        const states = await require('country-state-city').City.getCitiesOfState(country, state);

        const toReturn = states.map(state => ({
            label: state.name,
            value: state.name
        }));

        return toReturn;
    }
);
```

#### google-cloud/vision
Reads the image and provides labels of what it identifies.
```javascript
const vision = require('@google-cloud/vision');

export const getImageLabels = webMethod(
    Permissions.Anyone,
    async (imageUrl) => {
        const credentials = JSON.parse(await wixSecretsBackend.getSecret("googleVisionCredentials"))

        const client = new vision.ImageAnnotatorClient({ credentials });

        try {
            const [result] = await client.labelDetection(getPublicURL(imageUrl));
            const labels = result.labelAnnotations.map(label => label.description);

            const lowerCaseLabels = labels.map(element => element.toLowerCase());

            return lowerCaseLabels;
        } catch (error) {
            console.error('Error detecting labels:', error);
            return []
        }
    }
);
```
#### Compromise
Extracts the nouns from a text.
```javascript
const nlp = require('compromise');

export const getTextLabels = webMethod(
    Permissions.Anyone,
    async (text) => {
        let doc = nlp(text);

        let categories = doc.match('#Noun').out('array');
        const lowerCaseCategories = categories.map(element => element.toLowerCase());

        return lowerCaseCategories
    }
);
```

#### Twilio
Used for sending the verification code via SMS, call or email and to verify the one-time digit code.

```javascript
export const sendVerification = webMethod(
    Permissions.Anyone,
    async (type =  "email" || "sms" || "call", to = "diego_deap@outlook.com") => {
        try {
            const { accountSid, authToken, service } = await auth()

            const client = twilio(accountSid, authToken);

            await client.verify.v2
                .services(service)
                .verifications.create({
                    channel: type,
                    to: to,
                });

            return true
        } catch (error) {
            return false
        }
    }
);

export const verify = webMethod(
    Permissions.Anyone,
    async (to = "", code = "") => {
        const { accountSid, authToken, service } = await auth()

        const client = twilio(accountSid, authToken);

        const verificationCheck = await client.verify.v2
            .services(service)
            .verificationChecks.create({
                code: code,
                to: to,
            });
        const status = verificationCheck.status === "approved" ? true : false;

        return status
    }
);
```