”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > 超级市场

超级市场

发布于2024-07-30
浏览:992

Mega Mart

This is a submission for the Wix Studio Challenge.

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!


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.

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

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

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

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.

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.

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

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

$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.

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

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 => `${word}`).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

$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

$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 



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().

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.

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.

$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.

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.

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.

$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.

$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().

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.

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.

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.

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.

export const sendVerification = webMethod(
    Permissions.Anyone,
    async (type =  "email" || "sms" || "call", to = "[email protected]") => {
        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
    }
);
版本声明 本文转载于:https://dev.to/deapco/mega-mart-3b9p?1如有侵犯,请联系[email protected]删除
最新教程 更多>
  • 如何从Java中的字符串中提取数字?
    如何从Java中的字符串中提取数字?
    从Java 在Java中的字符串时,可能需要仅提取数值数字时。可以使用各种方法(包括正则表达式)来实现此操作。正则表达式一种有效的方法是将替换方法使用与非数字(\\ d)相匹配的正则表达式,并用空字符串替换它们。例如:This approach effectively removes all c...
    编程 发布于2025-03-25
  • 如何使用PHP从7个数字(1、2、3、4、5、6、7)中生成所有5个数字的所有唯一组合?
    如何使用PHP从7个数字(1、2、3、4、5、6、7)中生成所有5个数字的所有唯一组合?
    PHP数组组合使用组合类别,该类别实现了所有可能的组合数字。这是其工作原理:类组合实现迭代仪 { 受保护的$ c = null; //数字组合 受保护的$ s = null; //源数组 保护$ n = 0; //数组中的元素数量 保护$ k = 0; //每种组...
    编程 发布于2025-03-25
  • 如何在其容器中为DIV创建平滑的左右CSS动画?
    如何在其容器中为DIV创建平滑的左右CSS动画?
    通用CSS动画,用于左右运动 ,我们将探索创建一个通用的CSS动画,以向左和右移动DIV,从而到达其容器的边缘。该动画可以应用于具有绝对定位的任何div,无论其未知长度如何。问题:使用左直接导致瞬时消失 更加流畅的解决方案:混合转换和左 [并实现平稳的,线性的运动,我们介绍了线性的转换。这...
    编程 发布于2025-03-25
  • 为什么我的代码不在d3.json()的回调中执行?
    为什么我的代码不在d3.json()的回调中执行?
    在D3的JSON呼叫中执行的代码在d3 callback 解决方案: d3 v5中的签名更改: d3.json()的签名()在d3 v5中发生了变化。现在,它返回承诺,而不是依靠回调函数。 The second argument is now an optional RequestInit obj...
    编程 发布于2025-03-25
  • 哪种在JavaScript中声明多个变量的方法更可维护?
    哪种在JavaScript中声明多个变量的方法更可维护?
    在JavaScript中声明多个变量:探索两个方法在JavaScript中,开发人员经常遇到需要声明多个变量的需要。对此的两种常见方法是:在单独的行上声明每个变量: 当涉及性能时,这两种方法本质上都是等效的。但是,可维护性可能会有所不同。 第一个方法被认为更易于维护。每个声明都是其自己的语句,使其...
    编程 发布于2025-03-25
  • 对象拟合:IE和Edge中的封面失败,如何修复?
    对象拟合:IE和Edge中的封面失败,如何修复?
    To resolve this issue, we employ a clever CSS solution that solves the problem:position: absolute;top: 50%;left: 50%;transform: translate(-50%, -50%)...
    编程 发布于2025-03-25
  • 如何使用FormData()处理多个文件上传?
    如何使用FormData()处理多个文件上传?
    )处理多个文件输入时,通常需要处理多个文件上传时,通常是必要的。 The fd.append("fileToUpload[]", files[x]); method can be used for this purpose, allowing you to send multi...
    编程 发布于2025-03-25
  • 在Ubuntu/linux上安装mysql-python时,如何修复\“ mysql_config \”错误?
    在Ubuntu/linux上安装mysql-python时,如何修复\“ mysql_config \”错误?
    mysql-python安装错误:“ mysql_config找不到”“ 由于缺少MySQL开发库而出现此错误。解决此问题,建议在Ubuntu上使用该分发的存储库。使用以下命令安装Python-MysqldB: sudo apt-get安装python-mysqldb sudo pip in...
    编程 发布于2025-03-25
  • 如何使用替换指令在GO MOD中解析模块路径差异?
    如何使用替换指令在GO MOD中解析模块路径差异?
    在使用GO MOD时,在GO MOD 中克服模块路径差异时,可能会遇到冲突,其中3个Party Package将另一个PAXPANCE带有导入式套件之间的另一个软件包,并在导入式套件之间导入另一个软件包。如回声消息所证明的那样: go.etcd.io/bbolt [&&&&&&&&&&&&&&&&...
    编程 发布于2025-03-25
  • 如何自定义Bootstrap 4文件输入中的“选择文件... \”占位符?
    如何自定义Bootstrap 4文件输入中的“选择文件... \”占位符?
    克服Bootstrap 4文件输入的挑战,尽管文件选择了文件选择,文件浏览器都会呈现持久的文本“选择文件...”。此问题源于自定义文件控制CSS类中的隐藏值。 While it is possible to retrieve the chosen file's value using Ja...
    编程 发布于2025-03-25
  • 如何在GO编译器中自定义编译优化?
    如何在GO编译器中自定义编译优化?
    在GO编译器中自定义编译优化 GO中的默认编译过程遵循特定的优化策略。 However, users may need to adjust these optimizations for specific requirements.Optimization Control in Go Compi...
    编程 发布于2025-03-25
  • 编程语言及其应用程序
    编程语言及其应用程序
    当涉及编程时,Python将自己固定为世界上最受欢迎,最广泛使用的语言之一。它简单的语法,功能强大的功能和无与伦比的多功能性使其成为开发人员的最爱 - 他们是否正在构建复杂的机器学习模型,自动化日常任务或开发动态的Web应用程序。 ,但让我们专门讨论Web开发。如果您曾经梦想着创建光滑,功能性的网...
    编程 发布于2025-03-25
  • \“(1)vs.(;;):编译器优化是否消除了性能差异?\”
    \“(1)vs.(;;):编译器优化是否消除了性能差异?\”
    答案: 在大多数现代编译器中,while(1)和(1)和(;;)之间没有性能差异。编译器: perl: 1 输入 - > 2 2 NextState(Main 2 -E:1)V-> 3 9 Leaveloop VK/2-> A 3 toterloop(next-> 8 last-> 9 ...
    编程 发布于2025-03-25
  • 如何在PDO中使用通配符安全地绑定像参数?
    如何在PDO中使用通配符安全地绑定像参数?
    在pDO In the query below, we're trying to bind the variable $partial% using PDO:select wrd from tablename WHERE wrd LIKE '$partial%'It'...
    编程 发布于2025-03-25
  • 如何将熊猫数据框中的逗号分隔字符串分为单独的行?
    如何将熊猫数据框中的逗号分隔字符串分为单独的行?
    在pandas dataframes中将comma-pandas dataframe strings拆分为单独的行使用series.explode()或dataframe.explode():将CSV字符串转换为列表:如果目标完全可以将CSV字符串转换为列表,则可以通过使用str.split()。...
    编程 发布于2025-03-25

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3