Files
cannaiq/wordpress-plugin/widgets/card-template-premium.php
Kelly c33ed1cae9
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
feat: CannaiQ Menus WordPress Plugin v2.0.0 - Modular Component Library
New modular component widgets:
- Discount Ribbon (ribbon/pill/text styles)
- Strain Badge (Sativa/Indica/Hybrid colored pills)
- THC/CBD Meter (progress bars or badges)
- Effects Display (styled chips with icons)
- Price Block (original + sale price)
- Cart Button (styled CTA linking to menu)
- Stock Indicator (in/out of stock badges)
- Product Image + Badges (image with overlays)

New card template:
- Premium Product Card (ready-to-use template)

Extended dynamic tags (30+ total):
- Discount %, Strain Badge, THC/CBD Badge
- Effects Chips, Terpenes, Price Display
- Menu URL, Stock Status, and more

New files:
- assets/css/components.css
- includes/effects-icons.php (SVG icons)
- 10 new widget files
- dynamic-tags-extended.php

Branding updated to "CannaiQ" throughout.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-17 00:21:40 -07:00

511 lines
19 KiB
PHP

<?php
/**
* CannaIQ Premium Card Template Widget
*
* Pre-built product card template showcasing all modular components.
* Includes: discount ribbon, product image with overlays, name, brand,
* effects, price block, and cart button.
*
* @package CannaIQ_Menus
* @since 2.0.0
*/
if (!defined('ABSPATH')) {
exit;
}
// Ensure effects icons are loaded
require_once dirname(__DIR__) . '/includes/effects-icons.php';
class CannaIQ_Premium_Card_Widget extends \Elementor\Widget_Base {
public function get_name() {
return 'cannaiq_premium_card';
}
public function get_title() {
return __('Premium Product Card', 'cannaiq-menus');
}
public function get_icon() {
return 'eicon-single-product';
}
public function get_categories() {
return ['cannaiq'];
}
public function get_keywords() {
return ['product', 'card', 'premium', 'template', 'cannaiq'];
}
protected function register_controls() {
// Content Section
$this->start_controls_section(
'content_section',
[
'label' => __('Content', 'cannaiq-menus'),
'tab' => \Elementor\Controls_Manager::TAB_CONTENT,
]
);
$this->add_control(
'content_note',
[
'type' => \Elementor\Controls_Manager::RAW_HTML,
'raw' => __('This card uses the current product context from Product Loop or Product Grid. Place it inside a CannaiQ Product Loop widget.', 'cannaiq-menus'),
'content_classes' => 'elementor-panel-alert elementor-panel-alert-info',
]
);
$this->end_controls_section();
// Components Section
$this->start_controls_section(
'components_section',
[
'label' => __('Components', 'cannaiq-menus'),
'tab' => \Elementor\Controls_Manager::TAB_CONTENT,
]
);
$this->add_control(
'show_discount',
[
'label' => __('Show Discount Ribbon', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'cannaiq-menus'),
'label_off' => __('No', 'cannaiq-menus'),
'return_value' => 'yes',
'default' => 'yes',
]
);
$this->add_control(
'show_image',
[
'label' => __('Show Product Image', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'cannaiq-menus'),
'label_off' => __('No', 'cannaiq-menus'),
'return_value' => 'yes',
'default' => 'yes',
]
);
$this->add_control(
'show_strain_badge',
[
'label' => __('Show Strain Badge', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'cannaiq-menus'),
'label_off' => __('No', 'cannaiq-menus'),
'return_value' => 'yes',
'default' => 'yes',
]
);
$this->add_control(
'show_thc_badge',
[
'label' => __('Show THC Badge', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'cannaiq-menus'),
'label_off' => __('No', 'cannaiq-menus'),
'return_value' => 'yes',
'default' => 'yes',
]
);
$this->add_control(
'show_name',
[
'label' => __('Show Product Name', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'cannaiq-menus'),
'label_off' => __('No', 'cannaiq-menus'),
'return_value' => 'yes',
'default' => 'yes',
]
);
$this->add_control(
'show_brand',
[
'label' => __('Show Brand', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'cannaiq-menus'),
'label_off' => __('No', 'cannaiq-menus'),
'return_value' => 'yes',
'default' => 'yes',
]
);
$this->add_control(
'show_effects',
[
'label' => __('Show Effects', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'cannaiq-menus'),
'label_off' => __('No', 'cannaiq-menus'),
'return_value' => 'yes',
'default' => 'yes',
]
);
$this->add_control(
'effects_limit',
[
'label' => __('Effects Limit', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::NUMBER,
'default' => 3,
'min' => 1,
'max' => 5,
'condition' => [
'show_effects' => 'yes',
],
]
);
$this->add_control(
'show_price',
[
'label' => __('Show Price', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'cannaiq-menus'),
'label_off' => __('No', 'cannaiq-menus'),
'return_value' => 'yes',
'default' => 'yes',
]
);
$this->add_control(
'show_weight',
[
'label' => __('Show Weight', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'cannaiq-menus'),
'label_off' => __('No', 'cannaiq-menus'),
'return_value' => 'yes',
'default' => 'yes',
]
);
$this->add_control(
'show_cart_button',
[
'label' => __('Show Cart Button', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::SWITCHER,
'label_on' => __('Yes', 'cannaiq-menus'),
'label_off' => __('No', 'cannaiq-menus'),
'return_value' => 'yes',
'default' => 'yes',
]
);
$this->add_control(
'button_text',
[
'label' => __('Button Text', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::TEXT,
'default' => 'ADD TO CART',
'condition' => [
'show_cart_button' => 'yes',
],
]
);
$this->end_controls_section();
// Style Section
$this->start_controls_section(
'card_style_section',
[
'label' => __('Card Style', 'cannaiq-menus'),
'tab' => \Elementor\Controls_Manager::TAB_STYLE,
]
);
$this->add_control(
'card_background',
[
'label' => __('Background', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::COLOR,
'default' => '#ffffff',
'selectors' => [
'{{WRAPPER}} .cannaiq-product-card' => 'background-color: {{VALUE}};',
],
]
);
$this->add_control(
'card_border_radius',
[
'label' => __('Border Radius', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::SLIDER,
'size_units' => ['px'],
'range' => [
'px' => [
'min' => 0,
'max' => 30,
],
],
'default' => [
'size' => 12,
],
'selectors' => [
'{{WRAPPER}} .cannaiq-product-card' => 'border-radius: {{SIZE}}{{UNIT}};',
],
]
);
$this->add_control(
'card_padding',
[
'label' => __('Padding', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::SLIDER,
'size_units' => ['px'],
'range' => [
'px' => [
'min' => 0,
'max' => 40,
],
],
'default' => [
'size' => 16,
],
'selectors' => [
'{{WRAPPER}} .cannaiq-product-card__body' => 'padding: {{SIZE}}{{UNIT}};',
],
]
);
$this->add_group_control(
\Elementor\Group_Control_Box_Shadow::get_type(),
[
'name' => 'card_shadow',
'selector' => '{{WRAPPER}} .cannaiq-product-card',
]
);
$this->end_controls_section();
// Typography Section
$this->start_controls_section(
'typography_section',
[
'label' => __('Typography', 'cannaiq-menus'),
'tab' => \Elementor\Controls_Manager::TAB_STYLE,
]
);
$this->add_group_control(
\Elementor\Group_Control_Typography::get_type(),
[
'name' => 'title_typography',
'label' => __('Title', 'cannaiq-menus'),
'selector' => '{{WRAPPER}} .cannaiq-product-card__title',
]
);
$this->add_control(
'title_color',
[
'label' => __('Title Color', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::COLOR,
'default' => '#1f2937',
'selectors' => [
'{{WRAPPER}} .cannaiq-product-card__title' => 'color: {{VALUE}};',
],
]
);
$this->add_control(
'brand_color',
[
'label' => __('Brand Color', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::COLOR,
'default' => '#6b7280',
'selectors' => [
'{{WRAPPER}} .cannaiq-product-card__brand' => 'color: {{VALUE}};',
],
]
);
$this->end_controls_section();
// Button Style Section
$this->start_controls_section(
'button_style_section',
[
'label' => __('Button Style', 'cannaiq-menus'),
'tab' => \Elementor\Controls_Manager::TAB_STYLE,
'condition' => [
'show_cart_button' => 'yes',
],
]
);
$this->add_control(
'button_background',
[
'label' => __('Background', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::COLOR,
'default' => '#1f2937',
'selectors' => [
'{{WRAPPER}} .cannaiq-cart-button' => 'background-color: {{VALUE}}; border-color: {{VALUE}};',
],
]
);
$this->add_control(
'button_text_color',
[
'label' => __('Text Color', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::COLOR,
'default' => '#ffffff',
'selectors' => [
'{{WRAPPER}} .cannaiq-cart-button' => 'color: {{VALUE}};',
],
]
);
$this->add_control(
'button_hover_background',
[
'label' => __('Hover Background', 'cannaiq-menus'),
'type' => \Elementor\Controls_Manager::COLOR,
'default' => '#374151',
'selectors' => [
'{{WRAPPER}} .cannaiq-cart-button:hover' => 'background-color: {{VALUE}}; border-color: {{VALUE}};',
],
]
);
$this->end_controls_section();
}
protected function render() {
$settings = $this->get_settings_for_display();
global $cannaiq_current_product;
$product = $cannaiq_current_product ?? [];
if (empty($product)) {
echo '<p>' . __('No product context available. Place this widget inside a Product Loop.', 'cannaiq-menus') . '</p>';
return;
}
// Extract product data
$name = $product['Name'] ?? $product['name'] ?? '';
$brand = $product['brand']['name'] ?? $product['brandName'] ?? $product['brand'] ?? '';
$image_url = $product['Image'] ?? $product['images'][0]['url'] ?? $product['image_url'] ?? '';
$strain_type = strtolower($product['strainType'] ?? $product['strain_type'] ?? '');
$thc = $product['THCContent']['range'][0] ?? $product['THC'] ?? $product['thc_percentage'] ?? null;
$weight = $product['Options'][0] ?? $product['rawOptions'][0] ?? $product['weight'] ?? '';
$menu_url = $product['menuUrl'] ?? $product['menu_url'] ?? $product['productUrl'] ?? '#';
// Price
$original_price = $product['Prices'][0] ?? $product['regular_price'] ?? null;
$sale_price = $product['specialPrice'] ?? $product['sale_price'] ?? null;
$is_on_sale = $sale_price && $sale_price > 0 && $sale_price < $original_price;
$discount_percent = 0;
if ($is_on_sale && $original_price > 0) {
$discount_percent = round((($original_price - $sale_price) / $original_price) * 100);
}
// Effects
$effects = $product['effects'] ?? [];
if (!empty($effects) && !isset($effects[0])) {
arsort($effects);
$effects = array_keys($effects);
}
$effects_limit = intval($settings['effects_limit']) ?: 3;
$effects = array_slice($effects, 0, $effects_limit);
// Strain colors
$strain_colors = [
'sativa' => '#22c55e',
'indica' => '#8b5cf6',
'hybrid' => '#f97316',
];
?>
<div class="cannaiq-product-card">
<!-- Image Section -->
<?php if ($settings['show_image'] === 'yes'): ?>
<div class="cannaiq-product-card__image">
<div class="cannaiq-product-image" style="aspect-ratio: 1;">
<?php if (!empty($image_url)): ?>
<img src="<?php echo esc_url($image_url); ?>" alt="<?php echo esc_attr($name); ?>" />
<?php endif; ?>
<!-- Discount Ribbon -->
<?php if ($settings['show_discount'] === 'yes' && $discount_percent > 0): ?>
<div class="cannaiq-product-image__overlay cannaiq-product-image__overlay--top-left">
<span class="cannaiq-discount-ribbon cannaiq-discount-ribbon--ribbon"><?php echo esc_html($discount_percent); ?>% OFF</span>
</div>
<?php endif; ?>
<!-- Bottom badges -->
<?php if (($settings['show_strain_badge'] === 'yes' && !empty($strain_type)) || ($settings['show_thc_badge'] === 'yes' && $thc > 0)): ?>
<div class="cannaiq-product-image__overlay cannaiq-product-image__overlay--bottom-left">
<div class="cannaiq-product-image__badges">
<?php if ($settings['show_strain_badge'] === 'yes' && !empty($strain_type) && in_array($strain_type, ['sativa', 'indica', 'hybrid'])): ?>
<span class="cannaiq-strain-badge cannaiq-strain-badge--pill" style="background-color: <?php echo esc_attr($strain_colors[$strain_type]); ?>; color: white;"><?php echo esc_html(strtoupper($strain_type)); ?></span>
<?php endif; ?>
<?php if ($settings['show_thc_badge'] === 'yes' && $thc > 0): ?>
<span class="cannaiq-potency-badge cannaiq-potency-badge--pill" style="background-color: #1f2937; color: white;"><?php echo esc_html(number_format((float)$thc, 1)); ?>% THC</span>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
<!-- Body Section -->
<div class="cannaiq-product-card__body">
<?php if ($settings['show_name'] === 'yes' && !empty($name)): ?>
<h3 class="cannaiq-product-card__title"><?php echo esc_html($name); ?></h3>
<?php endif; ?>
<?php if ($settings['show_brand'] === 'yes' && !empty($brand)): ?>
<p class="cannaiq-product-card__brand">by <?php echo esc_html($brand); ?></p>
<?php endif; ?>
<?php if ($settings['show_effects'] === 'yes' && !empty($effects)): ?>
<div style="margin: 12px 0;">
<?php echo cannaiq_render_effects($effects, ['limit' => $effects_limit, 'show_icon' => true, 'size' => 'small']); ?>
</div>
<?php endif; ?>
<!-- Footer -->
<div class="cannaiq-product-card__footer">
<?php if ($settings['show_price'] === 'yes' && $original_price > 0): ?>
<div class="cannaiq-price-block" style="margin-bottom: 12px;">
<?php if ($settings['show_weight'] === 'yes' && !empty($weight)): ?>
<span class="cannaiq-price-block__weight"><?php echo esc_html($weight); ?></span>
<?php endif; ?>
<?php if ($is_on_sale): ?>
<span class="cannaiq-price-block__original">$<?php echo esc_html(number_format((float)$original_price, 2)); ?></span>
<span class="cannaiq-price-block__sale">$<?php echo esc_html(number_format((float)$sale_price, 2)); ?></span>
<?php else: ?>
<span class="cannaiq-price-block__regular">$<?php echo esc_html(number_format((float)$original_price, 2)); ?></span>
<?php endif; ?>
</div>
<?php endif; ?>
<?php if ($settings['show_cart_button'] === 'yes'): ?>
<a href="<?php echo esc_url($menu_url); ?>" class="cannaiq-cart-button cannaiq-cart-button--solid cannaiq-cart-button--full" target="_blank" rel="noopener noreferrer">
<?php echo esc_html($settings['button_text']); ?>
</a>
<?php endif; ?>
</div>
</div>
</div>
<?php
}
}