diff --git a/admin-spa/src/routes/Settings/Notifications/ActivityLog.tsx b/admin-spa/src/routes/Settings/Notifications/ActivityLog.tsx index 89535fb..82a7070 100644 --- a/admin-spa/src/routes/Settings/Notifications/ActivityLog.tsx +++ b/admin-spa/src/routes/Settings/Notifications/ActivityLog.tsx @@ -143,7 +143,11 @@ export default function ActivityLog() { {__('Recent Activity')} - {data?.total ? `${data.total} ${__('notifications found')}` : __('Loading...')} + {isLoading + ? __('Loading...') + : data?.total + ? `${data.total} ${__('notifications found')}` + : __('No notifications recorded yet')} diff --git a/customer-spa/src/styles/theme.css b/customer-spa/src/styles/theme.css index 7e9a7cc..a805858 100644 --- a/customer-spa/src/styles/theme.css +++ b/customer-spa/src/styles/theme.css @@ -9,14 +9,14 @@ /* ======================================== * COLORS * ======================================== */ - + /* Brand Colors (injected from settings) */ --color-primary: #3B82F6; --color-secondary: #8B5CF6; --color-accent: #10B981; --color-background: #FFFFFF; --color-text: #1F2937; - + /* Color Shades (auto-generated) */ --color-primary-50: #EFF6FF; --color-primary-100: #DBEAFE; @@ -28,13 +28,13 @@ --color-primary-700: #1D4ED8; --color-primary-800: #1E40AF; --color-primary-900: #1E3A8A; - + /* Semantic Colors */ --color-success: #10B981; --color-warning: #F59E0B; --color-error: #EF4444; --color-info: #3B82F6; - + /* Neutral Colors */ --color-gray-50: #F9FAFB; --color-gray-100: #F3F4F6; @@ -46,16 +46,16 @@ --color-gray-700: #374151; --color-gray-800: #1F2937; --color-gray-900: #111827; - + /* ======================================== * TYPOGRAPHY * ======================================== */ - + /* Font Families (injected from settings) */ --font-heading: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; --font-body: 'Lora', Georgia, serif; --font-mono: 'IBM Plex Mono', 'Courier New', monospace; - + /* Font Weights */ --font-weight-heading: 700; --font-weight-body: 400; @@ -64,19 +64,29 @@ --font-weight-medium: 500; --font-weight-semibold: 600; --font-weight-bold: 700; - + /* Font Sizes (8px base scale) */ - --text-xs: 0.75rem; /* 12px */ - --text-sm: 0.875rem; /* 14px */ - --text-base: 1rem; /* 16px */ - --text-lg: 1.125rem; /* 18px */ - --text-xl: 1.25rem; /* 20px */ - --text-2xl: 1.5rem; /* 24px */ - --text-3xl: 1.875rem; /* 30px */ - --text-4xl: 2.25rem; /* 36px */ - --text-5xl: 3rem; /* 48px */ - --text-6xl: 3.75rem; /* 60px */ - + --text-xs: 0.75rem; + /* 12px */ + --text-sm: 0.875rem; + /* 14px */ + --text-base: 1rem; + /* 16px */ + --text-lg: 1.125rem; + /* 18px */ + --text-xl: 1.25rem; + /* 20px */ + --text-2xl: 1.5rem; + /* 24px */ + --text-3xl: 1.875rem; + /* 30px */ + --text-4xl: 2.25rem; + /* 36px */ + --text-5xl: 3rem; + /* 48px */ + --text-6xl: 3.75rem; + /* 60px */ + /* Line Heights */ --line-height-none: 1; --line-height-tight: 1.25; @@ -84,70 +94,87 @@ --line-height-normal: 1.5; --line-height-relaxed: 1.625; --line-height-loose: 2; - + /* ======================================== * SPACING (8px grid system) * ======================================== */ - + --space-0: 0; - --space-1: 0.5rem; /* 8px */ - --space-2: 1rem; /* 16px */ - --space-3: 1.5rem; /* 24px */ - --space-4: 2rem; /* 32px */ - --space-5: 2.5rem; /* 40px */ - --space-6: 3rem; /* 48px */ - --space-8: 4rem; /* 64px */ - --space-10: 5rem; /* 80px */ - --space-12: 6rem; /* 96px */ - --space-16: 8rem; /* 128px */ - --space-20: 10rem; /* 160px */ - --space-24: 12rem; /* 192px */ - + --space-1: 0.5rem; + /* 8px */ + --space-2: 1rem; + /* 16px */ + --space-3: 1.5rem; + /* 24px */ + --space-4: 2rem; + /* 32px */ + --space-5: 2.5rem; + /* 40px */ + --space-6: 3rem; + /* 48px */ + --space-8: 4rem; + /* 64px */ + --space-10: 5rem; + /* 80px */ + --space-12: 6rem; + /* 96px */ + --space-16: 8rem; + /* 128px */ + --space-20: 10rem; + /* 160px */ + --space-24: 12rem; + /* 192px */ + /* ======================================== * BORDER RADIUS * ======================================== */ - + --radius-none: 0; - --radius-sm: 0.25rem; /* 4px */ - --radius-md: 0.5rem; /* 8px */ - --radius-lg: 1rem; /* 16px */ - --radius-xl: 1.5rem; /* 24px */ - --radius-2xl: 2rem; /* 32px */ + --radius-sm: 0.25rem; + /* 4px */ + --radius-md: 0.5rem; + /* 8px */ + --radius-lg: 1rem; + /* 16px */ + --radius-xl: 1.5rem; + /* 24px */ + --radius-2xl: 2rem; + /* 32px */ --radius-full: 9999px; - + /* ======================================== * SHADOWS * ======================================== */ - + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); --shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25); --shadow-inner: inset 0 2px 4px 0 rgb(0 0 0 / 0.05); - + /* ======================================== * TRANSITIONS * ======================================== */ - + --transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1); --transition-base: 250ms cubic-bezier(0.4, 0, 0.2, 1); --transition-slow: 350ms cubic-bezier(0.4, 0, 0.2, 1); - + /* ======================================== * BREAKPOINTS (for reference in JS) * ======================================== */ - + --breakpoint-sm: 640px; --breakpoint-md: 768px; --breakpoint-lg: 1024px; --breakpoint-xl: 1280px; --breakpoint-2xl: 1536px; - + /* ======================================== * Z-INDEX SCALE * ======================================== */ - + --z-base: 0; --z-dropdown: 1000; --z-sticky: 1020; @@ -166,7 +193,7 @@ :root { --color-background: #1F2937; --color-text: #F9FAFB; - + /* Invert gray scale for dark mode */ --color-gray-50: #111827; --color-gray-100: #1F2937; @@ -205,7 +232,12 @@ body { background-color: var(--color-background); } -h1, h2, h3, h4, h5, h6 { +h1, +h2, +h3, +h4, +h5, +h6 { font-family: var(--font-heading); font-weight: var(--font-weight-heading); line-height: var(--line-height-tight); @@ -247,7 +279,7 @@ a:hover { } button { - font-family: var(--font-heading); + font-family: var(--font-body); cursor: pointer; } @@ -296,4 +328,4 @@ img { .container { max-width: 1536px; } -} +} \ No newline at end of file diff --git a/includes/Admin/Menu.php b/includes/Admin/Menu.php index a36ce9e..bc4662b 100644 --- a/includes/Admin/Menu.php +++ b/includes/Admin/Menu.php @@ -111,6 +111,21 @@ class Menu { 'title' => __( 'WooNooW Standalone Admin', 'woonoow' ), ], ] ); + + // Add Store link if customer SPA is not disabled + $customer_spa_enabled = get_option( 'woonoow_customer_spa_enabled', true ); + if ( $customer_spa_enabled ) { + $store_url = home_url( '/store/' ); + $wp_admin_bar->add_node( [ + 'id' => 'woonoow-store', + 'title' => '' . __( 'Store', 'woonoow' ) . '', + 'href' => $store_url, + 'meta' => [ + 'title' => __( 'View Customer Store', 'woonoow' ), + 'target' => '_blank', + ], + ] ); + } } } \ No newline at end of file diff --git a/includes/Api/ProductsController.php b/includes/Api/ProductsController.php index e7a0563..ed8847d 100644 --- a/includes/Api/ProductsController.php +++ b/includes/Api/ProductsController.php @@ -475,6 +475,29 @@ class ProductsController { $product->set_featured((bool) $data['featured']); } + // Downloadable files + if (isset($data['downloads']) && is_array($data['downloads'])) { + $wc_downloads = []; + foreach ($data['downloads'] as $download) { + if (!empty($download['file'])) { + $wc_downloads[] = [ + 'id' => $download['id'] ?? md5($download['file']), + 'name' => self::sanitize_text($download['name'] ?? ''), + 'file' => esc_url_raw($download['file']), + ]; + } + } + $product->set_downloads($wc_downloads); + } + if (isset($data['download_limit'])) { + $limit = $data['download_limit'] === '' ? -1 : (int) $data['download_limit']; + $product->set_download_limit($limit); + } + if (isset($data['download_expiry'])) { + $expiry = $data['download_expiry'] === '' ? -1 : (int) $data['download_expiry']; + $product->set_download_expiry($expiry); + } + // Categories if (isset($data['categories'])) { $product->set_category_ids($data['categories']); @@ -700,6 +723,21 @@ class ProductsController { $data['downloadable'] = $product->is_downloadable(); $data['featured'] = $product->is_featured(); + // Downloadable files + if ($product->is_downloadable()) { + $downloads = []; + foreach ($product->get_downloads() as $download_id => $download) { + $downloads[] = [ + 'id' => $download_id, + 'name' => $download->get_name(), + 'file' => $download->get_file(), + ]; + } + $data['downloads'] = $downloads; + $data['download_limit'] = $product->get_download_limit() !== -1 ? (string) $product->get_download_limit() : ''; + $data['download_expiry'] = $product->get_download_expiry() !== -1 ? (string) $product->get_download_expiry() : ''; + } + // Images array (URLs) for frontend - featured + gallery $images = []; $featured_image_id = $product->get_image_id(); diff --git a/includes/Frontend/AccountController.php b/includes/Frontend/AccountController.php index 92db591..5718e46 100644 --- a/includes/Frontend/AccountController.php +++ b/includes/Frontend/AccountController.php @@ -234,9 +234,8 @@ class AccountController { * Upload customer avatar */ public static function upload_avatar(WP_REST_Request $request) { - // Check if custom avatars are enabled - $settings = get_option('woonoow_customer_settings', []); - $allow_custom_avatar = $settings['allow_custom_avatar'] ?? false; + // Check if custom avatars are enabled (stored as 'yes' or 'no') + $allow_custom_avatar = get_option('woonoow_allow_custom_avatar', 'no') === 'yes'; if (!$allow_custom_avatar) { return new WP_Error('avatar_disabled', 'Custom avatars are not enabled', ['status' => 403]); @@ -358,10 +357,11 @@ class AccountController { */ public static function get_avatar_settings(WP_REST_Request $request) { $user_id = get_current_user_id(); - $settings = get_option('woonoow_customer_settings', []); + // Use correct option key (stored as 'yes' or 'no') + $allow_custom_avatar = get_option('woonoow_allow_custom_avatar', 'no') === 'yes'; return new WP_REST_Response([ - 'allow_custom_avatar' => $settings['allow_custom_avatar'] ?? false, + 'allow_custom_avatar' => $allow_custom_avatar, 'current_avatar' => get_user_meta($user_id, 'woonoow_custom_avatar', true) ?: null, 'gravatar_url' => get_avatar_url($user_id), ], 200);