feat: Tax route fix + Local Pickup + Email/Notifications settings
## 1. Fixed Tax Settings Route ✅ - Changed /settings/taxes → /settings/tax in nav tree - Now matches App.tsx route - Tax page now loads correctly ## 2. Advanced Local Pickup ✅ Frontend (LocalPickup.tsx): - Add/edit/delete pickup locations - Enable/disable locations - Full address fields (street, city, state, postcode) - Phone number and business hours - Clean modal UI for adding locations Backend (PickupLocationsController.php): - GET /settings/pickup-locations - POST /settings/pickup-locations (create) - POST /settings/pickup-locations/:id (update) - DELETE /settings/pickup-locations/:id - POST /settings/pickup-locations/:id/toggle - Stores in wp_options as array ## 3. Email/Notifications Settings ✅ Frontend (Notifications.tsx): - List all WooCommerce emails - Separate customer vs admin emails - Enable/disable toggle for each email - Show from name/email - Link to WooCommerce for advanced config Backend (EmailController.php): - GET /settings/emails - List all emails - POST /settings/emails/:id/toggle - Enable/disable - Uses WC()->mailer()->get_emails() - Auto-detects recipient type (customer/admin) ## Features: ✅ Simple, non-tech-savvy UI ✅ All CRUD operations ✅ Real-time updates ✅ Links to WooCommerce for advanced settings ✅ Mobile responsive Next: Test all settings pages
This commit is contained in:
@@ -197,6 +197,8 @@ import SettingsStore from '@/routes/Settings/Store';
|
|||||||
import SettingsPayments from '@/routes/Settings/Payments';
|
import SettingsPayments from '@/routes/Settings/Payments';
|
||||||
import SettingsShipping from '@/routes/Settings/Shipping';
|
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 SettingsNotifications from '@/routes/Settings/Notifications';
|
||||||
import MorePage from '@/routes/More';
|
import MorePage from '@/routes/More';
|
||||||
|
|
||||||
// Addon Route Component - Dynamically loads addon components
|
// Addon Route Component - Dynamically loads addon components
|
||||||
@@ -428,9 +430,10 @@ function AppRoutes() {
|
|||||||
<Route path="/settings/payments" element={<SettingsPayments />} />
|
<Route path="/settings/payments" element={<SettingsPayments />} />
|
||||||
<Route path="/settings/shipping" element={<SettingsShipping />} />
|
<Route path="/settings/shipping" element={<SettingsShipping />} />
|
||||||
<Route path="/settings/tax" element={<SettingsTax />} />
|
<Route path="/settings/tax" element={<SettingsTax />} />
|
||||||
|
<Route path="/settings/local-pickup" element={<SettingsLocalPickup />} />
|
||||||
<Route path="/settings/checkout" element={<SettingsIndex />} />
|
<Route path="/settings/checkout" element={<SettingsIndex />} />
|
||||||
<Route path="/settings/customers" element={<SettingsIndex />} />
|
<Route path="/settings/customers" element={<SettingsIndex />} />
|
||||||
<Route path="/settings/notifications" element={<SettingsIndex />} />
|
<Route path="/settings/notifications" element={<SettingsNotifications />} />
|
||||||
<Route path="/settings/brand" element={<SettingsIndex />} />
|
<Route path="/settings/brand" element={<SettingsIndex />} />
|
||||||
|
|
||||||
{/* Dynamic Addon Routes */}
|
{/* Dynamic Addon Routes */}
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ function getStaticFallbackTree(): MainNode[] {
|
|||||||
{ label: 'Store Details', mode: 'spa' as const, path: '/settings/store' },
|
{ label: 'Store Details', mode: 'spa' as const, path: '/settings/store' },
|
||||||
{ label: 'Payments', mode: 'spa' as const, path: '/settings/payments' },
|
{ label: 'Payments', mode: 'spa' as const, path: '/settings/payments' },
|
||||||
{ label: 'Shipping & Delivery', mode: 'spa' as const, path: '/settings/shipping' },
|
{ label: 'Shipping & Delivery', mode: 'spa' as const, path: '/settings/shipping' },
|
||||||
{ label: 'Taxes', mode: 'spa' as const, path: '/settings/taxes' },
|
{ label: 'Tax', mode: 'spa' as const, path: '/settings/tax' },
|
||||||
{ label: 'Checkout', mode: 'spa' as const, path: '/settings/checkout' },
|
{ label: 'Checkout', mode: 'spa' as const, path: '/settings/checkout' },
|
||||||
{ label: 'Customer Accounts', mode: 'spa' as const, path: '/settings/customers' },
|
{ label: 'Customer Accounts', mode: 'spa' as const, path: '/settings/customers' },
|
||||||
{ label: 'Notifications', mode: 'spa' as const, path: '/settings/notifications' },
|
{ label: 'Notifications', mode: 'spa' as const, path: '/settings/notifications' },
|
||||||
|
|||||||
374
admin-spa/src/routes/Settings/LocalPickup.tsx
Normal file
374
admin-spa/src/routes/Settings/LocalPickup.tsx
Normal file
@@ -0,0 +1,374 @@
|
|||||||
|
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 { Button } from '@/components/ui/button';
|
||||||
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||||
|
import { MapPin, Plus, Trash2, RefreshCw, Edit } from 'lucide-react';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
import { __ } from '@/lib/i18n';
|
||||||
|
|
||||||
|
interface PickupLocation {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
address: string;
|
||||||
|
city: string;
|
||||||
|
state: string;
|
||||||
|
postcode: string;
|
||||||
|
phone?: string;
|
||||||
|
hours?: string;
|
||||||
|
enabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function LocalPickupSettings() {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const [showDialog, setShowDialog] = useState(false);
|
||||||
|
const [editingLocation, setEditingLocation] = useState<PickupLocation | null>(null);
|
||||||
|
|
||||||
|
// Fetch pickup locations
|
||||||
|
const { data: locations = [], isLoading, refetch } = useQuery({
|
||||||
|
queryKey: ['pickup-locations'],
|
||||||
|
queryFn: () => api.get('/settings/pickup-locations'),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Save location mutation
|
||||||
|
const saveMutation = useMutation({
|
||||||
|
mutationFn: async (location: Partial<PickupLocation>) => {
|
||||||
|
if (location.id) {
|
||||||
|
return api.post(`/settings/pickup-locations/${location.id}`, location);
|
||||||
|
}
|
||||||
|
return api.post('/settings/pickup-locations', location);
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['pickup-locations'] });
|
||||||
|
toast.success(__('Pickup location saved'));
|
||||||
|
setShowDialog(false);
|
||||||
|
setEditingLocation(null);
|
||||||
|
},
|
||||||
|
onError: (error: any) => {
|
||||||
|
toast.error(error?.message || __('Failed to save location'));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Delete location mutation
|
||||||
|
const deleteMutation = useMutation({
|
||||||
|
mutationFn: async (id: string) => {
|
||||||
|
return api.del(`/settings/pickup-locations/${id}`);
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['pickup-locations'] });
|
||||||
|
toast.success(__('Pickup location deleted'));
|
||||||
|
},
|
||||||
|
onError: (error: any) => {
|
||||||
|
toast.error(error?.message || __('Failed to delete location'));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Toggle location mutation
|
||||||
|
const toggleMutation = useMutation({
|
||||||
|
mutationFn: async ({ id, enabled }: { id: string; enabled: boolean }) => {
|
||||||
|
return api.post(`/settings/pickup-locations/${id}/toggle`, { enabled });
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['pickup-locations'] });
|
||||||
|
toast.success(__('Location updated'));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const formData = new FormData(e.currentTarget);
|
||||||
|
|
||||||
|
const location: Partial<PickupLocation> = {
|
||||||
|
id: editingLocation?.id,
|
||||||
|
name: formData.get('name') as string,
|
||||||
|
address: formData.get('address') as string,
|
||||||
|
city: formData.get('city') as string,
|
||||||
|
state: formData.get('state') as string,
|
||||||
|
postcode: formData.get('postcode') as string,
|
||||||
|
phone: formData.get('phone') as string,
|
||||||
|
hours: formData.get('hours') as string,
|
||||||
|
enabled: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
saveMutation.mutate(location);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<SettingsLayout
|
||||||
|
title={__('Local Pickup Locations')}
|
||||||
|
description={__('Manage pickup locations for local pickup orders')}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-center py-12">
|
||||||
|
<RefreshCw className="h-6 w-6 animate-spin text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
</SettingsLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SettingsLayout
|
||||||
|
title={__('Local Pickup Locations')}
|
||||||
|
description={__('Manage pickup locations for local pickup orders')}
|
||||||
|
action={
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => refetch()}
|
||||||
|
>
|
||||||
|
<RefreshCw className="h-4 w-4 mr-2" />
|
||||||
|
{__('Refresh')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
setEditingLocation(null);
|
||||||
|
setShowDialog(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Plus className="h-4 w-4 mr-2" />
|
||||||
|
{__('Add Location')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="space-y-6">
|
||||||
|
{/* Info Card */}
|
||||||
|
<SettingsCard
|
||||||
|
title={__('About Local Pickup')}
|
||||||
|
description={__('Allow customers to pick up orders from your physical locations')}
|
||||||
|
>
|
||||||
|
<div className="text-sm text-muted-foreground space-y-2">
|
||||||
|
<p>
|
||||||
|
{__('Add multiple pickup locations where customers can collect their orders. Each location can have its own address, phone number, and business hours.')}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{__('Customers will see available pickup locations during checkout and can choose their preferred location.')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</SettingsCard>
|
||||||
|
|
||||||
|
{/* Pickup Locations List */}
|
||||||
|
<SettingsCard
|
||||||
|
title={__('Pickup Locations')}
|
||||||
|
description={locations.length === 0
|
||||||
|
? __('No pickup locations configured yet')
|
||||||
|
: __(`${locations.length} location(s) configured`)}
|
||||||
|
>
|
||||||
|
{locations.length === 0 ? (
|
||||||
|
<div className="text-center py-8">
|
||||||
|
<MapPin className="h-12 w-12 mx-auto text-muted-foreground mb-3" />
|
||||||
|
<p className="text-sm text-muted-foreground mb-4">
|
||||||
|
{__('Add your first pickup location to get started')}
|
||||||
|
</p>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setEditingLocation(null);
|
||||||
|
setShowDialog(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Plus className="h-4 w-4 mr-2" />
|
||||||
|
{__('Add Location')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-3">
|
||||||
|
{locations.map((location: PickupLocation) => (
|
||||||
|
<div
|
||||||
|
key={location.id}
|
||||||
|
className="rounded-lg border p-4 hover:bg-muted/50 transition-colors"
|
||||||
|
>
|
||||||
|
<div className="flex items-start justify-between gap-4">
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="flex items-center gap-2 mb-2">
|
||||||
|
<MapPin className="h-4 w-4 text-muted-foreground" />
|
||||||
|
<h3 className="font-medium">{location.name}</h3>
|
||||||
|
<span className={`text-xs px-2 py-0.5 rounded-full ${
|
||||||
|
location.enabled
|
||||||
|
? 'bg-green-100 text-green-700'
|
||||||
|
: 'bg-gray-100 text-gray-600'
|
||||||
|
}`}>
|
||||||
|
{location.enabled ? __('Active') : __('Inactive')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-sm text-muted-foreground space-y-1">
|
||||||
|
<p>{location.address}</p>
|
||||||
|
<p>{location.city}, {location.state} {location.postcode}</p>
|
||||||
|
{location.phone && <p>📞 {location.phone}</p>}
|
||||||
|
{location.hours && <p>🕐 {location.hours}</p>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => toggleMutation.mutate({
|
||||||
|
id: location.id,
|
||||||
|
enabled: !location.enabled
|
||||||
|
})}
|
||||||
|
disabled={toggleMutation.isPending}
|
||||||
|
>
|
||||||
|
{location.enabled ? __('Disable') : __('Enable')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
setEditingLocation(location);
|
||||||
|
setShowDialog(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Edit className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => {
|
||||||
|
if (confirm(__('Are you sure you want to delete this location?'))) {
|
||||||
|
deleteMutation.mutate(location.id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={deleteMutation.isPending}
|
||||||
|
>
|
||||||
|
<Trash2 className="h-4 w-4 text-destructive" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</SettingsCard>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Add/Edit Location Dialog */}
|
||||||
|
<Dialog open={showDialog} onOpenChange={setShowDialog}>
|
||||||
|
<DialogContent className="max-w-2xl">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>
|
||||||
|
{editingLocation ? __('Edit Pickup Location') : __('Add Pickup Location')}
|
||||||
|
</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium mb-1.5 block">
|
||||||
|
{__('Location Name')} *
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
name="name"
|
||||||
|
type="text"
|
||||||
|
className="w-full px-3 py-2 border rounded-md"
|
||||||
|
defaultValue={editingLocation?.name}
|
||||||
|
placeholder={__('e.g., Main Store, Warehouse, Downtown Branch')}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium mb-1.5 block">
|
||||||
|
{__('Street Address')} *
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
name="address"
|
||||||
|
type="text"
|
||||||
|
className="w-full px-3 py-2 border rounded-md"
|
||||||
|
defaultValue={editingLocation?.address}
|
||||||
|
placeholder={__('123 Main Street')}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-3 gap-3">
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium mb-1.5 block">
|
||||||
|
{__('City')} *
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
name="city"
|
||||||
|
type="text"
|
||||||
|
className="w-full px-3 py-2 border rounded-md"
|
||||||
|
defaultValue={editingLocation?.city}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium mb-1.5 block">
|
||||||
|
{__('State/Province')} *
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
name="state"
|
||||||
|
type="text"
|
||||||
|
className="w-full px-3 py-2 border rounded-md"
|
||||||
|
defaultValue={editingLocation?.state}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium mb-1.5 block">
|
||||||
|
{__('Postcode')} *
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
name="postcode"
|
||||||
|
type="text"
|
||||||
|
className="w-full px-3 py-2 border rounded-md"
|
||||||
|
defaultValue={editingLocation?.postcode}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium mb-1.5 block">
|
||||||
|
{__('Phone Number')}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
name="phone"
|
||||||
|
type="tel"
|
||||||
|
className="w-full px-3 py-2 border rounded-md"
|
||||||
|
defaultValue={editingLocation?.phone}
|
||||||
|
placeholder={__('(123) 456-7890')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="text-sm font-medium mb-1.5 block">
|
||||||
|
{__('Business Hours')}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
name="hours"
|
||||||
|
type="text"
|
||||||
|
className="w-full px-3 py-2 border rounded-md"
|
||||||
|
defaultValue={editingLocation?.hours}
|
||||||
|
placeholder={__('Mon-Fri: 9AM-5PM, Sat: 10AM-2PM')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-end gap-2 pt-4">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
setShowDialog(false);
|
||||||
|
setEditingLocation(null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{__('Cancel')}
|
||||||
|
</Button>
|
||||||
|
<Button type="submit" disabled={saveMutation.isPending}>
|
||||||
|
{saveMutation.isPending ? __('Saving...') : __('Save Location')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</SettingsLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
228
admin-spa/src/routes/Settings/Notifications.tsx
Normal file
228
admin-spa/src/routes/Settings/Notifications.tsx
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
import React 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 { ToggleField } from './components/ToggleField';
|
||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { ExternalLink, RefreshCw, Mail } from 'lucide-react';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
import { __ } from '@/lib/i18n';
|
||||||
|
|
||||||
|
export default function NotificationsSettings() {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const wcAdminUrl = (window as any).WNW_CONFIG?.wpAdminUrl || '/wp-admin';
|
||||||
|
|
||||||
|
// Fetch email settings
|
||||||
|
const { data: settings, isLoading, refetch } = useQuery({
|
||||||
|
queryKey: ['email-settings'],
|
||||||
|
queryFn: () => api.get('/settings/emails'),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Toggle email mutation
|
||||||
|
const toggleMutation = useMutation({
|
||||||
|
mutationFn: async ({ emailId, enabled }: { emailId: string; enabled: boolean }) => {
|
||||||
|
return api.post(`/settings/emails/${emailId}/toggle`, { enabled });
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['email-settings'] });
|
||||||
|
toast.success(__('Email settings updated'));
|
||||||
|
},
|
||||||
|
onError: (error: any) => {
|
||||||
|
toast.error(error?.message || __('Failed to update email settings'));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<SettingsLayout
|
||||||
|
title={__('Notifications')}
|
||||||
|
description={__('Manage email notifications sent to customers and admins')}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-center py-12">
|
||||||
|
<RefreshCw className="h-6 w-6 animate-spin text-muted-foreground" />
|
||||||
|
</div>
|
||||||
|
</SettingsLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const customerEmails = settings?.emails?.filter((e: any) => e.recipient === 'customer') || [];
|
||||||
|
const adminEmails = settings?.emails?.filter((e: any) => e.recipient === 'admin') || [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SettingsLayout
|
||||||
|
title={__('Notifications')}
|
||||||
|
description={__('Manage email notifications sent to customers and admins')}
|
||||||
|
action={
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => refetch()}
|
||||||
|
>
|
||||||
|
<RefreshCw className="h-4 w-4 mr-2" />
|
||||||
|
{__('Refresh')}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="space-y-6">
|
||||||
|
{/* Customer Emails */}
|
||||||
|
<SettingsCard
|
||||||
|
title={__('Customer Notifications')}
|
||||||
|
description={__('Emails sent to customers about their orders')}
|
||||||
|
>
|
||||||
|
<div className="space-y-4">
|
||||||
|
{customerEmails.map((email: any) => (
|
||||||
|
<div key={email.id} className="flex items-start justify-between py-3 border-b last:border-0">
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="flex items-center gap-2 mb-1">
|
||||||
|
<Mail className="h-4 w-4 text-muted-foreground" />
|
||||||
|
<h3 className="font-medium">{email.title}</h3>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-muted-foreground">{email.description}</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<ToggleField
|
||||||
|
checked={email.enabled === 'yes'}
|
||||||
|
onChange={(checked) => toggleMutation.mutate({
|
||||||
|
emailId: email.id,
|
||||||
|
enabled: checked
|
||||||
|
})}
|
||||||
|
disabled={toggleMutation.isPending}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
asChild
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href={`${wcAdminUrl}/admin.php?page=wc-settings&tab=email§ion=${email.id}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<ExternalLink className="h-4 w-4" />
|
||||||
|
</a>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</SettingsCard>
|
||||||
|
|
||||||
|
{/* Admin Emails */}
|
||||||
|
<SettingsCard
|
||||||
|
title={__('Admin Notifications')}
|
||||||
|
description={__('Emails sent to store administrators')}
|
||||||
|
>
|
||||||
|
<div className="space-y-4">
|
||||||
|
{adminEmails.map((email: any) => (
|
||||||
|
<div key={email.id} className="flex items-start justify-between py-3 border-b last:border-0">
|
||||||
|
<div className="flex-1">
|
||||||
|
<div className="flex items-center gap-2 mb-1">
|
||||||
|
<Mail className="h-4 w-4 text-muted-foreground" />
|
||||||
|
<h3 className="font-medium">{email.title}</h3>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-muted-foreground">{email.description}</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<ToggleField
|
||||||
|
checked={email.enabled === 'yes'}
|
||||||
|
onChange={(checked) => toggleMutation.mutate({
|
||||||
|
emailId: email.id,
|
||||||
|
enabled: checked
|
||||||
|
})}
|
||||||
|
disabled={toggleMutation.isPending}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
asChild
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href={`${wcAdminUrl}/admin.php?page=wc-settings&tab=email§ion=${email.id}`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<ExternalLink className="h-4 w-4" />
|
||||||
|
</a>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</SettingsCard>
|
||||||
|
|
||||||
|
{/* Email Sender Settings */}
|
||||||
|
<SettingsCard
|
||||||
|
title={__('Email Sender')}
|
||||||
|
description={__('Configure who emails are sent from')}
|
||||||
|
>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex items-center justify-between py-2">
|
||||||
|
<div>
|
||||||
|
<p className="font-medium text-sm">{__('From Name')}</p>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{settings?.from_name || __('Not configured')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
asChild
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href={`${wcAdminUrl}/admin.php?page=wc-settings&tab=email`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{__('Change')}
|
||||||
|
</a>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between py-2 border-t">
|
||||||
|
<div>
|
||||||
|
<p className="font-medium text-sm">{__('From Email')}</p>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{settings?.from_email || __('Not configured')}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
asChild
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href={`${wcAdminUrl}/admin.php?page=wc-settings&tab=email`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{__('Change')}
|
||||||
|
</a>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</SettingsCard>
|
||||||
|
|
||||||
|
{/* Advanced Settings Link */}
|
||||||
|
<div className="rounded-lg border border-dashed p-6 text-center">
|
||||||
|
<p className="text-sm text-muted-foreground mb-4">
|
||||||
|
{__('For email templates, styling, and advanced configuration, use the WooCommerce settings page')}
|
||||||
|
</p>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
asChild
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href={`${wcAdminUrl}/admin.php?page=wc-settings&tab=email`}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<ExternalLink className="h-4 w-4 mr-2" />
|
||||||
|
{__('Open Email Settings in WooCommerce')}
|
||||||
|
</a>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</SettingsLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
144
includes/Api/EmailController.php
Normal file
144
includes/Api/EmailController.php
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Email Settings REST API Controller
|
||||||
|
*
|
||||||
|
* @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 EmailController extends WP_REST_Controller {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register routes
|
||||||
|
*/
|
||||||
|
public function register_routes() {
|
||||||
|
$namespace = 'woonoow/v1';
|
||||||
|
|
||||||
|
// Get email settings
|
||||||
|
register_rest_route(
|
||||||
|
$namespace,
|
||||||
|
'/settings/emails',
|
||||||
|
array(
|
||||||
|
'methods' => WP_REST_Server::READABLE,
|
||||||
|
'callback' => array( $this, 'get_settings' ),
|
||||||
|
'permission_callback' => array( $this, 'check_permission' ),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Toggle email
|
||||||
|
register_rest_route(
|
||||||
|
$namespace,
|
||||||
|
'/settings/emails/(?P<email_id>[a-zA-Z0-9_-]+)/toggle',
|
||||||
|
array(
|
||||||
|
'methods' => WP_REST_Server::CREATABLE,
|
||||||
|
'callback' => array( $this, 'toggle_email' ),
|
||||||
|
'permission_callback' => array( $this, 'check_permission' ),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check permission
|
||||||
|
*/
|
||||||
|
public function check_permission() {
|
||||||
|
return current_user_can( 'manage_woocommerce' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get email settings
|
||||||
|
*/
|
||||||
|
public function get_settings( WP_REST_Request $request ) {
|
||||||
|
try {
|
||||||
|
$email_settings = array();
|
||||||
|
$emails = \WC()->mailer()->get_emails();
|
||||||
|
|
||||||
|
foreach ( $emails as $email ) {
|
||||||
|
$recipient = 'customer';
|
||||||
|
|
||||||
|
// Determine recipient type
|
||||||
|
if ( strpos( $email->id, 'admin' ) !== false ||
|
||||||
|
strpos( $email->id, 'new_order' ) !== false ||
|
||||||
|
strpos( $email->id, 'cancelled_order' ) !== false ||
|
||||||
|
strpos( $email->id, 'failed_order' ) !== false ) {
|
||||||
|
$recipient = 'admin';
|
||||||
|
}
|
||||||
|
|
||||||
|
$email_settings[] = array(
|
||||||
|
'id' => $email->id,
|
||||||
|
'title' => $email->get_title(),
|
||||||
|
'description' => $email->get_description(),
|
||||||
|
'enabled' => $email->is_enabled() ? 'yes' : 'no',
|
||||||
|
'recipient' => $recipient,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$settings = array(
|
||||||
|
'emails' => $email_settings,
|
||||||
|
'from_name' => get_option( 'woocommerce_email_from_name' ),
|
||||||
|
'from_email' => get_option( 'woocommerce_email_from_address' ),
|
||||||
|
);
|
||||||
|
|
||||||
|
return new WP_REST_Response( $settings, 200 );
|
||||||
|
} catch ( \Exception $e ) {
|
||||||
|
return new WP_REST_Response(
|
||||||
|
array(
|
||||||
|
'error' => 'fetch_failed',
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
),
|
||||||
|
500
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle email
|
||||||
|
*/
|
||||||
|
public function toggle_email( WP_REST_Request $request ) {
|
||||||
|
try {
|
||||||
|
$email_id = $request->get_param( 'email_id' );
|
||||||
|
$enabled = rest_sanitize_boolean( $request->get_param( 'enabled' ) );
|
||||||
|
$emails = \WC()->mailer()->get_emails();
|
||||||
|
|
||||||
|
if ( ! isset( $emails[ $email_id ] ) ) {
|
||||||
|
return new WP_REST_Response(
|
||||||
|
array(
|
||||||
|
'error' => 'not_found',
|
||||||
|
'message' => __( 'Email not found', 'woonoow' ),
|
||||||
|
),
|
||||||
|
404
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$email = $emails[ $email_id ];
|
||||||
|
|
||||||
|
// Update email settings
|
||||||
|
$email->update_option( 'enabled', $enabled ? 'yes' : 'no' );
|
||||||
|
|
||||||
|
return new WP_REST_Response(
|
||||||
|
array(
|
||||||
|
'success' => true,
|
||||||
|
'enabled' => $enabled,
|
||||||
|
'message' => $enabled
|
||||||
|
? __( 'Email enabled', 'woonoow' )
|
||||||
|
: __( 'Email disabled', 'woonoow' ),
|
||||||
|
),
|
||||||
|
200
|
||||||
|
);
|
||||||
|
} catch ( \Exception $e ) {
|
||||||
|
return new WP_REST_Response(
|
||||||
|
array(
|
||||||
|
'error' => 'toggle_failed',
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
),
|
||||||
|
500
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
248
includes/Api/PickupLocationsController.php
Normal file
248
includes/Api/PickupLocationsController.php
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Pickup Locations REST API Controller
|
||||||
|
*
|
||||||
|
* @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 PickupLocationsController extends WP_REST_Controller {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register routes
|
||||||
|
*/
|
||||||
|
public function register_routes() {
|
||||||
|
$namespace = 'woonoow/v1';
|
||||||
|
|
||||||
|
// Get all pickup locations
|
||||||
|
register_rest_route(
|
||||||
|
$namespace,
|
||||||
|
'/settings/pickup-locations',
|
||||||
|
array(
|
||||||
|
array(
|
||||||
|
'methods' => WP_REST_Server::READABLE,
|
||||||
|
'callback' => array( $this, 'get_locations' ),
|
||||||
|
'permission_callback' => array( $this, 'check_permission' ),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'methods' => WP_REST_Server::CREATABLE,
|
||||||
|
'callback' => array( $this, 'create_location' ),
|
||||||
|
'permission_callback' => array( $this, 'check_permission' ),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update/Delete specific location
|
||||||
|
register_rest_route(
|
||||||
|
$namespace,
|
||||||
|
'/settings/pickup-locations/(?P<id>[a-zA-Z0-9_-]+)',
|
||||||
|
array(
|
||||||
|
array(
|
||||||
|
'methods' => WP_REST_Server::EDITABLE,
|
||||||
|
'callback' => array( $this, 'update_location' ),
|
||||||
|
'permission_callback' => array( $this, 'check_permission' ),
|
||||||
|
),
|
||||||
|
array(
|
||||||
|
'methods' => WP_REST_Server::DELETABLE,
|
||||||
|
'callback' => array( $this, 'delete_location' ),
|
||||||
|
'permission_callback' => array( $this, 'check_permission' ),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Toggle location
|
||||||
|
register_rest_route(
|
||||||
|
$namespace,
|
||||||
|
'/settings/pickup-locations/(?P<id>[a-zA-Z0-9_-]+)/toggle',
|
||||||
|
array(
|
||||||
|
'methods' => WP_REST_Server::CREATABLE,
|
||||||
|
'callback' => array( $this, 'toggle_location' ),
|
||||||
|
'permission_callback' => array( $this, 'check_permission' ),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check permission
|
||||||
|
*/
|
||||||
|
public function check_permission() {
|
||||||
|
return current_user_can( 'manage_woocommerce' );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all pickup locations
|
||||||
|
*/
|
||||||
|
public function get_locations( WP_REST_Request $request ) {
|
||||||
|
$locations = get_option( 'woonoow_pickup_locations', array() );
|
||||||
|
return new WP_REST_Response( array_values( $locations ), 200 );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create pickup location
|
||||||
|
*/
|
||||||
|
public function create_location( WP_REST_Request $request ) {
|
||||||
|
try {
|
||||||
|
$locations = get_option( 'woonoow_pickup_locations', array() );
|
||||||
|
|
||||||
|
$id = uniqid( 'loc_' );
|
||||||
|
$location = array(
|
||||||
|
'id' => $id,
|
||||||
|
'name' => sanitize_text_field( $request->get_param( 'name' ) ),
|
||||||
|
'address' => sanitize_text_field( $request->get_param( 'address' ) ),
|
||||||
|
'city' => sanitize_text_field( $request->get_param( 'city' ) ),
|
||||||
|
'state' => sanitize_text_field( $request->get_param( 'state' ) ),
|
||||||
|
'postcode' => sanitize_text_field( $request->get_param( 'postcode' ) ),
|
||||||
|
'phone' => sanitize_text_field( $request->get_param( 'phone' ) ),
|
||||||
|
'hours' => sanitize_text_field( $request->get_param( 'hours' ) ),
|
||||||
|
'enabled' => true,
|
||||||
|
);
|
||||||
|
|
||||||
|
$locations[ $id ] = $location;
|
||||||
|
update_option( 'woonoow_pickup_locations', $locations );
|
||||||
|
|
||||||
|
return new WP_REST_Response( $location, 201 );
|
||||||
|
} catch ( \Exception $e ) {
|
||||||
|
return new WP_REST_Response(
|
||||||
|
array(
|
||||||
|
'error' => 'create_failed',
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
),
|
||||||
|
500
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update pickup location
|
||||||
|
*/
|
||||||
|
public function update_location( WP_REST_Request $request ) {
|
||||||
|
try {
|
||||||
|
$id = $request->get_param( 'id' );
|
||||||
|
$locations = get_option( 'woonoow_pickup_locations', array() );
|
||||||
|
|
||||||
|
if ( ! isset( $locations[ $id ] ) ) {
|
||||||
|
return new WP_REST_Response(
|
||||||
|
array(
|
||||||
|
'error' => 'not_found',
|
||||||
|
'message' => __( 'Location not found', 'woonoow' ),
|
||||||
|
),
|
||||||
|
404
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$location = array(
|
||||||
|
'id' => $id,
|
||||||
|
'name' => sanitize_text_field( $request->get_param( 'name' ) ),
|
||||||
|
'address' => sanitize_text_field( $request->get_param( 'address' ) ),
|
||||||
|
'city' => sanitize_text_field( $request->get_param( 'city' ) ),
|
||||||
|
'state' => sanitize_text_field( $request->get_param( 'state' ) ),
|
||||||
|
'postcode' => sanitize_text_field( $request->get_param( 'postcode' ) ),
|
||||||
|
'phone' => sanitize_text_field( $request->get_param( 'phone' ) ),
|
||||||
|
'hours' => sanitize_text_field( $request->get_param( 'hours' ) ),
|
||||||
|
'enabled' => $locations[ $id ]['enabled'], // Preserve enabled status
|
||||||
|
);
|
||||||
|
|
||||||
|
$locations[ $id ] = $location;
|
||||||
|
update_option( 'woonoow_pickup_locations', $locations );
|
||||||
|
|
||||||
|
return new WP_REST_Response( $location, 200 );
|
||||||
|
} catch ( \Exception $e ) {
|
||||||
|
return new WP_REST_Response(
|
||||||
|
array(
|
||||||
|
'error' => 'update_failed',
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
),
|
||||||
|
500
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete pickup location
|
||||||
|
*/
|
||||||
|
public function delete_location( WP_REST_Request $request ) {
|
||||||
|
try {
|
||||||
|
$id = $request->get_param( 'id' );
|
||||||
|
$locations = get_option( 'woonoow_pickup_locations', array() );
|
||||||
|
|
||||||
|
if ( ! isset( $locations[ $id ] ) ) {
|
||||||
|
return new WP_REST_Response(
|
||||||
|
array(
|
||||||
|
'error' => 'not_found',
|
||||||
|
'message' => __( 'Location not found', 'woonoow' ),
|
||||||
|
),
|
||||||
|
404
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
unset( $locations[ $id ] );
|
||||||
|
update_option( 'woonoow_pickup_locations', $locations );
|
||||||
|
|
||||||
|
return new WP_REST_Response(
|
||||||
|
array(
|
||||||
|
'success' => true,
|
||||||
|
'message' => __( 'Location deleted', 'woonoow' ),
|
||||||
|
),
|
||||||
|
200
|
||||||
|
);
|
||||||
|
} catch ( \Exception $e ) {
|
||||||
|
return new WP_REST_Response(
|
||||||
|
array(
|
||||||
|
'error' => 'delete_failed',
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
),
|
||||||
|
500
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle pickup location
|
||||||
|
*/
|
||||||
|
public function toggle_location( WP_REST_Request $request ) {
|
||||||
|
try {
|
||||||
|
$id = $request->get_param( 'id' );
|
||||||
|
$enabled = rest_sanitize_boolean( $request->get_param( 'enabled' ) );
|
||||||
|
$locations = get_option( 'woonoow_pickup_locations', array() );
|
||||||
|
|
||||||
|
if ( ! isset( $locations[ $id ] ) ) {
|
||||||
|
return new WP_REST_Response(
|
||||||
|
array(
|
||||||
|
'error' => 'not_found',
|
||||||
|
'message' => __( 'Location not found', 'woonoow' ),
|
||||||
|
),
|
||||||
|
404
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$locations[ $id ]['enabled'] = $enabled;
|
||||||
|
update_option( 'woonoow_pickup_locations', $locations );
|
||||||
|
|
||||||
|
return new WP_REST_Response(
|
||||||
|
array(
|
||||||
|
'success' => true,
|
||||||
|
'enabled' => $enabled,
|
||||||
|
'message' => $enabled
|
||||||
|
? __( 'Location enabled', 'woonoow' )
|
||||||
|
: __( 'Location disabled', 'woonoow' ),
|
||||||
|
),
|
||||||
|
200
|
||||||
|
);
|
||||||
|
} catch ( \Exception $e ) {
|
||||||
|
return new WP_REST_Response(
|
||||||
|
array(
|
||||||
|
'error' => 'toggle_failed',
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
),
|
||||||
|
500
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,8 @@ use WooNooW\API\PaymentsController;
|
|||||||
use WooNooW\API\StoreController;
|
use WooNooW\API\StoreController;
|
||||||
use WooNooW\Api\ShippingController;
|
use WooNooW\Api\ShippingController;
|
||||||
use WooNooW\Api\TaxController;
|
use WooNooW\Api\TaxController;
|
||||||
|
use WooNooW\Api\PickupLocationsController;
|
||||||
|
use WooNooW\Api\EmailController;
|
||||||
|
|
||||||
class Routes {
|
class Routes {
|
||||||
public static function init() {
|
public static function init() {
|
||||||
@@ -59,6 +61,14 @@ class Routes {
|
|||||||
// Tax controller
|
// Tax controller
|
||||||
$tax_controller = new TaxController();
|
$tax_controller = new TaxController();
|
||||||
$tax_controller->register_routes();
|
$tax_controller->register_routes();
|
||||||
|
|
||||||
|
// Pickup locations controller
|
||||||
|
$pickup_controller = new PickupLocationsController();
|
||||||
|
$pickup_controller->register_routes();
|
||||||
|
|
||||||
|
// Email controller
|
||||||
|
$email_controller = new EmailController();
|
||||||
|
$email_controller->register_routes();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user