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:
dwindown
2025-11-10 23:44:18 +07:00
parent 9497a534c4
commit 0e41d3ded5
4 changed files with 350 additions and 7 deletions

294
NOTIFICATION_STRATEGY.md Normal file
View 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.

View File

@@ -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

View File

@@ -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>

View File

@@ -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 ); ?>">