ホームページ  >  記事  >  ウェブフロントエンド  >  メガマート

メガマート

王林
王林オリジナル
2024-07-18 18:38:58583ブラウズ

Mega Mart

これは Wix Studio チャレンジへの提出物です。

背景

AI がビジネス環境を変革しているのに、なぜ時代遅れの手法に従うのでしょうか?私がこの e コマースを作成したのは、昨今の企業にとって AI がトレンドになっているためです。 Mega Mart は、幅広い商品を提供するだけでなく、Wix Studio で作成された高度な機能を通じて UI/UX を強化するプラットフォームです。


私が作ったもの

私は、非効率的な商品発見の問題を解決するために

高度な AI 検索オプションを開発しました。その目的は、ユーザーにとってより直感的で楽しいショッピング体験を生み出すことです。

誰かがお気に入りの T シャツを着ているのに、ブランドを聞きたくなかったという経験はありませんか? Mega Market を使用すると、

写真を撮ってプラットフォームにアップロードするだけです。製品データベースで検索します。

店員に聞くのと同じように欲しい商品を探せるのに、なぜモールに行くのでしょうか?

自然言語でクエリを作成するだけで、探している商品の例が表示されます。たとえば、「女性用の冬用ジャケットが必要です」または「カメラ付きの黒いスマートフォンが欲しいです。」と入力してみてください。

最も良い点は、

このサイトは完全に Wix 要素と Velo API を使用して構築されているため、チェックアウト プロセスを完了するために Wix サブスクリプションを必要としないということです。


メガマーケットでの購入方法

アカウントを作成します (または作成しません...)

Mega Mart では、

アカウントの有無にかかわらず商品を購入することができます アカウントを作成すると、いくつかの
特典が得られます:

    チェックアウト中に、
  • 個人情報と以前の住所が自動的に入力されるので、時間を節約できます。
  • さらに、
  • 即時キャッシュバックを受け取り、今後の購入に使用できます。
  • 製品は気に入っていますが、まだ購入したくないですか?あなたのアカウントには
  • ウィッシュリスト機能が含まれており、後で使用するために商品を保存できます。
商品アイテムを探す

前述したように、デバイスから

画像をアップロードするか、AI を使用するか、すべてのアイテムを参照して最も気に入ったものを選択することで、商品を検索できます。

カートを確認する

チェックアウトする前に、ご希望の商品をすべてカートに追加していることをご確認ください。必要に応じてアイテムの

数量を調整するか、間違いを削除してください。さらに、合計金額と配達予定日を確認してください。

チェックアウト

ログインしている場合、個人情報は

自動的に入力されます。 以前に保存した住所のいずれかを選択するか、新しい住所を入力すると、今後の注文のためにアカウントに追加されます。 クレジット カードまたはデビット カードの情報を入力し、最後のボタンをクリックしてチェックアウトを完了します。

ご注文が完了しました!

注文のページにリダイレクトされ、そこで購入の詳細とその

ステータス (処理中か配送中か配送済みか) を確認できます。ご注文の確認メールも届きます。


メガマートの特典

パスワードなし

最近、パスワードを覚えておきたい人がいるでしょうか? Mega Mart では、それらを心配する必要はありません。アカウントにログインするたびに、電子メール、SMS、または電話で

ワンタイム ディジット コード が届きます。

キャッシュバック

ショッピングでご褒美をもらうのが嬉しくない人はいないでしょうか? Mega Mart で購入するたびに、

1% のキャッシュバックが付与され、今後の注文の支払いに使用できます。

ウィッシュリスト

お気に入りの製品を見つけたものの、まだ購入する準備ができていないことがありますか? Mega Mart では、

ウィッシュリストに保存して、今後購入することができます。

注文状況

荷物のステータスを確認するためにページを切り替える必要はありません。注文のページから、注文が進行中か、輸送中か、すでに配達されているかを簡単に確認できます。

完全レスポンシブ

Web サイトをコンピューター、タブレット、または携帯電話でも同じようにスムーズに表示できるかどうか疑問に思ったことはありますか? Mega Mart では、サイトはすべてのデバイスで完全に応答します。

アドレスを保存する

ショッピングがどれほど便利になるか知りたいですか? Mega Mart では、今後の購入に備えて住所が保存され、アカウント ページから簡単に管理できます。

アカウントは必要ありません

多くの e コマース サイトでは買い物をするためにアカウントの作成が必要ですが、Mega Mart ではゲストとして購入できます。ただし、Mega Mart の常連客として得られるメリットをすべて覚えておいてください。


開発の道のり

Wix Studio を利用することで、フロントエンド開発を加速することができ、すべてのデバイスで完全にレスポンシブなデザインを確保しながら時間を大幅に短縮できました。 JavaScript は、Wix Studio 要素とのユーザーインタラクションを強化し、動的機能をシームレスに有効にする上で重要な役割を果たします。さらに、当社のサイトでは、データベース、Web メソッド、ルーターのセキュリティを含む堅牢なセキュリティ対策を採用し、ユーザー データを保護し、安全なブラウジング エクスペリエンスを確保しています。

Mega Mart の作成方法について説明する前に、Wix Market からアプリをインストールしていないことを言っておかなければなりません。 Wix ストアを使用して商品をカートに保存し、一部のページで商品を取得しましたが、Wix Elements を使用して商品ページ、カート、チェックアウト ページを完全に一から作成することにしました。

免責事項
これはコンテスト用のデモなので、データベースには 29 製品しか保存していません。 AI 画像商品認識と AI 検索方法を最大限に活用するには、まず店内で利用可能な商品についてよく理解してください。たとえば、プリンターやハンマーを検索しても、これらのアイテムはデモ データベースにないため、結果が得られない場合があります。ただし、このプロジェクトがデータベースに数千の製品を保有するホーム デポやウォルマートのような大企業向けに拡大された場合、AI 機能は包括的な検索を実行し、より広範囲の結果を提供するでしょう。


試してみてください!

Mega Mart があなたのショッピング体験に何をもたらすか知りたいですか?クリックして可能性を探って発見してください。
行きましょう!


どうやって構築したか

この e コマース サイトの開発は、特に厳しいスケジュールを考えると簡単ではありませんでしたが、わずか 2 週間ですべての機能を備えたサイトを構築できたことを誇りに思います。

私が開発した最初のページはホームページで、ベストセラーや新着商品を紹介したり、店舗での買い物のメリットを強調したりするなど、他の e コマース サイトで一般的に見られる機能を組み込むことを目的としていました。
以下は、コード内でベストセラーと最新製品のデータを取得する方法の例です。

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}`)
    })
})

ご覧のとおり、このコードには、ユーザーがモバイル デバイスで閲覧しているかどうかを検出するロジックも含まれています。モバイル表示ではリピーターは 2 列に表示されますが、タブレットおよびコンピューター モードでは 1 行 5 列に配置されます。

ログインして登録

登録ページとログインページには直感的な UI/UX インターフェイスが必要だと思います。どちらかのプロセスがユーザーにとって困難またはイライラする場合、ユーザーはサイトを離れ、販売機会が失われる可能性があります。
これを防ぐために、メガマートではパスワードレスシステムを採用しています。ログインするたびに、SMS、電話、または電子メールでワンタイム コードが送信されるため、複数のパスワードを記憶する必要がなくなります。

登録する

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

このコードは、ボタンがクリックされたときの登録プロセスを処理します。確認コード入力ボックスが表示され、確認コードの入力が有効な場合は、提供された確認コードを使用してユーザーの電子メールまたは電話番号が検証されます。いずれかの検証が成功すると、ユーザーの登録が試行され、認証のためにセッション トークンが適用されます。検証に失敗した場合は、エラー メッセージが表示されます。
ボックスが表示されない場合は、ユーザーの電子メールと電話に確認コードが送信され、ユーザーが登録を完了できるように UI が更新されます。

ログイン

ユーザーに確認コードを送信しています

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

最初に、入力されたメールアドレスまたは電話番号が存在するかどうかが確認されます。
どのボタンがクリックされたかに応じて、電子メール、SMS、または電話で確認コードが送信されます。次に、ユーザーが受け取ったコードを書き込むための検証コード入力を展開します。
検証が失敗した場合は、適切なエラー メッセージが表示されます。最後に、読み込み中の画像が非表示になり、ボタンが再び有効になります。

確認コードを確認しています

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

}

検証タイプ (電子メールまたは電話) に基づいて、提供されたコードを検証します。検証が成功すると、ユーザーのログインが試行され、認証のためにセッション トークンが適用されます。

ユーザーをリダイレクトする

ユーザーがログインしているか登録しているかに関係なく、プロセスが成功すると、最後にアクセスしたページにリダイレクトされます。

import { authentication } from "wix-members"
import wixLocationFrontend from 'wix-location-frontend'

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

検索バーウィンドウ

ユーザーがどのページからでも商品を検索できるようにするために、ライトボックス内に検索バーを追加しました。

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 => `<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

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

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 = "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
    }
);

以上がメガマートの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。