// Page Code (Home Page - or any page needing the age gate)
import wixStorage from 'wix-storage';
import wixLocation from 'wix-location';
import {local} from 'wix-storage';
$w.onReady(function () {
// --- Age Verification ---
const ageVerified = wixStorage.local.getItem('ageVerified');
if (!ageVerified) {
$w('#ageVerificationLightbox').show(); // Show the lightbox
$w('#yesButton').onClick(() => {
wixStorage.local.setItem('ageVerified', 'true');
$w('#ageVerificationLightbox').hide();
showSpinWheel(); // Show the spin wheel after age verification
});
$w('#noButton').onClick(() => {
// Redirect to the previous page (or a default page)
wixLocation.to(wixLocation.referrer || "/"); //go back to referer or home if no referer
});
} else {
// User is already age-verified, show the spin wheel immediately
showSpinWheel();
}
// --- Spin Wheel Game ---
function showSpinWheel() {
$w('#spinWheelContainer').show(); // Make sure the container is initially hidden in the Wix Editor
let spinning = false;
$w('#spinButton').onClick(() => {
if (spinning) return; // Prevent multiple spins
spinning = true;
// Define the prizes and their probabilities (adjust as needed!)
const prizes = [
{ name: "10% Off", probability: 0.3, type: "discount", value: 0.1 },
{ name: "Free Delivery", probability: 0.25, type: "freeDelivery" },
{ name: "Free Pre-Roll (with purchase)", probability: 0.15, type: "product", value: "pre-roll" },
{ name: "$5 Off", probability: 0.2, type: "discount", value: 0.05}, //fixed amount off, make calculation in cart
{ name: "Better Luck Next Time!", probability: 0.1, type: "none" },
];
// Weighted random selection
let randomNumber = Math.random();
let cumulativeProbability = 0;
let selectedPrize = null;
for (const prize of prizes) {
cumulativeProbability += prize.probability;
if (randomNumber <= cumulativeProbability) {
selectedPrize = prize;
break;
}
}
// --- Simulate the Spin ---
// This is where you control the visual spinning animation.
// We'll use Wix's animation API for a basic example.
// For a *much* better effect, you'd use a dedicated
// JavaScript library for wheel spinning animations.
let spinDuration = 3000; // 3 seconds
let rotations = 10; // Number of full rotations
let finalRotation = 360 * rotations;
//Calculate stop base on prize
let prizeIndex = prizes.findIndex(p => p.name === selectedPrize.name)
let segmentAngle = 360 / prizes.length;
//Adjust to stop in the middle of prize segment
let prizeAngle = (prizeIndex * segmentAngle) + (segmentAngle/2);
$w('#wheelImage').style.transform = `rotate(0deg)`; // Reset rotation
$w('#wheelImage').show(); //Show the image in the container
$w('#wheelImage').rotate(finalRotation + prizeAngle, {
duration: spinDuration,
easing: "easeOutQuint", // Use a nice easing function for a smooth deceleration
}).then(() => {
spinning = false;
displayPrize(selectedPrize);
});
});
}
function displayPrize(prize) {
let prizeText = "";
let prizeCode = "";
//Optional, save the prize to a local storage
local.setItem("customerPrize", JSON.stringify(prize));
switch (prize.type) {
case "discount":
prizeText = `You won ${prize.name}!`;
// In a real implementation, you would generate a *unique* discount code here
// and store it (e.g., in a Wix Data Collection) associated with the user.
// For this example, we'll just display the discount.
prizeCode = `Use code: DISCOUNT${(prize.value * 100).toFixed(0)}`; //e.g DISCOUNT10
break;
case "freeDelivery":
prizeText = "You won Free Delivery!";
prizeCode = "Free delivery will be automatically applied at checkout."; // Or generate a code
break;
case "product":
prizeText = `You won a ${prize.value}!`;
prizeCode = "Add it to your cart with your next purchase."; // Instructions
break;
case "none":
prizeText = "Better Luck Next Time!";
break;
}
$w('#prizeMessage').text = prizeText;
$w('#prizeCode').text = prizeCode;
$w('#prizePopup').show(); // Show a popup with the prize details
$w('#closePrizePopupButton').onClick(() => {
$w('#prizePopup').hide();
$w('#spinWheelContainer').hide();//Hide spinwheel container
});
}
});
// backend/products.jsw
import {ok, notFound, serverError} from 'wix-http-functions';
import wixData from 'wix-data';
export async function get_products(request) {
try {
const options = {
suppressAuth: true // Careful with this! Implement proper authentication
};
let query = wixData.query("Products"); // Assuming your collection is named "Products"
// Add filtering, sorting, and pagination based on request.query parameters
const results = await query.find(options);
const response = {
headers: {
"Content-Type": "application/json",
},
body: {
products: results.items,
totalCount: results.totalCount, // For pagination
// ... other relevant data
},
};
return ok(response);
} catch (error) {
return serverError({body: {error: error.message}});
}
}
// Similar functions for POST, PUT, DELETE
// Page Code (Example - Product Display)
import {fetch} from 'wix-fetch';
$w.onReady(async function () {
// Fetch products from the API
const response = await fetch("/_functions/products", {method: 'get'}); // Use the correct endpoint URL
if (response.ok) {
const data = await response.json();
// Populate a repeater with the product data
$w("#productRepeater").data = data.products;
$w("#productRepeater").onItemReady(($item, itemData, index) => {
// Set the repeater item elements based on the product data
$item("#productName").text = itemData.name;
$item("#productDescription").text = itemData.description;
$item("#productImage").src = itemData.imageURL; // Assuming you have an Image element
$item("#productPrice").text = `$${itemData.price.toFixed(2)}`;
$item("#addToCartButton").onClick(() => {
// Handle adding the product to the cart (implementation depends on your cart logic)
console.log(`Adding ${itemData.name} to cart`);
// You might use Wix Stores for cart functionality, or implement your own
});
// ... set other elements (THC, CBD, etc.) ...
});
} else {
console.error("Error fetching products:", response.status);
// Handle the error (e.g., display an error message to the user)
}
// Set up event handlers for filtering and sorting
$w("#typeFilter").onChange(() => filterProducts()); // Dropdown for product type
$w("#strainSearch").onInput(() => filterProducts()); // Input for strain search
$w("#sortSelect").onChange(() => filterProducts());
//Implement filter and sorting of products
async function filterProducts() {
const type = $w("#typeFilter").value;
const strain = $w("#strainSearch").value;
const sortBy = $w("#sortSelect").value;
let url = "/_functions/products?";
if (type) url += `type=${type}&`;
if (strain) url += `strain=${strain}&`;
if (sortBy) url += `sort=${sortBy}&`;
//Remove the last char from string if '&'
url = url.slice(0, -1);
const response = await fetch(url, { method: 'get' });
//Re-populate the repeater with the response
if (response.ok) {
const data = await response.json();
// Populate a repeater with the product data
$w("#productRepeater").data = data.products;
$w("#productRepeater").onItemReady(($item, itemData, index) => {
// Set the repeater item elements based on the product data
$item("#productName").text = itemData.name;
$item("#productDescription").text = itemData.description;
$item("#productImage").src = itemData.imageURL; // Assuming you have an Image element
$item("#productPrice").text = `$${itemData.price.toFixed(2)}`;
$item("#addToCartButton").onClick(() => {
// Handle adding the product to the cart (implementation depends on your cart logic)
console.log(`Adding ${itemData.name} to cart`);
// You might use Wix Stores for cart functionality, or implement your own
});
// ... set other elements (THC, CBD, etc.) ...
});
} else {
console.error("Error fetching products:", response.status);
// Handle the error (e.g., display an error message to the user)
}
}
});
// backend/cannabisAPI.jsw
import {ok, notFound, badRequest, serverError, forbidden} from 'wix-http-functions';
import wixData from 'wix-data';
import {fetch} from 'wix-fetch';
// --- Helper Functions ---
// VERY BASIC scraping example. DO NOT RELY ON THIS FOR PRODUCTION.
async function scrapeProductData(url) {
try {
const response = await fetch(url, {method: 'get'});
if (!response.ok) {
console.error(`Scraping failed for ${url}: ${response.status}`);
return null;
}
const html = await response.text();
// *** Extremely simplified parsing - You'll need MUCH more robust logic ***
// This is just to illustrate the *concept*. Use a library like Cheerio
// on a separate server for real-world scraping.
const productName = html.match(/(.*?)<\/h1>/)?.[1]; // Extremely fragile!
const productDescription = html.match(/(.*?)<\/p>/)?.[1]; // Extremely fragile!
if (!productName) return null
return {
name: productName,
description: productDescription,
// ... extract other data (THC, CBD, image URL, etc.) ...
};
} catch (error) {
console.error(`Error scraping ${url}:`, error);
return null;
}
}
// --- API Endpoints ---
// Get all products (with filtering and pagination)
export async function get_products(request) {
try {
// --- AGE VERIFICATION (ESSENTIAL - Integrate with a service!) ---
// You MUST verify the user's age before allowing access to product data.
// This is a placeholder. Replace with a real age verification check.
// const ageVerified = await verifyUserAge(request); // Hypothetical function
// if (!ageVerified) {
// return forbidden({body: {error: 'Age verification failed'}});
// }
const options = {
suppressAuth: true // Only if age verification is handled elsewhere!
};
let query = wixData.query("Products");
// Filtering (example - add more based on your needs)
if (request.query.type) {
query = query.eq("Type", request.query.type); // e.g., ?type=flower
}
if (request.query.strain) {
query = query.contains("Strain", request.query.strain); // e.g., ?strain=kush
}
if (request.query.brand) {
query = query.contains("Brand", request.query.brand);
}
// Pagination
const limit = parseInt(request.query.limit) || 20;
const skip = parseInt(request.query.offset) || 0;
query = query.limit(limit).skip(skip);
const results = await query.find(options);
return ok({
headers: {"Content-Type": "application/json"},
body: {
products: results.items,
totalCount: results.totalCount,
},
});
} catch (error) {
return serverError({body: {error: error.message}});
}
}
// Get a single product by ID
export async function get_products_id(request) {
try {
const options = {
suppressAuth: true // Only if age verification is handled elsewhere!
};
const productId = request.path[0];
const product = await wixData.get("Products", productId, options);
if (!product) {
return notFound({body: {error: 'Product not found'}});
}
return ok({
headers: {"Content-Type": "application/json"},
body: product,
});
} catch (error) {
return serverError({body: {error: error.message}});
}
}
// Add a product (ADMIN ONLY - Requires authentication)
export async function post_products(request) {
try {
// --- Authentication (ESSENTIAL) ---
// You MUST authenticate the user (e.g., using Wix Members Area)
// and check if they have admin privileges before allowing product creation.
// This is a placeholder. Implement proper authentication!
// if (!isAdminUser(request)) {
// return forbidden({body: {error: 'Unauthorized'}});
// }
const options = {
suppressAuth: true
};
const productData = await request.body.json();
// --- Data Validation (ESSENTIAL) ---
// Validate ALL fields (name, description, THC, CBD, etc.)
// to ensure data integrity and prevent errors.
if (!productData.name || !productData.description || !productData.price) {
return badRequest({body: {error: 'Missing required fields'}});
}
// --- Scrape Data (Optional - Use with caution) ---
// If a scraping URL is provided, attempt to scrape additional data.
// if (productData.scrapeUrl) {
// const scrapedData = await scrapeProductData(productData.scrapeUrl);
// if (scrapedData) {
// // Merge scraped data with provided data (prioritize provided data)
// productData = {...scrapedData, ...productData};
// }
// }
const toSave = {
"title": productData.name, // Use "title" for Wix Data primary field
...productData, // Spread the rest of the product data
};
const result = await wixData.insert("Products", toSave, options);
return ok({
headers: {"Content-Type": "application/json"},
body: result,
});
} catch (error) {
return serverError({body: {error: error.message}});
}
}
// --- Other Endpoints (Orders, Customers, Reviews) ---
// Implement similar endpoints for creating, retrieving, updating,
// and deleting orders, customers, and reviews. Include robust
// authentication and authorization as needed. Remember age verification
// for all customer-related actions.
//Example function to get orders.
export async function get_orders(request) {
try {
const options = {
suppressAuth: true //Requires secure authentication to implement
};
let query = wixData.query("Orders");
const results = await query.find(options);
return ok({
headers: {"Content-Type": "application/json"},
body: {
products: results.items,
totalCount: results.totalCount,
},
});
} catch (error) {
return serverError({body: {error: error.message}});
}
}
// --- Scheduled Task (Example - for scraping) ---
// You can use Wix's Scheduled Jobs to run scraping tasks periodically.
// This is a VERY basic example. You'll need a much more sophisticated
// solution for real-world scraping, possibly involving a separate server.
// export async function scheduledScrape() {
// const sitesToScrape = ['https://example.com/products', 'https://another-site.com/menu'];
// for (const url of sitesToScrape) {
// const scrapedData = await scrapeProductData(url);
// if (scrapedData) {
// // Process and save the scraped data to your "Products" collection.
// // Handle potential duplicates, updates, etc.
// }
// }
// }
Welcome to De Ja Vu, DC's favorite cannabis dispensary and the ultimate destination for cannabis enthusiasts! Discover our curated selection of exotic products, designed to help you find the perfect strain to elevate your experience. Our knowledgeable staff is here to guide you through the vibrant world of cannabis in welcoming atmosphere. Join us and elevate your journey today!
// backend/cannabisAPI.jsw
import {ok, notFound, badRequest, serverError, forbidden} from 'wix-http-functions';
import wixData from 'wix-data';
import {fetch} from 'wix-fetch';
// --- Helper Functions ---
// VERY BASIC scraping example. DO NOT RELY ON THIS FOR PRODUCTION.
async function scrapeProductData(url) {
try {
const response = await fetch(url, {method: 'get'});
if (!response.ok) {
console.error(`Scraping failed for ${url}: ${response.status}`);
return null;
}
const html = await response.text();
// *** Extremely simplified parsing - You'll need MUCH more robust logic ***
// This is just to illustrate the *concept*. Use a library like Cheerio
// on a separate server for real-world scraping.
const productName = html.match(/(.*?)<\/h1>/)?.[1]; // Extremely fragile!
const productDescription = html.match(/(.*?)<\/p>/)?.[1]; // Extremely fragile!
if (!productName) return null
return {
name: productName,
description: productDescription,
// ... extract other data (THC, CBD, image URL, etc.) ...
};
} catch (error) {
console.error(`Error scraping ${url}:`, error);
return null;
}
}
// --- API Endpoints ---
// Get all products (with filtering and pagination)
export async function get_products(request) {
try {
// --- AGE VERIFICATION (ESSENTIAL - Integrate with a service!) ---
// You MUST verify the user's age before allowing access to product data.
// This is a placeholder. Replace with a real age verification check.
// const ageVerified = await verifyUserAge(request); // Hypothetical function
// if (!ageVerified) {
// return forbidden({body: {error: 'Age verification failed'}});
// }
const options = {
suppressAuth: true // Only if age verification is handled elsewhere!
};
let query = wixData.query("Products");
// Filtering (example - add more based on your needs)
if (request.query.type) {
query = query.eq("Type", request.query.type); // e.g., ?type=flower
}
if (request.query.strain) {
query = query.contains("Strain", request.query.strain); // e.g., ?strain=kush
}
if (request.query.brand) {
query = query.contains("Brand", request.query.brand);
}
// Pagination
const limit = parseInt(request.query.limit) || 20;
const skip = parseInt(request.query.offset) || 0;
query = query.limit(limit).skip(skip);
const results = await query.find(options);
return ok({
headers: {"Content-Type": "application/json"},
body: {
products: results.items,
totalCount: results.totalCount,
},
});
} catch (error) {
return serverError({body: {error: error.message}});
}
}
// Get a single product by ID
export async function get_products_id(request) {
try {
const options = {
suppressAuth: true // Only if age verification is handled elsewhere!
};
const productId = request.path[0];
const product = await wixData.get("Products", productId, options);
if (!product) {
return notFound({body: {error: 'Product not found'}});
}
return ok({
headers: {"Content-Type": "application/json"},
body: product,
});
} catch (error) {
return serverError({body: {error: error.message}});
}
}
// Add a product (ADMIN ONLY - Requires authentication)
export async function post_products(request) {
try {
// --- Authentication (ESSENTIAL) ---
// You MUST authenticate the user (e.g., using Wix Members Area)
// and check if they have admin privileges before allowing product creation.
// This is a placeholder. Implement proper authentication!
// if (!isAdminUser(request)) {
// return forbidden({body: {error: 'Unauthorized'}});
// }
const options = {
suppressAuth: true
};
const productData = await request.body.json();
// --- Data Validation (ESSENTIAL) ---
// Validate ALL fields (name, description, THC, CBD, etc.)
// to ensure data integrity and prevent errors.
if (!productData.name || !productData.description || !productData.price) {
return badRequest({body: {error: 'Missing required fields'}});
}
// --- Scrape Data (Optional - Use with caution) ---
// If a scraping URL is provided, attempt to scrape additional data.
// if (productData.scrapeUrl) {
// const scrapedData = await scrapeProductData(productData.scrapeUrl);
// if (scrapedData) {
// // Merge scraped data with provided data (prioritize provided data)
// productData = {...scrapedData, ...productData};
// }
// }
const toSave = {
"title": productData.name, // Use "title" for Wix Data primary field
...productData, // Spread the rest of the product data
};
const result = await wixData.insert("Products", toSave, options);
return ok({
headers: {"Content-Type": "application/json"},
body: result,
});
} catch (error) {
return serverError({body: {error: error.message}});
}
}
// --- Other Endpoints (Orders, Customers, Reviews) ---
// Implement similar endpoints for creating, retrieving, updating,
// and deleting orders, customers, and reviews. Include robust
// authentication and authorization as needed. Remember age verification
// for all customer-related actions.
//Example function to get orders.
export async function get_orders(request) {
try {
const options = {
suppressAuth: true //Requires secure authentication to implement
};
let query = wixData.query("Orders");
const results = await query.find(options);
return ok({
headers: {"Content-Type": "application/json"},
body: {
products: results.items,
totalCount: results.totalCount,
},
});
} catch (error) {
return serverError({body: {error: error.message}});
}
}
// --- Scheduled Task (Example - for scraping) ---
// You can use Wix's Scheduled Jobs to run scraping tasks periodically.
// This is a VERY basic example. You'll need a much more sophisticated
// solution for real-world scraping, possibly involving a separate server.
// export async function scheduledScrape() {
// const sitesToScrape = ['https://example.com/products', 'https://another-site.com/menu'];
// for (const url of sitesToScrape) {
// const scrapedData = await scrapeProductData(url);
// if (scrapedData) {
// // Process and save the scraped data to your "Products" collection.
// // Handle potential duplicates, updates, etc.
// }
// }
// }