fix: Login branding, submenu display, favicon standalone, notification strategy
## 1. Apply Logo to Standalone Login Screen ✅ **Issue:** Login page shows "WooNooW" text instead of brand logo **Fix:** - Fetch branding from `/store/settings` API - Display logo image if available - Fallback to store name text - Show tagline below logo - Use store name in footer **Result:** ```tsx {branding.logo ? ( <img src={branding.logo} alt={storeName} className="h-16" /> ) : ( <h1>{branding.storeName}</h1> )} {branding.tagline && <p>{branding.tagline}</p>} ``` --- ## 2. Fix Submenu Display Issue ✅ **Issue:** - Click Settings → redirects to Store Details ✓ - Settings submenu shows correctly ✓ - Click other settings pages → Dashboard submenu appears ✗ **Root Cause:** `useActiveSection` hook didn't recognize `/settings` path **Fix:** ```tsx // Special case: /settings should match settings section if (pathname === '/settings' || pathname.startsWith('/settings/')) { const settingsNode = navTree.find(n => n.key === 'settings'); if (settingsNode) return settingsNode; } ``` **Result:** Settings submenu now displays correctly on all settings pages --- ## 3. Apply Favicon in Standalone ✅ **Issue:** Favicon not showing in standalone mode (/admin) **Root Cause:** Standalone doesn't call `wp_head()`, so Branding class hooks don't run **Fix:** Added favicon directly to StandaloneAdmin.php ```php $icon = get_option('woonoow_store_icon', ''); if (!empty($icon)) { echo '<link rel="icon" href="' . esc_url($icon) . '">' echo '<link rel="apple-touch-icon" href="' . esc_url($icon) . '">' } ``` **Also:** Changed title to use store name dynamically --- ## 4. Notification Settings Strategy ✅ **Your Concern:** "We should not be optimistic the notification media is only email" **Agreed!** Created comprehensive strategy document: `NOTIFICATION_STRATEGY.md` ### Architecture: **Core (WooNooW):** - Notification events system - Email channel (built-in) - Addon integration framework - Settings UI with addon slots **Addons:** - WhatsApp - Telegram - SMS - Discord - Slack - Push notifications - etc. ### Settings Structure: ``` Notifications ├── Events (What to notify) │ ├── Order Placed │ │ ├── ✓ Email (to admin) │ │ ├── ✓ WhatsApp (to customer) [addon] │ │ └── ✗ Telegram [addon] │ └── Low Stock Alert │ ├── Channels (How to notify) │ ├── Email (Built-in) ✓ │ ├── WhatsApp [Addon] │ ├── Telegram [Addon] │ └── SMS [Addon] │ └── Templates ├── Email Templates └── Channel Templates [per addon] ``` ### Integration Points: ```php // Register channel add_filter('woonoow_notification_channels', function($channels) { $channels['whatsapp'] = [ 'id' => 'whatsapp', 'label' => 'WhatsApp', 'icon' => 'message-circle', ]; return $channels; }); // Send notification add_action('woonoow_notification_send_whatsapp', function($event, $data) { // Send WhatsApp message }, 10, 2); ``` ### Benefits: - ✅ Extensible (any channel via addons) - ✅ Flexible (multiple channels per event) - ✅ No bloat (core = email only) - ✅ Revenue opportunity (premium addons) - ✅ Community friendly (free addons welcome) --- ## Summary ✅ Login screen shows brand logo + tagline ✅ Settings submenu displays correctly ✅ Favicon works in standalone mode ✅ Notification strategy documented (addon-based) **Key Decision:** Notifications = Framework + Email core, everything else via addons **Ready to implement notification system!**
This commit is contained in:
294
NOTIFICATION_STRATEGY.md
Normal file
294
NOTIFICATION_STRATEGY.md
Normal file
@@ -0,0 +1,294 @@
|
||||
# WooNooW Notification Strategy
|
||||
|
||||
## Philosophy
|
||||
|
||||
**Core Principle:** WooNooW provides the notification framework and email as the default channel. Additional channels (WhatsApp, Telegram, SMS, etc.) are provided by addons.
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
### Core (WooNooW Plugin)
|
||||
|
||||
**Provides:**
|
||||
1. **Notification Events System**
|
||||
- Order placed
|
||||
- Order status changed
|
||||
- Low stock alert
|
||||
- New customer registered
|
||||
- etc.
|
||||
|
||||
2. **Email Channel (Built-in)**
|
||||
- Default notification channel
|
||||
- Always available
|
||||
- Template system
|
||||
- Queue system for bulk sending
|
||||
|
||||
3. **Notification Settings UI**
|
||||
- Enable/disable notifications per event
|
||||
- Configure which channels to use per event
|
||||
- Template editor (for email)
|
||||
- Channel-specific settings (provided by addons)
|
||||
|
||||
4. **Addon Integration Points**
|
||||
- `woonoow_notification_channels` filter - Register new channels
|
||||
- `woonoow_notification_send_{channel}` action - Send via channel
|
||||
- `woonoow_notification_settings_{channel}` filter - Channel settings UI
|
||||
- `woonoow_notification_template_{channel}` filter - Channel templates
|
||||
|
||||
---
|
||||
|
||||
## Settings Page Structure
|
||||
|
||||
```
|
||||
Notifications
|
||||
├── Events (What to notify)
|
||||
│ ├── Orders
|
||||
│ │ ├── Order Placed
|
||||
│ │ │ ├── ✓ Email (to admin)
|
||||
│ │ │ ├── ✓ WhatsApp (to customer) [if addon active]
|
||||
│ │ │ └── ✗ Telegram
|
||||
│ │ ├── Order Completed
|
||||
│ │ ├── Order Cancelled
|
||||
│ │ └── Order Refunded
|
||||
│ ├── Products
|
||||
│ │ ├── Low Stock Alert
|
||||
│ │ └── Out of Stock Alert
|
||||
│ └── Customers
|
||||
│ ├── New Customer
|
||||
│ └── Customer Note Added
|
||||
│
|
||||
├── Channels (How to notify)
|
||||
│ ├── Email (Built-in) ✓
|
||||
│ │ ├── From Name
|
||||
│ │ ├── From Email
|
||||
│ │ ├── SMTP Settings (optional)
|
||||
│ │ └── Templates
|
||||
│ ├── WhatsApp [Addon]
|
||||
│ │ ├── API Key
|
||||
│ │ ├── Phone Number
|
||||
│ │ └── Message Templates
|
||||
│ ├── Telegram [Addon]
|
||||
│ │ ├── Bot Token
|
||||
│ │ ├── Chat ID
|
||||
│ │ └── Message Format
|
||||
│ └── SMS [Addon]
|
||||
│ ├── Provider (Twilio, etc.)
|
||||
│ ├── API Credentials
|
||||
│ └── Message Templates
|
||||
│
|
||||
└── Templates
|
||||
├── Email Templates
|
||||
├── WhatsApp Templates [if addon active]
|
||||
└── Telegram Templates [if addon active]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation
|
||||
|
||||
### 1. Core Notification System
|
||||
|
||||
```php
|
||||
// includes/Core/Notifications/NotificationManager.php
|
||||
class NotificationManager {
|
||||
private $channels = [];
|
||||
|
||||
public function register_channel($id, $label, $callback) {
|
||||
$this->channels[$id] = [
|
||||
'label' => $label,
|
||||
'callback' => $callback,
|
||||
];
|
||||
}
|
||||
|
||||
public function send($event, $data, $channels = ['email']) {
|
||||
foreach ($channels as $channel) {
|
||||
if (isset($this->channels[$channel])) {
|
||||
call_user_func($this->channels[$channel]['callback'], $event, $data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Email Channel (Built-in)
|
||||
|
||||
```php
|
||||
// includes/Core/Notifications/Channels/EmailChannel.php
|
||||
class EmailChannel {
|
||||
public static function init() {
|
||||
add_filter('woonoow_notification_channels', [__CLASS__, 'register']);
|
||||
add_action('woonoow_notification_send_email', [__CLASS__, 'send'], 10, 2);
|
||||
}
|
||||
|
||||
public static function register($channels) {
|
||||
$channels['email'] = [
|
||||
'id' => 'email',
|
||||
'label' => 'Email',
|
||||
'icon' => 'mail',
|
||||
'builtin' => true,
|
||||
];
|
||||
return $channels;
|
||||
}
|
||||
|
||||
public static function send($event, $data) {
|
||||
// Send email using WooNooW mail queue
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Addon Example: WhatsApp Channel
|
||||
|
||||
```php
|
||||
// woonoow-whatsapp/includes/WhatsAppChannel.php
|
||||
class WhatsAppChannel {
|
||||
public static function init() {
|
||||
add_filter('woonoow_notification_channels', [__CLASS__, 'register']);
|
||||
add_action('woonoow_notification_send_whatsapp', [__CLASS__, 'send'], 10, 2);
|
||||
add_filter('woonoow_notification_settings_whatsapp', [__CLASS__, 'settings']);
|
||||
}
|
||||
|
||||
public static function register($channels) {
|
||||
$channels['whatsapp'] = [
|
||||
'id' => 'whatsapp',
|
||||
'label' => 'WhatsApp',
|
||||
'icon' => 'message-circle',
|
||||
'addon' => 'woonoow-whatsapp',
|
||||
];
|
||||
return $channels;
|
||||
}
|
||||
|
||||
public static function send($event, $data) {
|
||||
// Send WhatsApp message via API
|
||||
}
|
||||
|
||||
public static function settings() {
|
||||
return [
|
||||
'api_key' => get_option('woonoow_whatsapp_api_key'),
|
||||
'phone_number' => get_option('woonoow_whatsapp_phone'),
|
||||
];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Frontend (React SPA)
|
||||
|
||||
### Notifications Settings Page
|
||||
|
||||
```tsx
|
||||
// admin-spa/src/routes/Settings/Notifications.tsx
|
||||
|
||||
interface NotificationChannel {
|
||||
id: string;
|
||||
label: string;
|
||||
icon: string;
|
||||
builtin?: boolean;
|
||||
addon?: string;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
interface NotificationEvent {
|
||||
id: string;
|
||||
label: string;
|
||||
description: string;
|
||||
category: 'orders' | 'products' | 'customers';
|
||||
channels: {
|
||||
[channelId: string]: {
|
||||
enabled: boolean;
|
||||
recipient: 'admin' | 'customer' | 'both';
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export default function NotificationsPage() {
|
||||
// Fetch available channels (core + addons)
|
||||
const { data: channels } = useQuery({
|
||||
queryKey: ['notification-channels'],
|
||||
queryFn: () => api.get('/notifications/channels'),
|
||||
});
|
||||
|
||||
// Fetch notification events configuration
|
||||
const { data: events } = useQuery({
|
||||
queryKey: ['notification-events'],
|
||||
queryFn: () => api.get('/notifications/events'),
|
||||
});
|
||||
|
||||
return (
|
||||
<SettingsLayout title="Notifications">
|
||||
{/* Available Channels */}
|
||||
<SettingsCard title="Notification Channels">
|
||||
{channels?.map(channel => (
|
||||
<ChannelCard key={channel.id} channel={channel} />
|
||||
))}
|
||||
</SettingsCard>
|
||||
|
||||
{/* Events Configuration */}
|
||||
<SettingsCard title="Order Notifications">
|
||||
{events?.filter(e => e.category === 'orders').map(event => (
|
||||
<EventCard
|
||||
key={event.id}
|
||||
event={event}
|
||||
channels={channels}
|
||||
/>
|
||||
))}
|
||||
</SettingsCard>
|
||||
|
||||
{/* More categories... */}
|
||||
</SettingsLayout>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Benefits
|
||||
|
||||
1. **Extensible:** Addons can add any notification channel
|
||||
2. **Flexible:** Each event can use multiple channels
|
||||
3. **User-Friendly:** Clear UI showing what's available
|
||||
4. **No Bloat:** Core only includes email
|
||||
5. **Addon Revenue:** Premium channels (WhatsApp, SMS) can be paid addons
|
||||
|
||||
---
|
||||
|
||||
## Example Addons
|
||||
|
||||
### Free Addons
|
||||
- **WooNooW Telegram** - Send notifications via Telegram bot
|
||||
- **WooNooW Discord** - Send notifications to Discord channel
|
||||
|
||||
### Premium Addons
|
||||
- **WooNooW WhatsApp Pro** - WhatsApp Business API integration
|
||||
- **WooNooW SMS** - SMS notifications via Twilio/Nexmo
|
||||
- **WooNooW Push** - Browser push notifications
|
||||
- **WooNooW Slack** - Team notifications via Slack
|
||||
|
||||
---
|
||||
|
||||
## Migration Path
|
||||
|
||||
**Phase 1 (Current):**
|
||||
- Build notification framework
|
||||
- Implement email channel
|
||||
- Create settings UI with addon slots
|
||||
|
||||
**Phase 2:**
|
||||
- Build first addon (Telegram) as proof of concept
|
||||
- Document addon API
|
||||
- Create addon template
|
||||
|
||||
**Phase 3:**
|
||||
- Build premium addons (WhatsApp, SMS)
|
||||
- Marketplace listing
|
||||
- Community addons
|
||||
|
||||
---
|
||||
|
||||
## Key Takeaway
|
||||
|
||||
**WooNooW Core = Framework + Email**
|
||||
**Everything Else = Addons**
|
||||
|
||||
This keeps the core lean while providing unlimited extensibility for notification channels.
|
||||
@@ -5,6 +5,12 @@ export function useActiveSection(): { main: MainNode; all: MainNode[] } {
|
||||
const { pathname } = useLocation();
|
||||
|
||||
function pick(): MainNode {
|
||||
// Special case: /settings should match settings section
|
||||
if (pathname === '/settings' || pathname.startsWith('/settings/')) {
|
||||
const settingsNode = navTree.find(n => n.key === 'settings');
|
||||
if (settingsNode) return settingsNode;
|
||||
}
|
||||
|
||||
// Try to find section by matching path prefix
|
||||
for (const node of navTree) {
|
||||
if (node.path === '/') continue; // Skip dashboard for now
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
@@ -12,8 +12,27 @@ export function Login() {
|
||||
const [password, setPassword] = useState('');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [branding, setBranding] = useState<{ logo: string; tagline: string; storeName: string }>({
|
||||
logo: '',
|
||||
tagline: '',
|
||||
storeName: 'WooNooW'
|
||||
});
|
||||
const navigate = useNavigate();
|
||||
|
||||
// Fetch branding
|
||||
useEffect(() => {
|
||||
fetch((window.WNW_CONFIG?.restUrl || '') + '/store/settings')
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
setBranding({
|
||||
logo: data.store_logo || '',
|
||||
tagline: data.store_tagline || '',
|
||||
storeName: data.store_name || 'WooNooW'
|
||||
});
|
||||
})
|
||||
.catch(err => console.error('Failed to load branding:', err));
|
||||
}, []);
|
||||
|
||||
const handleLogin = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setIsLoading(true);
|
||||
@@ -52,10 +71,23 @@ export function Login() {
|
||||
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-xl p-8">
|
||||
{/* Logo */}
|
||||
<div className="text-center mb-8">
|
||||
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">
|
||||
WooNooW
|
||||
</h1>
|
||||
<p className="text-gray-600 dark:text-gray-400 mt-2">
|
||||
{branding.logo ? (
|
||||
<img
|
||||
src={branding.logo}
|
||||
alt={branding.storeName}
|
||||
className="h-16 mx-auto mb-3 object-contain"
|
||||
/>
|
||||
) : (
|
||||
<h1 className="text-3xl font-bold text-gray-900 dark:text-white mb-3">
|
||||
{branding.storeName}
|
||||
</h1>
|
||||
)}
|
||||
{branding.tagline && (
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400 mb-2">
|
||||
{branding.tagline}
|
||||
</p>
|
||||
)}
|
||||
<p className="text-gray-600 dark:text-gray-400">
|
||||
{__('Sign in to your admin dashboard')}
|
||||
</p>
|
||||
</div>
|
||||
@@ -138,7 +170,7 @@ export function Login() {
|
||||
|
||||
{/* Site Info */}
|
||||
<div className="text-center mt-6 text-sm text-gray-600 dark:text-gray-400">
|
||||
{window.WNW_CONFIG?.siteName || 'WooNooW'}
|
||||
{branding.storeName}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -92,7 +92,18 @@ class StandaloneAdmin {
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<title>WooNooW Admin</title>
|
||||
<title><?php echo esc_html( get_option( 'blogname', 'WooNooW' ) ); ?> Admin</title>
|
||||
|
||||
<?php
|
||||
// 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
|
||||
}
|
||||
?>
|
||||
|
||||
<!-- WooNooW Assets Only - NO wp_head() -->
|
||||
<link rel="stylesheet" href="<?php echo esc_url( $css_url ); ?>">
|
||||
|
||||
Reference in New Issue
Block a user