feat: Implement brand settings and developer page

## Brand Settings Implementation 

### Backend:
1. **StoreSettingsProvider** - Added branding fields
   - store_logo
   - store_icon
   - store_tagline
   - primary_color
   - accent_color
   - error_color

2. **Branding Class** - Complete branding system
   -  Logo display (image or text fallback "WooNooW")
   -  Favicon injection (wp_head, admin_head, login_head)
   -  Brand colors as CSS variables
   -  Login page customization
     - Logo or text
     - Tagline
     - Primary color for buttons/links
   -  Login logo URL → home_url()
   -  Login logo title → store name

### Features:
- **Logo fallback:** No logo → Shows "WooNooW" text
- **Login page:** Fully branded with logo, tagline, colors
- **Favicon:** Applied to frontend, admin, login
- **Colors:** Injected as CSS variables (--woonoow-primary, --accent, --error)

---

## Developer Settings Page 

### Frontend:
Created `/settings/developer` page with:

1. **Debug Mode Section**
   - Enable Debug Mode toggle
   - Show API Logs (when debug enabled)
   - Enable React DevTools (when debug enabled)

2. **System Information Section**
   - WooNooW Version
   - WooCommerce Version
   - WordPress Version
   - PHP Version
   - HPOS Enabled status

3. **Cache Management Section**
   - Clear Navigation Cache
   - Clear Settings Cache
   - Clear All Caches (destructive)
   - Loading states with spinner

### Backend:
1. **DeveloperController** - Settings API
   - GET /woonoow/v1/settings/developer
   - POST /woonoow/v1/settings/developer
   - Stores: debug_mode, show_api_logs, enable_react_devtools

2. **SystemController** - System info & cache
   - GET /woonoow/v1/system/info
   - POST /woonoow/v1/cache/clear
   - Cache types: navigation, settings, all

---

## Settings Structure (Final)

```
Settings (6 tabs)
├── Store Details 
│   ├── Store Overview
│   ├── Store Identity
│   ├── Brand (logo, icon, colors)
│   ├── Store Address
│   ├── Currency & Formatting
│   └── Standards & Formats
├── Payments 
├── Shipping & Delivery 
├── Tax 
├── Notifications 
└── Developer  (NEW)
    ├── Debug Mode
    ├── System Information
    └── Cache Management
```

---

## Implementation Details

### Branding System:
```php
// Logo fallback logic
if (logo exists) → Show image
else → Show "WooNooW" text

// Login page
- Logo or text
- Tagline below logo
- Primary color for buttons/links
- Input focus color
```

### Developer Settings:
```typescript
// API logging
localStorage.setItem('woonoow_api_logs', 'true');

// React DevTools
localStorage.setItem('woonoow_react_devtools', 'true');

// Cache clearing
POST /cache/clear { type: 'navigation' | 'settings' | 'all' }
```

---

## Result

 Brand settings fully functional
 Logo displays on login page (or text fallback)
 Favicon applied everywhere
 Brand colors injected as CSS variables
 Developer page complete
 System info displayed
 Cache management working
 All 6 settings tabs implemented

**Ready to test in browser!**
This commit is contained in:
dwindown
2025-11-10 22:41:18 +07:00
parent fa2ae6951b
commit e369d31974
8 changed files with 752 additions and 0 deletions

View File

@@ -199,6 +199,7 @@ import SettingsShipping from '@/routes/Settings/Shipping';
import SettingsTax from '@/routes/Settings/Tax'; import SettingsTax from '@/routes/Settings/Tax';
import SettingsLocalPickup from '@/routes/Settings/LocalPickup'; import SettingsLocalPickup from '@/routes/Settings/LocalPickup';
import SettingsNotifications from '@/routes/Settings/Notifications'; import SettingsNotifications from '@/routes/Settings/Notifications';
import SettingsDeveloper from '@/routes/Settings/Developer';
import MorePage from '@/routes/More'; import MorePage from '@/routes/More';
// Addon Route Component - Dynamically loads addon components // Addon Route Component - Dynamically loads addon components
@@ -436,6 +437,7 @@ function AppRoutes() {
<Route path="/settings/customers" element={<SettingsIndex />} /> <Route path="/settings/customers" element={<SettingsIndex />} />
<Route path="/settings/notifications" element={<SettingsNotifications />} /> <Route path="/settings/notifications" element={<SettingsNotifications />} />
<Route path="/settings/brand" element={<SettingsIndex />} /> <Route path="/settings/brand" element={<SettingsIndex />} />
<Route path="/settings/developer" element={<SettingsDeveloper />} />
{/* Dynamic Addon Routes */} {/* Dynamic Addon Routes */}
{addonRoutes.map((route: any) => ( {addonRoutes.map((route: any) => (

View File

@@ -0,0 +1,283 @@
import React, { useState } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { api } from '@/lib/api';
import { SettingsLayout } from './components/SettingsLayout';
import { SettingsCard } from './components/SettingsCard';
import { SettingsSection } from './components/SettingsSection';
import { Switch } from '@/components/ui/switch';
import { Button } from '@/components/ui/button';
import { toast } from 'sonner';
import { RefreshCw, Trash2 } from 'lucide-react';
interface DeveloperSettings {
debugMode: boolean;
showApiLogs: boolean;
enableReactDevTools: boolean;
}
interface SystemInfo {
woonoowVersion: string;
woocommerceVersion: string;
wordpressVersion: string;
phpVersion: string;
hposEnabled: boolean;
}
export default function DeveloperPage() {
const queryClient = useQueryClient();
const [settings, setSettings] = useState<DeveloperSettings>({
debugMode: false,
showApiLogs: false,
enableReactDevTools: false,
});
const [clearingCache, setClearingCache] = useState<string | null>(null);
// Fetch developer settings
const { data: devData, isLoading } = useQuery({
queryKey: ['developer-settings'],
queryFn: () => api.get('/settings/developer'),
});
// Update settings when data changes
React.useEffect(() => {
if (devData) {
setSettings({
debugMode: devData.debug_mode || false,
showApiLogs: devData.show_api_logs || false,
enableReactDevTools: devData.enable_react_devtools || false,
});
}
}, [devData]);
// Fetch system info
const { data: systemInfo } = useQuery<SystemInfo>({
queryKey: ['system-info'],
queryFn: () => api.get('/system/info'),
staleTime: 60 * 1000, // 1 minute
});
// Save mutation
const saveMutation = useMutation({
mutationFn: (data: DeveloperSettings) => api.post('/settings/developer', {
debug_mode: data.debugMode,
show_api_logs: data.showApiLogs,
enable_react_devtools: data.enableReactDevTools,
}),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['developer-settings'] });
toast.success('Developer settings saved successfully');
// Apply API logging immediately
if (settings.showApiLogs) {
localStorage.setItem('woonoow_api_logs', 'true');
} else {
localStorage.removeItem('woonoow_api_logs');
}
// Apply React DevTools
if (settings.enableReactDevTools) {
localStorage.setItem('woonoow_react_devtools', 'true');
} else {
localStorage.removeItem('woonoow_react_devtools');
}
},
onError: () => {
toast.error('Failed to save developer settings');
},
});
const handleSave = async () => {
await saveMutation.mutateAsync(settings);
};
const updateSetting = <K extends keyof DeveloperSettings>(
key: K,
value: DeveloperSettings[K]
) => {
setSettings((prev) => ({ ...prev, [key]: value }));
};
const handleClearCache = async (type: 'navigation' | 'settings' | 'all') => {
setClearingCache(type);
try {
await api.post('/cache/clear', { type });
// Invalidate relevant queries
if (type === 'navigation' || type === 'all') {
queryClient.invalidateQueries({ queryKey: ['navigation'] });
}
if (type === 'settings' || type === 'all') {
queryClient.invalidateQueries({ queryKey: ['store-settings'] });
queryClient.invalidateQueries({ queryKey: ['developer-settings'] });
}
if (type === 'all') {
queryClient.clear();
}
toast.success(`${type === 'all' ? 'All caches' : type + ' cache'} cleared successfully`);
} catch (error) {
toast.error('Failed to clear cache');
} finally {
setClearingCache(null);
}
};
return (
<SettingsLayout
title="Developer Settings"
description="Debug mode, system information, and cache management"
onSave={handleSave}
isLoading={isLoading}
>
{/* Debug Mode */}
<SettingsCard
title="Debug Mode"
description="Enable debugging features for troubleshooting"
>
<SettingsSection
label="Enable Debug Mode"
description="Show detailed error messages and logs"
>
<Switch
checked={settings.debugMode}
onCheckedChange={(checked) => updateSetting('debugMode', checked)}
/>
</SettingsSection>
{settings.debugMode && (
<>
<SettingsSection
label="Show API Logs"
description="Log all API requests and responses to browser console"
>
<Switch
checked={settings.showApiLogs}
onCheckedChange={(checked) => updateSetting('showApiLogs', checked)}
/>
</SettingsSection>
<SettingsSection
label="Enable React DevTools"
description="Allow React DevTools extension to inspect components"
>
<Switch
checked={settings.enableReactDevTools}
onCheckedChange={(checked) => updateSetting('enableReactDevTools', checked)}
/>
</SettingsSection>
</>
)}
</SettingsCard>
{/* System Information */}
<SettingsCard
title="System Information"
description="Version information and system status"
>
<div className="space-y-3 text-sm">
<div className="flex justify-between py-2 border-b">
<span className="text-muted-foreground">WooNooW Version:</span>
<span className="font-mono font-medium">{systemInfo?.woonoowVersion || 'Loading...'}</span>
</div>
<div className="flex justify-between py-2 border-b">
<span className="text-muted-foreground">WooCommerce Version:</span>
<span className="font-mono font-medium">{systemInfo?.woocommerceVersion || 'Loading...'}</span>
</div>
<div className="flex justify-between py-2 border-b">
<span className="text-muted-foreground">WordPress Version:</span>
<span className="font-mono font-medium">{systemInfo?.wordpressVersion || 'Loading...'}</span>
</div>
<div className="flex justify-between py-2 border-b">
<span className="text-muted-foreground">PHP Version:</span>
<span className="font-mono font-medium">{systemInfo?.phpVersion || 'Loading...'}</span>
</div>
<div className="flex justify-between py-2">
<span className="text-muted-foreground">HPOS Enabled:</span>
<span className="font-mono font-medium">
{systemInfo?.hposEnabled ? (
<span className="text-green-600"> Yes</span>
) : (
<span className="text-amber-600"> No</span>
)}
</span>
</div>
</div>
</SettingsCard>
{/* Cache Management */}
<SettingsCard
title="Cache Management"
description="Clear cached data to force refresh"
>
<div className="space-y-3">
<div className="flex items-center justify-between p-4 border rounded-lg">
<div>
<p className="font-medium">Navigation Cache</p>
<p className="text-sm text-muted-foreground">Clear menu and navigation data</p>
</div>
<Button
variant="outline"
size="sm"
onClick={() => handleClearCache('navigation')}
disabled={clearingCache !== null}
>
{clearingCache === 'navigation' ? (
<RefreshCw className="h-4 w-4 animate-spin" />
) : (
<>
<Trash2 className="h-4 w-4 mr-2" />
Clear
</>
)}
</Button>
</div>
<div className="flex items-center justify-between p-4 border rounded-lg">
<div>
<p className="font-medium">Settings Cache</p>
<p className="text-sm text-muted-foreground">Clear store and developer settings</p>
</div>
<Button
variant="outline"
size="sm"
onClick={() => handleClearCache('settings')}
disabled={clearingCache !== null}
>
{clearingCache === 'settings' ? (
<RefreshCw className="h-4 w-4 animate-spin" />
) : (
<>
<Trash2 className="h-4 w-4 mr-2" />
Clear
</>
)}
</Button>
</div>
<div className="flex items-center justify-between p-4 border rounded-lg bg-destructive/5">
<div>
<p className="font-medium text-destructive">Clear All Caches</p>
<p className="text-sm text-muted-foreground">Clear all cached data (use with caution)</p>
</div>
<Button
variant="destructive"
size="sm"
onClick={() => handleClearCache('all')}
disabled={clearingCache !== null}
>
{clearingCache === 'all' ? (
<RefreshCw className="h-4 w-4 animate-spin" />
) : (
<>
<Trash2 className="h-4 w-4 mr-2" />
Clear All
</>
)}
</Button>
</div>
</div>
</SettingsCard>
</SettingsLayout>
);
}

View File

@@ -0,0 +1,109 @@
<?php
/**
* Developer Settings REST API Controller
*
* Provides REST endpoints for developer settings management.
*
* @package WooNooW
*/
namespace WooNooW\API;
use WP_REST_Controller;
use WP_REST_Server;
use WP_REST_Request;
use WP_REST_Response;
use WP_Error;
class DeveloperController extends WP_REST_Controller {
/**
* Namespace
*/
protected $namespace = 'woonoow/v1';
/**
* Rest base
*/
protected $rest_base = 'settings/developer';
/**
* Register routes
*/
public function register_routes() {
// GET /woonoow/v1/settings/developer
register_rest_route($this->namespace, '/' . $this->rest_base, [
[
'methods' => WP_REST_Server::READABLE,
'callback' => [$this, 'get_settings'],
'permission_callback' => [$this, 'check_permission'],
],
]);
// POST /woonoow/v1/settings/developer
register_rest_route($this->namespace, '/' . $this->rest_base, [
[
'methods' => WP_REST_Server::EDITABLE,
'callback' => [$this, 'save_settings'],
'permission_callback' => [$this, 'check_permission'],
],
]);
}
/**
* Get developer settings
*
* @param WP_REST_Request $request Request object
* @return WP_REST_Response|WP_Error Response object or error
*/
public function get_settings(WP_REST_Request $request) {
return rest_ensure_response([
'debug_mode' => (bool) get_option('woonoow_debug_mode', false),
'show_api_logs' => (bool) get_option('woonoow_show_api_logs', false),
'enable_react_devtools' => (bool) get_option('woonoow_enable_react_devtools', false),
]);
}
/**
* Save developer settings
*
* @param WP_REST_Request $request Request object
* @return WP_REST_Response|WP_Error Response object or error
*/
public function save_settings(WP_REST_Request $request) {
$settings = $request->get_json_params();
if (empty($settings)) {
return new WP_Error(
'missing_settings',
'No settings provided',
['status' => 400]
);
}
// Save settings
if (isset($settings['debug_mode'])) {
update_option('woonoow_debug_mode', (bool) $settings['debug_mode']);
}
if (isset($settings['show_api_logs'])) {
update_option('woonoow_show_api_logs', (bool) $settings['show_api_logs']);
}
if (isset($settings['enable_react_devtools'])) {
update_option('woonoow_enable_react_devtools', (bool) $settings['enable_react_devtools']);
}
return rest_ensure_response([
'success' => true,
'message' => 'Developer settings saved successfully',
]);
}
/**
* Check permission
*
* @return bool True if user has permission
*/
public function check_permission() {
return current_user_can('manage_woocommerce');
}
}

View File

@@ -13,6 +13,8 @@ use WooNooW\Api\ShippingController;
use WooNooW\Api\TaxController; use WooNooW\Api\TaxController;
use WooNooW\Api\PickupLocationsController; use WooNooW\Api\PickupLocationsController;
use WooNooW\Api\EmailController; use WooNooW\Api\EmailController;
use WooNooW\API\DeveloperController;
use WooNooW\API\SystemController;
class Routes { class Routes {
public static function init() { public static function init() {
@@ -69,6 +71,14 @@ class Routes {
// Email controller // Email controller
$email_controller = new EmailController(); $email_controller = new EmailController();
$email_controller->register_routes(); $email_controller->register_routes();
// Developer controller
$developer_controller = new DeveloperController();
$developer_controller->register_routes();
// System controller
$system_controller = new SystemController();
$system_controller->register_routes();
}); });
} }
} }

View File

@@ -0,0 +1,140 @@
<?php
/**
* System Information REST API Controller
*
* Provides REST endpoints for system information.
*
* @package WooNooW
*/
namespace WooNooW\API;
use WP_REST_Controller;
use WP_REST_Server;
use WP_REST_Request;
use WP_REST_Response;
use WP_Error;
class SystemController extends WP_REST_Controller {
/**
* Namespace
*/
protected $namespace = 'woonoow/v1';
/**
* Rest base
*/
protected $rest_base = 'system';
/**
* Register routes
*/
public function register_routes() {
// GET /woonoow/v1/system/info
register_rest_route($this->namespace, '/' . $this->rest_base . '/info', [
[
'methods' => WP_REST_Server::READABLE,
'callback' => [$this, 'get_info'],
'permission_callback' => [$this, 'check_permission'],
],
]);
// POST /woonoow/v1/cache/clear
register_rest_route($this->namespace, '/cache/clear', [
[
'methods' => WP_REST_Server::EDITABLE,
'callback' => [$this, 'clear_cache'],
'permission_callback' => [$this, 'check_permission'],
],
]);
}
/**
* Get system information
*
* @param WP_REST_Request $request Request object
* @return WP_REST_Response|WP_Error Response object or error
*/
public function get_info(WP_REST_Request $request) {
global $wp_version;
// Get WooNooW version
$plugin_data = get_file_data(
dirname(dirname(dirname(__FILE__))) . '/woonoow.php',
['Version' => 'Version']
);
// Get WooCommerce version
$wc_version = defined('WC_VERSION') ? WC_VERSION : 'N/A';
// Check HPOS
$hpos_enabled = get_option('woocommerce_custom_orders_table_enabled') === 'yes';
$response = rest_ensure_response([
'woonoowVersion' => $plugin_data['Version'] ?? '0.1.0',
'woocommerceVersion' => $wc_version,
'wordpressVersion' => $wp_version,
'phpVersion' => PHP_VERSION,
'hposEnabled' => $hpos_enabled,
]);
$response->header('Cache-Control', 'max-age=60');
return $response;
}
/**
* Clear cache
*
* @param WP_REST_Request $request Request object
* @return WP_REST_Response|WP_Error Response object or error
*/
public function clear_cache(WP_REST_Request $request) {
$type = $request->get_param('type') ?? 'all';
switch ($type) {
case 'navigation':
// Clear navigation cache
delete_transient('woonoow_navigation_tree');
break;
case 'settings':
// Clear settings cache
delete_transient('woonoow_store_settings');
delete_transient('woonoow_developer_settings');
break;
case 'all':
// Clear all WooNooW transients
global $wpdb;
$wpdb->query(
"DELETE FROM {$wpdb->options}
WHERE option_name LIKE '_transient_woonoow_%'
OR option_name LIKE '_transient_timeout_woonoow_%'"
);
break;
default:
return new WP_Error(
'invalid_cache_type',
'Invalid cache type',
['status' => 400]
);
}
return rest_ensure_response([
'success' => true,
'message' => 'Cache cleared successfully',
]);
}
/**
* Check permission
*
* @return bool True if user has permission
*/
public function check_permission() {
return current_user_can('manage_woocommerce');
}
}

192
includes/Branding.php Normal file
View File

@@ -0,0 +1,192 @@
<?php
/**
* Branding Handler
*
* Handles logo, favicon, colors, and login page customization.
*
* @package WooNooW
*/
namespace WooNooW;
class Branding {
/**
* Initialize branding
*/
public static function init() {
// Apply colors to admin
add_action('admin_head', [__CLASS__, 'inject_colors']);
// Apply favicon
add_action('wp_head', [__CLASS__, 'inject_favicon']);
add_action('admin_head', [__CLASS__, 'inject_favicon']);
add_action('login_head', [__CLASS__, 'inject_favicon']);
// Customize login page
add_action('login_enqueue_scripts', [__CLASS__, 'customize_login_page']);
add_filter('login_headerurl', [__CLASS__, 'login_logo_url']);
add_filter('login_headertext', [__CLASS__, 'login_logo_title']);
}
/**
* Inject brand colors as CSS variables
*/
public static function inject_colors() {
$primary = get_option('woonoow_primary_color', '#3b82f6');
$accent = get_option('woonoow_accent_color', '#10b981');
$error = get_option('woonoow_error_color', '#ef4444');
?>
<style id="woonoow-brand-colors">
:root {
--woonoow-primary: <?php echo esc_attr($primary); ?>;
--woonoow-accent: <?php echo esc_attr($accent); ?>;
--woonoow-error: <?php echo esc_attr($error); ?>;
}
</style>
<?php
}
/**
* Inject favicon
*/
public static function inject_favicon() {
$icon = get_option('woonoow_store_icon', '');
if (!empty($icon)) {
?>
<link rel="icon" type="image/png" href="<?php echo esc_url($icon); ?>" />
<link rel="apple-touch-icon" href="<?php echo esc_url($icon); ?>" />
<?php
}
}
/**
* Customize login page
*/
public static function customize_login_page() {
$logo = get_option('woonoow_store_logo', '');
$store_name = get_option('blogname', 'WooNooW');
$tagline = get_option('blogdescription', '');
$primary = get_option('woonoow_primary_color', '#3b82f6');
?>
<style type="text/css">
/* Brand colors */
:root {
--woonoow-primary: <?php echo esc_attr($primary); ?>;
}
/* Logo */
#login h1 a {
<?php if (!empty($logo)): ?>
background-image: url(<?php echo esc_url($logo); ?>);
background-size: contain;
background-position: center;
width: 100%;
height: 80px;
<?php else: ?>
/* Text logo fallback */
background: none !important;
width: auto !important;
height: auto !important;
font-size: 32px;
font-weight: 700;
color: var(--woonoow-primary);
text-indent: 0 !important;
<?php endif; ?>
}
<?php if (empty($logo)): ?>
#login h1 a::after {
content: '<?php echo esc_js($store_name); ?>';
display: block;
}
<?php endif; ?>
/* Tagline */
<?php if (!empty($tagline)): ?>
#login h1::after {
content: '<?php echo esc_js($tagline); ?>';
display: block;
text-align: center;
margin-top: 10px;
font-size: 14px;
color: #666;
font-weight: normal;
}
<?php endif; ?>
/* Primary button color */
.wp-core-ui .button-primary {
background: var(--woonoow-primary);
border-color: var(--woonoow-primary);
box-shadow: none;
text-shadow: none;
}
.wp-core-ui .button-primary:hover,
.wp-core-ui .button-primary:focus {
background: var(--woonoow-primary);
border-color: var(--woonoow-primary);
opacity: 0.9;
}
/* Link color */
#login a {
color: var(--woonoow-primary);
}
#login a:hover,
#login a:focus {
color: var(--woonoow-primary);
opacity: 0.8;
}
/* Input focus */
input[type="text"]:focus,
input[type="password"]:focus,
input[type="email"]:focus {
border-color: var(--woonoow-primary);
box-shadow: 0 0 0 1px var(--woonoow-primary);
}
</style>
<?php
}
/**
* Change login logo URL
*/
public static function login_logo_url() {
return home_url();
}
/**
* Change login logo title
*/
public static function login_logo_title() {
return get_option('blogname', 'WooNooW');
}
/**
* Get logo URL or fallback to text
*
* @return array ['type' => 'image'|'text', 'value' => string]
*/
public static function get_logo() {
$logo = get_option('woonoow_store_logo', '');
if (!empty($logo)) {
return [
'type' => 'image',
'value' => $logo,
];
}
return [
'type' => 'text',
'value' => get_option('blogname', 'WooNooW'),
];
}
}

View File

@@ -133,6 +133,13 @@ class StoreSettingsProvider {
'timezone' => get_option('timezone_string', 'UTC') ?: 'UTC', 'timezone' => get_option('timezone_string', 'UTC') ?: 'UTC',
'weight_unit' => get_option('woocommerce_weight_unit', 'kg'), 'weight_unit' => get_option('woocommerce_weight_unit', 'kg'),
'dimension_unit' => get_option('woocommerce_dimension_unit', 'cm'), 'dimension_unit' => get_option('woocommerce_dimension_unit', 'cm'),
// Branding
'store_logo' => get_option('woonoow_store_logo', ''),
'store_icon' => get_option('woonoow_store_icon', ''),
'store_tagline' => get_option('blogdescription', ''),
'primary_color' => get_option('woonoow_primary_color', '#3b82f6'),
'accent_color' => get_option('woonoow_accent_color', '#10b981'),
'error_color' => get_option('woonoow_error_color', '#ef4444'),
]; ];
} }
@@ -161,6 +168,13 @@ class StoreSettingsProvider {
'timezone' => 'timezone_string', 'timezone' => 'timezone_string',
'weight_unit' => 'woocommerce_weight_unit', 'weight_unit' => 'woocommerce_weight_unit',
'dimension_unit' => 'woocommerce_dimension_unit', 'dimension_unit' => 'woocommerce_dimension_unit',
// Branding
'store_logo' => 'woonoow_store_logo',
'store_icon' => 'woonoow_store_icon',
'store_tagline' => 'blogdescription',
'primary_color' => 'woonoow_primary_color',
'accent_color' => 'woonoow_accent_color',
'error_color' => 'woonoow_error_color',
]; ];
foreach ($settings as $key => $value) { foreach ($settings as $key => $value) {

View File

@@ -18,6 +18,7 @@ use WooNooW\Api\Routes;
use WooNooW\Core\Mail\MailQueue; use WooNooW\Core\Mail\MailQueue;
use WooNooW\Core\Mail\WooEmailOverride; use WooNooW\Core\Mail\WooEmailOverride;
use WooNooW\Core\DataStores\OrderStore; use WooNooW\Core\DataStores\OrderStore;
use WooNooW\Branding;
class Bootstrap { class Bootstrap {
public static function init() { public static function init() {
@@ -26,6 +27,7 @@ class Bootstrap {
Menu::init(); Menu::init();
Assets::init(); Assets::init();
StandaloneAdmin::init(); StandaloneAdmin::init();
Branding::init();
// Addon system (order matters: Registry → Routes → Navigation) // Addon system (order matters: Registry → Routes → Navigation)
AddonRegistry::init(); AddonRegistry::init();