Initial commit - Dutchie dispensary scraper

This commit is contained in:
Kelly
2025-11-28 19:45:44 -07:00
commit 5757a8e9bd
23375 changed files with 3788799 additions and 0 deletions

View File

@@ -0,0 +1,302 @@
/**
* Dutchie Menus - WordPress Plugin Styles
*/
/* Product Grid */
.dutchie-product-grid {
display: grid;
gap: 24px;
margin: 20px 0;
}
.dutchie-grid-cols-2 {
grid-template-columns: repeat(2, 1fr);
}
.dutchie-grid-cols-3 {
grid-template-columns: repeat(3, 1fr);
}
.dutchie-grid-cols-4 {
grid-template-columns: repeat(4, 1fr);
}
.dutchie-grid-cols-6 {
grid-template-columns: repeat(6, 1fr);
}
@media (max-width: 1024px) {
.dutchie-grid-cols-4,
.dutchie-grid-cols-6 {
grid-template-columns: repeat(3, 1fr);
}
}
@media (max-width: 768px) {
.dutchie-grid-cols-3,
.dutchie-grid-cols-4,
.dutchie-grid-cols-6 {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 480px) {
.dutchie-product-grid {
grid-template-columns: 1fr;
}
}
/* Product Card */
.dutchie-product-card {
background: #fff;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: transform 0.2s, box-shadow 0.2s;
display: flex;
flex-direction: column;
}
.dutchie-product-card:hover {
transform: translateY(-4px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
}
.dutchie-product-image {
width: 100%;
aspect-ratio: 1;
overflow: hidden;
background: #f5f5f5;
}
.dutchie-product-image img {
width: 100%;
height: 100%;
object-fit: cover;
}
.dutchie-product-content {
padding: 16px;
flex: 1;
display: flex;
flex-direction: column;
}
.dutchie-product-title {
font-size: 18px;
font-weight: 600;
margin: 0 0 8px 0;
color: #333;
line-height: 1.4;
}
.dutchie-product-brand {
font-size: 14px;
color: #666;
margin-bottom: 8px;
}
.dutchie-product-description {
font-size: 14px;
color: #666;
line-height: 1.6;
margin: 0 0 12px 0;
}
.dutchie-product-meta {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin: 12px 0;
}
.dutchie-meta-item {
font-size: 13px;
padding: 4px 10px;
border-radius: 4px;
background: #f0f0f0;
}
.dutchie-meta-item strong {
color: #333;
}
.dutchie-thc {
background: #e8f5e9;
color: #2e7d32;
}
.dutchie-cbd {
background: #e3f2fd;
color: #1565c0;
}
.dutchie-product-price {
font-size: 20px;
font-weight: 700;
color: #2e7d32;
margin-top: auto;
padding-top: 12px;
}
.dutchie-out-of-stock {
display: inline-block;
padding: 6px 12px;
background: #ffebee;
color: #c62828;
font-size: 13px;
font-weight: 600;
border-radius: 4px;
margin-top: 8px;
}
/* Single Product */
.dutchie-single-product {
background: #fff;
border-radius: 8px;
padding: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
margin: 20px 0;
}
.dutchie-layout-horizontal {
display: grid;
grid-template-columns: 400px 1fr;
gap: 32px;
}
.dutchie-layout-vertical {
display: flex;
flex-direction: column;
gap: 24px;
}
@media (max-width: 768px) {
.dutchie-layout-horizontal {
grid-template-columns: 1fr;
}
}
.dutchie-single-product-image {
width: 100%;
aspect-ratio: 1;
overflow: hidden;
border-radius: 8px;
background: #f5f5f5;
}
.dutchie-single-product-image img {
width: 100%;
height: 100%;
object-fit: cover;
}
.dutchie-single-product-details {
display: flex;
flex-direction: column;
gap: 16px;
}
.dutchie-single-product-title {
font-size: 28px;
font-weight: 700;
margin: 0;
color: #333;
line-height: 1.3;
}
.dutchie-single-product-brand {
font-size: 16px;
color: #666;
}
.dutchie-single-product-brand strong {
color: #333;
}
.dutchie-single-product-description {
font-size: 16px;
line-height: 1.6;
color: #555;
}
.dutchie-single-product-info {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
padding: 16px;
background: #f8f8f8;
border-radius: 8px;
}
.dutchie-info-item {
display: flex;
flex-direction: column;
gap: 4px;
}
.dutchie-info-item strong {
font-size: 14px;
color: #333;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.dutchie-info-item span {
font-size: 16px;
color: #555;
}
.dutchie-info-item.dutchie-terpenes span,
.dutchie-info-item.dutchie-effects span,
.dutchie-info-item.dutchie-flavors span {
font-size: 14px;
}
.dutchie-single-product-price {
font-size: 32px;
font-weight: 700;
color: #2e7d32;
}
/* Loading State */
.dutchie-loading {
text-align: center;
padding: 40px;
color: #666;
}
.dutchie-loading:after {
content: "...";
animation: dots 1.5s steps(4, end) infinite;
}
@keyframes dots {
0%, 20% {
color: rgba(0, 0, 0, 0);
text-shadow: 0.25em 0 0 rgba(0, 0, 0, 0),
0.5em 0 0 rgba(0, 0, 0, 0);
}
40% {
color: #666;
text-shadow: 0.25em 0 0 rgba(0, 0, 0, 0),
0.5em 0 0 rgba(0, 0, 0, 0);
}
60% {
text-shadow: 0.25em 0 0 #666,
0.5em 0 0 rgba(0, 0, 0, 0);
}
80%, 100% {
text-shadow: 0.25em 0 0 #666,
0.5em 0 0 #666;
}
}
/* Error State */
.dutchie-error {
padding: 16px;
background: #ffebee;
color: #c62828;
border-radius: 4px;
border-left: 4px solid #c62828;
}

View File

@@ -0,0 +1,57 @@
/**
* Dutchie Menus - WordPress Plugin JavaScript
*/
(function($) {
'use strict';
/**
* Initialize plugin
*/
$(document).ready(function() {
// Lazy load images
if ('IntersectionObserver' in window) {
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
if (img.dataset.src) {
img.src = img.dataset.src;
img.removeAttribute('data-src');
observer.unobserve(img);
}
}
});
});
document.querySelectorAll('.dutchie-product-image img[data-src]').forEach(img => {
imageObserver.observe(img);
});
}
// Add animation to product cards on scroll
if ('IntersectionObserver' in window) {
const cardObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.style.opacity = '0';
entry.target.style.transform = 'translateY(20px)';
setTimeout(() => {
entry.target.style.transition = 'opacity 0.5s, transform 0.5s';
entry.target.style.opacity = '1';
entry.target.style.transform = 'translateY(0)';
}, 10);
cardObserver.unobserve(entry.target);
}
});
}, {
threshold: 0.1
});
document.querySelectorAll('.dutchie-product-card').forEach(card => {
cardObserver.observe(card);
});
}
});
})(jQuery);

Binary file not shown.

View File

@@ -0,0 +1,196 @@
# Dispensary Analytics WordPress Plugin
Beautiful WordPress plugin for displaying Arizona dispensary product data with Elementor widgets and shortcodes.
**By CreationShop** - [https://creationshop.io](https://creationshop.io)
## Features
- **Elementor Widgets**: Drag-and-drop widgets for products, stores, brands, and specials
- **Beautiful Carousels**: Auto-playing product sliders with Swiper.js
- **Flexible Shortcodes**: Easy-to-use shortcodes for any page builder
- **Field Selection**: Choose which data fields to display
- **Multiple Styles**: Modern, Minimal, and Bold card designs
- **Responsive Design**: Perfect on all devices
- **High-Performance**: Lazy loading and optimized API calls
- **SEO Friendly**: Semantic HTML and proper image alt tags
## Installation
1. Upload the `dutchie-analytics` folder to `/wp-content/plugins/`
2. Activate the plugin through the 'Plugins' menu in WordPress
3. Go to Settings → Dutchie Analytics
4. Enter your API URL and authentication token
5. Test the connection
## Configuration
### API Settings
Navigate to **Settings → Dutchie Analytics**:
- **API URL**: Your Dutchie Analytics API endpoint (e.g., `https://api.dutchieanalytics.com/api`)
- **API Token**: Your JWT authentication token
## Shortcodes
### Products Grid
Display a grid of products:
```
[dutchie_products store_id="1" limit="12" columns="4"]
```
**Parameters:**
- `store_id` (required): Store ID
- `limit` (optional): Number of products (default: 12)
- `columns` (optional): Grid columns (default: 4)
- `category_id` (optional): Filter by category
- `in_stock` (optional): Show only in-stock products (default: true)
- `search` (optional): Search term
- `fields` (optional): Comma-separated fields to display
- `style` (optional): Card style - modern, minimal, bold (default: modern)
### Products Carousel
Display a sliding carousel:
```
[dutchie_carousel store_id="1" limit="20" slides_per_view="4"]
```
**Parameters:**
- `store_id` (required): Store ID
- `limit` (optional): Number of products (default: 20)
- `slides_per_view` (optional): Slides visible at once (default: 4)
- `category_id` (optional): Filter by category
- `autoplay` (optional): Enable autoplay (default: true)
- `style` (optional): Card style (default: modern)
### Stores List
Display all stores:
```
[dutchie_stores]
```
### Brands List
Display brands for a store:
```
[dutchie_brands store_id="1"]
```
### Daily Specials
Display store specials:
```
[dutchie_specials store_id="1" date="2025-01-15"]
```
**Parameters:**
- `store_id` (required): Store ID
- `date` (optional): Date for specials (default: today)
## Field Selection
Control which fields to display using the `fields` parameter:
```
[dutchie_products store_id="1" fields="id,name,price,brand,thc_percentage,in_stock"]
```
**Available Fields:**
- `id` - Product ID
- `name` - Product name
- `brand` - Brand name
- `price` - Price
- `description` - Description
- `thc_percentage` - THC %
- `cbd_percentage` - CBD %
- `weight` - Weight
- `strain_type` - Strain type
- `in_stock` - Stock status
- `image_url_full` - High-resolution image (2000x2000)
## Elementor Widgets
Find all widgets in the **Dutchie Analytics** category in Elementor:
1. **Products Grid** - Customizable product grid
2. **Products Carousel** - Auto-playing slider
3. **Stores List** - Store directory
4. **Brands List** - Brand badges
5. **Specials** - Daily deals
### Widget Controls
Each widget provides:
- **Content Tab**: Data source, filters, display options
- **Style Tab**: Colors, spacing, card styles
- **Advanced Tab**: CSS classes, animations
## Card Styles
### Modern (Default)
- Clean white cards
- Subtle shadows
- Smooth hover effects
- Blue accent colors
### Minimal
- Simple borders
- No shadows
- Hover border highlights
- Ultra-clean design
### Bold
- Gradient backgrounds
- Vibrant colors
- Large shadows
- Eye-catching design
## Customization
### Custom CSS
Add custom styles in your theme's CSS:
```css
.dutchie-product-card-modern {
border-radius: 20px;
/* Your custom styles */
}
```
### Hooks and Filters
Coming soon: WordPress hooks for advanced customization.
## Requirements
- WordPress 5.0 or higher
- PHP 7.4 or higher
- Elementor (optional, for widgets)
## Support
For support, visit: [https://dutchieanalytics.com/support](https://dutchieanalytics.com/support)
## Changelog
### 1.0.0
- Initial release
- Elementor widgets
- Shortcodes
- Field selection
- Multiple card styles
- Carousel support
## License
GPL v2 or later

View File

@@ -0,0 +1,326 @@
# Dispensary Analytics - User Guide
## Table of Contents
1. [Installation & Setup](#installation--setup)
2. [Plugin Configuration](#plugin-configuration)
3. [Using Elementor Widgets](#using-elementor-widgets)
4. [Using Shortcodes](#using-shortcodes)
5. [Widget Reference](#widget-reference)
6. [Shortcode Reference](#shortcode-reference)
7. [Troubleshooting](#troubleshooting)
---
## Installation & Setup
### Requirements
- WordPress 5.0 or higher
- PHP 7.4 or higher
- Elementor plugin (for widget functionality)
### Installation Steps
1. Download the `dutchie-analytics.zip` file
2. Go to WordPress Admin → Plugins → Add New
3. Click "Upload Plugin" and select the zip file
4. Click "Install Now" and then "Activate"
---
## Plugin Configuration
### Setting Up API Connection
1. Go to **WordPress Admin → Settings → Dispensary Analytics**
2. Configure the following settings:
- **API URL**: Your backend API URL (e.g., `http://localhost:3010/api` or `https://yourdomain.com/api`)
- **API Token**: Your authentication token (if required)
3. Click "Save Changes"
---
## Using Elementor Widgets
### Finding the Widgets
1. Edit any page with **Elementor**
2. Look for the **"Dutchie Analytics"** category in the widgets panel
3. You'll find 5 widgets available:
- Products Grid
- Products Carousel
- Stores List
- Brands List
- Specials
### Adding a Widget to Your Page
1. Open a page in **Elementor Editor**
2. Drag the desired widget from the sidebar to your page
3. Configure the widget settings in the left panel
4. Click "Update" to save your changes
---
## Using Shortcodes
Shortcodes allow you to display dispensary data in any post, page, or widget area without using Elementor.
### How to Use Shortcodes
1. Edit any post or page
2. Add a shortcode block (or paste directly in the classic editor)
3. Copy and paste one of the shortcodes below
4. Customize the parameters as needed
5. Publish or update your page
---
## Widget Reference
### 1. Products Grid Widget
**Purpose**: Display products in a responsive grid layout
**Widget Settings**:
- **Store ID** (required): The dispensary store ID
- **Category ID** (optional): Filter by specific category
- **Limit**: Number of products to show (default: 12)
- **Columns**: Number of columns (default: 4)
- **In Stock Only**: Show only available products (default: Yes)
- **Search**: Filter by keyword
- **Style**: Choose visual style (modern, classic, minimal)
- **Fields**: Customize which product fields to display
**Elementor Location**: Dutchie Analytics → Products Grid
---
### 2. Products Carousel Widget
**Purpose**: Display products in a sliding carousel/slider
**Widget Settings**:
- **Store ID** (required): The dispensary store ID
- **Category ID** (optional): Filter by specific category
- **Limit**: Number of products to show (default: 12)
- **Slides Per View**: How many products visible at once (default: 4)
- **Autoplay**: Enable automatic sliding (default: Yes)
- **Loop**: Enable infinite loop (default: Yes)
- **Navigation**: Show prev/next arrows (default: Yes)
- **Pagination**: Show dot indicators (default: Yes)
**Elementor Location**: Dutchie Analytics → Products Carousel
---
### 3. Stores List Widget
**Purpose**: Display a list of dispensary locations
**Widget Settings**:
- **Limit**: Number of stores to show (default: 10)
- **State**: Filter by state (e.g., "Arizona")
- **City**: Filter by city
- **Style**: List style (cards, list, table)
- **Show Address**: Display store address (default: Yes)
- **Show Phone**: Display phone number (default: Yes)
- **Show Hours**: Display business hours (default: Yes)
**Elementor Location**: Dutchie Analytics → Stores List
---
### 4. Brands List Widget
**Purpose**: Display available brands
**Widget Settings**:
- **Store ID** (optional): Filter brands by store
- **Limit**: Number of brands to show (default: 20)
- **Style**: Display style (grid, list, carousel)
- **Show Logo**: Display brand logos (default: Yes)
- **Show Product Count**: Show number of products per brand (default: Yes)
**Elementor Location**: Dutchie Analytics → Brands List
---
### 5. Specials Widget
**Purpose**: Display current deals and promotions
**Widget Settings**:
- **Store ID** (required): The dispensary store ID
- **Limit**: Number of specials to show (default: 6)
- **Active Only**: Show only active promotions (default: Yes)
- **Style**: Display style (banner, card, minimal)
- **Show Expiration**: Display when deal expires (default: Yes)
**Elementor Location**: Dutchie Analytics → Specials
---
## Shortcode Reference
### 1. Products Grid Shortcode
Display products in a grid layout.
**Basic Usage**:
```
[dutchie_products store_id="1"]
```
**Full Example**:
```
[dutchie_products store_id="1" limit="12" category_id="5" columns="4" in_stock="true" style="modern" search="indica"]
```
**Parameters**:
- `store_id` (required) - Store ID number
- `limit` - Number of products (default: 12)
- `category_id` - Filter by category ID
- `columns` - Grid columns: 2, 3, 4, or 6 (default: 4)
- `in_stock` - Show only in-stock: "true" or "false" (default: true)
- `search` - Filter by keyword
- `style` - Visual style: "modern", "classic", "minimal"
- `fields` - Comma-separated fields: "name,price,brand,thc,cbd"
---
### 2. Products Carousel Shortcode
Display products in a carousel/slider.
**Basic Usage**:
```
[dutchie_carousel store_id="1"]
```
**Full Example**:
```
[dutchie_carousel store_id="1" limit="16" slides_per_view="4" autoplay="true" loop="true"]
```
**Parameters**:
- `store_id` (required) - Store ID number
- `limit` - Number of products (default: 12)
- `category_id` - Filter by category ID
- `slides_per_view` - Products visible at once (default: 4)
- `autoplay` - Auto-slide: "true" or "false" (default: true)
- `loop` - Infinite loop: "true" or "false" (default: true)
- `navigation` - Show arrows: "true" or "false" (default: true)
- `pagination` - Show dots: "true" or "false" (default: true)
---
### 3. Stores List Shortcode
Display dispensary locations.
**Basic Usage**:
```
[dutchie_stores]
```
**Full Example**:
```
[dutchie_stores limit="10" state="Arizona" city="Phoenix" style="cards"]
```
**Parameters**:
- `limit` - Number of stores (default: 10)
- `state` - Filter by state
- `city` - Filter by city
- `style` - Display style: "cards", "list", "table"
---
### 4. Brands List Shortcode
Display available brands.
**Basic Usage**:
```
[dutchie_brands]
```
**Full Example**:
```
[dutchie_brands store_id="1" limit="20" style="grid"]
```
**Parameters**:
- `store_id` - Filter by store ID
- `limit` - Number of brands (default: 20)
- `style` - Display style: "grid", "list", "carousel"
---
### 5. Specials Shortcode
Display current deals and promotions.
**Basic Usage**:
```
[dutchie_specials store_id="1"]
```
**Full Example**:
```
[dutchie_specials store_id="1" limit="6" active_only="true" style="banner"]
```
**Parameters**:
- `store_id` (required) - Store ID number
- `limit` - Number of specials (default: 6)
- `active_only` - Show only active: "true" or "false" (default: true)
- `style` - Display style: "banner", "card", "minimal"
---
## Troubleshooting
### Widget Not Appearing
- Make sure Elementor is installed and activated
- Clear Elementor cache: Elementor → Tools → Regenerate CSS & Data
- Check if plugin is activated
### No Data Displaying
- Verify API URL in Settings → Dispensary Analytics
- Check that store_id is correct
- Test API connection in browser
- Check browser console for errors (F12)
### Shortcode Shows as Text
- Make sure you're using the correct shortcode format: `[shortcode_name]`
- Verify the plugin is activated
- Check that the post/page content is not in HTML mode
### Styling Issues
- Clear WordPress cache
- Clear browser cache (Ctrl+F5)
- Check for theme CSS conflicts
- Try switching to a default WordPress theme temporarily
### API Connection Issues
- Verify API URL is correct and accessible
- Check if API token is required and configured
- Ensure backend server is running
- Check server firewall settings
---
## Support
For additional support:
- Check plugin settings: WordPress Admin → Settings → Dispensary Analytics
- Review backend API logs
- Contact: creationshop.io
---
## Version Information
**Current Version**: 1.0.2
**Author**: Creationshop LLC
**License**: GPL v2 or later

View File

@@ -0,0 +1,456 @@
/**
* Dutchie Analytics - Beautiful Styles
* Modern, clean, and professional design
*/
/* Products Grid */
.dutchie-products-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 24px;
margin: 24px 0;
}
.dutchie-products-grid[data-columns="3"] {
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
}
.dutchie-products-grid[data-columns="4"] {
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
}
.dutchie-products-grid[data-columns="5"] {
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
}
/* Product Cards - Modern Style */
.dutchie-product-card-modern {
background: #ffffff;
border-radius: 16px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
border: 1px solid #e5e7eb;
}
.dutchie-product-card-modern:hover {
transform: translateY(-4px);
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.12);
}
/* Product Cards - Minimal Style */
.dutchie-product-card-minimal {
background: #ffffff;
border-radius: 8px;
overflow: hidden;
box-shadow: none;
transition: all 0.2s ease;
border: 1px solid #e5e7eb;
}
.dutchie-product-card-minimal:hover {
border-color: #3b82f6;
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.1);
}
/* Product Cards - Bold Style */
.dutchie-product-card-bold {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 20px;
overflow: hidden;
box-shadow: 0 8px 16px rgba(102, 126, 234, 0.3);
transition: all 0.3s ease;
color: #ffffff;
}
.dutchie-product-card-bold:hover {
transform: scale(1.05);
box-shadow: 0 12px 28px rgba(102, 126, 234, 0.4);
}
.dutchie-product-card-bold .dutchie-product-name,
.dutchie-product-card-bold .dutchie-product-brand,
.dutchie-product-card-bold .dutchie-product-description {
color: #ffffff;
}
/* Product Image */
.dutchie-product-image {
position: relative;
width: 100%;
aspect-ratio: 1;
background: #f9fafb;
overflow: hidden;
}
.dutchie-product-image img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
.dutchie-product-card:hover .dutchie-product-image img {
transform: scale(1.05);
}
/* Stock Badge */
.dutchie-stock-badge {
position: absolute;
top: 12px;
right: 12px;
padding: 6px 12px;
border-radius: 20px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
backdrop-filter: blur(10px);
}
.dutchie-stock-badge.in-stock {
background: rgba(16, 185, 129, 0.9);
color: #ffffff;
}
.dutchie-stock-badge.out-of-stock {
background: rgba(239, 68, 68, 0.9);
color: #ffffff;
}
/* Product Content */
.dutchie-product-content {
padding: 20px;
}
.dutchie-product-name {
font-size: 16px;
font-weight: 600;
color: #111827;
margin: 0 0 8px 0;
line-height: 1.4;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.dutchie-product-brand {
font-size: 13px;
color: #6b7280;
margin: 0 0 12px 0;
font-weight: 500;
}
.dutchie-product-description {
font-size: 13px;
color: #6b7280;
line-height: 1.5;
margin: 0 0 16px 0;
}
/* Product Meta */
.dutchie-product-meta {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
flex-wrap: wrap;
}
.dutchie-product-price {
font-size: 20px;
font-weight: 700;
color: #3b82f6;
}
.dutchie-thc-badge,
.dutchie-cbd-badge {
display: inline-flex;
align-items: center;
padding: 4px 10px;
border-radius: 12px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.dutchie-thc-badge {
background: #ecfdf5;
color: #059669;
}
.dutchie-cbd-badge {
background: #eff6ff;
color: #2563eb;
}
.dutchie-product-weight {
font-size: 12px;
color: #9ca3af;
margin: 8px 0 0 0;
}
/* Carousel Wrapper */
.dutchie-products-carousel-wrapper {
margin: 24px 0;
position: relative;
}
.dutchie-products-carousel-wrapper .swiper {
padding: 0 50px 50px 50px;
}
.dutchie-products-carousel-wrapper .swiper-button-prev,
.dutchie-products-carousel-wrapper .swiper-button-next {
width: 44px;
height: 44px;
background: #ffffff;
border-radius: 50%;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transition: all 0.2s ease;
}
.dutchie-products-carousel-wrapper .swiper-button-prev:hover,
.dutchie-products-carousel-wrapper .swiper-button-next:hover {
background: #3b82f6;
color: #ffffff;
transform: scale(1.1);
}
.dutchie-products-carousel-wrapper .swiper-button-prev::after,
.dutchie-products-carousel-wrapper .swiper-button-next::after {
font-size: 18px;
}
.dutchie-products-carousel-wrapper .swiper-pagination-bullet {
width: 10px;
height: 10px;
background: #d1d5db;
opacity: 1;
transition: all 0.2s ease;
}
.dutchie-products-carousel-wrapper .swiper-pagination-bullet-active {
background: #3b82f6;
width: 28px;
border-radius: 5px;
}
/* Stores Grid */
.dutchie-stores-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 24px;
margin: 24px 0;
}
.dutchie-store-card {
background: #ffffff;
border-radius: 16px;
padding: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
transition: all 0.3s ease;
text-align: center;
border: 1px solid #e5e7eb;
}
.dutchie-store-card:hover {
transform: translateY(-4px);
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.12);
}
.dutchie-store-logo {
width: 80px;
height: 80px;
object-fit: contain;
margin: 0 auto 16px;
display: block;
}
.dutchie-store-card h3 {
font-size: 18px;
font-weight: 600;
color: #111827;
margin: 0 0 12px 0;
}
.dutchie-store-stats {
font-size: 14px;
color: #6b7280;
margin: 0 0 16px 0;
}
.dutchie-store-link {
display: inline-block;
padding: 10px 20px;
background: #3b82f6;
color: #ffffff;
text-decoration: none;
border-radius: 8px;
font-weight: 500;
font-size: 14px;
transition: all 0.2s ease;
}
.dutchie-store-link:hover {
background: #2563eb;
transform: scale(1.05);
color: #ffffff;
}
/* Brands Grid */
.dutchie-brands-grid {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin: 24px 0;
}
.dutchie-brand-badge {
display: inline-flex;
align-items: center;
padding: 10px 18px;
background: #f3f4f6;
border: 1px solid #e5e7eb;
border-radius: 24px;
font-size: 14px;
font-weight: 500;
color: #374151;
transition: all 0.2s ease;
}
.dutchie-brand-badge:hover {
background: #3b82f6;
color: #ffffff;
border-color: #3b82f6;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.2);
}
/* Specials Grid */
.dutchie-specials-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 24px;
margin: 24px 0;
}
.dutchie-special-card {
background: linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%);
border-radius: 16px;
padding: 24px;
box-shadow: 0 8px 16px rgba(251, 191, 36, 0.3);
color: #ffffff;
position: relative;
overflow: hidden;
}
.dutchie-special-card::before {
content: '';
position: absolute;
top: -50%;
right: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle, rgba(255, 255, 255, 0.1) 0%, transparent 70%);
pointer-events: none;
}
.dutchie-special-card img {
width: 100%;
height: 200px;
object-fit: cover;
border-radius: 12px;
margin-bottom: 16px;
}
.dutchie-special-card h3 {
font-size: 20px;
font-weight: 700;
color: #ffffff;
margin: 0 0 12px 0;
}
.dutchie-special-card p {
font-size: 14px;
color: rgba(255, 255, 255, 0.9);
margin: 0 0 16px 0;
}
.dutchie-discount-badge {
display: inline-block;
padding: 8px 16px;
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(10px);
border: 2px solid rgba(255, 255, 255, 0.4);
border-radius: 20px;
font-size: 16px;
font-weight: 700;
color: #ffffff;
text-transform: uppercase;
letter-spacing: 1px;
}
/* Responsive Design */
@media (max-width: 768px) {
.dutchie-products-grid,
.dutchie-stores-grid,
.dutchie-specials-grid {
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 16px;
}
.dutchie-products-carousel-wrapper .swiper {
padding: 0 0 40px 0;
}
.dutchie-product-name {
font-size: 15px;
}
.dutchie-product-price {
font-size: 18px;
}
}
@media (max-width: 480px) {
.dutchie-products-grid,
.dutchie-stores-grid,
.dutchie-specials-grid {
grid-template-columns: 1fr;
}
}
/* Loading State */
.dutchie-loading {
text-align: center;
padding: 40px;
color: #6b7280;
}
.dutchie-loading::after {
content: '';
display: inline-block;
width: 32px;
height: 32px;
border: 3px solid #e5e7eb;
border-top-color: #3b82f6;
border-radius: 50%;
animation: dutchie-spin 0.8s linear infinite;
}
@keyframes dutchie-spin {
to { transform: rotate(360deg); }
}
/* Error State */
.dutchie-error {
background: #fee2e2;
border: 1px solid #fecaca;
border-radius: 8px;
padding: 16px;
color: #991b1b;
margin: 16px 0;
}

View File

@@ -0,0 +1,45 @@
/**
* Dutchie Analytics - Frontend JavaScript
*/
(function($) {
'use strict';
// Lazy load images
if ('IntersectionObserver' in window) {
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
if (img.dataset.src) {
img.src = img.dataset.src;
img.removeAttribute('data-src');
observer.unobserve(img);
}
}
});
});
$('.dutchie-product-image img[data-src]').each(function() {
imageObserver.observe(this);
});
}
// Add hover effects
$('.dutchie-product-card').on('mouseenter', function() {
$(this).addClass('dutchie-hover');
}).on('mouseleave', function() {
$(this).removeClass('dutchie-hover');
});
// Optional: Track product views for analytics
$('.dutchie-product-card').on('click', function() {
const productId = $(this).data('product-id');
if (productId && typeof gtag !== 'undefined') {
gtag('event', 'view_item', {
'item_id': productId
});
}
});
})(jQuery);

View File

@@ -0,0 +1,56 @@
#!/bin/bash
# WordPress Plugin Version Bump Script
# Usage: ./bump-version.sh [major|minor|patch]
PLUGIN_FILE="dutchie-analytics.php"
BUMP_TYPE=${1:-patch} # Default to patch if not specified
# Get current version
CURRENT_VERSION=$(grep "^ \* Version:" "$PLUGIN_FILE" | sed 's/.*Version: \(.*\)/\1/')
if [ -z "$CURRENT_VERSION" ]; then
echo "Error: Could not find current version"
exit 1
fi
echo "Current version: $CURRENT_VERSION"
# Split version into parts
IFS='.' read -r -a VERSION_PARTS <<< "$CURRENT_VERSION"
MAJOR="${VERSION_PARTS[0]}"
MINOR="${VERSION_PARTS[1]}"
PATCH="${VERSION_PARTS[2]}"
# Bump version based on type
case $BUMP_TYPE in
major)
MAJOR=$((MAJOR + 1))
MINOR=0
PATCH=0
;;
minor)
MINOR=$((MINOR + 1))
PATCH=0
;;
patch)
PATCH=$((PATCH + 1))
;;
*)
echo "Error: Invalid bump type. Use: major, minor, or patch"
exit 1
;;
esac
NEW_VERSION="$MAJOR.$MINOR.$PATCH"
echo "New version: $NEW_VERSION"
# Update version in plugin file
sed -i "s/^ \* Version: .*/ * Version: $NEW_VERSION/" "$PLUGIN_FILE"
sed -i "s/define('DUTCHIE_ANALYTICS_VERSION', '.*');/define('DUTCHIE_ANALYTICS_VERSION', '$NEW_VERSION');/" "$PLUGIN_FILE"
echo "✓ Version bumped from $CURRENT_VERSION to $NEW_VERSION"
echo ""
echo "Changes made to: $PLUGIN_FILE"
echo " - Plugin header: Version: $NEW_VERSION"
echo " - Constant: DUTCHIE_ANALYTICS_VERSION = '$NEW_VERSION'"

View File

@@ -0,0 +1,130 @@
<?php
/**
* Plugin Name: Dispensary Analytics
* Plugin URI: https://creationshop.io
* Description: Display Arizona dispensary product data, brands, and specials with beautiful Elementor widgets and shortcodes
* Version: 1.0.2
* Author: Creationshop LLC
* Author URI: creationshop.io
* License: GPL v2 or later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
* Text Domain: dutchie-analytics
* Requires at least: 5.0
* Requires PHP: 7.4
*/
if (!defined('ABSPATH')) {
exit; // Exit if accessed directly
}
define('DUTCHIE_ANALYTICS_VERSION', '1.0.2');
define('DUTCHIE_ANALYTICS_PATH', plugin_dir_path(__FILE__));
define('DUTCHIE_ANALYTICS_URL', plugin_dir_url(__FILE__));
// Autoloader
spl_autoload_register(function ($class) {
$prefix = 'DutchieAnalytics\\';
$base_dir = DUTCHIE_ANALYTICS_PATH . 'includes/';
$len = strlen($prefix);
if (strncmp($prefix, $class, $len) !== 0) {
return;
}
$relative_class = substr($class, $len);
$file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';
if (file_exists($file)) {
require $file;
}
});
// Initialize plugin
function dutchie_analytics_init() {
// Load API Client
require_once DUTCHIE_ANALYTICS_PATH . 'includes/API_Client.php';
// Load Shortcodes
require_once DUTCHIE_ANALYTICS_PATH . 'includes/Shortcodes.php';
new DutchieAnalytics\Shortcodes();
// Load Admin Settings
if (is_admin()) {
require_once DUTCHIE_ANALYTICS_PATH . 'includes/Admin_Settings.php';
new DutchieAnalytics\Admin_Settings();
}
// Register Elementor Widgets
add_action('elementor/widgets/register', function($widgets_manager) {
require_once DUTCHIE_ANALYTICS_PATH . 'includes/elementor/Products_Grid_Widget.php';
require_once DUTCHIE_ANALYTICS_PATH . 'includes/elementor/Products_Carousel_Widget.php';
require_once DUTCHIE_ANALYTICS_PATH . 'includes/elementor/Stores_List_Widget.php';
require_once DUTCHIE_ANALYTICS_PATH . 'includes/elementor/Brands_List_Widget.php';
require_once DUTCHIE_ANALYTICS_PATH . 'includes/elementor/Specials_Widget.php';
$widgets_manager->register(new \DutchieAnalytics\Elementor\Products_Grid_Widget());
$widgets_manager->register(new \DutchieAnalytics\Elementor\Products_Carousel_Widget());
$widgets_manager->register(new \DutchieAnalytics\Elementor\Stores_List_Widget());
$widgets_manager->register(new \DutchieAnalytics\Elementor\Brands_List_Widget());
$widgets_manager->register(new \DutchieAnalytics\Elementor\Specials_Widget());
});
// Register Elementor Widget Category
add_action('elementor/elements/categories_registered', function($elements_manager) {
$elements_manager->add_category(
'dutchie-analytics',
[
'title' => __('Dutchie Analytics', 'dutchie-analytics'),
'icon' => 'fa fa-plug',
]
);
});
}
add_action('plugins_loaded', 'dutchie_analytics_init');
// Enqueue styles and scripts
function dutchie_analytics_enqueue_assets() {
wp_enqueue_style(
'dutchie-analytics-styles',
DUTCHIE_ANALYTICS_URL . 'assets/css/dutchie-analytics.css',
[],
DUTCHIE_ANALYTICS_VERSION
);
wp_enqueue_script(
'dutchie-analytics-scripts',
DUTCHIE_ANALYTICS_URL . 'assets/js/dutchie-analytics.js',
['jquery'],
DUTCHIE_ANALYTICS_VERSION,
true
);
// Enqueue Swiper for carousels
wp_enqueue_style(
'swiper-css',
'https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css',
[],
'11.0.0'
);
wp_enqueue_script(
'swiper-js',
'https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js',
[],
'11.0.0',
true
);
}
add_action('wp_enqueue_scripts', 'dutchie_analytics_enqueue_assets');
// Activation hook
register_activation_hook(__FILE__, function() {
// Set default options
add_option('dutchie_analytics_api_url', 'http://localhost:3010/api');
add_option('dutchie_analytics_api_token', '');
});
// Deactivation hook
register_deactivation_hook(__FILE__, function() {
// Cleanup if needed
});

View File

@@ -0,0 +1,121 @@
<?php
namespace DutchieAnalytics;
class API_Client {
private $api_url;
private $api_token;
public function __construct() {
$this->api_url = get_option('dutchie_analytics_api_url', 'http://localhost:3010/api');
// Try to get API key from permissions first, fallback to manual token
$this->api_token = get_option('dutchie_analytics_api_token', '');
if (empty($this->api_token) && class_exists('DutchieAnalytics\API_Permissions')) {
$this->api_token = API_Permissions::get_site_api_key();
}
}
/**
* Make API request
*/
private function request($endpoint, $params = []) {
$url = $this->api_url . $endpoint;
if (!empty($params)) {
$url .= '?' . http_build_query($params);
}
$args = [
'headers' => [
'X-API-Key' => $this->api_token,
'Content-Type' => 'application/json',
],
'timeout' => 30,
];
$response = wp_remote_get($url, $args);
if (is_wp_error($response)) {
error_log('Dutchie Analytics API Error: ' . $response->get_error_message());
return null;
}
$body = wp_remote_retrieve_body($response);
return json_decode($body, true);
}
/**
* Get all stores
*/
public function get_stores() {
return $this->request('/stores');
}
/**
* Get single store
*/
public function get_store($id) {
return $this->request('/stores/' . $id);
}
/**
* Get store brands
*/
public function get_store_brands($store_id) {
return $this->request('/stores/' . $store_id . '/brands');
}
/**
* Get store specials
*/
public function get_store_specials($store_id, $date = null) {
$params = $date ? ['date' => $date] : [];
return $this->request('/stores/' . $store_id . '/specials', $params);
}
/**
* Get products
*
* @param array $args {
* @type int $store_id Filter by store ID
* @type int $category_id Filter by category ID
* @type bool $in_stock Filter by stock availability
* @type string $search Search query
* @type int $limit Results per page (default: 50, max: 1000)
* @type int $offset Pagination offset
* @type string $fields Comma-separated list of fields to return
* }
*/
public function get_products($args = []) {
$defaults = [
'limit' => 50,
'offset' => 0,
];
$params = wp_parse_args($args, $defaults);
return $this->request('/products', $params);
}
/**
* Get single product
*/
public function get_product($id) {
return $this->request('/products/' . $id);
}
/**
* Get categories
*/
public function get_categories($store_id = null) {
$params = $store_id ? ['store_id' => $store_id] : [];
return $this->request('/categories', $params);
}
/**
* Get category tree
*/
public function get_category_tree($store_id) {
return $this->request('/categories/tree', ['store_id' => $store_id]);
}
}

View File

@@ -0,0 +1,113 @@
<?php
namespace DutchieAnalytics;
class Admin_Settings {
public function __construct() {
add_action('admin_menu', [$this, 'add_settings_page']);
add_action('admin_init', [$this, 'register_settings']);
}
public function add_settings_page() {
add_options_page(
__('Dutchie Analytics Settings', 'dutchie-analytics'),
__('Dutchie Analytics', 'dutchie-analytics'),
'manage_options',
'dutchie-analytics',
[$this, 'render_settings_page']
);
}
public function register_settings() {
register_setting('dutchie_analytics_settings', 'dutchie_analytics_api_url');
register_setting('dutchie_analytics_settings', 'dutchie_analytics_api_token');
add_settings_section(
'dutchie_analytics_main',
__('API Configuration', 'dutchie-analytics'),
[$this, 'render_section_description'],
'dutchie-analytics'
);
add_settings_field(
'dutchie_analytics_api_url',
__('API URL', 'dutchie-analytics'),
[$this, 'render_api_url_field'],
'dutchie-analytics',
'dutchie_analytics_main'
);
add_settings_field(
'dutchie_analytics_api_token',
__('API Token', 'dutchie-analytics'),
[$this, 'render_api_token_field'],
'dutchie-analytics',
'dutchie_analytics_main'
);
}
public function render_settings_page() {
?>
<div class="wrap">
<h1><?php echo esc_html(get_admin_page_title()); ?></h1>
<form action="options.php" method="post">
<?php
settings_fields('dutchie_analytics_settings');
do_settings_sections('dutchie-analytics');
submit_button(__('Save Settings', 'dutchie-analytics'));
?>
</form>
<hr>
<h2><?php _e('Test Connection', 'dutchie-analytics'); ?></h2>
<?php
$api = new API_Client();
$stores = $api->get_stores();
if ($stores && isset($stores['stores'])) {
echo '<p style="color: green;"><strong>✓ Connection successful!</strong></p>';
echo '<p>' . sprintf(__('Found %d stores', 'dutchie-analytics'), count($stores['stores'])) . '</p>';
} else {
echo '<p style="color: red;"><strong>✗ Connection failed</strong></p>';
echo '<p>' . __('Please check your API URL and Token', 'dutchie-analytics') . '</p>';
}
?>
<hr>
<h2><?php _e('Documentation', 'dutchie-analytics'); ?></h2>
<h3><?php _e('Shortcodes', 'dutchie-analytics'); ?></h3>
<ul>
<li><code>[dutchie_products store_id="1" limit="12"]</code> - Display products grid</li>
<li><code>[dutchie_carousel store_id="1" limit="20"]</code> - Display products carousel</li>
<li><code>[dutchie_stores]</code> - Display all stores</li>
<li><code>[dutchie_brands store_id="1"]</code> - Display store brands</li>
<li><code>[dutchie_specials store_id="1"]</code> - Display today's specials</li>
</ul>
<h3><?php _e('Field Selection', 'dutchie-analytics'); ?></h3>
<p>You can select which fields to display using the <code>fields</code> parameter:</p>
<code>[dutchie_products store_id="1" fields="id,name,price,brand,in_stock"]</code>
<h3><?php _e('Elementor Widgets', 'dutchie-analytics'); ?></h3>
<p>Find all widgets in the <strong>Dutchie Analytics</strong> category in Elementor.</p>
</div>
<?php
}
public function render_section_description() {
echo '<p>' . __('Configure your Dutchie Analytics API connection', 'dutchie-analytics') . '</p>';
}
public function render_api_url_field() {
$value = get_option('dutchie_analytics_api_url', 'http://localhost:3010/api');
echo '<input type="text" name="dutchie_analytics_api_url" value="' . esc_attr($value) . '" class="regular-text">';
echo '<p class="description">' . __('Example: https://api.dutchieanalytics.com/api', 'dutchie-analytics') . '</p>';
}
public function render_api_token_field() {
$value = get_option('dutchie_analytics_api_token', '');
echo '<input type="password" name="dutchie_analytics_api_token" value="' . esc_attr($value) . '" class="regular-text">';
echo '<p class="description">' . __('Your JWT authentication token', 'dutchie-analytics') . '</p>';
}
}

View File

@@ -0,0 +1,349 @@
<?php
namespace DutchieAnalytics;
class Shortcodes {
public function __construct() {
add_shortcode('dutchie_products', [$this, 'products_grid']);
add_shortcode('dutchie_carousel', [$this, 'products_carousel']);
add_shortcode('dutchie_stores', [$this, 'stores_list']);
add_shortcode('dutchie_brands', [$this, 'brands_list']);
add_shortcode('dutchie_specials', [$this, 'specials']);
}
/**
* Products Grid Shortcode
* [dutchie_products store_id="1" limit="12" category_id="5" fields="name,price,brand"]
*/
public function products_grid($atts) {
$atts = shortcode_atts([
'store_id' => '',
'category_id' => '',
'limit' => 12,
'in_stock' => 'true',
'search' => '',
'columns' => 4,
'fields' => '',
'style' => 'modern',
], $atts);
if (empty($atts['store_id'])) {
return '<p>' . __('Please specify a store_id', 'dutchie-analytics') . '</p>';
}
$api = new API_Client();
$args = [
'store_id' => $atts['store_id'],
'limit' => (int) $atts['limit'],
];
if (!empty($atts['category_id'])) {
$args['category_id'] = $atts['category_id'];
}
if (!empty($atts['search'])) {
$args['search'] = $atts['search'];
}
if ($atts['in_stock'] === 'true') {
$args['in_stock'] = true;
}
if (!empty($atts['fields'])) {
$args['fields'] = $atts['fields'];
}
$data = $api->get_products($args);
$products = $data['products'] ?? [];
if (empty($products)) {
return '<p>' . __('No products found', 'dutchie-analytics') . '</p>';
}
ob_start();
?>
<div class="dutchie-products-grid" data-columns="<?php echo esc_attr($atts['columns']); ?>">
<?php foreach ($products as $product): ?>
<div class="dutchie-product-card dutchie-product-card-<?php echo esc_attr($atts['style']); ?>">
<?php if (!empty($product['image_url_full'])): ?>
<div class="dutchie-product-image">
<img src="<?php echo esc_url($product['image_url_full']); ?>" alt="<?php echo esc_attr($product['name']); ?>" loading="lazy">
<?php if ($product['in_stock']): ?>
<span class="dutchie-stock-badge in-stock"><?php _e('In Stock', 'dutchie-analytics'); ?></span>
<?php else: ?>
<span class="dutchie-stock-badge out-of-stock"><?php _e('Out of Stock', 'dutchie-analytics'); ?></span>
<?php endif; ?>
</div>
<?php endif; ?>
<div class="dutchie-product-content">
<h3 class="dutchie-product-name"><?php echo esc_html($product['name']); ?></h3>
<?php if (!empty($product['brand'])): ?>
<p class="dutchie-product-brand"><?php echo esc_html($product['brand']); ?></p>
<?php endif; ?>
<?php if (!empty($product['description'])): ?>
<p class="dutchie-product-description"><?php echo esc_html(wp_trim_words($product['description'], 20)); ?></p>
<?php endif; ?>
<div class="dutchie-product-meta">
<?php if (!empty($product['price'])): ?>
<span class="dutchie-product-price">$<?php echo number_format($product['price'], 2); ?></span>
<?php endif; ?>
<?php if (!empty($product['thc_percentage'])): ?>
<span class="dutchie-thc-badge"><?php echo esc_html($product['thc_percentage']); ?>% THC</span>
<?php endif; ?>
<?php if (!empty($product['cbd_percentage'])): ?>
<span class="dutchie-cbd-badge"><?php echo esc_html($product['cbd_percentage']); ?>% CBD</span>
<?php endif; ?>
</div>
<?php if (!empty($product['weight'])): ?>
<p class="dutchie-product-weight"><?php echo esc_html($product['weight']); ?></p>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
</div>
<?php
return ob_get_clean();
}
/**
* Products Carousel Shortcode
* [dutchie_carousel store_id="1" limit="20"]
*/
public function products_carousel($atts) {
$atts = shortcode_atts([
'store_id' => '',
'category_id' => '',
'limit' => 20,
'in_stock' => 'true',
'slides_per_view' => 4,
'autoplay' => 'true',
'style' => 'modern',
], $atts);
if (empty($atts['store_id'])) {
return '<p>' . __('Please specify a store_id', 'dutchie-analytics') . '</p>';
}
$api = new API_Client();
$args = [
'store_id' => $atts['store_id'],
'limit' => (int) $atts['limit'],
];
if (!empty($atts['category_id'])) {
$args['category_id'] = $atts['category_id'];
}
if ($atts['in_stock'] === 'true') {
$args['in_stock'] = true;
}
$data = $api->get_products($args);
$products = $data['products'] ?? [];
if (empty($products)) {
return '<p>' . __('No products found', 'dutchie-analytics') . '</p>';
}
$carousel_id = 'dutchie-carousel-' . uniqid();
ob_start();
?>
<div class="dutchie-products-carousel-wrapper">
<div class="swiper <?php echo esc_attr($carousel_id); ?>">
<div class="swiper-wrapper">
<?php foreach ($products as $product): ?>
<div class="swiper-slide">
<div class="dutchie-product-card dutchie-product-card-<?php echo esc_attr($atts['style']); ?>">
<?php if (!empty($product['image_url_full'])): ?>
<div class="dutchie-product-image">
<img src="<?php echo esc_url($product['image_url_full']); ?>" alt="<?php echo esc_attr($product['name']); ?>" loading="lazy">
<?php if ($product['in_stock']): ?>
<span class="dutchie-stock-badge in-stock"><?php _e('In Stock', 'dutchie-analytics'); ?></span>
<?php else: ?>
<span class="dutchie-stock-badge out-of-stock"><?php _e('Out of Stock', 'dutchie-analytics'); ?></span>
<?php endif; ?>
</div>
<?php endif; ?>
<div class="dutchie-product-content">
<h3 class="dutchie-product-name"><?php echo esc_html($product['name']); ?></h3>
<?php if (!empty($product['brand'])): ?>
<p class="dutchie-product-brand"><?php echo esc_html($product['brand']); ?></p>
<?php endif; ?>
<div class="dutchie-product-meta">
<?php if (!empty($product['price'])): ?>
<span class="dutchie-product-price">$<?php echo number_format($product['price'], 2); ?></span>
<?php endif; ?>
<?php if (!empty($product['thc_percentage'])): ?>
<span class="dutchie-thc-badge"><?php echo esc_html($product['thc_percentage']); ?>% THC</span>
<?php endif; ?>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<div class="swiper-button-prev"></div>
<div class="swiper-button-next"></div>
<div class="swiper-pagination"></div>
</div>
</div>
<script>
jQuery(document).ready(function($) {
new Swiper('.<?php echo esc_js($carousel_id); ?>', {
slidesPerView: 1,
spaceBetween: 24,
loop: true,
<?php if ($atts['autoplay'] === 'true'): ?>
autoplay: {
delay: 3000,
disableOnInteraction: false,
},
<?php endif; ?>
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
pagination: {
el: '.swiper-pagination',
clickable: true,
},
breakpoints: {
640: { slidesPerView: 2 },
768: { slidesPerView: 3 },
1024: { slidesPerView: <?php echo (int) $atts['slides_per_view']; ?> },
}
});
});
</script>
<?php
return ob_get_clean();
}
/**
* Stores List Shortcode
* [dutchie_stores]
*/
public function stores_list($atts) {
$api = new API_Client();
$data = $api->get_stores();
$stores = $data['stores'] ?? [];
if (empty($stores)) {
return '<p>' . __('No stores found', 'dutchie-analytics') . '</p>';
}
ob_start();
?>
<div class="dutchie-stores-grid">
<?php foreach ($stores as $store): ?>
<div class="dutchie-store-card">
<?php if (!empty($store['logo_url'])): ?>
<img src="<?php echo esc_url($store['logo_url']); ?>" alt="<?php echo esc_attr($store['name']); ?>" class="dutchie-store-logo">
<?php endif; ?>
<h3><?php echo esc_html($store['name']); ?></h3>
<p class="dutchie-store-stats">
<?php printf(__('%d Products', 'dutchie-analytics'), $store['product_count']); ?>
<?php printf(__('%d Categories', 'dutchie-analytics'), $store['category_count']); ?>
</p>
<?php if ($store['dutchie_url']): ?>
<a href="<?php echo esc_url($store['dutchie_url']); ?>" target="_blank" class="dutchie-store-link">
<?php _e('View Store', 'dutchie-analytics'); ?>
</a>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
<?php
return ob_get_clean();
}
/**
* Brands List Shortcode
* [dutchie_brands store_id="1"]
*/
public function brands_list($atts) {
$atts = shortcode_atts(['store_id' => ''], $atts);
if (empty($atts['store_id'])) {
return '<p>' . __('Please specify a store_id', 'dutchie-analytics') . '</p>';
}
$api = new API_Client();
$data = $api->get_store_brands($atts['store_id']);
$brands = $data['brands'] ?? [];
if (empty($brands)) {
return '<p>' . __('No brands found', 'dutchie-analytics') . '</p>';
}
ob_start();
?>
<div class="dutchie-brands-grid">
<?php foreach ($brands as $brand): ?>
<div class="dutchie-brand-badge"><?php echo esc_html($brand); ?></div>
<?php endforeach; ?>
</div>
<?php
return ob_get_clean();
}
/**
* Specials Shortcode
* [dutchie_specials store_id="1" date="2025-01-15"]
*/
public function specials($atts) {
$atts = shortcode_atts([
'store_id' => '',
'date' => '',
], $atts);
if (empty($atts['store_id'])) {
return '<p>' . __('Please specify a store_id', 'dutchie-analytics') . '</p>';
}
$api = new API_Client();
$data = $api->get_store_specials($atts['store_id'], $atts['date'] ?: null);
$specials = $data['specials'] ?? [];
if (empty($specials)) {
return '<p>' . __('No specials for this date', 'dutchie-analytics') . '</p>';
}
ob_start();
?>
<div class="dutchie-specials-grid">
<?php foreach ($specials as $special): ?>
<div class="dutchie-special-card">
<?php if (!empty($special['product_image'])): ?>
<img src="<?php echo esc_url($special['product_image']); ?>" alt="<?php echo esc_attr($special['name']); ?>">
<?php endif; ?>
<h3><?php echo esc_html($special['name']); ?></h3>
<?php if (!empty($special['description'])): ?>
<p><?php echo esc_html($special['description']); ?></p>
<?php endif; ?>
<?php if (!empty($special['discount_percentage'])): ?>
<span class="dutchie-discount-badge"><?php echo esc_html($special['discount_percentage']); ?>% OFF</span>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
<?php
return ob_get_clean();
}
}

View File

@@ -0,0 +1,82 @@
<?php
namespace DutchieAnalytics\Elementor;
use Elementor\Widget_Base;
use Elementor\Controls_Manager;
use DutchieAnalytics\API_Client;
class Brands_List_Widget extends Widget_Base {
public function get_name() {
return 'dutchie_brands_list';
}
public function get_title() {
return __('Brands List', 'dutchie-analytics');
}
public function get_icon() {
return 'eicon-tags';
}
public function get_categories() {
return ['dutchie-analytics'];
}
protected function register_controls() {
$this->start_controls_section(
'content_section',
[
'label' => __('Content', 'dutchie-analytics'),
'tab' => Controls_Manager::TAB_CONTENT,
]
);
$api = new API_Client();
$stores_data = $api->get_stores();
$stores = [];
if ($stores_data && isset($stores_data['stores'])) {
foreach ($stores_data['stores'] as $store) {
$stores[$store['id']] = $store['name'];
}
}
$this->add_control(
'store_id',
[
'label' => __('Store', 'dutchie-analytics'),
'type' => Controls_Manager::SELECT,
'options' => $stores,
'default' => array_key_first($stores) ?: '',
]
);
$this->end_controls_section();
}
protected function render() {
$settings = $this->get_settings_for_display();
if (empty($settings['store_id'])) {
echo '<p>' . __('Please select a store', 'dutchie-analytics') . '</p>';
return;
}
$api = new API_Client();
$data = $api->get_store_brands($settings['store_id']);
$brands = $data['brands'] ?? [];
if (empty($brands)) {
echo '<p>' . __('No brands found', 'dutchie-analytics') . '</p>';
return;
}
?>
<div class="dutchie-brands-grid">
<?php foreach ($brands as $brand): ?>
<div class="dutchie-brand-badge"><?php echo esc_html($brand); ?></div>
<?php endforeach; ?>
</div>
<?php
}
}

View File

@@ -0,0 +1,336 @@
<?php
namespace DutchieAnalytics\Elementor;
use Elementor\Widget_Base;
use Elementor\Controls_Manager;
use DutchieAnalytics\API_Client;
class Products_Carousel_Widget extends Widget_Base {
public function get_name() {
return 'dutchie_products_carousel';
}
public function get_title() {
return __('Products Carousel', 'dutchie-analytics');
}
public function get_icon() {
return 'eicon-slider-push';
}
public function get_categories() {
return ['dutchie-analytics'];
}
protected function register_controls() {
// Content Section
$this->start_controls_section(
'content_section',
[
'label' => __('Content', 'dutchie-analytics'),
'tab' => Controls_Manager::TAB_CONTENT,
]
);
// Store selection
$api = new API_Client();
$stores_data = $api->get_stores();
$stores = [];
if ($stores_data && isset($stores_data['stores'])) {
foreach ($stores_data['stores'] as $store) {
$stores[$store['id']] = $store['name'];
}
}
$this->add_control(
'store_id',
[
'label' => __('Store', 'dutchie-analytics'),
'type' => Controls_Manager::SELECT,
'options' => $stores,
'default' => array_key_first($stores) ?: '',
]
);
$this->add_control(
'category_id',
[
'label' => __('Category', 'dutchie-analytics'),
'type' => Controls_Manager::TEXT,
'placeholder' => __('Leave empty for all categories', 'dutchie-analytics'),
]
);
$this->add_control(
'limit',
[
'label' => __('Number of Products', 'dutchie-analytics'),
'type' => Controls_Manager::NUMBER,
'default' => 20,
'min' => 1,
'max' => 100,
]
);
$this->add_control(
'in_stock_only',
[
'label' => __('In Stock Only', 'dutchie-analytics'),
'type' => Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'dutchie-analytics'),
'label_off' => __('No', 'dutchie-analytics'),
'return_value' => 'yes',
'default' => 'yes',
]
);
$this->add_control(
'slides_per_view',
[
'label' => __('Slides Per View', 'dutchie-analytics'),
'type' => Controls_Manager::NUMBER,
'default' => 4,
'min' => 1,
'max' => 6,
]
);
$this->add_control(
'space_between',
[
'label' => __('Space Between Slides', 'dutchie-analytics'),
'type' => Controls_Manager::NUMBER,
'default' => 24,
'min' => 0,
'max' => 100,
]
);
$this->add_control(
'autoplay',
[
'label' => __('Autoplay', 'dutchie-analytics'),
'type' => Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'dutchie-analytics'),
'label_off' => __('No', 'dutchie-analytics'),
'return_value' => 'yes',
'default' => 'yes',
]
);
$this->add_control(
'loop',
[
'label' => __('Loop', 'dutchie-analytics'),
'type' => Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'dutchie-analytics'),
'label_off' => __('No', 'dutchie-analytics'),
'return_value' => 'yes',
'default' => 'yes',
]
);
// Field selection
$this->add_control(
'show_brand',
[
'label' => __('Show Brand', 'dutchie-analytics'),
'type' => Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'dutchie-analytics'),
'label_off' => __('No', 'dutchie-analytics'),
'return_value' => 'yes',
'default' => 'yes',
]
);
$this->add_control(
'show_description',
[
'label' => __('Show Description', 'dutchie-analytics'),
'type' => Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'dutchie-analytics'),
'label_off' => __('No', 'dutchie-analytics'),
'return_value' => 'yes',
'default' => 'no',
]
);
$this->add_control(
'show_thc',
[
'label' => __('Show THC %', 'dutchie-analytics'),
'type' => Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'dutchie-analytics'),
'label_off' => __('No', 'dutchie-analytics'),
'return_value' => 'yes',
'default' => 'yes',
]
);
$this->add_control(
'show_price',
[
'label' => __('Show Price', 'dutchie-analytics'),
'type' => Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'dutchie-analytics'),
'label_off' => __('No', 'dutchie-analytics'),
'return_value' => 'yes',
'default' => 'yes',
]
);
$this->end_controls_section();
// Style Section
$this->start_controls_section(
'style_section',
[
'label' => __('Style', 'dutchie-analytics'),
'tab' => Controls_Manager::TAB_STYLE,
]
);
$this->add_control(
'card_style',
[
'label' => __('Card Style', 'dutchie-analytics'),
'type' => Controls_Manager::SELECT,
'options' => [
'modern' => __('Modern', 'dutchie-analytics'),
'minimal' => __('Minimal', 'dutchie-analytics'),
'bold' => __('Bold', 'dutchie-analytics'),
],
'default' => 'modern',
]
);
$this->end_controls_section();
}
protected function render() {
$settings = $this->get_settings_for_display();
if (empty($settings['store_id'])) {
echo '<p>' . __('Please select a store', 'dutchie-analytics') . '</p>';
return;
}
$api = new API_Client();
$args = [
'store_id' => $settings['store_id'],
'limit' => $settings['limit'],
];
if (!empty($settings['category_id'])) {
$args['category_id'] = $settings['category_id'];
}
if ($settings['in_stock_only'] === 'yes') {
$args['in_stock'] = true;
}
$data = $api->get_products($args);
$products = $data['products'] ?? [];
if (empty($products)) {
echo '<p>' . __('No products found', 'dutchie-analytics') . '</p>';
return;
}
$carousel_id = 'dutchie-carousel-' . uniqid();
$card_class = 'dutchie-product-card-' . $settings['card_style'];
?>
<div class="dutchie-products-carousel-wrapper">
<div class="swiper <?php echo esc_attr($carousel_id); ?>">
<div class="swiper-wrapper">
<?php foreach ($products as $product): ?>
<div class="swiper-slide">
<div class="dutchie-product-card <?php echo esc_attr($card_class); ?>">
<?php if (!empty($product['image_url_full'])): ?>
<div class="dutchie-product-image">
<img src="<?php echo esc_url($product['image_url_full']); ?>" alt="<?php echo esc_attr($product['name']); ?>">
<?php if ($product['in_stock']): ?>
<span class="dutchie-stock-badge in-stock"><?php _e('In Stock', 'dutchie-analytics'); ?></span>
<?php else: ?>
<span class="dutchie-stock-badge out-of-stock"><?php _e('Out of Stock', 'dutchie-analytics'); ?></span>
<?php endif; ?>
</div>
<?php endif; ?>
<div class="dutchie-product-content">
<h3 class="dutchie-product-name"><?php echo esc_html($product['name']); ?></h3>
<?php if ($settings['show_brand'] === 'yes' && !empty($product['brand'])): ?>
<p class="dutchie-product-brand"><?php echo esc_html($product['brand']); ?></p>
<?php endif; ?>
<?php if ($settings['show_description'] === 'yes' && !empty($product['description'])): ?>
<p class="dutchie-product-description"><?php echo esc_html(wp_trim_words($product['description'], 15)); ?></p>
<?php endif; ?>
<div class="dutchie-product-meta">
<?php if ($settings['show_price'] === 'yes' && !empty($product['price'])): ?>
<span class="dutchie-product-price">$<?php echo number_format($product['price'], 2); ?></span>
<?php endif; ?>
<?php if ($settings['show_thc'] === 'yes' && !empty($product['thc_percentage'])): ?>
<span class="dutchie-thc-badge"><?php echo esc_html($product['thc_percentage']); ?>% THC</span>
<?php endif; ?>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<!-- Navigation -->
<div class="swiper-button-prev"></div>
<div class="swiper-button-next"></div>
<!-- Pagination -->
<div class="swiper-pagination"></div>
</div>
</div>
<script>
jQuery(document).ready(function($) {
new Swiper('.<?php echo esc_js($carousel_id); ?>', {
slidesPerView: 1,
spaceBetween: <?php echo (int) $settings['space_between']; ?>,
<?php if ($settings['loop'] === 'yes'): ?>
loop: true,
<?php endif; ?>
<?php if ($settings['autoplay'] === 'yes'): ?>
autoplay: {
delay: 3000,
disableOnInteraction: false,
},
<?php endif; ?>
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
pagination: {
el: '.swiper-pagination',
clickable: true,
},
breakpoints: {
640: {
slidesPerView: 2,
},
768: {
slidesPerView: 3,
},
1024: {
slidesPerView: <?php echo (int) $settings['slides_per_view']; ?>,
},
}
});
});
</script>
<?php
}
}

View File

@@ -0,0 +1,194 @@
<?php
namespace DutchieAnalytics\Elementor;
use Elementor\Widget_Base;
use Elementor\Controls_Manager;
use DutchieAnalytics\API_Client;
class Products_Grid_Widget extends Widget_Base {
public function get_name() {
return 'dutchie_products_grid';
}
public function get_title() {
return __('Products Grid', 'dutchie-analytics');
}
public function get_icon() {
return 'eicon-gallery-grid';
}
public function get_categories() {
return ['dutchie-analytics'];
}
protected function register_controls() {
$this->start_controls_section(
'content_section',
[
'label' => __('Content', 'dutchie-analytics'),
'tab' => Controls_Manager::TAB_CONTENT,
]
);
$api = new API_Client();
$stores_data = $api->get_stores();
$stores = [];
if ($stores_data && isset($stores_data['stores'])) {
foreach ($stores_data['stores'] as $store) {
$stores[$store['id']] = $store['name'];
}
}
$this->add_control(
'store_id',
[
'label' => __('Store', 'dutchie-analytics'),
'type' => Controls_Manager::SELECT,
'options' => $stores,
'default' => array_key_first($stores) ?: '',
]
);
$this->add_control(
'category_id',
[
'label' => __('Category', 'dutchie-analytics'),
'type' => Controls_Manager::TEXT,
'placeholder' => __('Leave empty for all categories', 'dutchie-analytics'),
]
);
$this->add_control(
'limit',
[
'label' => __('Number of Products', 'dutchie-analytics'),
'type' => Controls_Manager::NUMBER,
'default' => 12,
'min' => 1,
'max' => 100,
]
);
$this->add_control(
'columns',
[
'label' => __('Columns', 'dutchie-analytics'),
'type' => Controls_Manager::NUMBER,
'default' => 4,
'min' => 1,
'max' => 6,
]
);
$this->add_control(
'in_stock_only',
[
'label' => __('In Stock Only', 'dutchie-analytics'),
'type' => Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'dutchie-analytics'),
'label_off' => __('No', 'dutchie-analytics'),
'return_value' => 'yes',
'default' => 'yes',
]
);
$this->add_control(
'card_style',
[
'label' => __('Card Style', 'dutchie-analytics'),
'type' => Controls_Manager::SELECT,
'options' => [
'modern' => __('Modern', 'dutchie-analytics'),
'minimal' => __('Minimal', 'dutchie-analytics'),
'bold' => __('Bold', 'dutchie-analytics'),
],
'default' => 'modern',
]
);
$this->end_controls_section();
}
protected function render() {
$settings = $this->get_settings_for_display();
if (empty($settings['store_id'])) {
echo '<p>' . __('Please select a store', 'dutchie-analytics') . '</p>';
return;
}
$api = new API_Client();
$args = [
'store_id' => $settings['store_id'],
'limit' => $settings['limit'],
];
if (!empty($settings['category_id'])) {
$args['category_id'] = $settings['category_id'];
}
if ($settings['in_stock_only'] === 'yes') {
$args['in_stock'] = true;
}
$data = $api->get_products($args);
$products = $data['products'] ?? [];
if (empty($products)) {
echo '<p>' . __('No products found', 'dutchie-analytics') . '</p>';
return;
}
?>
<div class="dutchie-products-grid" data-columns="<?php echo esc_attr($settings['columns']); ?>">
<?php foreach ($products as $product): ?>
<div class="dutchie-product-card dutchie-product-card-<?php echo esc_attr($settings['card_style']); ?>">
<?php if (!empty($product['image_url_full'])): ?>
<div class="dutchie-product-image">
<img src="<?php echo esc_url($product['image_url_full']); ?>" alt="<?php echo esc_attr($product['name']); ?>" loading="lazy">
<?php if ($product['in_stock']): ?>
<span class="dutchie-stock-badge in-stock"><?php _e('In Stock', 'dutchie-analytics'); ?></span>
<?php else: ?>
<span class="dutchie-stock-badge out-of-stock"><?php _e('Out of Stock', 'dutchie-analytics'); ?></span>
<?php endif; ?>
</div>
<?php endif; ?>
<div class="dutchie-product-content">
<h3 class="dutchie-product-name"><?php echo esc_html($product['name']); ?></h3>
<?php if (!empty($product['brand'])): ?>
<p class="dutchie-product-brand"><?php echo esc_html($product['brand']); ?></p>
<?php endif; ?>
<?php if (!empty($product['description'])): ?>
<p class="dutchie-product-description"><?php echo esc_html(wp_trim_words($product['description'], 20)); ?></p>
<?php endif; ?>
<div class="dutchie-product-meta">
<?php if (!empty($product['price'])): ?>
<span class="dutchie-product-price">$<?php echo number_format($product['price'], 2); ?></span>
<?php endif; ?>
<?php if (!empty($product['thc_percentage'])): ?>
<span class="dutchie-thc-badge"><?php echo esc_html($product['thc_percentage']); ?>% THC</span>
<?php endif; ?>
<?php if (!empty($product['cbd_percentage'])): ?>
<span class="dutchie-cbd-badge"><?php echo esc_html($product['cbd_percentage']); ?>% CBD</span>
<?php endif; ?>
</div>
<?php if (!empty($product['weight'])): ?>
<p class="dutchie-product-weight"><?php echo esc_html($product['weight']); ?></p>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
</div>
<?php
}
}

View File

@@ -0,0 +1,105 @@
<?php
namespace DutchieAnalytics\Elementor;
use Elementor\Widget_Base;
use Elementor\Controls_Manager;
use DutchieAnalytics\API_Client;
class Specials_Widget extends Widget_Base {
public function get_name() {
return 'dutchie_specials';
}
public function get_title() {
return __('Daily Specials', 'dutchie-analytics');
}
public function get_icon() {
return 'eicon-flash';
}
public function get_categories() {
return ['dutchie-analytics'];
}
protected function register_controls() {
$this->start_controls_section(
'content_section',
[
'label' => __('Content', 'dutchie-analytics'),
'tab' => Controls_Manager::TAB_CONTENT,
]
);
$api = new API_Client();
$stores_data = $api->get_stores();
$stores = [];
if ($stores_data && isset($stores_data['stores'])) {
foreach ($stores_data['stores'] as $store) {
$stores[$store['id']] = $store['name'];
}
}
$this->add_control(
'store_id',
[
'label' => __('Store', 'dutchie-analytics'),
'type' => Controls_Manager::SELECT,
'options' => $stores,
'default' => array_key_first($stores) ?: '',
]
);
$this->add_control(
'date',
[
'label' => __('Date', 'dutchie-analytics'),
'type' => Controls_Manager::DATE_TIME,
'picker_options' => [
'enableTime' => false,
],
]
);
$this->end_controls_section();
}
protected function render() {
$settings = $this->get_settings_for_display();
if (empty($settings['store_id'])) {
echo '<p>' . __('Please select a store', 'dutchie-analytics') . '</p>';
return;
}
$api = new API_Client();
$date = !empty($settings['date']) ? date('Y-m-d', strtotime($settings['date'])) : null;
$data = $api->get_store_specials($settings['store_id'], $date);
$specials = $data['specials'] ?? [];
if (empty($specials)) {
echo '<p>' . __('No specials for this date', 'dutchie-analytics') . '</p>';
return;
}
?>
<div class="dutchie-specials-grid">
<?php foreach ($specials as $special): ?>
<div class="dutchie-special-card">
<?php if (!empty($special['product_image'])): ?>
<img src="<?php echo esc_url($special['product_image']); ?>" alt="<?php echo esc_attr($special['name']); ?>" loading="lazy">
<?php endif; ?>
<h3><?php echo esc_html($special['name']); ?></h3>
<?php if (!empty($special['description'])): ?>
<p><?php echo esc_html($special['description']); ?></p>
<?php endif; ?>
<?php if (!empty($special['discount_percentage'])): ?>
<span class="dutchie-discount-badge"><?php echo esc_html($special['discount_percentage']); ?>% OFF</span>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
<?php
}
}

View File

@@ -0,0 +1,82 @@
<?php
namespace DutchieAnalytics\Elementor;
use Elementor\Widget_Base;
use Elementor\Controls_Manager;
use DutchieAnalytics\API_Client;
class Stores_List_Widget extends Widget_Base {
public function get_name() {
return 'dutchie_stores_list';
}
public function get_title() {
return __('Stores List', 'dutchie-analytics');
}
public function get_icon() {
return 'eicon-products';
}
public function get_categories() {
return ['dutchie-analytics'];
}
protected function register_controls() {
$this->start_controls_section(
'content_section',
[
'label' => __('Content', 'dutchie-analytics'),
'tab' => Controls_Manager::TAB_CONTENT,
]
);
$this->add_control(
'columns',
[
'label' => __('Columns', 'dutchie-analytics'),
'type' => Controls_Manager::NUMBER,
'default' => 3,
'min' => 1,
'max' => 4,
]
);
$this->end_controls_section();
}
protected function render() {
$api = new API_Client();
$data = $api->get_stores();
$stores = $data['stores'] ?? [];
if (empty($stores)) {
echo '<p>' . __('No stores found', 'dutchie-analytics') . '</p>';
return;
}
?>
<div class="dutchie-stores-grid">
<?php foreach ($stores as $store): ?>
<div class="dutchie-store-card">
<?php if (!empty($store['logo_url'])): ?>
<img src="<?php echo esc_url($store['logo_url']); ?>" alt="<?php echo esc_attr($store['name']); ?>" class="dutchie-store-logo">
<?php endif; ?>
<h3><?php echo esc_html($store['name']); ?></h3>
<p class="dutchie-store-stats">
<?php printf(__('%d Products', 'dutchie-analytics'), $store['product_count']); ?>
<?php printf(__('%d Categories', 'dutchie-analytics'), $store['category_count']); ?>
</p>
<?php if ($store['dutchie_url']): ?>
<a href="<?php echo esc_url($store['dutchie_url']); ?>" target="_blank" class="dutchie-store-link">
<?php _e('View Store', 'dutchie-analytics'); ?>
</a>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
<?php
}
}

View File

@@ -0,0 +1,305 @@
<?php
/**
* Plugin Name: Dutchie Menus
* Plugin URI: https://github.com/yourusername/dutchie-menus
* Description: Display cannabis product menus from your Dutchie scraper with Elementor integration
* Version: 1.0.0
* Author: Your Name
* Author URI: https://yoursite.com
* License: GPL v2 or later
* Text Domain: dutchie-menus
* Requires PHP: 7.4
*/
if (!defined('ABSPATH')) {
exit; // Exit if accessed directly
}
define('DUTCHIE_MENUS_VERSION', '1.0.0');
define('DUTCHIE_MENUS_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('DUTCHIE_MENUS_PLUGIN_URL', plugin_dir_url(__FILE__));
/**
* Main Plugin Class
*/
class Dutchie_Menus_Plugin {
private static $instance = null;
public static function instance() {
if (is_null(self::$instance)) {
self::$instance = new self();
}
return self::$instance;
}
public function __construct() {
add_action('plugins_loaded', [$this, 'init']);
add_action('elementor/widgets/register', [$this, 'register_elementor_widgets']);
add_action('admin_menu', [$this, 'add_admin_menu']);
add_action('admin_init', [$this, 'register_settings']);
add_action('wp_enqueue_scripts', [$this, 'enqueue_scripts']);
}
public function init() {
// Initialize plugin
load_plugin_textdomain('dutchie-menus', false, dirname(plugin_basename(__FILE__)) . '/languages');
// Register shortcodes
add_shortcode('dutchie_products', [$this, 'products_shortcode']);
add_shortcode('dutchie_product', [$this, 'single_product_shortcode']);
}
/**
* Register Elementor Widgets
*/
public function register_elementor_widgets($widgets_manager) {
require_once DUTCHIE_MENUS_PLUGIN_DIR . 'widgets/product-grid.php';
require_once DUTCHIE_MENUS_PLUGIN_DIR . 'widgets/single-product.php';
$widgets_manager->register(new \Dutchie_Menus_Product_Grid_Widget());
$widgets_manager->register(new \Dutchie_Menus_Single_Product_Widget());
}
/**
* Enqueue Scripts and Styles
*/
public function enqueue_scripts() {
wp_enqueue_style(
'dutchie-menus-styles',
DUTCHIE_MENUS_PLUGIN_URL . 'assets/css/dutchie-menus.css',
[],
DUTCHIE_MENUS_VERSION
);
wp_enqueue_script(
'dutchie-menus-script',
DUTCHIE_MENUS_PLUGIN_URL . 'assets/js/dutchie-menus.js',
['jquery'],
DUTCHIE_MENUS_VERSION,
true
);
}
/**
* Add Admin Menu
*/
public function add_admin_menu() {
add_menu_page(
'Dutchie Menus',
'Dutchie Menus',
'manage_options',
'dutchie-menus',
[$this, 'admin_page'],
'dashicons-products',
30
);
}
/**
* Register Plugin Settings
*/
public function register_settings() {
register_setting('dutchie_menus_settings', 'dutchie_api_url');
register_setting('dutchie_menus_settings', 'dutchie_api_token');
register_setting('dutchie_menus_settings', 'dutchie_default_store_id');
}
/**
* Admin Page
*/
public function admin_page() {
?>
<div class="wrap">
<h1>Dutchie Menus Settings</h1>
<form method="post" action="options.php">
<?php settings_fields('dutchie_menus_settings'); ?>
<?php do_settings_sections('dutchie_menus_settings'); ?>
<table class="form-table">
<tr>
<th scope="row"><label for="dutchie_api_url">API URL</label></th>
<td>
<input type="url" id="dutchie_api_url" name="dutchie_api_url"
value="<?php echo esc_attr(get_option('dutchie_api_url', 'http://localhost:3010')); ?>"
class="regular-text" />
<p class="description">Your Dutchie Menus API endpoint (e.g., http://localhost:3010)</p>
</td>
</tr>
<tr>
<th scope="row"><label for="dutchie_api_token">API Token</label></th>
<td>
<input type="password" id="dutchie_api_token" name="dutchie_api_token"
value="<?php echo esc_attr(get_option('dutchie_api_token')); ?>"
class="regular-text" />
<p class="description">Your authentication token from the admin dashboard</p>
</td>
</tr>
<tr>
<th scope="row"><label for="dutchie_default_store_id">Default Store ID</label></th>
<td>
<input type="number" id="dutchie_default_store_id" name="dutchie_default_store_id"
value="<?php echo esc_attr(get_option('dutchie_default_store_id', '1')); ?>"
class="small-text" />
<p class="description">Default store ID to use</p>
</td>
</tr>
</table>
<?php submit_button(); ?>
</form>
<hr />
<h2>Test Connection</h2>
<button type="button" id="test-api-connection" class="button">Test API Connection</button>
<div id="api-test-result" style="margin-top: 10px;"></div>
<script>
jQuery(document).ready(function($) {
$('#test-api-connection').on('click', function() {
var apiUrl = $('#dutchie_api_url').val();
var apiToken = $('#dutchie_api_token').val();
$('#api-test-result').html('<p>Testing connection...</p>');
$.ajax({
url: apiUrl + '/api/auth/me',
method: 'GET',
headers: {
'Authorization': 'Bearer ' + apiToken
},
success: function(response) {
$('#api-test-result').html(
'<div class="notice notice-success"><p><strong>Success!</strong> Connected as: ' +
response.user.email + '</p></div>'
);
},
error: function(xhr) {
$('#api-test-result').html(
'<div class="notice notice-error"><p><strong>Error:</strong> ' +
(xhr.responseJSON?.error || 'Connection failed') + '</p></div>'
);
}
});
});
});
</script>
</div>
<?php
}
/**
* Products Shortcode
*/
public function products_shortcode($atts) {
$atts = shortcode_atts([
'store_id' => get_option('dutchie_default_store_id', 1),
'category_id' => '',
'limit' => 12,
'columns' => 3,
'in_stock' => 'true'
], $atts);
$products = $this->fetch_products($atts);
if (!$products) {
return '<p>No products found.</p>';
}
ob_start();
include DUTCHIE_MENUS_PLUGIN_DIR . 'templates/product-grid.php';
return ob_get_clean();
}
/**
* Single Product Shortcode
*/
public function single_product_shortcode($atts) {
$atts = shortcode_atts([
'id' => 0
], $atts);
if (!$atts['id']) {
return '<p>Product ID required.</p>';
}
$product = $this->fetch_product($atts['id']);
if (!$product) {
return '<p>Product not found.</p>';
}
ob_start();
include DUTCHIE_MENUS_PLUGIN_DIR . 'templates/single-product.php';
return ob_get_clean();
}
/**
* Fetch Products from API
*/
public function fetch_products($args = []) {
$api_url = get_option('dutchie_api_url', 'http://localhost:3010');
$api_token = get_option('dutchie_api_token');
if (!$api_token) {
return false;
}
$query_args = http_build_query($args);
$url = $api_url . '/api/products?' . $query_args;
$response = wp_remote_get($url, [
'headers' => [
'Authorization' => 'Bearer ' . $api_token
],
'timeout' => 30
]);
if (is_wp_error($response)) {
return false;
}
$body = wp_remote_retrieve_body($response);
$data = json_decode($body, true);
return $data['products'] ?? false;
}
/**
* Fetch Single Product from API
*/
public function fetch_product($id) {
$api_url = get_option('dutchie_api_url', 'http://localhost:3010');
$api_token = get_option('dutchie_api_token');
if (!$api_token) {
return false;
}
$url = $api_url . '/api/products/' . intval($id);
$response = wp_remote_get($url, [
'headers' => [
'Authorization' => 'Bearer ' . $api_token
],
'timeout' => 30
]);
if (is_wp_error($response)) {
return false;
}
$body = wp_remote_retrieve_body($response);
$data = json_decode($body, true);
return $data['product'] ?? false;
}
}
// Initialize Plugin
function dutchie_menus() {
return Dutchie_Menus_Plugin::instance();
}
dutchie_menus();

View File

@@ -0,0 +1,69 @@
<?php
/**
* Template for Product Grid (used by shortcode)
*/
if (!defined('ABSPATH')) {
exit;
}
$columns = isset($atts['columns']) ? $atts['columns'] : 3;
?>
<div class="dutchie-product-grid dutchie-grid-cols-<?php echo esc_attr($columns); ?>">
<?php foreach ($products as $product): ?>
<div class="dutchie-product-card">
<?php if (!empty($product['image_url_full'])): ?>
<div class="dutchie-product-image">
<img src="<?php echo esc_url($product['image_url_full']); ?>"
alt="<?php echo esc_attr($product['name']); ?>"
loading="lazy" />
</div>
<?php endif; ?>
<div class="dutchie-product-content">
<h3 class="dutchie-product-title">
<?php echo esc_html($product['name']); ?>
</h3>
<?php if (!empty($product['brand'])): ?>
<div class="dutchie-product-brand">
<?php echo esc_html($product['brand']); ?>
</div>
<?php endif; ?>
<?php if (!empty($product['description'])): ?>
<p class="dutchie-product-description">
<?php echo esc_html(wp_trim_words($product['description'], 15)); ?>
</p>
<?php endif; ?>
<div class="dutchie-product-meta">
<?php if (!empty($product['metadata']['thc'])): ?>
<span class="dutchie-meta-item dutchie-thc">
<strong>THC:</strong> <?php echo esc_html($product['metadata']['thc']); ?>
</span>
<?php endif; ?>
<?php if (!empty($product['metadata']['cbd'])): ?>
<span class="dutchie-meta-item dutchie-cbd">
<strong>CBD:</strong> <?php echo esc_html($product['metadata']['cbd']); ?>
</span>
<?php endif; ?>
</div>
<?php if (isset($product['price'])): ?>
<div class="dutchie-product-price">
$<?php echo number_format($product['price'], 2); ?>
</div>
<?php endif; ?>
<?php if (!$product['in_stock']): ?>
<div class="dutchie-out-of-stock">
<?php _e('Out of Stock', 'dutchie-menus'); ?>
</div>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
</div>

View File

@@ -0,0 +1,98 @@
<?php
/**
* Template for Single Product (used by shortcode)
*/
if (!defined('ABSPATH')) {
exit;
}
?>
<div class="dutchie-single-product dutchie-layout-horizontal">
<?php if (!empty($product['image_url_full'])): ?>
<div class="dutchie-single-product-image">
<img src="<?php echo esc_url($product['image_url_full']); ?>"
alt="<?php echo esc_attr($product['name']); ?>" />
</div>
<?php endif; ?>
<div class="dutchie-single-product-details">
<h2 class="dutchie-single-product-title">
<?php echo esc_html($product['name']); ?>
</h2>
<?php if (!empty($product['brand'])): ?>
<div class="dutchie-single-product-brand">
<strong><?php _e('Brand:', 'dutchie-menus'); ?></strong>
<?php echo esc_html($product['brand']); ?>
</div>
<?php endif; ?>
<?php if (!empty($product['description'])): ?>
<div class="dutchie-single-product-description">
<?php echo wp_kses_post(nl2br($product['description'])); ?>
</div>
<?php endif; ?>
<div class="dutchie-single-product-info">
<?php if (!empty($product['metadata']['thc'])): ?>
<div class="dutchie-info-item">
<strong><?php _e('THC:', 'dutchie-menus'); ?></strong>
<span><?php echo esc_html($product['metadata']['thc']); ?></span>
</div>
<?php endif; ?>
<?php if (!empty($product['metadata']['cbd'])): ?>
<div class="dutchie-info-item">
<strong><?php _e('CBD:', 'dutchie-menus'); ?></strong>
<span><?php echo esc_html($product['metadata']['cbd']); ?></span>
</div>
<?php endif; ?>
<?php if (!empty($product['metadata']['terpenes'])): ?>
<div class="dutchie-info-item dutchie-terpenes">
<strong><?php _e('Terpenes:', 'dutchie-menus'); ?></strong>
<?php if (is_array($product['metadata']['terpenes'])): ?>
<span><?php echo esc_html(implode(', ', $product['metadata']['terpenes'])); ?></span>
<?php else: ?>
<span><?php echo esc_html($product['metadata']['terpenes']); ?></span>
<?php endif; ?>
</div>
<?php endif; ?>
<?php if (!empty($product['metadata']['effects'])): ?>
<div class="dutchie-info-item dutchie-effects">
<strong><?php _e('Effects:', 'dutchie-menus'); ?></strong>
<?php if (is_array($product['metadata']['effects'])): ?>
<span><?php echo esc_html(implode(', ', $product['metadata']['effects'])); ?></span>
<?php else: ?>
<span><?php echo esc_html($product['metadata']['effects']); ?></span>
<?php endif; ?>
</div>
<?php endif; ?>
<?php if (!empty($product['metadata']['flavors'])): ?>
<div class="dutchie-info-item dutchie-flavors">
<strong><?php _e('Flavors:', 'dutchie-menus'); ?></strong>
<?php if (is_array($product['metadata']['flavors'])): ?>
<span><?php echo esc_html(implode(', ', $product['metadata']['flavors'])); ?></span>
<?php else: ?>
<span><?php echo esc_html($product['metadata']['flavors']); ?></span>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
<?php if (isset($product['price'])): ?>
<div class="dutchie-single-product-price">
$<?php echo number_format($product['price'], 2); ?>
</div>
<?php endif; ?>
<?php if (!$product['in_stock']): ?>
<div class="dutchie-out-of-stock">
<?php _e('Out of Stock', 'dutchie-menus'); ?>
</div>
<?php endif; ?>
</div>
</div>

View File

@@ -0,0 +1,317 @@
<?php
/**
* Elementor Product Grid Widget
*/
if (!defined('ABSPATH')) {
exit;
}
class Dutchie_Menus_Product_Grid_Widget extends \Elementor\Widget_Base {
public function get_name() {
return 'dutchie_product_grid';
}
public function get_title() {
return __('Dutchie Product Grid', 'dutchie-menus');
}
public function get_icon() {
return 'eicon-products';
}
public function get_categories() {
return ['general'];
}
protected function register_controls() {
// Content Section
$this->start_controls_section(
'content_section',
[
'label' => __('Content', 'dutchie-menus'),
'tab' => \Elementor\Controls_Manager::TAB_CONTENT,
]
);
$this->add_control(
'store_id',
[
'label' => __('Store ID', 'dutchie-menus'),
'type' => \Elementor\Controls_Manager::NUMBER,
'default' => get_option('dutchie_default_store_id', 1),
'min' => 1,
]
);
$this->add_control(
'category_id',
[
'label' => __('Category ID', 'dutchie-menus'),
'type' => \Elementor\Controls_Manager::NUMBER,
'default' => '',
'description' => __('Leave empty to show all categories', 'dutchie-menus'),
]
);
$this->add_control(
'limit',
[
'label' => __('Number of Products', 'dutchie-menus'),
'type' => \Elementor\Controls_Manager::NUMBER,
'default' => 12,
'min' => 1,
'max' => 100,
]
);
$this->add_control(
'columns',
[
'label' => __('Columns', 'dutchie-menus'),
'type' => \Elementor\Controls_Manager::SELECT,
'default' => '3',
'options' => [
'2' => __('2 Columns', 'dutchie-menus'),
'3' => __('3 Columns', 'dutchie-menus'),
'4' => __('4 Columns', 'dutchie-menus'),
'6' => __('6 Columns', 'dutchie-menus'),
],
]
);
$this->add_control(
'in_stock_only',
[
'label' => __('In Stock Only', 'dutchie-menus'),
'type' => \Elementor\Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'dutchie-menus'),
'label_off' => __('No', 'dutchie-menus'),
'return_value' => 'yes',
'default' => 'yes',
]
);
$this->add_control(
'search',
[
'label' => __('Search Filter', 'dutchie-menus'),
'type' => \Elementor\Controls_Manager::TEXT,
'default' => '',
'description' => __('Filter products by name', 'dutchie-menus'),
]
);
$this->end_controls_section();
// Display Options Section
$this->start_controls_section(
'display_section',
[
'label' => __('Display Options', 'dutchie-menus'),
'tab' => \Elementor\Controls_Manager::TAB_CONTENT,
]
);
$this->add_control(
'show_image',
[
'label' => __('Show Image', 'dutchie-menus'),
'type' => \Elementor\Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'dutchie-menus'),
'label_off' => __('No', 'dutchie-menus'),
'return_value' => 'yes',
'default' => 'yes',
]
);
$this->add_control(
'show_price',
[
'label' => __('Show Price', 'dutchie-menus'),
'type' => \Elementor\Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'dutchie-menus'),
'label_off' => __('No', 'dutchie-menus'),
'return_value' => 'yes',
'default' => 'yes',
]
);
$this->add_control(
'show_thc',
[
'label' => __('Show THC', 'dutchie-menus'),
'type' => \Elementor\Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'dutchie-menus'),
'label_off' => __('No', 'dutchie-menus'),
'return_value' => 'yes',
'default' => 'yes',
]
);
$this->add_control(
'show_cbd',
[
'label' => __('Show CBD', 'dutchie-menus'),
'type' => \Elementor\Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'dutchie-menus'),
'label_off' => __('No', 'dutchie-menus'),
'return_value' => 'yes',
'default' => 'yes',
]
);
$this->add_control(
'show_description',
[
'label' => __('Show Description', 'dutchie-menus'),
'type' => \Elementor\Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'dutchie-menus'),
'label_off' => __('No', 'dutchie-menus'),
'return_value' => 'yes',
'default' => 'no',
]
);
$this->end_controls_section();
// Style Section
$this->start_controls_section(
'style_section',
[
'label' => __('Style', 'dutchie-menus'),
'tab' => \Elementor\Controls_Manager::TAB_STYLE,
]
);
$this->add_control(
'card_background',
[
'label' => __('Card Background', 'dutchie-menus'),
'type' => \Elementor\Controls_Manager::COLOR,
'default' => '#ffffff',
'selectors' => [
'{{WRAPPER}} .dutchie-product-card' => 'background-color: {{VALUE}};',
],
]
);
$this->add_control(
'card_border_radius',
[
'label' => __('Border Radius', 'dutchie-menus'),
'type' => \Elementor\Controls_Manager::SLIDER,
'size_units' => ['px'],
'range' => [
'px' => [
'min' => 0,
'max' => 50,
],
],
'default' => [
'size' => 8,
],
'selectors' => [
'{{WRAPPER}} .dutchie-product-card' => 'border-radius: {{SIZE}}{{UNIT}};',
],
]
);
$this->add_control(
'text_color',
[
'label' => __('Text Color', 'dutchie-menus'),
'type' => \Elementor\Controls_Manager::COLOR,
'default' => '#333333',
'selectors' => [
'{{WRAPPER}} .dutchie-product-card' => 'color: {{VALUE}};',
],
]
);
$this->end_controls_section();
}
protected function render() {
$settings = $this->get_settings_for_display();
$args = [
'store_id' => $settings['store_id'],
'limit' => $settings['limit'],
'in_stock' => $settings['in_stock_only'] === 'yes' ? 'true' : 'false',
];
if (!empty($settings['category_id'])) {
$args['category_id'] = $settings['category_id'];
}
if (!empty($settings['search'])) {
$args['search'] = $settings['search'];
}
$plugin = Dutchie_Menus_Plugin::instance();
$products = $plugin->fetch_products($args);
if (!$products) {
echo '<p>' . __('No products found.', 'dutchie-menus') . '</p>';
return;
}
$columns = $settings['columns'];
?>
<div class="dutchie-product-grid dutchie-grid-cols-<?php echo esc_attr($columns); ?>">
<?php foreach ($products as $product): ?>
<div class="dutchie-product-card">
<?php if ($settings['show_image'] === 'yes' && !empty($product['image_url_full'])): ?>
<div class="dutchie-product-image">
<img src="<?php echo esc_url($product['image_url_full']); ?>"
alt="<?php echo esc_attr($product['name']); ?>"
loading="lazy" />
</div>
<?php endif; ?>
<div class="dutchie-product-content">
<h3 class="dutchie-product-title">
<?php echo esc_html($product['name']); ?>
</h3>
<?php if ($settings['show_description'] === 'yes' && !empty($product['description'])): ?>
<p class="dutchie-product-description">
<?php echo esc_html(wp_trim_words($product['description'], 15)); ?>
</p>
<?php endif; ?>
<div class="dutchie-product-meta">
<?php if ($settings['show_thc'] === 'yes' && !empty($product['metadata']['thc'])): ?>
<span class="dutchie-meta-item dutchie-thc">
<strong>THC:</strong> <?php echo esc_html($product['metadata']['thc']); ?>
</span>
<?php endif; ?>
<?php if ($settings['show_cbd'] === 'yes' && !empty($product['metadata']['cbd'])): ?>
<span class="dutchie-meta-item dutchie-cbd">
<strong>CBD:</strong> <?php echo esc_html($product['metadata']['cbd']); ?>
</span>
<?php endif; ?>
</div>
<?php if ($settings['show_price'] === 'yes' && isset($product['price'])): ?>
<div class="dutchie-product-price">
$<?php echo number_format($product['price'], 2); ?>
</div>
<?php endif; ?>
<?php if (!$product['in_stock']): ?>
<div class="dutchie-out-of-stock">
<?php _e('Out of Stock', 'dutchie-menus'); ?>
</div>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
</div>
<?php
}
}

View File

@@ -0,0 +1,325 @@
<?php
/**
* Elementor Single Product Widget
*/
if (!defined('ABSPATH')) {
exit;
}
class Dutchie_Menus_Single_Product_Widget extends \Elementor\Widget_Base {
public function get_name() {
return 'dutchie_single_product';
}
public function get_title() {
return __('Dutchie Single Product', 'dutchie-menus');
}
public function get_icon() {
return 'eicon-single-product';
}
public function get_categories() {
return ['general'];
}
protected function register_controls() {
// Content Section
$this->start_controls_section(
'content_section',
[
'label' => __('Content', 'dutchie-menus'),
'tab' => \Elementor\Controls_Manager::TAB_CONTENT,
]
);
$this->add_control(
'product_id',
[
'label' => __('Product ID', 'dutchie-menus'),
'type' => \Elementor\Controls_Manager::NUMBER,
'default' => '',
'description' => __('Enter the product ID to display', 'dutchie-menus'),
]
);
$this->add_control(
'layout',
[
'label' => __('Layout', 'dutchie-menus'),
'type' => \Elementor\Controls_Manager::SELECT,
'default' => 'horizontal',
'options' => [
'horizontal' => __('Horizontal', 'dutchie-menus'),
'vertical' => __('Vertical', 'dutchie-menus'),
],
]
);
$this->end_controls_section();
// Display Options Section
$this->start_controls_section(
'display_section',
[
'label' => __('Display Options', 'dutchie-menus'),
'tab' => \Elementor\Controls_Manager::TAB_CONTENT,
]
);
$this->add_control(
'show_image',
[
'label' => __('Show Image', 'dutchie-menus'),
'type' => \Elementor\Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'dutchie-menus'),
'label_off' => __('No', 'dutchie-menus'),
'return_value' => 'yes',
'default' => 'yes',
]
);
$this->add_control(
'show_price',
[
'label' => __('Show Price', 'dutchie-menus'),
'type' => \Elementor\Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'dutchie-menus'),
'label_off' => __('No', 'dutchie-menus'),
'return_value' => 'yes',
'default' => 'yes',
]
);
$this->add_control(
'show_description',
[
'label' => __('Show Description', 'dutchie-menus'),
'type' => \Elementor\Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'dutchie-menus'),
'label_off' => __('No', 'dutchie-menus'),
'return_value' => 'yes',
'default' => 'yes',
]
);
$this->add_control(
'show_thc',
[
'label' => __('Show THC', 'dutchie-menus'),
'type' => \Elementor\Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'dutchie-menus'),
'label_off' => __('No', 'dutchie-menus'),
'return_value' => 'yes',
'default' => 'yes',
]
);
$this->add_control(
'show_cbd',
[
'label' => __('Show CBD', 'dutchie-menus'),
'type' => \Elementor\Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'dutchie-menus'),
'label_off' => __('No', 'dutchie-menus'),
'return_value' => 'yes',
'default' => 'yes',
]
);
$this->add_control(
'show_terpenes',
[
'label' => __('Show Terpenes', 'dutchie-menus'),
'type' => \Elementor\Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'dutchie-menus'),
'label_off' => __('No', 'dutchie-menus'),
'return_value' => 'yes',
'default' => 'yes',
]
);
$this->add_control(
'show_effects',
[
'label' => __('Show Effects', 'dutchie-menus'),
'type' => \Elementor\Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'dutchie-menus'),
'label_off' => __('No', 'dutchie-menus'),
'return_value' => 'yes',
'default' => 'yes',
]
);
$this->add_control(
'show_brand',
[
'label' => __('Show Brand', 'dutchie-menus'),
'type' => \Elementor\Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'dutchie-menus'),
'label_off' => __('No', 'dutchie-menus'),
'return_value' => 'yes',
'default' => 'yes',
]
);
$this->end_controls_section();
// Style Section
$this->start_controls_section(
'style_section',
[
'label' => __('Style', 'dutchie-menus'),
'tab' => \Elementor\Controls_Manager::TAB_STYLE,
]
);
$this->add_control(
'background_color',
[
'label' => __('Background Color', 'dutchie-menus'),
'type' => \Elementor\Controls_Manager::COLOR,
'default' => '#ffffff',
'selectors' => [
'{{WRAPPER}} .dutchie-single-product' => 'background-color: {{VALUE}};',
],
]
);
$this->add_control(
'border_radius',
[
'label' => __('Border Radius', 'dutchie-menus'),
'type' => \Elementor\Controls_Manager::SLIDER,
'size_units' => ['px'],
'range' => [
'px' => [
'min' => 0,
'max' => 50,
],
],
'default' => [
'size' => 8,
],
'selectors' => [
'{{WRAPPER}} .dutchie-single-product' => 'border-radius: {{SIZE}}{{UNIT}};',
],
]
);
$this->add_control(
'text_color',
[
'label' => __('Text Color', 'dutchie-menus'),
'type' => \Elementor\Controls_Manager::COLOR,
'default' => '#333333',
'selectors' => [
'{{WRAPPER}} .dutchie-single-product' => 'color: {{VALUE}};',
],
]
);
$this->end_controls_section();
}
protected function render() {
$settings = $this->get_settings_for_display();
if (empty($settings['product_id'])) {
echo '<p>' . __('Please enter a product ID.', 'dutchie-menus') . '</p>';
return;
}
$plugin = Dutchie_Menus_Plugin::instance();
$product = $plugin->fetch_product($settings['product_id']);
if (!$product) {
echo '<p>' . __('Product not found.', 'dutchie-menus') . '</p>';
return;
}
$layout = $settings['layout'];
?>
<div class="dutchie-single-product dutchie-layout-<?php echo esc_attr($layout); ?>">
<?php if ($settings['show_image'] === 'yes' && !empty($product['image_url_full'])): ?>
<div class="dutchie-single-product-image">
<img src="<?php echo esc_url($product['image_url_full']); ?>"
alt="<?php echo esc_attr($product['name']); ?>" />
</div>
<?php endif; ?>
<div class="dutchie-single-product-details">
<h2 class="dutchie-single-product-title">
<?php echo esc_html($product['name']); ?>
</h2>
<?php if ($settings['show_brand'] === 'yes' && !empty($product['brand'])): ?>
<div class="dutchie-single-product-brand">
<strong><?php _e('Brand:', 'dutchie-menus'); ?></strong>
<?php echo esc_html($product['brand']); ?>
</div>
<?php endif; ?>
<?php if ($settings['show_description'] === 'yes' && !empty($product['description'])): ?>
<div class="dutchie-single-product-description">
<?php echo wp_kses_post(nl2br($product['description'])); ?>
</div>
<?php endif; ?>
<div class="dutchie-single-product-info">
<?php if ($settings['show_thc'] === 'yes' && !empty($product['metadata']['thc'])): ?>
<div class="dutchie-info-item">
<strong><?php _e('THC:', 'dutchie-menus'); ?></strong>
<span><?php echo esc_html($product['metadata']['thc']); ?></span>
</div>
<?php endif; ?>
<?php if ($settings['show_cbd'] === 'yes' && !empty($product['metadata']['cbd'])): ?>
<div class="dutchie-info-item">
<strong><?php _e('CBD:', 'dutchie-menus'); ?></strong>
<span><?php echo esc_html($product['metadata']['cbd']); ?></span>
</div>
<?php endif; ?>
<?php if ($settings['show_terpenes'] === 'yes' && !empty($product['metadata']['terpenes'])): ?>
<div class="dutchie-info-item dutchie-terpenes">
<strong><?php _e('Terpenes:', 'dutchie-menus'); ?></strong>
<?php if (is_array($product['metadata']['terpenes'])): ?>
<span><?php echo esc_html(implode(', ', $product['metadata']['terpenes'])); ?></span>
<?php else: ?>
<span><?php echo esc_html($product['metadata']['terpenes']); ?></span>
<?php endif; ?>
</div>
<?php endif; ?>
<?php if ($settings['show_effects'] === 'yes' && !empty($product['metadata']['effects'])): ?>
<div class="dutchie-info-item dutchie-effects">
<strong><?php _e('Effects:', 'dutchie-menus'); ?></strong>
<?php if (is_array($product['metadata']['effects'])): ?>
<span><?php echo esc_html(implode(', ', $product['metadata']['effects'])); ?></span>
<?php else: ?>
<span><?php echo esc_html($product['metadata']['effects']); ?></span>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
<?php if ($settings['show_price'] === 'yes' && isset($product['price'])): ?>
<div class="dutchie-single-product-price">
$<?php echo number_format($product['price'], 2); ?>
</div>
<?php endif; ?>
<?php if (!$product['in_stock']): ?>
<div class="dutchie-out-of-stock">
<?php _e('Out of Stock', 'dutchie-menus'); ?>
</div>
<?php endif; ?>
</div>
</div>
<?php
}
}