diff --git a/wordpress-plugin/VERSION b/wordpress-plugin/VERSION index 94fe62c2..dc1e644a 100644 --- a/wordpress-plugin/VERSION +++ b/wordpress-plugin/VERSION @@ -1 +1 @@ -1.5.4 +1.6.0 diff --git a/wordpress-plugin/assets/css/cannaiq-menus.css b/wordpress-plugin/assets/css/cannaiq-menus.css index 6768181b..dfbd7665 100644 --- a/wordpress-plugin/assets/css/cannaiq-menus.css +++ b/wordpress-plugin/assets/css/cannaiq-menus.css @@ -312,3 +312,184 @@ border-radius: 4px; border-left: 4px solid #c62828; } + +/* ======================================== + Brand Grid Widget + ======================================== */ +.cannaiq-brand-grid { + display: grid; + gap: 20px; + margin: 20px 0; +} + +.cannaiq-brand-card { + background: #fff; + border-radius: 8px; + padding: 20px; + text-align: center; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + transition: transform 0.2s, box-shadow 0.2s; +} + +.cannaiq-brand-card:hover { + transform: translateY(-4px); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); +} + +.cannaiq-brand-name { + font-size: 16px; + font-weight: 600; + margin: 0 0 8px 0; + color: #333; +} + +.cannaiq-brand-count { + font-size: 13px; + color: #666; +} + +/* ======================================== + Category List Widget + ======================================== */ +.cannaiq-category-grid { + display: grid; + gap: 16px; + margin: 20px 0; +} + +.cannaiq-category-list { + display: flex; + flex-direction: column; + gap: 8px; + margin: 20px 0; +} + +.cannaiq-category-pills { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin: 20px 0; +} + +.cannaiq-category-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 16px; + background: #fff; + border-radius: 8px; + text-decoration: none; + color: #333; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08); + transition: background 0.2s, transform 0.2s; +} + +.cannaiq-category-item:hover { + background: #f3f4f6; + transform: translateX(4px); +} + +.cannaiq-category-pills-item { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 8px 16px; + background: #f3f4f6; + border-radius: 20px; + text-decoration: none; + color: #333; + font-size: 14px; + transition: background 0.2s; +} + +.cannaiq-category-pills-item:hover { + background: #e5e7eb; +} + +.cannaiq-category-name { + font-weight: 500; +} + +.cannaiq-category-count { + font-size: 13px; + color: #666; +} + +/* ======================================== + Specials/Deals Grid Widget + ======================================== */ +.cannaiq-specials-grid { + display: grid; + gap: 24px; + margin: 20px 0; +} + +.cannaiq-special-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; + position: relative; +} + +.cannaiq-special-card:hover { + transform: translateY(-4px); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15); +} + +.cannaiq-discount-badge { + position: absolute; + top: 12px; + right: 12px; + background: #ef4444; + color: #fff; + font-size: 13px; + font-weight: 700; + padding: 4px 10px; + border-radius: 4px; + z-index: 1; +} + +.cannaiq-special-image { + width: 100%; + aspect-ratio: 1; + overflow: hidden; + background: #f5f5f5; +} + +.cannaiq-special-image img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.cannaiq-special-content { + padding: 16px; +} + +.cannaiq-special-title { + font-size: 16px; + font-weight: 600; + margin: 0 0 8px 0; + color: #333; + line-height: 1.4; +} + +.cannaiq-special-price { + display: flex; + align-items: center; + gap: 8px; + margin-top: 12px; +} + +.cannaiq-special-price .cannaiq-price-sale { + font-size: 20px; + font-weight: 700; + color: #16a34a; +} + +.cannaiq-special-price .cannaiq-price-regular { + font-size: 14px; + color: #999; +} diff --git a/wordpress-plugin/cannaiq-menus.php b/wordpress-plugin/cannaiq-menus.php index 8c2fc63e..f8ac5d7a 100644 --- a/wordpress-plugin/cannaiq-menus.php +++ b/wordpress-plugin/cannaiq-menus.php @@ -3,7 +3,7 @@ * Plugin Name: CannaIQ Menus * Plugin URI: https://cannaiq.co * Description: Display cannabis product menus from CannaIQ with Elementor integration. Real-time menu data updated daily. - * Version: 1.5.4 + * Version: 1.6.0 * Author: CannaIQ * Author URI: https://cannaiq.co * License: GPL v2 or later @@ -15,7 +15,7 @@ if (!defined('ABSPATH')) { exit; // Exit if accessed directly } -define('CANNAIQ_MENUS_VERSION', '1.5.4'); +define('CANNAIQ_MENUS_VERSION', '1.6.0'); define('CANNAIQ_MENUS_API_URL', 'https://cannaiq.co/api/v1'); define('CANNAIQ_MENUS_PLUGIN_DIR', plugin_dir_path(__FILE__)); define('CANNAIQ_MENUS_PLUGIN_URL', plugin_dir_url(__FILE__)); @@ -62,9 +62,15 @@ class CannaIQ_Menus_Plugin { public function register_elementor_widgets($widgets_manager) { require_once CANNAIQ_MENUS_PLUGIN_DIR . 'widgets/product-grid.php'; require_once CANNAIQ_MENUS_PLUGIN_DIR . 'widgets/single-product.php'; + require_once CANNAIQ_MENUS_PLUGIN_DIR . 'widgets/brand-grid.php'; + require_once CANNAIQ_MENUS_PLUGIN_DIR . 'widgets/category-list.php'; + require_once CANNAIQ_MENUS_PLUGIN_DIR . 'widgets/specials-grid.php'; $widgets_manager->register(new \CannaIQ_Menus_Product_Grid_Widget()); $widgets_manager->register(new \CannaIQ_Menus_Single_Product_Widget()); + $widgets_manager->register(new \CannaIQ_Menus_Brand_Grid_Widget()); + $widgets_manager->register(new \CannaIQ_Menus_Category_List_Widget()); + $widgets_manager->register(new \CannaIQ_Menus_Specials_Grid_Widget()); } /** @@ -392,6 +398,152 @@ class CannaIQ_Menus_Plugin { return $data['product'] ?? false; } + + /** + * Fetch Categories from API + */ + public function fetch_categories($args = []) { + $api_token = get_option('cannaiq_api_token'); + + if (!$api_token) { + return false; + } + + $query_args = http_build_query($args); + $url = CANNAIQ_MENUS_API_URL . '/categories' . ($query_args ? '?' . $query_args : ''); + + $response = wp_remote_get($url, [ + 'headers' => [ + 'X-API-Key' => $api_token + ], + 'timeout' => 30 + ]); + + if (is_wp_error($response)) { + return false; + } + + $body = wp_remote_retrieve_body($response); + $data = json_decode($body, true); + + return $data['categories'] ?? false; + } + + /** + * Fetch Brands from API + */ + public function fetch_brands($args = []) { + $api_token = get_option('cannaiq_api_token'); + + if (!$api_token) { + return false; + } + + $query_args = http_build_query($args); + $url = CANNAIQ_MENUS_API_URL . '/brands' . ($query_args ? '?' . $query_args : ''); + + $response = wp_remote_get($url, [ + 'headers' => [ + 'X-API-Key' => $api_token + ], + 'timeout' => 30 + ]); + + if (is_wp_error($response)) { + return false; + } + + $body = wp_remote_retrieve_body($response); + $data = json_decode($body, true); + + return $data['brands'] ?? false; + } + + /** + * Fetch Specials/Deals from API + */ + public function fetch_specials($args = []) { + $api_token = get_option('cannaiq_api_token'); + + if (!$api_token) { + return false; + } + + $query_args = http_build_query($args); + $url = CANNAIQ_MENUS_API_URL . '/specials' . ($query_args ? '?' . $query_args : ''); + + $response = wp_remote_get($url, [ + 'headers' => [ + 'X-API-Key' => $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; + } + + /** + * Get categories as options for Elementor select control + * Returns cached results for performance + */ + public function get_category_options() { + $cache_key = 'cannaiq_category_options'; + $cached = get_transient($cache_key); + + if ($cached !== false) { + return $cached; + } + + $categories = $this->fetch_categories(); + $options = ['' => __('All Categories', 'cannaiq-menus')]; + + if ($categories) { + foreach ($categories as $cat) { + $name = $cat['type'] ?? $cat['name'] ?? ''; + if ($name) { + $options[$name] = ucwords(str_replace('_', ' ', $name)); + } + } + } + + set_transient($cache_key, $options, 5 * MINUTE_IN_SECONDS); + return $options; + } + + /** + * Get brands as options for Elementor select control + * Returns cached results for performance + */ + public function get_brand_options() { + $cache_key = 'cannaiq_brand_options'; + $cached = get_transient($cache_key); + + if ($cached !== false) { + return $cached; + } + + $brands = $this->fetch_brands(['limit' => 200]); + $options = ['' => __('All Brands', 'cannaiq-menus')]; + + if ($brands) { + foreach ($brands as $brand) { + $name = $brand['brand'] ?? $brand['brand_name'] ?? ''; + if ($name) { + $options[$name] = $name; + } + } + } + + set_transient($cache_key, $options, 5 * MINUTE_IN_SECONDS); + return $options; + } } // Initialize Plugin diff --git a/wordpress-plugin/widgets/brand-grid.php b/wordpress-plugin/widgets/brand-grid.php new file mode 100644 index 00000000..d16dbe74 --- /dev/null +++ b/wordpress-plugin/widgets/brand-grid.php @@ -0,0 +1,184 @@ +start_controls_section( + 'content_section', + [ + 'label' => __('Content', 'cannaiq-menus'), + 'tab' => \Elementor\Controls_Manager::TAB_CONTENT, + ] + ); + + $this->add_control( + 'limit', + [ + 'label' => __('Number of Brands', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::NUMBER, + 'default' => 12, + 'min' => 1, + 'max' => 100, + ] + ); + + $this->add_control( + 'columns', + [ + 'label' => __('Columns', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::SELECT, + 'default' => '4', + 'options' => [ + '2' => __('2 Columns', 'cannaiq-menus'), + '3' => __('3 Columns', 'cannaiq-menus'), + '4' => __('4 Columns', 'cannaiq-menus'), + '6' => __('6 Columns', 'cannaiq-menus'), + ], + ] + ); + + $this->add_control( + 'show_product_count', + [ + 'label' => __('Show Product Count', '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( + 'link_to_products', + [ + 'label' => __('Link to Products Page', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::URL, + 'placeholder' => __('/products', 'cannaiq-menus'), + 'description' => __('Brand name will be appended as ?brand=Name', 'cannaiq-menus'), + ] + ); + + $this->end_controls_section(); + + // Style Section + $this->start_controls_section( + 'style_section', + [ + 'label' => __('Style', 'cannaiq-menus'), + 'tab' => \Elementor\Controls_Manager::TAB_STYLE, + ] + ); + + $this->add_control( + 'card_background', + [ + 'label' => __('Card Background', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::COLOR, + 'default' => '#ffffff', + 'selectors' => [ + '{{WRAPPER}} .cannaiq-brand-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' => 50, + ], + ], + 'default' => [ + 'size' => 8, + ], + 'selectors' => [ + '{{WRAPPER}} .cannaiq-brand-card' => 'border-radius: {{SIZE}}{{UNIT}};', + ], + ] + ); + + $this->add_control( + 'text_color', + [ + 'label' => __('Text Color', 'cannaiq-menus'), + 'type' => \Elementor\Controls_Manager::COLOR, + 'default' => '#333333', + 'selectors' => [ + '{{WRAPPER}} .cannaiq-brand-card' => 'color: {{VALUE}};', + ], + ] + ); + + $this->end_controls_section(); + } + + protected function render() { + $settings = $this->get_settings_for_display(); + + $plugin = CannaIQ_Menus_Plugin::instance(); + $brands = $plugin->fetch_brands(['limit' => $settings['limit']]); + + if (!$brands) { + echo '
' . __('No brands found.', 'cannaiq-menus') . '
'; + return; + } + + $columns = $settings['columns']; + $link_base = $settings['link_to_products']['url'] ?? ''; + ?> +' . __('No categories found.', 'cannaiq-menus') . '
'; + return; + } + + $layout = $settings['layout']; + $columns = $settings['columns']; + $link_base = $settings['link_to_products']['url'] ?? ''; + + $container_class = 'cannaiq-category-' . $layout; + if ($layout === 'grid') { + $container_class .= ' cannaiq-grid-cols-' . $columns; + } + ?> +' . __('No specials found.', 'cannaiq-menus') . '
'; + return; + } + + $columns = $settings['columns']; + ?> +