Wix Studio Challenge에 제출한 작품입니다.
AI가 비즈니스 환경을 변화시키고 있는데 왜 시대에 뒤떨어진 방식을 따를까요? 요즘 AI가 기업의 트렌드이기 때문에 이 전자상거래를 만들었습니다. 메가마트는 다양한 상품을 제공할 뿐만 아니라 Wix Studio에서 구현된 고급 기능을 통해 UI/UX를 향상시키는 플랫폼입니다.
저는 사용자에게 보다 직관적이고 즐거운 쇼핑 경험을 제공한다는 목표를 가지고 비효율적인 상품 검색 문제를 해결하기 위해 고급 AI 검색 옵션을 개발했습니다.
마음에 드는 티셔츠를 입었지만 브랜드를 묻고 싶지 않은 사람을 본 적이 있나요? Mega Market을 사용하면 사진을 찍어 플랫폼에 업로드하면 됩니다. 제품 데이터베이스에서 검색하겠습니다.
매장 직원에게 물어보듯 원하는 상품을 검색할 수 있는데 왜 쇼핑몰에 가시나요? 자연어로 검색어를 작성하시면 원하시는 제품의 예를 보여드리겠습니다. 예를 들어 여성용 겨울 재킷이 필요해요 또는 카메라가 달린 검은색 스마트폰을 원합니다라고 입력해 보세요.
가장 좋은 점은 사이트 전체가 Wix 요소와 Velo API를 사용하여 구축되었기 때문에 결제 프로세스를 완료하기 위해 Wix 구독이 필요하지 않다는 것입니다.
메가마트에서는 계정 유무에 관계없이 제품을 구매할 수 있습니다.
계정을 만들면 여러 가지 이점을 얻을 수 있습니다.
앞서 말씀드린 것처럼 기기에서 이미지를 업로드하거나, AI를 사용하거나, 모든 항목을 탐색하여 가장 마음에 드는 항목을 선택하여 제품을 검색할 수 있습니다.
결제하기 전에 원하는 제품을 모두 장바구니에 추가했는지 확인하세요. 필요한 경우 항목의 수량을 조정하거나 실수가 있는 경우 삭제하세요. 또한 총액과 예상 배송일을 검토하세요.
로그인을 하시면 개인정보가 자동으로 입력됩니다. 이전에 저장된 주소 중 하나를 선택하거나 새 주소를 입력하면 향후 주문을 위해 계정에 추가됩니다.
신용카드 또는 직불카드 정보를 입력한 후 마지막 버튼을 클릭하면 결제가 완료됩니다.
구매 세부정보와 상태(처리 중, 배송 중, 이미 배송 중)를 볼 수 있는 주문 페이지로 리디렉션됩니다. 또한 주문 확인 이메일을 받게 됩니다.
요즘 비밀번호를 기억하고 싶은 사람이 누가 있나요? 메가마트에서는 걱정할 필요가 없습니다. 계정에 로그인할 때마다 이메일, SMS 또는 전화를 통해 일회성 숫자 코드를 받게 됩니다.
쇼핑에 대한 보상을 받는 것을 좋아하지 않는 사람이 있을까요? 메가마트에서 상품을 구매하실 때마다 1% 캐시백을 받으실 수 있으며, 향후 주문 시 사용하실 수 있습니다.
마음에 드는 제품을 찾았지만 아직 구매할 준비가 되지 않으셨나요? 메가마트에서는 향후 구매를 위해 위시리스트에 저장하실 수 있습니다.
배송 상태를 확인하기 위해 페이지를 전환할 필요가 없습니다. 주문 페이지에서 진행 중, 배송 중, 배송 완료 여부를 쉽게 확인할 수 있습니다.
웹사이트가 컴퓨터, 태블릿, 휴대폰에서도 원활하게 작동할 수 있는지 궁금하신가요? Mega Mart의 사이트는 모든 기기에서 완벽하게 반응합니다.
얼마나 편리한 쇼핑이 가능한지 궁금하세요? 메가마트에서는 향후 구매를 위해 귀하의 주소가 저장되며, 계정 페이지에서 쉽게 관리할 수 있습니다.
많은 전자상거래 사이트에서 쇼핑하려면 계정 생성이 필요하지만 메가마트에서는 비회원으로 구매할 수 있습니다. 하지만 메가마트 단골고객으로서 받으실 수 있는 혜택은 모두 기억해두세요.
Wix Studio를 활용하여 프런트엔드 개발을 가속화하고 시간을 크게 단축하는 동시에 모든 기기에서 완벽하게 반응하는 디자인을 보장하는 데 도움이 되었습니다. JavaScript는 Wix Studio 요소와의 사용자 상호 작용을 향상시켜 동적 기능을 원활하게 활성화하는 데 중요한 역할을 합니다. 또한 당사 사이트는 사용자 데이터를 보호하고 안전한 검색 경험을 보장하기 위해 데이터베이스, 웹 방식 및 라우터 보안을 포함한 강력한 보안 조치를 사용합니다.
메가마트를 어떻게 만들었는지 알아보기 전에, Wix Market에서 어떤 앱도 설치하지 않았다는 점을 말씀드리는 것이 중요합니다. 장바구니에 제품을 저장하기 위해 Wix Stores를 사용한 후 일부 페이지에서 검색하는 동안 Wix Elements를 사용하여 제품 페이지, 장바구니 및 결제 페이지를 처음부터 완전히 구축하기로 결정했습니다.
면책조항
이것은 콘테스트의 데모이므로 데이터베이스에 29개의 제품만 저장했습니다. AI 이미지 상품 인식 및 AI 검색 방법을 최대한 활용하기 위해서는 먼저 매장에서 판매되는 상품을 숙지하시기 바랍니다. 예를 들어, 프린터나 망치를 검색하면 해당 항목이 데모 데이터베이스에 없기 때문에 결과가 나오지 않을 수 있습니다. 그러나 이 프로젝트가 데이터베이스에 수천 개의 제품을 보유하고 있는 Home Depot 또는 Walmart와 같은 대기업으로 확장된다면 AI 기능이 포괄적인 검색을 수행하고 더 넓은 범위의 결과를 제공할 것입니다.
메가마트가 귀하의 쇼핑 경험에 어떤 도움을 줄 수 있는지 궁금하십니까? 가능성을 탐색하고 발견하려면 클릭하세요.
가자!
이 전자상거래 사이트를 개발하는 것은 특히 촉박한 일정으로 인해 쉽지 않았지만 단 2주 만에 모든 기능을 갖춘 사이트를 구축하게 되어 자랑스럽습니다.
첫 번째 페이지는 홈 페이지로, 베스트셀러, 신상품 소개, 매장 쇼핑 혜택 강조 등 다른 전자상거래 사이트에서 흔히 볼 수 있는 기능을 통합하는 것을 목표로 했습니다.
다음은 코드에서 베스트셀러 및 최신 제품에 대한 데이터를 검색하는 방법의 예입니다.
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개의 열로 표시되지만, 태블릿 및 컴퓨터 모드에서는 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) })
사용자가 어떤 페이지에서든 제품을 검색할 수 있도록 라이트박스 안에 검색창을 추가했습니다.
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
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.
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)}`) }
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.
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.
$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'"); } }); }
$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); }
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.
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() }) })
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.
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.
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}`
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 |
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; } );
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 [] } } );
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 } );
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 중국어 웹사이트의 기타 관련 기사를 참조하세요!