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);